블로그 이미지
윤영식
Full Stacker, Application Architecter, KnowHow Dispenser and Bike Rider

Publication

Category

Recent Post

2013. 3. 20. 23:18 Testing, TDD/Test First

  TDD 또는 BDD로 진행을 하는 것은 소프트웨어의 명세를 만들어 가면서 보다 안정적인 서비스를 만드는 지름길이다. 물론 좀 더 시간을 투자해야하는 지름길이지만 나중의 유지보수를 생각하면 긴안목에서 역시 빠른 지름길이다. 특히 모바일 서비스의 경우는 몇번의 에러가 발생하면 사용자는 해당 앱을 삭제하고 절대로 사용하지 않는다는 보고가 있다. 그리고 페이스북에는 QA조직이 없고 개발자가 직접 모든 테스트를 수행하고 책임을 지도록 되어있다. 

  우리가 생산성을 위하여 프레임워크를 사용하듯, 안정성을 위해서는 테스트 코드가 필수이다. 또한 테스트 코드에 주석을 달고 Groc 같은 유틸로 문서화를 한다면 소프트웨어의 명세를 따로 하지 않아도 될 것이다. 

  백본기반의 개발에 있어서도 모델, 뷰, 라우터등의 TDD는 필요하며 이를 어떻게 하는지 따라해 보자



1) TDD 준비하기

  - backbone-mocha.zip 파일을 다운로드 받는다 

backbone-mocha.zip


  - tests/test-runner.html 을 수행한다 

  


  - test-runner.html 파일 이해

<!DOCTYPE html>

<html lang="en">

<head>

    <!-- Title &amp; Meta -->

    <title>Frontend tests</title>

    <meta charset="utf-8">


    <!-- Stylesheets -->

    <link rel="stylesheet" href="libs/mocha/mocha.css">

</head>

<body>


    <!-- mocha 결과 화면을 뿌리기 위한 div --> 

    <div id="mocha"></div>


    <!-- Testing Libraries 첨부 -->

    <script src="libs/mocha/mocha.js"></script>

    <script src="libs/chai/chai.js"></script>

    <script src="libs/sinon/sinon.js"></script> <!-- 본 예제에서는 sinon.js 안씀. 즉, 삭제가능 -->


    <!-- chai를 사용하고 mocha는 TDD를 사용한다. BDD는 bdd 소문자로 입력 

          TDD를 하면 mocha에서 suit(), setup(), teardown(), test() 4가지 메소드를 사용한다 

          첨부파일의 user.test.js 파일 참조


          chai는 should, expect, assert 방식을 선택할 수 있는데 여기서는 expect 방식을 선택

    -->

    <script>

        // Use the expect version of chai assertions - http://chaijs.com/api/bdd

        var expect = chai.expect;


        // Tell mocha we want TDD syntax

        mocha.setup('tdd');

    </script>


    <!-- 사용하는 Libs -->

    <script src="../libs/jquery/jquery-1.8.3.min.js"></script>

    <script src="../libs/underscore/underscore-min.js"></script>

    <script src="../libs/backbone/backbone-min.js"></script>


    <!-- 코딩한 원본 Source files -->

    <script src="../src/app.js"></script>

    <script src="../src/models/user.js"></script>

    <script src="../src/views/profile.js"></script>


    <!-- 개발한 소스와 맵핑되는 Test files -->

    <script src="models/user.test.js"></script>

    <script src="views/profile.test.js"></script>


    <!-- 최종적으로 mocha 를 수행한다 -->

    <script>

        mocha.run();

    </script>


</body>

</html>



2) Backbone Model TDD

  - tests/models/user.test.js 밑에 getFullName 테스트 코드 첨부

    + suite, setup, teardown, test : Mocha interface

    + expect : Chai interface

suite('User Model', function() {


    setup(function() {

        this.user = new app.models.User({

            first_name: "Yun",

            last_name: "DoWon"

        });

    });


    teardown(function() {

        this.user = null;

    });


    test('should exist', function() {

        expect(this.user).to.be.ok; // Tests this.user is truthy  <- chai 코드

    });


    test('calling getFullName should return first_name[space]last_name', function() {

        expect(this.user.getFullName()).to.equal('Yun DoWon');

    });


});

  

   - 호출하면 빨간색 Fail 발생

  - 에러 해결하여 초록 Green 으로 만들기위해 user.js 안에 getFullName 메소드 구현

(function(global, _, Backbone, undefined) {


    app.models.User = Backbone.Model.extend({

        getFullName: function() {

               // model 내역은 user.test.js의 setup에서 이미 설정해 놓았음

        return this.get('first_name') + " " + this.get('last_name');

        }

    });


})(this, _, Backbone);


  - 브라우져 호출 결과 : "User Model" 제목은 user.test.js 에서 지정한 제목임

  



3) Backbone View TDD

  - test/views 폴더 밑에 profile.test.js 파일을 만든다 

  - 다음 코드를 넣는다

suite('Profile View', function() {

 

    // Create a User model to pass into our view to give it data

    var model = new app.models.User({

        first_name: 'Yun',

        last_name: 'YoungSik',

        age: 23

    });

 

    setup(function() {

        this.profile = new app.views.Profile({

            // Pass in a jQuery in memory <div> for testing the view rendering

            el: $('<div>'), 

            

            // Pass in the User model 

            // dependency inversion makes this simple to test, 

            // we are in control of the dependencies rather 

            // than the view setting them up internally.

            model: model 

        });

    });

 

    teardown(function() {

        this.profile = null;

    });

 

    test('should exist', function() {

        expect(this.profile).to.be.ok;

    });

 

});


  - 브라우저에서 호출하기전 test-runner.html 에 profile.js 와 profile.test.js 를 넣는다

    <!-- Source files -->

    <script src="../src/app.js"></script>

    <script src="../src/models/user.js"></script>

    <script src="../src/views/profile.js"></script>


    <!-- Test -->

    <script src="models/user.test.js"></script>

    <script src="views/profile.test.js"></script>


  - 브라우져 호출 결과

  - src/views 폴더에 profile.js 파일을 생성하고 다음 코드는 넣고 다시 호출한다

(function(global, $, _, Backbone, undefined) {

 

    app.views.Profile = Backbone.View.extend({

 

    });

 

})(this, $, _, Backbone);

    

  - 정상 호출됨  

 


  - profile.test.js 안에 구현하고 싶은 다음 테스트 코드를 넣고 호출한다

    test('should exist', function() {

        expect(this.profile).to.be.ok;

    });

    // view 에 대한 render 테스트 : Regex 로 해당 내역이 화면출력으로 나왔는지 테스트

    test('render()', function() {

        this.profile.render();

     

        // 렌더링된 결과화면에 대하여 정규표현식으로 검사를 하는 것이 핵심!

        expect(this.profile.$el.html().match(/Yun/)).to.be.ok;

        expect(this.profile.$el.html().match(/YoungSik/)).to.be.ok;

        expect(this.profile.$el.html().match(/23/)).to.be.ok;

    });


  - profile.js 의 내역을 구현지 않았기 때문에 테스트에 대한 결과는 빨간색 fail 

  


  - profile.js 의 render() 메소드를 구현한다 

(function(global, $, _, Backbone, undefined) {

    app.views.Profile = Backbone.View.extend({

 

        render: function() {

            var html = "<h1>"+ this.model.getFullName() +"</h1>" +

                "<p>"+ this.model.get('first_name') +" is "+ this.model.get('age') + " years old";

            this.$el.html(html);

        }

 

    });

})(this, $, _, Backbone); 


  - 브라우져 호출 결과

  

 


테스트 코드를 만들고 실행하여 에러(빨간색)가 발생하면 테스트 코드에 대응하는 업무 코드를 짜고, 다시 실행하여 정상(녹색)적으로 돌아가게 한다. 정상에는 두 단계가 있는데, 비즈니스 로직이 없이 API와 정적값을 통해 정상(녹색) 통과를 하고 나면 이후 비즈니스 로직을 넣고 다시 정상(녹색) 통과를 하는지 체크한다. 



<참조> 

  - 원문 : Testing Backbone.js with mocha 

  - http://visionmedia.github.com/mocha/

  - http://chaijs.com/

posted by 윤영식
2013. 3. 20. 15:09 Backbone.js

기존에 작성한 index.html 파일안에는 템플릿에 대한 내역도 같이 포함되어 있다. 해당 템플릿 내역을 별도의 파일로 때어 내어 AMD  모듈화와 결합하는 방법을 알아본다. 



1) Require.js 플러그인 test.js 설치

  - 텍스트 파일을 로딩하는 text.js 파일을 적절한 위치에 설치 (여기서는 require.js 랑 같은 위치에 설치함)

  - 다운로드 : http://requirejs.org/docs/download.html#text

  - index.html 파일의 requirejs.config 에 의존성 설정 추가
requirejs.config({
    baseUrl: './js/', 
    paths: {
        'jquery': 'jquery',
        'underscore': 'underscore',
        'backbone': 'backbone',
        'text': 'text',
        'jquery.dateformat' : 'jquery-dateformat'
    },
    shim: {
        'underscore': {
            exports: '_'
        },
        'backbone': {
            deps: ['underscore', 'jquery'],
            exports: 'Backbone'
        },
        'jquery.dateformat': {
            deps: ['jquery']
        }
    }
});



2) index.html 에서 템플릿내역 분리하기 

  - 템플릿 영역 분리하여 ride_template.html 명칭으로 index.html 과 같은 위치에 저장

////////////////////////////////

// index.html 내부

<div id="rides">

    <div id="addRide">

        <label for="coverImage">CoverImage: </label><input id="coverImage" type="file" />

        <label for="title">Title: </label><input id="title" type="text" />

        <label for="rider">Rider: </label><input id="rider" type="text" />

        <label for="ridingDate">Riding date: </label><input id="ridingDate" type="text" />

        <label for="keywords">Keywords: </label><input id="keywords" type="text" />

        <button id="add">Add</button>

    </div>

    <!-- 주석처리 사용하지 않음 / 또는 제거함 

        script id="rideTemplate" type="text/template">

        <img src="<%= coverImage %>"/>

        <ul>

            <li><%= title %></li>

            <li><%= rider %></li>

            <li><%= ridingDate %></li>

            <li><%= keywords %></li>

        </ul>

        <button class="delete">Delete</button>

    </script-->

</div>


////////////////////////////////

// ride_template.html 전체 내역

<img src="<%= coverImage %>"/>

<ul>

    <li><%= title %></li>

    <li><%= rider %></li>

    <li><%= ridingDate %></li>

    <li><%= keywords %></li>

</ul>

<button class="delete">Delete</button>



3) ride.js 파일에 템플릿 의존성 주입

  - AMD는 SOLID의 Dependency Inversion Principle과 같은 원리. 즉, 스프링의 Inversion Of Control 과 같은 방식이다. 

  - 따라서 모듈 ride_template.html 을 ride.js 에 주입해 보자 

    +  define([의존관계], 파라미터) : 의존관계에 템플릿 파일 상대 경로 지정

    + 기존 template 프로퍼티의 값을 파라미터로 변경

define(['jquery', 'backbone', 'underscore', 'text!../ride_template.html', 'jquery.dateformat'], function($, Backbone, _, rideTemplate) {

   .. 중략 ..

   var RideView = Backbone.View.extend({

        tagName:"div",

        className:"rideContainer",

        //template: $("#rideTemplate").html(),

        template: rideTemplate,


   render:function () {

        .. 중략 ..



4) 브라우져 호출 

  - 호출후 크롬 개발자 도구의 DOM 구성 : rideContainer div 태그안에 ride_template.html 이 들어가 있다 

   


 ** 전체 소스 

riding.zip



<참조>

  - 백본에 템플릿 텍스트 적용하기

  - text.js 사용법 : prefix로 반드시 text!을 붙여서 정적파일을 로딩한다 


posted by 윤영식
2013. 3. 20. 09:36 HTML5, CSS3/jQuery

자바스크립트 코드에서 이벤트 핸들러처리가 끝난 후 preventDefault() 와 return false 등의 코드를 본적이 있을 것이다. 간혹 stopPropagation() 호출도 보이는데 이들의 차이점을 알아보자 



1) preventDefault vs return false, stopPropagation : jQuery를 사용하지 않은 경우 

  - stopPropagation 은 사용자 정의 이벤트의 bubbling up 되는 것을 막아준다 

    + 이벤트와 이벤트 핸들러를 찾기 위한 캡쳐링과 버블링에 대해 이곳을 참조한다.

    + 사용자 정의 이벤트는 개발자가 직접 이벤트 핸들러를 작성할 경우

    + 즉, 사용자가 작성한 이벤트 핸들러의 동작을 막아준다

  - preventDefault 는 기본 정의 이벤트의 동작을 막아준다 

    + <a href..> 앵커 태그와 같은 기본 제공 이벤트 (사이트로 이동하는 것) 

    + 즉, 기본 이벤트의 동작을 막아준다 

  - return false 는 jQuery 사용할 때와 안할 때의 반응이 틀리다

    + jQuery 사용안할 때는 preventDefault와 동일하게 동작한다 

  

  - 주석 a, b, c 케이스 테스트 : 화면의 "[Click me]" 레이블을 클릭할 경우

 

<html>

<body>

<a href="http://mobicon.tistory.com">

<div id="div1">

<input type="label" id="label1" value="[Click me]"/>

</div>

</a>


<script>

document.getElementById('div1').onclick = function() { 

alert('click div1'); 

};

document.getElementById('label1').onclick = function(e) { 

alert('click label1'); 

// a 

//e.stopPropagation();

// b

//e.preventDefault();

// c

//return false;

};

</script>

</body>

</html>


   + e.stopPropagation() 만 주석 제거

      "click label1" 경고창 뜨고 "mobicon.tistory.com" 사이트로 이동 ("click div1" 경고창 안뜸. 즉, 버블링 업 막아줌)

   + e.preventDefault() 만 주석 제거

      "click label1" 경고창 뜨고 "click div1" 경고창 뜸 그러나 "mobicon.tistory.com"으로 이동하지 않음 (즉, 기본 이벤트 막아줌)

   + return false 만 주석 제거

      "click label1" 경고창 뜨고 "click div1" 경고창 뜸 그러나 "mobicon.tistory.com"으로 이동하지 않음 (즉, 기본 이벤트 막아줌)


  - jQuery를 사용하지 않은 e.preventDefault와 return false 의 동작은 동일하다



2) preventDefault vs return false, stopPropagation : jQuery를 사용할 경우 

  - stopPropagation 상동

  - preventDefault 상동

  - return false 는 jQuery 사용할 때와 안할 때의 반응이 틀리다

    + jQuery 사용할 때는 preventDefault와 동일하게 동작한다 

<html>

<body>

<a href="http://mobicon.tistory.com">

<div id="div1">

<input type="label" id="label1" value="[Click me]"/>

</div>

</a>


<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>

<script>

$('#div1').click( function() { 

alert('click div1'); 

} );

$('#label1').click( function(e) { 

alert('click label1'); 

//e.stopPropagation();

//e.preventDefault();

//return false;

} );

</script>

</body>

</html>

   

   + e.stopPropagation() 만 주석 제거

      상동

   + e.preventDefault() 만 주석 제거

      상동

   + return false 만 주석 제거

      "click label1" 경고창 뜨고 "click div1" 경고창은 안 뜸 그리고 "mobicon.tistory.com"으로 이동하지 않음 


  - 결론적으로 jQuery를 사용하여 return false를 쓸 경우 stopPropgation() 와 preventDefault() 가 동시에 적용됨 



<참조>

  - stopPropagation()과 preventDefault() 차이

  - preventDefault() vs return false 차이

  - 자바스크립트 이벤트 핸들링

posted by 윤영식
2013. 3. 18. 14:42 Backbone.js

엔터프라이즈급의 SPA 개발을 위해서는 AMD를 필수적으로 사용해야 한다. 기존 "서점 샘플 응용하기 - 02"에서 진행하였던 자바스크립트 라이브러리들과 Backbone 기반 코드인 ride.js 파일을 Require.js를 이용하여 모듈화하고 재설정해 보자 



1) index.html 변경하기 

  - 기존 index.html 내역

    + <%= %> 구문으로 underscore template 사용

    + jquery dateformat plugin 을 html에서 직접 호출

    + <script> 의존 라이브러리를 개발자가 알아서 체크하고 의존성이 있는 것을 더 밑으로 열거함 

       자바스크립트만을 가지고 SPA 를 개발할 때 수 많은 의존관계를 지금의 방식으로 설정하는 것은 한계가 있음 

<!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="UTF-8"/>

    <title>Backbone.js Cycle Riding for Dowon</title>

    <link rel="stylesheet" href="css/screen.css">

</head>

<body>


<div id="rides">

    <div id="addRide">

        <label for="coverImage">CoverImage: </label><input id="coverImage" type="file" />

        <label for="title">Title: </label><input id="title" type="text" />

        <label for="rider">Rider: </label><input id="rider" type="text" />

        <label for="ridingDate">Riding date: </label><input id="ridingDate" type="text" />

        <label for="keywords">Keywords: </label><input id="keywords" type="text" />

        <button id="add">Add</button>

    </div>

    <script id="rideTemplate" type="text/template">

        <img src="<%= coverImage %>"/>

        <ul>

            <li><%= title %></li>

            <li><%= rider %></li>

            <li><%= $.format.date(new Date(ridingDate), 'yyyy/MM/dd') %></li>  <!-- jquery dataformat 직접 호출 --> 

            <li><%= keywords %></li>

        </ul>

        <button class="delete">Delete</button>

    </script>

</div>


<!-- 자바스크립트의 의존관계에 따라 의존성이 있는 라이브러리를 맨밑에 열거한다 --> 

<script src="js/jquery.js"></script>

<script src="js/jquery-dateformat.js"></script>

<script src="js/underscore.js"></script>

<script src="js/backbone.js"></script>

<script src="js/ride.js"></script>

</body>

</html>


  - Require.js 방식으로 index.html 변경 

<!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="UTF-8"/>

    <title>Backbone.js Cycle Riding for Dowon</title>

    <link rel="stylesheet" href="css/screen.css">

</head>

<body>


<div id="rides">

    <div id="addRide">

        <label for="coverImage">CoverImage: </label><input id="coverImage" type="file" />

        <label for="title">Title: </label><input id="title" type="text" />

        <label for="rider">Rider: </label><input id="rider" type="text" />

        <label for="ridingDate">Riding date: </label><input id="ridingDate" type="text" />

        <label for="keywords">Keywords: </label><input id="keywords" type="text" />

        <button id="add">Add</button>

    </div>

    <script id="rideTemplate" type="text/template">

        <img src="<%= coverImage %>"/>

        <ul>

            <li><%= title %></li>

            <li><%= rider %></li>

            <li><%= ridingDate %></li>  <!-- jquery dateformat은 ride.js 에서 포멧팅한다 --> 

            <li><%= keywords %></li>  

        </ul>

        <button class="delete">Delete</button>

    </script>

</div>


<!-- require.js 최상단에 위치 --> 

<script src="js/require.js"></script>

<script>

// 환경설정

requirejs.config({

    // 라이브러리 위치 (index.html 에서 상대경로)

    baseUrl: './js/', 

    // baseUrl+paths 로 실제 라이브러리 파일위치를 지정 (value값에서 .js 생략함)

    paths: {

        jquery: 'jquery',

        underscore: 'underscore',

        backbone: 'backbone',

        'jquery.dateformat' : 'jquery-dateformat'

    },

    // require.js 를 사용할 수 없는 이미 정의된 라이브러리를 require.js 방식으로 만들고자 할 때 설정

    shim: {

        'underscore': {

            exports: '_'

        },

        'backbone': {

            deps: ['underscore', 'jquery'],  // 의존하는 라이브러리 설정

            exports: 'Backbone'  // window객체에서 접근가능한 변수명 설정 

        },

        'jquery.dateformat': {   // jquery.dateformat 을 index.html 에서 사용하지 않고 ride.js 에서 사용하기 위해 설정

            deps: ['jquery']

        }

    }

});


// define 펑션으로 정의한 ride.js 모듈을 사용하기 위하여 require 펑션을 호출한다 

require(['ride'], function(ride) {

    console.log('let\'s go riding');

});

</script>


</body>

</html>



2) ride.js 모듈화 하기 

  - 기존 ride.js 

(function ($) {


    var Ride = Backbone.Model.extend({

    .. 중략 ..


})(jQuery);  // 글로벌 변수 jQuery 를 받아서 사용함 


  - define 펑션을 사용하여 ride.js 모듈화 하기 

// jquery, backbone을 사용한다

// jquery.dateformat은 jquery 플러그인으로 의존관계만 설정해 놓고 파라미터로 받지는 않는다 

// 파라미터로 jquery=$, backbone=Backbone, underscore=_ 로 레퍼런스를 받고 있다 

define(['jquery', 'backbone', 'underscore', 'jquery.dateformat'], function($, Backbone, _) {


    var Ride = Backbone.Model.extend({

    .. 중략 ..


    renderRide: function(item){

            // 화면에 포멧 변경로직을 넣지 않고 model에서 변경하여 준다

            // update ridingDate to YYYY/MM/dd from YYYY-MM-DDT00:00:00.000Z

            var ridingDate = item.get('ridingDate');

            var date = $.format.date(new Date(ridingDate), 'yyyy/MM/dd');

            item.set('ridingDate', date);


            var rideView = new RideView({

                model: item

            });

            this.$el.append(rideView.render().el);

     },

    .. 중략 ..


});



3) 호출하기 

  - 브라우져에서 호출

    + nodemon 으로 node 수행 

$ nodemon server.js

18 Mar 11:40:03 - [nodemon] v0.7.2

18 Mar 11:40:03 - [nodemon] watching: /Users/nulpulum/development/backbone/riding

18 Mar 11:40:03 - [nodemon] starting `node server.js`

Express server listening on port 8080 in development mode

18 Mar 13:28:12 - [nodemon] restarting due to changes...

18 Mar 13:28:12 - [nodemon] /Users/nulpulum/development/backbone/riding/public/js/ride.js


    + http://localhost:8080/  : 하나더 추가해 보았다 

  


<참조>

  - Require.js API

  - backbone.js 소스 주석 한글화

posted by 윤영식
2013. 3. 16. 16:56 NodeJS/Modules

AMD를  사용하지 않으면 엔터프라즈급의 웹앱을 만들 수가 없다. 요즘 Java 에서 Spring Framework이 엔터프라이즈 구현에 핵심이듯이 서로의 의존관계를 관리하고 주입하여 준다. 전통적인 방식은 HTML에서 <script> 태그를 사용하여 모듈들을 순차적으로 코딩한다. 그런 순차적 코딩이 아닌 모듈개념으로 어떻게 전환할 수 있는지 알아보자 



1) 예전 방식의 자바스크립트 로딩방식

  - html 코드

    + add.js : 더하기 연산

    + multi.js : 곱하기 연산

    + app.js : add, multi 연산을 사용하고 결과 출력

<html>

<body>

<script type="text/javascript" src="add.js"></script>

<script type="text/javascript" src="multi.js"></script>

<script type="text/javascript" src="app.js"></script>

</body>

</html>


  - 나머지 코드들

    + 전역변수 cal를 사용하기 위하여 모든 .js에서 undefined을 확인해야 한다

// add.js 

var cal = cal || {};


cal.add = function(x, y) {

return x + y;

}


// multi.js

var cal = cal || {};


cal.multiply = function(x, y) {

return x*y;

}


// app.js 

// 전역변수 cal를 이용하여 호출한다 

console.log('add result is ' + cal.add(4, 5));

console.log('multi result is ' + cal.multiply(4, 5));


  - html 호출 결과 

  



2) AMD 방식으로 로딩하기

  - add.js, multi.js  모듈화를 위하여 define() 펑션을 이용한다

  - app.js 에서 모듈을 사용하기 위하여 require() 펑션을 이용한다

// add.js 

// var cal 과 같은 변수 선언이 없어졌다. 

// 1) Functional Programming의 최대 장점이 변수 설정으로 인한 메모리 할당이 필요없어지게 되었다

// 2) Anonymous Function == Closure Function으로 만들었다  

define(function() {

return function(x, y) {

return x + y;

}

}) 


// multi.js

define(function() {

return function(x, y) {

return x * y;

}

})


// app.js

// 의존관계에 있는 것을 require의 첫번째 인자에 배열로 설정한다 

require(['add', 'multi'], function(add2, multi2) {

console.log('AMD : add result is ' + add2(4, 5));

console.log('AMD : multi result is ' + multi2(4, 5));

});


  - AMD 구현체 require.js를 사용하여 index.html 을 변경한다 

    + data-main 값으로 app.js 를 지정한다

    + src 값으로 require.js 를 지정한다 

<html>

<BODY>

<script data-main="app" src="http://requirejs.org/docs/release/2.1.5/minified/require.js"></script>

</BODY>

</html>


  - 결과 확인

  


** 테스트 소스

AMD.zip



<참조>

  - Require.js 홈페이지

  - Require.js 이용하여 AMD 모듈 작성하기

posted by 윤영식