본문으로 바로가기

개요

웹 개발자라면 흔히 파일 하나로 자바스크립트 코드를 작성하곤 합니다. 그리고 그 코드가 점점 커져서 나중에는 수정하는 것은 물론이고 유지보수시에도 정말 어려워집니다. 이런 문제의 해결하기 위해서 코드를 여러 파일로 쪼갤 수 있습니다. 

하지만 그러면 script 태그가 많아지고 다른 파일에서 정의한 함수를 조회하기 위한 글로벌 변수가 많아지게 됩니다. 

그래서 글로벌 네임스페이스는 지저분해지고, 추가한 js 파일들의 HTTP 요청이 네트워크 대역폭을 차지해서 정작 해당 페이지의 로딩은 느려지게 될 것입니다.

이런 일을 겪었다면 프런트 앤드 코드를 뭔가 다른 방법으로 관리해야 겠다는 필요성을 느끼게 됩니다. 특히 자바스크립트가 수천 라인이 넘는 대형 사이즈의 웹 앱을 제작해야 한다면 더욱 그렇습니다. 

유지보수를 쉽게 할 수 있도록 이 모든 문제를 해결할 새로운 방법이 필요합니다. 스크립트 로더가 바로 이를 위한 새로운 기법입니다. 

스크립트 로더들은 웹에서 쉽게 찾을 수 있지만 여기서는 그중에서도 RequireJS라는 라이브러리를 보겠습니다.

여러분은 단계별로 따라 하는 튜토리얼을 통해서 RequireJS 기반의 간단한 MVC(Model - View - Controller) 앱 제작 방법을 배울 수 있을 것입니다. 

스크립트 로더에 대한 사전 지식은 필요 없습니다. 

이제 기초부터 살펴 봅니다.




RequestJS 개념 및 장점

RequireJS는 AMD(Asynchronous Module Definition)의 구현체입니다. 

AMD란 모듈을 정의하는 방법과 모듈이 필요할 때 비동기로 로딩하는 방법을 정의한 API 입니다. 

제임스 버크(James Burke)가 개발했는데, 2년간의 개발 끝에 버전 1.0을 찍었습니다. 

여러분은 RequireJS로 Javascript코드를 모듈화 할 수 있고 비동기로 관리하면서 여러파일을 병렬로 다운로드 할 수 있습니다. 

스크립트 파일이 필요할 때만 병렬로 로딩되기 때문에, 페이지 로딩 속도는 빨라집니다. 



Front-end for MVC?

MVC는 서버 사이드 코드를 구조화하고 모듈로 만들며 유지보수가 용의하도록 돕는 매우 잘 알려진 디자인 패턴입니다. 

그렇다면 front-end에서 MVC를 사용하려면 어떻게 해야 할까요? 자바스크립트에서 이 디자인패턴을 적용할 수 있을 까요? 

만약 여러분이 자바스크립트를 단지 애니메이션과 폼 유효성 검사, 100라인이 넘지 않는 간단한 처리를 위해서 사용한다면 MVC를 사용해서 여러분의 스크립트 파일을 구조화할 필요 없습니다. 

RequireJS도 사용할 필요 없을 겁니다. 하지만, 뷰(view)가 많은 리치 웹 앱을 제작 중이라면 반드시 필요할 것입니다.



RequireJS Usage

RequireJS는 전통적인 <script> 태그를 사용한 스크립트 로딩 방법과 다른 접근 방법을 취하고 있습니다. 

이 방법은 빠르게 동작하고, 잘 최적화될 수 있는 동시에, 코드의 모듈화를 돕는 것을 목표로 합니다. 

그러한 면을 잘 보여주는 부분이 스크립트 태그에 대해 URL을 사용하는 대신에 모듈ID를 사용하는 것을 권장하는 것입니다.





자바스크립트 파일 로딩

RequireJS는 baseUrl에 포함된 모든 코드를 불러옵니다. 

baseUrl은 일반적으로 한 페이지에 로딩되는 최상위 스크립트의 data-main 어트리뷰트에서 쓰이는 스크립트와 같은 디렉토리로 설정됩니다. 

data-main 어트리뷰트는 require.js가 스크립트 로딩을 시작할 지 확인하는 특별한 어트리뷰트입니다. 


다음의 예제는 js라는 baseUrl을 가지게 됩니다.

html
<!--
    여기서는 "js" 디렉토리로 baserUrl을 설정하고 있습니다.
    그리고 'main' 이라는 모듈 ID를 가지는 스크립트를 불러오게 됩니다.
-->
<script data-main="js/main.js" src="js/require.js"></script>

baseUrl은 RequireJS config를 통해 직접 설정할 수도 있습니다. 

설정이 충돌되는 것이 없고 data-main이 사용되지 않았다면, 디폴트 baseUrl은 RequireJS가 실행되는 HTML 페이지를 포함하는 디렉토리입니다. 

RequireJS는 디폴트로 모든 디펜던시가 스크립트라고 가정하고 있습니다. 그렇기 때문에 ".js" 를 모듈ID에 붙여줄 필요는 없습니다. 

RequireJS는 모듈ID를 해석할 때 자동적으로 패스에 ".js"를 붙여줍니다. paths 설정을 통해서, 스크립트 모음의 위치를 설정해줄 수도 있습니다. 

이러한 특징들은 전통적인 <script> 태그와 비교할 때 스크립트 사용 시 문자열을 더 적게 사용하게 합니다.


RequireJS를 사용하면서 스크립트를 참조할 때 파일을 찾기 위해 "baseUrl + paths" 방식을 사용하지 않고 직접 참조하고 싶을 때가 있을 것입니다. 

만약 모듈ID가 다음의 특정 문자 중 하나를 가지고 있다면, ID는 "baseUrl + paths" 설정을 무시하고, 문서에 관한 일반적인 URL로 취급할 것입니다 :

  • .js로 끝나는 경우 
  • /로 시작하는 경우 
  • http: 나 https:와 같은 URL 프로토콜을 포함하고 있는 경우


일반적인 관점에서, 모듈ID의 paths를 설정하기 위해 baseUrl과 "paths" 설정을 사용하는 것이 최선입니다. 

이렇게 함으로써 빌드를 최적화하기 위해 다른 위치로 패스를 설정하는 것과 이름을 바꾸는 것에 대해서 더 많은 유연성을 가지게 됩니다. 


유사하게, 설정 폭탄을 피하기 위한 최선은, 스크립트에 대해 깊은 폴더 구조를 피하고, baseUrl안에 모든 스크립트를 넣어두는 것입니다. 

만약 라이브러리나 밴더가 공급한 코드를 당신의 애플리케이션의 코드와 분리하고 싶다면, 디렉토리 레이아웃을 다음과 같이 할 수 있습니다.

디렉토리 구조
www/
    index.html
    js/
        app/
            sub.js
        lib/
            jquery.js
            canvas.js
        app.js
        require.js


index.html 은 :

html
<script data-main="js/app.js" src="js/require.js">


app.js 는 다음과 같이 구성할 수 있습니다.

javascript
requirejs.config({
    // 기본적으로 js/lib 이하의 모든 모듈 ID들을 로딩합니다.
    baseUrl: 'js/lib',
    // 예외적으로, "app"으로 시작하는 모듈ID는 js/app 디렉토리로부터
    // 불러오게 됩니다. paths 설정은 baseUrl을 기준으로 설정되며,
    // ".js" 확장자를 포함해서는 안됩니다. path 설정은 디렉토리에 관한
    // 것이어야 하기 때문입니다.
    paths: {
        app: '../app'
    }
});

// 메인 앱 로직을 시작합니다.
requirejs(['jquery', 'canvas', 'app/sub'], function ($, canvas, sub) {
    //jQuery, canvas와 app/sub 모듈은 모두 로드되어
    //여기서 사용할 수 있습니다.
});


예제를 보면 jQuery와 같은 밴더 라이브러리는 파일명에 버전 넘버를 가지고 있지 않은 것을 알 수 있습니다. 

만약 버전 넘버를 따로 트래킹하고 싶다면 별도의 텍스트파일안에 저장하거나, volo 같은 툴을 사용하고 있다면, volo가 버전 정보를 package.json기록할 것입니다. 

파일은 jquery.js로 유지하는 것이 좋습니다. 이것을 통해 각 라이브러리를 위해 "paths" 설정을 넣어주는 대신에 최소한의 설정을 유지할 수 있습니다. 

예를 들어 "jquery-1.7.2"를 사용하기 위해 "jquery"라고 추가로 설정을 하지 않을 수 있도록 말입니다. 


이상적으로는 로딩되는 스크립트는 define() 선언에 의해 정의되어야합니다. 

그렇지만 의존성을 deinfe()을 통해 표현하지 않는 전통적/레가시적인 "브라우저 전역" 스크립트를 사용할 필요가 있을 수 있습니다. 

이러한 경우에 대해서 shim 설정을 사용할 수 있습니다. 이를 통해 적절하게 그들의 디펜던시들을 표현할 수 있습니다. 

만약 디펜던시를 적어주지 않는다면, RequireJS가 스크립트를 비동기적으로 불러올 때 에러가 발생되고 엉망이 될 것 입니다.



data-main entry point(진입 시점)

data-main 어트리뷰트는 RequireJS가 스크립트 로딩을 시작할 때 체크하는 특별한 어트리뷰트입니다.

html
<!-- require.js가 로딩될 때 js/main.js 에 대한 다른 스크립트 태그를 (비동기 어트리뷰트로) 주입하게 됩니다. -->
<script data-main="js/main" src="js/require.js"></script>

일반적으로 data-main 스크립트를 설정 옵션을 세팅하기 위해 사용한 후에 어플리케이션의 첫번째 모듈을 불러올 것 입니다. 


require.js가 생성하는 data-main 모듈에 대한 스크립트 태그는 비동기 어트리뷰트를 포함하고 있습니다. 

이것은 data-main 스크립트의 로딩과 실행이 같은 페이지 안의 참조되는 다른 스크립트보다 일찍 수행될 것이라고 가정할 수 없다는 것을 의미합니다.


예를 들어, 'foo' 모듈에 대한 require.config path가 'foo'모듈이 require()되기 전에 위치하지 않았을 때 다음과 같이 나열한다면 랜덤하게 실패하는 경우가 생길 것입니다.

html
<script data-main="js/main" src="js/require.js"></script>
<script src="js/other.js"></script>


main.js 내부 :

javascript
// main.js

require.config({
    paths: {
        foo: 'libs/foo-1.1.3'
    }
});


other.js 내부 :

javascript
// other.js

// 이 코드는 main.js 내의 require.config()가 실행되기 전에 호출될 수 있습니다. 
// 만약 그렇게 된다면 requirejs는  'scripts/libs/foo-1.1.3.js'
// 대신에 'scripts/foo.js' 를 불러오려고 할 것 입니다.
require( ['foo'], function( foo ) {

});



모듈(module) 정의하기 - define

모듈은 전통적인 스크립트 파일과는 다릅니다. 

모듈은 전역 네임스페이스를 오염시키지 않으며 스코프가 잘 형성되어 있는 객체입니다. 

이것은 디펜던시들의 목록을 명쾌하게 보여줄 수 있으며 전역 객체를 참조할 필요없이 디펜던시를 조작할 수 있도록 해주며. 모듈을 정의하는 함수의 인자로 디펜던시를 넘겨줍니다. 


RequireJS에서의 모듈은 모듈패턴의 확장입니다. 

다른 모듈을 참조하기 위해 전역객체를 필요로 하지 않는 이점도 가지고 있습니다. 

 모듈에 대한 RequireJS 문법을 통해 가능한한 빠르게 모듈을 로딩할 수 있으며, 순서가 정해져있지 않더라도 이를 정확한 디펜던시 순서로 정렬하며, 전역 변수들이 생성되지 않기 때문에 다양한 버전의 모듈을 한 페이지에 불러오는 것을 가능하게 합니다. (만약 CommonJS 모듈을 사용하는 것에 익숙하다면, RequireJS 모듈이 CommonJS 모듈 형식에 어떻게 맵핑되는지에 관한 CommonJS Notes도 함께 보시기 바랍니다.) 

디스크 상의 파일 하나 당 오직 하나의 모듈만 정의되어야 합니다. 모듈은 최적화 툴에 의해 최적화된 묶음으로 그루핑될 수 있습니다.


단순한 Name/Value 쌍

모듈이 어떤 디펜던시도 가지고 있지 않고, name/value 쌍으로 이루어져 있다면 객체 리터럴을 define에 넘겨줄 수 있습니다.

javascript
// my/shirt.js 파일의 내부 :
define({
    color: "black",
    size: "unisize"
});


함수 정의

만약 모듈이 디펜던시를 가지고 있지 않지만, 작업을 위해 함수를 사용할 필요가 있다면 define한 후에 define() 내에 함수를 추가합니다.

javascript
// my/shirt.js는 모듈 정의를 반환하기 전에 setup을 하게 됩니다.
define(function () {
    //여기서 setup이 동작합니다.

    return {
        color: "black",
        size: "unisize"
    }
});


디펜던시(dependency)가 있는 함수 정의(의존관계가 있는 함수 정의)

만약 모듈이 디펜던시가 있다면 첫번째 인자는 디펜던시들의 이름의 배열이어야하고, 두번째 인자는 함수 정의여야 합니다. 

함수는 모든 디펜던시가 로딩되었을 때 모듈에 대한 define을 호출할 것입니다. 

함수는 모듈을 정의한 객체를 반환해야합니다. 디펜던시는 정의된 함수에 함수 인자의 형태로 넘어갈 것이며, 디펜던시 배열의 순서와 같은 순서로 들어갈 것입니다.

javascript
// my/shirt.js 는 몇 개의 디펜던시를 가지고 있습니다.
// cart와 inventory 모듈은 shirt.js와 같은 위치에 있습니다.
define(["./cart", "./inventory"], function(cart, inventory) {
        //return an object to define the "my/shirt" module.
        return {
            color: "blue",
            size: "large",
            addToCart: function() {
                inventory.decrement(this);
                cart.add(this);
            }
        }
    }
);

이 예제에서 my/shirt 모듈이 생성되었으며, 이 모듈은 my/cart와 my/inventory에 의존하고 있습니다. 

디스크에서 파일구조는 다음과 같이 이루어져있습니다.

  • my/cart.js 
  • my/inventory.js 
  • my/shirt.js


함수 호출은 두개의 인자를 기술하고 있습니다. 

"cart"와 "inventory". 이 모듈들은 "./cart", "./inventory" 모됼명으로 나타내어졌습니다. 

함수는 my/cart와 my/inventory 두개의 모듈이 로딩될 때까지 호출되지 않습니다. 그리고 함수는 모듈들을 "cart"와 "inventory" 인자로 받게됩니다. 

모듈은 전역 변수를 정의하는 것을 분명하게 권장하지 않습니다. 모듈의 복수 버전은 한 페이지 내에 동시에 존재할 수 있습니다.(응용편을 참조바랍니다.) 

또한 함수 인자의 순서는 디펜던시들의 순서와 매칭되어야 합니다. 

함수 호출을 통해 반환되는 객체는 "my/shirt" 모듈에 define 호출을 합니다. 

이런 방법으로 모듈을 정의하게되면, "my/shirt"는 전역 객체 내에 존재하지 않게됩니다.



모듈을 함수로 정의하기

모듈은 따로 반환 객체를 가지고 있지 않습니다. 함수의 어떤 유효한 반환값도 허용됩니다. 

모듈 정의로써 함수를 반환하는 모듈의 예가 다음에 나와있습니다.

javascript
// foo/title.js 내부에 있는 모듈 정의.
// 여기서는 앞쪽에 있는 my/cart와 my/inventory 모듈을 사용하고 있습니다.
// 그렇지만 foo/title.js는 "my" 모듈과 다른 디렉토리에 있기 때문에
// 모듈 디펜던시명에 "my"를 사용하여 디펜던시를 찾을 수 있게 합니다.     
// 디펜던시명의 "my" 부분은 어떤 디렉토리와도 맴핑될 수 있습니다.
// 디폴트로는, "foo" 디렉토리의 형제 관계로 위치해있다고 가정합니다.
define(["my/cart", "my/inventory"],
    function(cart, inventory) {
        // "foo/title" 정의하기 위해 함수를 반환합니다.
        // 이는 window title을 get하거나 set합니다. 
        return function(title) {
            return title ? (window.title = title) :
                   inventory.storeName + ' ' + cart.name;
        }
    }
);


모듈을 단순화된 CommonJS 래퍼로 정의하기

만약 예전의 CommonJS 모듈 형식으로 작성된 코드를 재사용하고 싶을 때, 위에 사용된 디편던시들의 배열의 형태로 재작업하는 것은 어려울 것이며, 아마도 의존성을 위해 디펜던시들의 이름을 지역변수의 형태로 나열하는 것을 선호할 것 입니다. 

이러한 경우에 simplified CommonJS wrapper를 사용할 수가 있습니다

javascript
define(function(require, exports, module) {
        var a = require('a'),
            b = require('b');

        //Return the module value
        return function () {};
    }
);

이 wrapper는 함수의 내용에 관한 유용한 문자열 값을 주기 위해 Functon.prototype.toString()에 의존하고 있습니다. 

이것은 PS3와 같은 몇몇 디바이스와 오래된 Opera 모바일 브라우저에서 동작을 하지 않습니다. 이런 기기들에서 사용하기 위해 optimizer 사용할 수 있습니다. 

CommonJS 페이지의 Why AMD 페이지의 "Sugar" 섹션에서 더 많은 정보를 찾아볼 수 있습니다.

모듈을 이름으로 정의하기

당신은 define() 호출 시에 define()의 첫번째 인자로 모듈의 이름을 포함되어 있는 것을 보게 될 수도 있습니다.

javascript
//Explicitly defines the "foo/title" module:
    define("foo/title",
        ["my/cart", "my/inventory"],
        function(cart, inventory) {
            //Define foo/title object in here.
       }
    );

이것은 일반적으로 optimizer tool에서 생성되는 형태입니다. 

당신은 모듈의 이름을 명확하게 지을 수 있지만, 이것은 모듈의 이동성을 떨어트립니다. 

만약 당신이 파일을 다른 디렉토리로 옮기게 되면 당신은 이름을 변경해야 합니다. 

일반적이 경우에 모듈의 이름을 코딩하는 것은 피하고, 최적화툴로 모듈의 이름을 작성하도록 하는 것이 최선입니다. 

optimizer tool에서는 브라우저의 로딩 속도를 높이기 위해, 하나 이상의 모듈이 하나의 파일로 합쳐질 수 있도록 이름을 더해주는 것을 필요로합니다.



모듈 관련 기타 사항

  • 파일 하나 당 하나의 모듈 : 자바스크립트 파일 하나당 오직 하나의 모듈을 써야하며, name-to-file-path lookup 알고리즘 유형을 따라야합니다. 복수의 모듈은 최적화 도구에 의해 최적화된 파일로 그루핑될 것이다. 그렇지만 하나의 이상의 모듈을 하나의 파일 안에 넣어야 할 때만 최적화 도구를 사용해야 합니다. 
  • define() 내부에서의 상대적인 모듈명 사용 : require("./relative/name") 호출은 define() 함수 선언 내부에서 일어날 수 있으며, 반드시 "require"를 디펜던시로서 찾고 상대적인 이름을 통해서 이를 불러올 수 있습니다 


javascript
define(["require", "./relative/name"], function(require) {
    var mod = require("./relative/name");
});


CommonJS 모듈을 해석을 통해 단축된 문법을 사용할 수 있다면 더욱 좋습니다.

javascript
define(function(require) {
    var mod = require("./relative/name");
});

이 형태는 require() 호출을 찾기 위해 Function.prototype.toString()을 사용합니다. 그리고 그것들을 "require"를 따라 디펜던시 배열에 추가합니다. 

코드는 상대경로에 의해 정확하게 동작할 것 입니다. 상대경로는 디렉토리 안에 적은 모듈을 생성할 때 매우 유용합니다. 

이를 통해 당신은 디렉토리를 다른 사람이나 다른 프로젝트와 공유할 수 있습니다. 

그리고 디렉토리 내에 있는 다른 모듈을 디렉토리의 이름을 모르는 상태에서도 관리할 수 있을 것 입니다.


모듈에 대한 URL 생성

당신은 모듈에 관한 URL을 생성할 필요가 있을 것 입니다. 이를 위해 require를 디펜던시로 찾은 후에 require.toUrl() 사용하여 URL을 생성할 수 있습니다.

javascript
define(["require"], function(require) {
    var cssUrl = require.toUrl("./style.css");
});


콘솔 디버깅

자바스크립트 콘솔에서 require(["module/name"], function(){}) 호출을 통해 이미 로딩된 모듈로 작업을 할 필요가 있다면, 모듈을 불러오기 위해 모듈의 문자열 이름만을 사용하는 require() 형식을 사용할 수 있습니다.

javascript
require("module/name").callSomeFunction()

이 형식은 "module/name"을 require의 비동기 버전을 통해 이미 로드되어있는 경우에만 동작한다는 것을 기억하세요. './module/name'과 같이 상대경로를 사용하는 방식은 define 내부에서만 동작합니다.



순환 디펜던시

만약 당신이 순환 디펜던시를 정의했다면 (a는 b가 필요하고, b는 a가 필요한) 이 경우에 "b" 모듈 함수가 호출된다면, 이것은 "a"에 대해서 undefined 값을 가지게 될 것 입니다. 

"b"는 "a"를 모듈이 require() 메소드에 의해 정의된 이후에 가져올 수 있습니다. (반드시 require를 디펜던시로 명시해서 올바른 context 내에서 "a"를 찾아야 합니다.)

javascript
//Inside b.js:
define(["require", "a"],
    function(require, a) {
        //"a" in this case will be null if "a" also asked for "b",
        //a circular dependency.
        return function(title) {
            return require("a").doSomething();
        }
    }
);

일반적인 경우에는 모듈을 불러오기 위해서 require()를 사용할 필요는 없으며, 함수에 인자로 모듈을 넘겨주면 됩니다. 

순환 디펜던시는 드문 경우이며, 디자인에 대해서 다시 생각해봐야 한다는 신호이기도 합니다. 

그러나 때때로 필요할 경우가 있으며, 그런 경우에는 require()를 위에 기술해 놓은 것 처럼 사용해야합니다.


CommonJS 모듈이 친숙하다면, exports를 사용할 수도 있습니다. 이를 다른 모듈들에 의한 참조에 바로 사용될 수 있는 현재 모듈을 가리키는 빈 객체로 사용이 가능합니다. 

이것을 양쪽 모듈에 적용하면, 다른 모듈을 안전하게 기다리게 할 수 잇습니다. 

이것은 모듈값으로 함수가 아닌 객체를 exporting할 때에만 동작합니다 :

javascript
//Inside b.js:
define(function(require, exports, module) {
    //If "a" has used exports, then we have a real
    //object reference here. However, we cannot use
    //any of "a"'s properties until after "b" returns a value.
    var a = require("a");

    exports.foo = function () {
        return a.bar();
    };
});

또는 디펜던시 배열 접근 방법을 사용하고 있다면 특별한 'exports' 디펜던시를 찾을 수도 있습니다.




JSONP 서비스 디펜던시 작성하기

모듈 정의하지 않기

전역함수인 requirejs.undef()는 정의되지 않는 모듈을 허용하게 한다. 이 함수는 모듈의 이전의 정의를 잊도록 로더의 내부 상태를 리셋합니다. 

그러나 이것은 다른 모듈에서 이미 정의되어 그들이 실행될 때 관리하게된 모듈을 제거하지는 못합니다. 

이것은 사용될 미래의 모듈 로딩 또는 모듈값을 관리하는 다른 모듈이 없는 경우에 에러가 발생한 경우에만 유용합니다. 


errback 섹션에 있는 예제를 참고하기 바랍니다. 

 만약 당신이 정의되지 않은 작업에 대해 정교한 디펜던시 그래프 분석을 하길 원한다면 semi-private onResourceLoadAPI가 유용할 것 입니다.




메카니즘

RequireJS는 디펜던시를 head.appendChild()를 통해 스크립트 태그 형태로 불러들입니다. 

 RequireJS 모든 디펜던시가 로딩되기를 기다린 후에, 모듈을 정의한 함수를 호출하기 위한 올바른 순서를 계산합니다. 

그런 후에 모듈을 정의한 함수를 올바른 순서로 호출합니다. 동기적 로딩을 하는 서버-사이드 환경에서 RequireJS를 사용하는 것은 require.load()를 재정의하면 매우 쉬워집니다. 

빌드 시스템이 이를 수행하면, 서버사이드 환경을 위한 require.load 메소드는 build/jslib/requirePatch.js를 찾을 수 있을 것입니다. 

향후에, 이 코드는 부가적인 모듈로서 require/ 디렉토리로 가져올 것입니다. 

이를 통해 당신의 환경에 이것을 불러와 호스트 환경에 기초한 올바른 로딩 방법을 사용할 수 있게될 것 입니다.




Config Options(설정 옵션)

탑-레벨 HTML 페이지(또는 모듈을 정의하지 않는 탑-레벨스크립트 파일)에서 require()를 사용할 때, 설정 객체는 첫번째 옵션으로 넘겨지게 됩니다.

html
<script src="js/require.js"></script>
<script>
  require.config({
    baseUrl: "/another/path",
    paths: {
        "some": "some/v1.0"
    },
    waitSeconds: 15
  });
  require( ["some/module", "my/module", "a.js", "b.js"],
    function(someModule,    myModule) {
        //This function will be called when all the dependencies
        //listed above are loaded. Note that this function could
        //be called before the page is loaded.
        //This callback is optional.
    }
  );
</script>

또한 data-main 진입점에서 require.config를 실행할 수도 있습니다. 

그렇지만 data-main 스크립트는 비동기적으로 불러온다는 것을 알고 있어야 합니다. 

data-main과 require.config가 항상 스크립트 로딩 전에 로딩된다고 잘못된 가정을 하는 다른 진입점의 스크립트는 피하는 것이 좋습니다.


또한 당신은 설정 객체를 require.js가 로딩되기 전에 전역 변수인 require로 정의할 수 있습니다. 

이렇게하면 값은 자동적으로 적용되게 됩니다. 

다음 예제는 require.js가 require()를 정의하자마자 불러올 몇가지 디펜던시를 정의하고 있습니다.

html
<script>
    var require = {
        deps: ["some/module1", "my/module2", "a.js", "b.js"],
        callback: function(module1, module2) {
            //This function will be called when all the dependencies
            //listed above in deps are loaded. Note that this
            //function could be called before the page is loaded.
            //This callback is optional.
        }
    };
</script>
<script src="js/require.js"></script>


var require = {} 와 같이 사용한는 것이 최선이며, window.require = {}와 같이 사용하지 않는 것이 좋습니다. 

뒤의 방법은 IE에서 제대로 동작하지 않을 것 입니다.



설정을 메인 모듈 로딩과 분리하는 몇가지 패턴이 있습니다.

지원되는 설정 옵션은 다음과 같습니다.

baseUrl 설정

모든 모듈 검색에 사용될 루트 패스입니다. 

위의 예제에서 "my/module"의 스크립트 태그는 src="/another/path/my/modules.js"를 가지게 될 것 입니다. 

baseUrl은 plain .js파일 (슬래시로 시작하거나, 프로토콜은 가지고 있거나, .js로 끝나는 디펜던시 문자열로 표현되는)을 불러올 때는 사용되지 않습니다. 

이러한 문자열들은 그대로 사용되며, 그렇기 때문에 a.js와 b.js는 위에 코드를 포함하고 있는 HTML페이지와 같은 디렉토리에서 불러오게 됩니다. 

설정에 명확한 baseUrl이 보이지 않는다면, 기본값은 require.js를 불러오는 HTML 페이지의 위치가 될 것 입니다. 

만약 data-main 어트리뷰트가 사용된다면 그 위치가 baseUrl이 될 것 입니다. baseUrl은 require.js가 로드될 페이지와 다른 도메인의 URL이 될 수 있습니다. 

RequireJS 스크립트 로딩은 도메인을 넘어서 사용가능합니다. 유일한 제한은 텍스트에 의해 텍스트 콘텐츠를 로딩할 때 입니다.


플러그인 : 적어도 개발할 동안은 이런 패스들은 페이지와 같은 도메인에 있어야 합니다. optimization tool은 인라인 text가 될 것 입니다. 

플러그인 리소소는 optimization tool을 사용한 후에, 당신은 텍스트를 참조하는 리소스를 사용할 수 있습니다. 

플러그인 리소스는 다른 도메인에 존재합니다.


paths 설정

path는 직접적으로 baseUrl의 아래에서 찾을 수 없는 모듈명을 맵핑합니다. 

패스를 설정은 path 설정이 "/"로 시작하거나 URL 프로토콜("http:와 같은")을 가지고 있지 않다면 baseUrl과 관련이 있다고 가정합니다. 

위의 예제 코드에서, "some/module"의 스크립트 태그는 src="/another/path/some/v1.0/module.js"가 될 것입니다. 

모듈명을 위해 사용되는 path는 확장자를 포함하지 않아야합니다. 

path 맵핑은 디렉토리일 수 있기 때문입니다. path 매핑 코드는 모듈명을 path와 맵핑할 때 자동으로 .js 확장자를 더해줍니다. 

만약 require.toUrl이 사용되었다면, 적절한 확장자를 더해줄 것입니다. 이것은 텍스트 템플릿에 관한 것일 수도 있습니다. 

브라우저에서 실행될 때 paths fallback을 명시할 수 있습니다. 이를 통해 CDN location에서 로딩을 하는 것이 가능해집니다. 

CDN location의 로딩을 실패했다면 local location에서 로딩할 수 있도록 할 수 있기 때문입니다.


bundles 설정

RequireJS 2.1.10 부터 사용가능하며, 다른 스크립트에서 찾을 수 있는 복수의 모듈ID 설정을 하도록 해줍니다. 

예를 들어 :

javascript
requirejs.config({
    bundles: {
        'primary': ['main', 'util', 'text', 'text!template.html'],
        'secondary': ['text!secondary.html']
    }
});

require(['util', 'text'], function(util, text) {
    //The script for module ID 'primary' was loaded,
    //and that script included the define()'d
    //modules for 'util' and 'text'
});

위의 설정 상태 : 모듈 'main', 'util', 'text', 'text!template.html'은 로딩 모듈ID를 'primary'로 사용할 수 있을 것입니다. 

모듈 'text!secondary,html'는 모듈ID 'secondary'로 사용할 수 있습니다. 

이것은 하나의 스크립트 내부에서 복수의 define()된 모듈이 있을 때 해당 모듈을 찾아야 할 때 설정이 가능합니다. 

이것은 자동적으로 bundle의 모듈ID와 모듈들과 바인딩을 시키지 않습니다. bundle의 모듈id는 단순히 모듈의 묶음을 위치시키는 데에 사용됩니다. 

paths 설정과 유사한 게 사용할 수 있지만, 이것은 매우 장황하며, paths 설정 루트는 그 설정 내에서 로더 플러그인의 리소스ID를 허용하지 않습니다. 

이것은 패스 설정값이 ID들이 아닌 path 부분이기 때문입니다. 


bundles 설정은 빌드를 실행한 후에 빌드 타켓이 존재하는 모듈ID가 아닐 때나, 빌드된 JS파일 안에 로더 플러그인 리소스를 가지고 있고 그 JS파일은 로더 플러그인에 의해 로드되지 않아야 하는 경우라면 유용합니다. 

key/value가 모두 패스가 아니라 모듈ID라는 점에 주목하세요. 

이것들은 절대 모듈ID이여야 하며, paths 설정과 map 설정과 같은 모듈ID 접미사들이 아닙니다. 또한 번들 설정은 map 설정과는 다릅니다. 

map설정은 일대일 모듈,ID 관계이며 bundle 설정은 복사의 모듈ID들을 bundle의 모듈ID에 설정한다는 점에서 차이가 있습니다.


shim 설정

디펜던시들과 exports, 그리고 디펜던시를 선언을 위해 define()을 사용하지 않는 "브라우저 전역" 스크립트들의 커스텀 초기화들을 설정하고 모듈값을 설정합니다. 

아래의 예제는 RequireJS 2.1.0+ 이상의 문법이며, backbone.js와 underscore.js 그리고 jquery.js가 baseUrl 디렉토리 안에 있다고 가정합니다. 

그렇지 않은 경우라면 이를 위해 paths 설정을 할 필요가 있을 것입니다.

javascript
requirejs.config({
    // 기억하세요  shim 설정은 non-AMD 스크립트에 대해서만  사용되어야 하며
    // 스크립트들은 define() 호출을 미리하지 않았어야 합니다.
    // 만약 AMD 스크립트들에 대해 사용한다면 shim 설정은 정상적으로 동작하지 않을 수도 있습니다.
    // 특히 exports와 init 설정이 동작하지 않으며 deps 설정이 꼬이게 될 것 입니다.
    shim: {
        'backbone': {
            //이 스크립트들의 디펜던시들은 backbone.js가 로딩되기
            //전에 로딩되어야 합니다.
            deps: ['underscore', 'jquery'],
            //한 번 로딩이되면 전역 모듈값으로 'Backbone'을
            //사용합니다.
            exports: 'Backbone'
        },
        'underscore': {
            exports: '_'
        },
        'foo': {
            deps: ['bar'],
            exports: 'Foo',
            init: function (bar) {
                // noConflict 를 지원하는 라이브러리들에 대해 이를 호출하거나
                // 다른 cleanup 작업을 할 수 있는 함수를 사용하는 것이 허용됩니다.
                // 그렇지만, 이러한 라이브러리를 위한 플러그인은 여전히 전역이 되려고 할 것 입니다.
                // 만약 이 함수가 값을 반환한다면, "exports" 문자열을 통해서 발견한 객체 대신에,
                // 이것이 모듈의 exports 값으로 사용될 것 입니다. 
                // Note : jQurey는 define()을 통해 AMD 모듈로 등록되었다면, 
                // 아래의 jQuery에 대한 접근 섹션을 참조하세요.
                return this.Foo.noConflict();
            }
        }
    }
});

// 그리고 나서 분리된 파일보다 늦게, 'MyModel.js"부르는 모듈이 정의되면
// 'backbone'을 디펜던시로 명시합니다.
// RequireJS는 shim 설정을 적절하게 'backbone'을 불러와 모듈 지역 참조에 넘겨주기 위해 사용합니다.
//  전역 backbone 역시 여전히 페이지에 남아있습니다.
define(['backbone'], function (Backbone) {
    return Backbone.Model.extend({});
});

RequireJS 2.0.* 버전에서 shim 설정은 문자열 대신에 함수가 될 수도 있습니다. 

이런 경우에 위에 보이는 "init" 프로퍼티와 같이 기능합니다. "init" 패턴은 RequireJS 2.1.0+ 버전에서 사용됩니다. 

이를 통해 exports에 관한 문자열값은 enforceDefine에 사용될 수 있습니다. 

그렇지만 함수적인 작업은 라이브러리가 로딩된 것을 알 수 있을 때 한번만 가능합니다.


jQuery와 Backbone 플러그인들처럼 모듈값을 export 할 필요가 없는 모듈들이라면 shim 설정은 디펜던시들의 배열 형태일 수 있습니다.

javascript
requirejs.config({
    shim: {
        'jquery.colorize': ['jquery'],
        'jquery.scroll': ['jquery'],
        'backbone.layoutmanager': ['backbone']
    }
});


만약 IE에서 404 로드를 감지하고 싶다면 paths fallback 또는 errback을 사용하고, 실제로 스크립트가 로딩됐는지 확인할 수 있도록 문자열 exports 값이 loader에 주어져야 합니다(init 에서 반환되는 값은 enforceDefine 체크에 쓰이지 않습니다.)

javascript
requirejs.config({
    shim: {
        'jquery.colorize': {
            deps: ['jquery'],
            exports: 'jQuery.fn.colorize'
        },
        'jquery.scroll': {
            deps: ['jquery'],
            exports: 'jQuery.fn.scroll'
        },
        'backbone.layoutmanager': {
            deps: ['backbone']
            exports: 'Backbone.LayoutManager'
        }
    }
});


shim 설정에 있어서 주목해야할 부분들 

  • shim 설정은 코드 관계만을 설정해야합니다. shim 설정의 일부인 모듈을 불러오거나 shim 설정을 사용할 때는 일반적인 require/define 호출이 필요합니다. shim 그 자체를 코드를 불러오는 트리거로 사용하지 마십시오. 
  •  shim된 스크립트에 대한 디펜던시로서만 다른 "shim" 모듈을 사용하십시오. 또는 디펜던시가 없으면서 전역을 생성을 한 후에 define()을 호출하는 AMD 라이브러리(like jQurey 또는 lodash)의 경우에도 사용할 수 있습니다. 반면에 AMD 모듈을 shim 설정 모듈에 대한 디펜던시로 사용한다면 AMD 모듈은 빌드가 실행될 때 shim된 코드가 빌드될 때까지 인식되지 않을 것이며 에러가 발생될 것 입니다. 궁극의 수정은 모든 shim된 코드를 부가적인 AMD define() 호출을 하도록 업그레이드 하는 것입니다. 
  • shim된 코드를 AMD define() 호출을 하도록 업그레이드하는 것이 불가능하다면 RequireJS 2.1.11 버전의 optimizer는 wrapShim build option을 가지고 있습니다. 이것은 빌드 시에 자동적으로 shim된 코드를 define()으로 감싸주는 작업을 합니다. 이것은 shim된 디펜던시의 스코프를 변경시키며, 그로 인해 항상 정상적으로 동작할 것이라는 보장을 할 수 없게 됩니다. 그렇지만 예를 들어 shim된 디펜던시가 Backbone의 AMD 버전에 의존하고 있다면 이것은 도움이 될 수 있습니다. 
  •  init 함수는 AMD 모듈에 대해 호출되지 않습니다. 예를 들어 shim init 함수는 jQuery의 noConflict를 호출할 수 없습니다. jQuery에 대한 접근을 위해 noConflict를 사용하기 위해 모듈을 맵핑하기를 살펴보십시오. 
  • shim 설정은 node상의 RequireJS에서 AMD 모듈을 실행할 때에는 지원되지 않습니다. (이는 최적화 도구 사용을 위해 동작합니다.) shim된 모듈에 의존성을 가지고 있을 때 Node 안에서는 fail이 발생할 수 있습니다. Node는 브라우저에서와 같은 전역 환경을 제공하지 않기 때문입니다. RequireJS 2.1.7의 경우에는 console에서 shim 설정이 동작할 수도 있고 그렇지 않을 수도 있다는 경고를 줄 것 입니다. 만약 이 메시지를 숨기고 싶다면 requirejs.config({surpress : {nodeShim : true }});



"sihm" 설정을 위한 최적화도구의 중요한 부분들

  • 파일을 shim 설정 어디에서 찾을 것인지 명시하기 위해 mainConfigFile 빌드 옵션을 사용해야만 합니다. 그렇지 않으면 optimizer는 shim 설정을 알 수가 없습니다. 다른 옵션은 빌드 프로파일 안의 shim 설정과 중복하게 됩니다. 
  •  빌드 시에 CDN과 shim 설정을 섞지 마십시오. 예제 시나리오 : 당신은 jQuery를 CDN으로 부터 로딩합니다. 그렇지만 jQuery에 의존하는 Backbone의 stock 버전과 같은 파일을 shim 설정을 불러오기 위해 shim 설정을 사용할 수 있습니다. 빌드를 할 때 빌드된 파일에서 jQuery를 확인하고 CDN에서 불러오지 않습니다. 그렇지 않으면 Backbone은 빌드된 파일에서 inline하게 될 것이며 이것은 CDN에서 불러온 jQuery가 로딩되기 전에 실행될 것입니다. 이것은 shim 설정이 모든 디펜던시가 로딩될 때까지 파일의 로딩을 딜레이시키기고 다른 define 자동 래핑을 하지 않았기 때문입니다. 빌드 후에 디펜던시는 이미 inline되었고, shim 설정은 define()되지 않은 코드를 이후까지 딜레이 시키지 못합니다. 빌드 후에 define()된 모듈은 CDN으로 로딩된 코드와 작업을 하게 됩니다. 그것들은 적절하게 그들의 소스를 디펜던시가 로딩될 때까지 실행되지 않는 define 팩토리 함수로 래핑해주었기 때문입니다. 
  • 그렇다면 여기서 배울 수 있는 점은 무엇일까요 : shim 설정은 모듈화되지않은 코드, 레거시 코드에 대한 stop-gap measure 입니다. define()된 모듈을 사용하는 것이 더 낫습니다. 
  •  local, 복수 파일 빌드, CDN이 지원됩니다. 어떤 shim된 스크립트가 있다면, 그의 디펜던시는 shim된 스크립트 실행전에 로딩되어야만 합니다. 이것은 shim된 스크립트를 포함하는 빌드레이어 안에서 디펜던시를 직접적으로 빌드하거나 또는 이의 디펜던시들을 shim된 스크립트를 가지고 있는 빌드레이어 내에서 require([], function(){}) 호출로 불러온 후에 require([]) 호출을 실행해야합니다. 
  • 만약 당신이 ugilifyjs를 사용해서 코드를 압축한다면, uglify 옵션을 toplevel을 true로 놓지 말거나 커맨드 라인을 사용한다면 -mt를 넘겨주면 안됩니다. 이 옵션은 shim이 exports를 찾기 위해 사용하는 전역 name들을 훼손시킵니다.


map

모듈 prefix가 주어져있을 때 모듈을 주어진 ID로 부르는 대신에 다른 모듈 ID로 대체하는 것입니다.

이러한 종류의 기능은 보다 큰 프로젝트를 할 때 매우 중요합니다. 

예를 들면 두 개의 다른 버전의 'foo'를 사용할 필요가 있으면서 서로 협력해야하는 두 묶음 모듈을 가지고 있는 프로젝트 같은 곳에서 말입니다. 

context-backed multiversion support는 어렵습니다. path설정은 모듈 ID들에 대한 루트 패스 를 설정해줄 뿐입니다. 

하나의 모듈ID를 다른 것에 매핑해주는 것은 아닙니다.


map 예제 :

javascript
requirejs.config({
    map: {
        'some/newmodule': {
            'foo': 'foo1.2'
        },
        'some/oldmodule': {
            'foo': 'foo1.0'
        }
    }
});


모듈은 디스크 상에 다음과 같이 위치되어 있습니다.

  • foo1.0.js 
  • foo1.2.js 
  • some/ newmodule.js 
  • oldmodule.js


"some/newmodule"이 'require('foo')'를 하게되면 fool.2.js 파일을 가져온다. 

'some/oldmodule'이 'require('foo')'를 하게되면 fool.0.js 파일을 가져올 것이다. 

이 기능은 오직 스크립트가 define()을 호출하고 익명 모듈을 등록하는 실제 AMD 모듈일 때만 잘 동작합니다. 

또한 map 설정에 있어서 절대 모듈ID만을 사용합니다. 상대ID('../some/thing'과 같은)는 동작하지 않습니다.


"*" map 값도 지원을 합니다. 

이것은 "모든 모듈이 로딩되면, 이 map 설정을 사용하여라" 와 같은 의미가 됩니다. 더 명시적인 map 설정이 있다면, 그것은 "*" 설정에 우선합니다. 

예를 들어 :

javascript
requirejs.config({
    map: {
        '*': {
            'foo': 'foo1.2'
        },
        'some/oldmodule': {
            'foo': 'foo1.0'
        }
    }
});

이것은 "some/oldmodule"을 제외한 모든 모듈에 대해 "foo"를 사용하게 되면 "foo1.2"를 사용하라는 의미가 됩니다. 

오직 "some/oldmodule" 만이 "foo"를 찾찾게 되면 "foo1.0"을 사용합니다.


map 설정으로 빌드를 할 때 map 설정은 optimizer에 들어가야 하며, 산출물 빌드는 map 설정을 준비하는 RequireJS 설정 호출을 반드시 포함하고 있어야 합니다. optimizer는 빌드 동안에 ID명을 변경하지 않습니다.

프로젝트 내의 몇몇 디펜던시 참조가 런타임 변수 상태에 의존할 수 있기 때문입니다. 

그렇기 때문에 optimizer는 빌드 후에 map 설정을 필요로 하지 않습니다.


config

설정정보를 모듈안으로 넘기려는 일반적인 필요성이 있었습니다. 

설정 정보는 보통 애플리케이션의 일부로 알려져 있고, 그것들은 모듈 안으로 넘길 방법이 필요했습니다. 

RequireJS에서는, requirejs.config()의 config 옵션을 통해 사용할 수 있습니다. 

모듈들은 이 정보를 특정 디펜던시 "모듈"을 요청하고 module.confing()를 호출 함으로써 정보를 읽을 수 있게 됩니다. 

예제는 다음과 같습니다 :

javascript
requirejs.config({
    config: {
        'bar': {
            size: 'large'
        },
        'baz': {
            color: 'blue'
        }
    }
});

// bar.js, which uses simplified CJS wrapping:
// http://requirejs.org/docs/whyamd.html#sugar
define(function (require, exports, module) {
    //Will be the value 'large'
    var size = module.config().size;
});

// baz.js which uses a dependency array,
// it asks for the special module ID, 'module':
// https://github.com/jrburke/requirejs/wiki/Differences-between-the-simplified-CommonJS-wrapper-and-standard-AMD-define#wiki-magic
define(['module'], function (module) {
    //Will be the value 'blue'
    var color = module.config().color;
});


config를 패키지에 넘겨주게 되면, 패키지ID가 아닌 패키지 안의 메인 모듈을 대상으로 하게됩니다.

javascript
requirejs.config({
    //Pass an API key for use in the pixie package's
    //main module.
    config: {
        'pixie/index': {
            apiKey: 'XJKDLNS'
        }
    },
    //Set up config for the "pixie" package, whose main
    //module is the index.js file in the pixie folder.
    packages: [
        {
            name: 'pixie',
            main: 'index'
        }
    ]
});


package

CommonJS 페키지에서 설정 로딩 모듈입니다. 

더 많은 정보를 원하신다면 package topic을 보시기 바랍니다.


nodeIdCompat

Node는 모듈ID example.js와 example을 같게 처리한다.

RequireJS에서는 둘을 다른 것이 됩니다. 만약 npm에서 설치된 모듈을 사용하게 되었다면, 이 설정값을 true로 해서 resolution 이슈를 피하기 바랍니다.


waitSeconds

스크립트 로딩을 기다리는 시간입니다. 초단위입니다. 

타임아웃되지 않도록 이것을 설정할 수 있습니다. 

디폴트는 7초입니다.


context

로딩 context에 주어지는 이름입니다. 

이것은 require.js가 top-level require 호출이 유니크한 context 문자열을 명시해놓는 동안 한 페이지 안에 다양한 버전의 모듈을 로딩하도록 해줍니다. 

정확하게 사용하기 위해서 Multiversion Support 섹션을 살펴보기 바랍니다.


deps

로딩할 디펜던시들의 배열입니다. require가 require.js가 로딩되기 전에 config 객체로 정의될 때 유용합니다. 

deps는 require([]) 호출과 비슷하게 사용하면 되지만, 로더가 설정을 진행하자마자 실행됩니다. 

이것은 모듈들에 대한 요청을 시작하는 다른 require() 호출에 의해 막히는 일이 없으며, 이것은 단지 설정 블락의 부분으로써 비동기적으로 로딩되는 모듈을 명시하는 방법일 뿐입니다.


callback

deps가 로딩된 후에 실행되는 함수입니다. 

require가 requre.js가 로딩되기 전에 설정 객체로서 정의될 때와 설정의 deps 배열이 로딩된 후에 require될 함수를 명시하고자 할 때 유용합니다.


enforceDefine

이것이 true로 설정되면, 스크립트 로딩이 define()을 호출하지 않았거나 shim exports 문자열 값이 체크될 수 있을 때 에러가 던져집니다. 

Catching load failure in IE를 보시면 더 많은 정보를 얻을 수 있습니다.


xhtml

true로 설정되었다면, document.createElementNS()를 통해 스크립트 엘리먼트를 생성할 것입니다.


urlArgs

RequireJS 리소스를 불러오기 위해 사용하는 URL에 추가적인 쿼리 문자열 인자를 덧붙입니다. 

브라우저나 서버가 올바르게 설정되지 않았을 때 캐쉬를 날리는 가장 효과적인 방법입니다. 

urlArgs를 사용해 캐쉬를 날리는 예제는 다음과 같습니다.

javascript
urlArgs: "bust=" +  (new Date()).getTime()

개발하는 동안 이것을 사용하는 것이 유용할 수 있지만, 코드를 deploying 할 때에는 제거해야합니다.


scriptType

RequireJS를 사용해서 문서 안에 삽입될 스크립트 태그의 type="" 어트리뷰트의 값을 명시합니다. 

디폴트는 "text/javascript" 입니다. 파이어폭스의 Javascript 1.8의 기능을 이용하려면, "text/javascipt;version=1.8"이라고 작성하면 됩니다.


skipDataMain

RequireJS 2.1.9부터 사용가능합니다 

이것이 true로 설정되어 있다면 data-main 어트리뷰트의 스케닝을 스킵하고 모듈 로딩을 시작합니다. 

RequireJS가 페이지 상의 RequireJS 라이브러리와 상호작용할 유틸리티 라이브러리 안에 들어있을 때 embeded 버전이 data-main 로딩을 하지 말아야 할 때 유용합니다.



Jaehee's WebClub


'Web Tech > RequireJS' 카테고리의 다른 글

RequireJS 의 모듈  (3) 2016.01.25
RequireJS - module loader(모듈 로더)  (0) 2016.01.25
RequireJS - AMD의 이해와 개발  (0) 2015.06.11
javaScript 모듈의 양대 산맥,CommonJS vs AMD  (0) 2015.06.11

댓글을 달아 주세요