JavaScript 모듈화를 위한 RequireJS 핵심정리

웹 어플리케이션들이 점점 성장해가면서, 형님격인 데스크탑 어플리케이션들로부터 다양한 테크닉과 패턴들을 상속받고 있습니다. 그것들 중에 하나가 종속성 관리입니다. 자바스크립트 자체로는 이러한 기능을 제공해주지 못하기 때문에, JAVA나 그밖에 다른 서버사이드 언어처럼 “a.js” 파일로부터 “b.js” 파일을 include 할 수 있는 방법이 없습니다. 하지만 이를 해결할 수 있는 많은 방법이 존재하고 이를 통해 자바스크립트로 코딩할 때 종속성 관리에 도움을 받을 수 있습니다. 여기에서는 RequireJS로 종속성을 관리하는 방법에 대해 소개하고자 합니다.

RequireJS는 자바스크립트 파일/모듈 로더입니다. 브라우저상에서 사용하는데 최적화되어 있지만 Rhino나 Node.js와 같은 다른 자바스크립트 환경에서도 사용될 수 있습니다. 대규모 웹 어플리케이션일수록 RequireJS와 같은 모듈 방식의 스크립트 로더를 사용한다면 웹 어플리케이션의 품질과 속도를 향상시킬 수 있습니다.

가장 먼저 해야할 일은 require.js 파일을 페이지에 포함시키는 일입니다.

 

그런 후에 다음과 같이 설정합니다.

 

무언가를 포함시키고 싶다면, 그때마다 require 함수를 사용해서 가져온다는 지극히 단순한 구조입니다. 이렇게 require 함수로 무언가를 로드하면, 로드하기 전에 또 거기에 종속성이 있는지 판별한뒤에 종속성이 있는 것들을 먼저 가져온 후 require로 로드하려는 것을 불러옵니다. 그 후에 콜백함수가 실행되는 것입니다.

이 코드가 실행되면 alert box에 jQuery의 버전이 표시될 것입니다. 하지만 이런 방식으로 불러오는 것은 권장할 만한 일이 아닙니다. jQuery가 실행될 때 전역 네임스페이스가 우리가 잘 아는 $ 변수로 오염되기 때문입니다. 물론 한가지 버전의 jQuery만 쓴다면 큰 문제가 되지 않지만, 만약 동시에 두 개의 다른 버전의 jQuery를 써야한다면 어떻게 해야할까요?

 

RequireJS modules

RequireJS의 모듈 패턴은 전역 네임스페이스에 덮어쓰거나 오염시키는 일 없이, 별도의 컨텍스트에서 자바스크립트를 실행한 뒤에 결과를 다시 호출한 곳으로 리턴해줄 수 있습니다.

모듈은 전통적인 스크립트 파일과는 달리 전역 네임스페이스 오염을 피할 수 있는 well-scoped 객체를 선언한다는 점에서 차이가 있습니다. 모듈은 해당 모듈의 디펜던시 목록을 명시할 수 있고 전역 객체에 대한 참조 없이도 이러한 디펜던시들을 모듈 상의 함수에 인자(arguments)로 받음으로써 핸들링할 수 있게 해줍니다.

다음과 같은 방법으로 jQuery를 모듈화할 수 있습니다.

 

주의해서 살펴볼 점은, 위의 예제에서 jQuery가 기본적으로 전역 객체로 선언되는 것을 막기위해 jQuery.noConflict 를 사용해서 jQuery 객체를 가져왔다는 사실입니다.
이 모듈화된 jQuery 라이브러리를 사용하려면, 다음과 같은 방식으로 사용하면 됩니다.

 

중요한 점은 jQuery 객체는 이제 오직 모듈 안에 선언된 function scope 안에서만 사용가능하다는 점입니다. 이러한 방식으로 이제 어플리케이션에서 충돌없이 다른 두 종류의 jQuery 버전을 안전하게 사용할 수 있습니다.

 

 

RequireJS 사용예

가장 기본적인 형태의 사용예를 통해 실제 어떤 식으로 RequireJS를 활용할 수 있는지 확인해 봅시다.

먼저 index.html 파일은 아래와 같이 구성할 수 있을 것입니다.

 

위와 같이 require.js 파일을 로드해주는 부분에 data-main 속성을 설정해서 RequireJS가 로드된 후 바로 로드해서 실행해줄 JavaScript 파일을 지정해줄 수 있습니다. 위의 예에서는 require.js 파일이 로드된 후에 바로 js 폴더 아래에 main.js 파일을 불러와서 실행하게 됩니다.

main.js 파일은 아래와 같이 구성될 수 있습니다.

 

위에서 볼 수 있는 것과 같이 require.config 를 통해 requireJS의 기본 설정을 변경할 수 있습니다.

먼저 baseUrl 옵션을 통해 불러올 JavaScript 파일들의 기본 경로를 정해 줄 수 있습니다. 만약 data-main 속성이 사용되었다면, 그 경로가 baseUrl이 됩니다.

path는 baseUrl 아래에서 직접적으로 찾을 수 없는 모듈명들을 위해 경로를 매핑해주는 속성입니다. ”/”로 시작하거나 “http” 등으로 시작하지 않으면, 기본적으로는 baseUrl에 상대적으로 설정하게 됩니다. path는 또한 확장자를 명시하지 않은 module명을 위해 사용될 수 있는데, path 매핑 코드는 자동적으로 .js 확장자를 붙여서 모듈명을 매핑합니다.

AMD(asynchronous module definition) 형식을 지원하지 않는 라이브러리의 경우 shim을 사용해서 모듈로 불러올 수 있습니다.  아래 사이트를 참고하면 shim 옵션에 대한 이해를 높일 수 있습니다.
http://gregfranko.com/blog/require-dot-js-2-dot-0-shim-configuration/

이 외에도 다양한 옵션들이 존재하는데, 아래의 requireJS 사이트를 참고하면 해당 옵션들에 대한 자세한 정보를 얻을 수 있습니다.
http://requirejs.org/docs/api.html

 

설정을 마친 뒤에, require 혹은 requirejs 함수를 사용해서 디펜던시를 로드하는 것을 예제를 통해 확인할 수 있습니다.

디펜던시가 로드된 후에 콜백 함수가 호출되는데, 주의할 점은 콜백함수 호출 시점이 페이지 로드 완료 시점보다 빠를 수 있다는 점입니다. 따라서 DOM 조작 등을 수행할 경우에는 이 부분을 주의해서 로직을 작성해야 합니다.

 

결론

RequireJS 외에도 Curl.js 라는 라이브러리 역시 거의 유사한 문법으로 모듈화에 사용할 수 있고, HeadJS나 LazyLoad, LABjs 등도 동적 스크립트 로딩에 활용할 수 있지만, 역시 RequireJS가 다른 라이브러리에 비해 가장 많은 지지도를 차지하고 있습니다.

여기에서는 internationalization 이나 build process 와 같은 RequireJS에서 사용가능한 다양한 기능들에 대해 모두 설명하지는 못 했습니다. RequireJS에 대한 더 자세한 활용법은 RequireJS documentation을 통해서 얻을 수 있을 것입니다.

하지만 여기에서 웹 어플리케이션의 모듈화에 대한 작은 안목과 활용 방법은 충분히 얻을 수 있었기를 기대하며 글을 마칩니다.

 

재하 안 | 2013년 5월 9일



출처 - http://jcf.daewoobrenic.co.kr/blog/?p=235








2013. 4. 20.

RequireJS – JavaScript 파일 및 모듈 로더


RequireJS?

RequireJS는 JavaScript 파일과 모듈 로더이다.
RequireJS는 브라우저에 최적화되어 있지만 Rhino나 NodeJS등의 환경에서도 사용할 수 있다. RequireJS같은 모듈 로더를 사용하면 당신의 코드의 성능과 품질이 좋아질 것이다
Java나 Python 같은 잘 정립된 언어에서는 객체의 모듈화를 언어 자체에서 잘 지원하지만 JavaScript는 그렇지 않다. 

또한, 프로그램이 커질수록 스크립트가 중복되어 발생하는 경우가 발생할 수 있고 그런 경우에 코드를 common.js, event-binder.js 등의 코드로 나누어 관리하는경우가 많아지는데, 이 경우에도 분리한 코드를 잘 로딩하지 않으면 코드간 의존성이 망가지며 프로그램 자체의 구성이 엉성해지는 경우도 많다. 

RequireJS는 자바스크립트 파일을 동적으로 로딩할 수 있고, AMD 모듈화를 적용한 코드라면 모듈로서도 사용할 수 있으며 (그렇지 않아도 사용할 수 있는 방법이 있다 아래에서 더 살펴보자) 자바스크립트 코드간의 의존성도 줄 수 있다. 

또, 앞서 말했듯이 어떠한 자바스크립트 코드가 실행되려면 다른 스크립트가 먼저 로딩되어야 한다거나 하는 경우가 있는데, 자칫 스크립트 로딩의 순서가 꼬일 경우 에러를 뱉어내며 동작하지 않을 수 있다. 

RequireJS를 사용하면 코드간 의존성을 줌으로서 아예 그러한 경우를 막을 수 있고, 좀 더 체계적인 소스코드를 만들어낼 수 있다. 장점만을 죽 늘어놓았는데 자세한건 좀 더 살펴보자.  

JavaScript에게 모듈이란

먼저 모듈에 대해 간단히 짚고 넘어가보자. 

모듈의 개념은 Divide and Conquer 로 설명되는 각 기능(알고리즘)의 분할과 그 분할의 결합으로 생각해볼 수 있다. 

보통의 성숙된 언어에서는 이러한 모듈화를 언어 차원에서 지원하고 있는데, 예를 들어  java의 경우에는 모듈이 instance로 생성되어지며 모듈끼리의 구분은 package로 구분된다. 

그리고 모듈의 구현은 접근 제어자(private, public 등)의 사용으로 캡슐화를 보장하며, 필요한 것만 공개해서 그 모듈을 사용하려는 사용자가 쓸데없이 많은 지식을 가질 필요 없이 부담없이 사용할 수 있다.

JavaScript는 이러한 모듈화나 접근 제어를 언어레벨에서 명시적인 지원을 하지 않으며 package 등의 구분도 없기에... 파일간의 변수 충돌이나 전역공간 변수 난립(global scope pollution)등의 문제가 발생할 요지가 많다. 

보통 이러한 경우를 고려한 JavaScript의 일반적인 모듈 구현은 다음과 같다
(function(exports) {
  "use strict";

  // private 변수
  var name = "I'am a module";

  // 외부에 공개할 public 객체
  var pub = {};

  pub.getName() {
    return name;
  }

  // 외부 공개
  exports.aModule = pub;

})(exports || global);

(exports || global).aModule.getName(); // I'm a module
이러한 구현은 변수를 private 화 할 수 있으며 그로 인한 캡슐화로 모듈 사용이 쉬운 장점이 있지만, 여러개의 모듈을 선언하면서 exports 객체에 프로퍼티가 겹칠 경우 앞서 선언된 공개 속성은 덮어써지는 문제가 있고,  모듈간 의존성이 있을때 의존성을 정의하기가 매우 어렵다. 

그리고 익명 함수와 exports 객체를 사용하는 애매한 코드로 인해 눈에 잘 들어오지 않는다. 이러한 경우를 해결하기 위해 여러 Module Loader가 공개되어 있는데,  그 중 하나가 RequireJS이다. RequireJS에서는 모듈의 고유성과 의존성을 잘 지원하고 있다. 

RequireJS는 AMD 모듈 로딩 표준을 따르기에 기본적으로 모든 모듈이 비동기적이다.

모든 모듈은 비동기적으로 명시적으로 요청하거나 의존성이 있을 때 로딩(Lazy Loading) 된다. 필요한 자바스크립트 파일을 어플리케이션 시작 전 전부 로딩하지 않고, 실제 필요한 때 (사용자의 입력이나 메소드 호출 등의 특별한 경우) 에 로딩하게 할 수 있어서 전체적인 페이지의 속도 향상에도 도움이 된다.  

 

설치법

설치법은 간단하다. 아래와 같이 그냥 스크립트 태그를 쓰면 끝이다.
<script type="text/javascript" data-main="main" src="/js/lib/requirejs.js"></script>
위 태그를 주의깊게 보면 data-main 이라는 속성에 main 이라는 값이 할당되어 있는데, 이 속성은 옵션으로 이 속성을 주게 되면 requirejs가 전부 로딩되면 저 경로의 속성 이름에 해당하는 파일을 자동으로 로딩한 뒤 실행한다. 

경로는 절대 경로가 아니면 requirejs 기준의 상대 경로를 따른다. 모든 모듈의 경로 또한 requirejs가 로딩되는 경로에 상대적이나, 나중에 설정을 통해 바꿀 수 있다. 
  

모듈 이름과 File Scope

RequireJS에서 모든 모듈은 이름이 주어지며, 모듈 이름은 보통 파일 경로가 된다. 

파일 경로는 사실 FileSystem에서 유일하게 존재할 수 있으므로 같은 이름을 가진 모듈이면 모듈끼리 덮어써지거나 충돌할 일이 없어진다. 

예를 들어보자. 

  • 모듈 파일이 /js/controller/main.js 에 존재한다면 모듈 이름은 "/js/controller/main" 이 된다.
  • 모듈 파일이 /js/controller/list.js 로 새로운 모듈을 생성하여 저장하면 역시 모듈 이름은 "/js/controller/list" 가 된다. 
(JavaScript 파일이고, 상대 경로일 경우 .js가 생략된다. )

이제 실제 모듈을 정의하고 사용하는 방법을 알아보자.  

 

RequireJS 모듈 정의 및 사용

이러한 문법을 사용한다.
// 일반적인 RequireJS 모듈 정의
define(function() {

  "use strict";

  var exports = {
    version: "1.0"
  };

  var private1 = "private1";
  var private2 = "private2";

  exports.doSomething = function() { /* ... do something ... */ };
  exports.stopSomething = function() { /* ... stop something ... */ };

  return exports;
});
모듈의 정의에는 define 이라는 글로벌 함수를 사용하며 인자로 함수 하나를 받는데 그 함수에서 반환하는 객체가 모듈이 된다. 

인자 함수는 일종의 객체 팩토리인 셈이다. 

JavaScript는 함수 자체가 스코프를 생성하므로 이 안에서 필요한 만큼의 private 를 선언하고, 외부 공개가 필요한 객체나 함수는 return 으로 반환하면 된다. 다수를 공개하고 싶다면 객체 형식으로 묶어 반환하면 된다. 정리해보면 다음과 같은 코드이다.
// 의존성이 없는 모듈의 정의
define([팩토리 함수]);
그런데 잊은게 있다. 

분명 앞에서 RequireJS는 모듈간의 의존성을 정의하는 방법을 제공한다고 했다. 이대로는 아까 순수하게 JavaScript로 만든 모듈 코드와 별반 다를게 없어 보인다. 

물론 의존성을 줄 수 있다. 

팩토리 함수 앞 인자로 생성될 모듈이 "의존하고 있는 모듈 이름을 문자열로 담은 배열" 을 주면 된다.
// 의존성이 있는 모듈 정의
define([의존 모듈 이름 배열], [팩토리 함수]);
한번 실제 모듈 코드를 만들어 보자. 일단 파일 구조는 다음과 같다고 가정한다.
  • /main.js
  • /App.js
  • /sub/Logger.js
  • /sub/MainController.js
먼저 만들 것은 간단한 로그 모듈이다. 이 코드는 Logger.js 이다
// @file Logger.js
define(function() {

  "use strict";

  var console = global.console || window.console;
  var exports = {
    version: "0.1.0",
    author: "javarouka"
  };

  exports.log = function() {
    var args = Array.prototype.unshift.call(arguments, new Date().toLocaleString());
    console.log.apply(console, arguments);
  };

  return exports;

})
로그 모듈로 일반적인 로그에 앞에 로그가 찍히는 현재 날자를 추가해준다. 

팩토리 함수 안에 var로 선언된 것들은 private 으로 내부에서만 사용하며, exports 객체의 속성으로 지정된 log 함수만 공개되어 외부에서는 log를 통해 이 모듈을 사용할 수 있다. 

이제 이 로그 모듈에 의존성이 있는 모듈을 만들어보자.
// @file MainController.js
// @dependency Logger.js
define(["sub/Logger"], function(logger) {

  "use strict";

  var exports = {
    type: "controller",
    name: "Main"
  };

  var bindEvent = function() {
    logger.log("bind event...")
    // do something
  };

  var view = function() {
    logger.log("render ui");
    // do something
  };

  exports.execute = function(routeParameters) {
    logger.log(exports.name + " controller execute...");
    // do something
  }

  return exports;

});
일단 팩토리 함수 앞에 인자로 의존성 모듈의 이름 배열을 주는데 상대 경로일 경우 .js 확장자를 생략해야 한다. "sub/Logger" 라고 주면 된다. 

주게 되면 팩토리 함수의 인자로 전달되는데 배열에 지정한 순서대로 전달된다. 

이 모듈에서는 logger 라는 변수로 사용하고 있다. 마지막으로 App.js.
// @file ApplicationContext.js
// @dependency sub/MainController.js
define(["sub/MainController"], function(main) {

  "use strict";

  // do something...

});
App.js 는 MainController.js에 의존성이 있다. App.js 가 로딩되려면 의존성에 따라
  1. Logger.js
  2. MainController.js
  3. App.js
순서대로 로딩되게 될 것이다. 

이제 실제 코드를 사용할 main.js 를 작성하자
// @file main.js
// @dependency App.js, sub/Logger.js
require([ "App", "sub/Logger" ], function(app, logger) {

  "use strict";

  app.start();
  logger.log("Application start");

}, function(err) {
  // ERROR handling
});
모듈 정의가 아닌 단순히 코드를 실행할 때는 require 함수를 사용한다. 

require 는 define과 비슷하게 첫번째 인자로 의존성, 두번째 인자로 실행 코드 함수, 세번째 인자는 옵션으로 에러 핸들러이다. 

실행 코드 함수에서 코드상에서 잡을 수 있는 오류가 나거나,  로딩에 실패할 경우 실행된다.  

여기서 짚고 넘어갈 것이 있다. Logger.js 는 두 부분에서 의존성이 있다. 

이 경우에는 먼저 로딩되는 모듈이나 코드에서 한번 로딩되면, 그 다음에는 모듈을 다시 로딩하지 않고 로딩된 모듈을 다시 사용한다. 

Java Spring의 싱글톤 레지스트리와 살짝 비슷하다. 

정리하면 다음과 같다.
  • define: 모듈을 정의할 때
  • require: 정의된 모듈에 의존성을 주는 코드를 작성할 때
이제 대략적인 사용법은 다 정리된 것 같다. 다음은 설정법을 살펴보자 
  

환경 설정

RequireJS는 몇가지 설정을 통해 사용자의 환경에 더욱 잘 조정해 맞출 수 있다. 

문법은 다음과 같다.
// 이 코드를 RequireJS가 로딩된 뒤 기타 모듈을 로딩하기 전에 둔다.
require.config({
  baseUrl: "/js/some/path", // 모듈을 로딩할 기본 패스를 지정한다.

  // 모듈의 기본 패스를 지정한다
  // 모듈의 이름과 실제 경로를 매핑할 수 있어 별칭(alias) 기능도 할 수 있다
  paths: {
    "javarouka": "modules/javarouka", // 이 모듈은 /js/some/path/module/javarouka.js 경로.

    // 모듈 패스를 배열로 주게 되면 먼저 앞의 URL로 로딩해보고 안되면 다음 경로에서 로딩한다.
    // CDN 등을 사용할 때 좋다.
    "YIHanghee": [
      "https://cdn.example.com/YIHanghee",
      "modules/YIHanghee"
     ]
  },
  waitSeconds: 15 // 모듈의 로딩 시간을 지정한다. 이 시간을 초과하면 Timeout Error 가 throw 된다
});
이러한 설정 파일은 다음과 같이 별도의 분리된 파일로 나눌 수 있다
<script type="text/javascript">
var require = {
  // 설정 내용
};
</script>
<script type="text/javascript" src="/js/requirejs.js"></script>
다음에는 RequireJS 형식으로 작성되지 않은 다른 코드를 RequireJS에 포함시켜 사용하며 코드간 의존성을 주는 방법을 알아보겠다.

 

구 소스코드(global-traditional)와 같이 사용하는 법 - Shim

그런데 ReuireJS를 실무에 바로 적용하려면 어려움이 따른다. 바로 하위 호환성 문제이다.

기존의 구(global - traditional) 코드 대부분은 아마도 define이나 require를 사용하지 않은 소스코드가 많다. 그리고 그것들도 나름의 의존성을 가지고 있을 수 있다. 

이것들을 RequireJS에서도 온전히 사용하려면 그냥은 안된다. 이것을 지원하기 위해 RequireJS에는 shim 이라는 기능이 있다. 설정 파일에 shim 속성으로 미리 구 코드의 의존성을 정의할 수 있다. 

문법은 다음과 같다.
requirejs.config({
  paths: {
    // jquery 로딩 시 필요한 경로를 지정한다.
    'jquery': [ 
      '/js/jquery', 
      '//ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min' 
    ],
    // underscore 도 마찬가지.
    'underscore': [ 
      '/js/underscore.min', 
      '//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.2/underscore-min' 
    ],
    // backbone
    'backbone': [
      'js/backbone.min',
      '//cdnjs.cloudflare.com/ajax/libs/backbone.js/1.0.0/backbone-min'
    ]
  },
  shim: {
    // underscore에 대한 shim 속성을 정의한다.
    'underscore': {
      // 반환될 객체는 exports속성으로 정의한다.
      // underscore는 아래와 같은 _ 이름으로 공개되는 것이 사용이 직관적이다.
      exports: function() { return _; }
    },
    // shim 속성의 키로 모듈 이름을 지정하고 내부 속성을 나열한다.
    // backbone은 underscore와 jquery 에 의존성이 있다.
    'backbone': {
      // 백본이 의존하는 underscore와 jquery를 deps 속성에 정의
      // 이름은 위에 이미 지정한 별칭(키 이름)으로 해도 된다.
      deps: ['underscore', 'jquery'],
      // 반환될 객체를 정의한다.
      // 문자열로 줄 경우,
      // 글로벌 객체에서 저 속성을 키로 하는 객체가 모듈이 된다.
      // 함수를 줄 경우,
      // 함수의 실행 결과로 리턴되는 객체가 모듈이 된다.
      exports: 'Backbone'
    }
  }
});
먼저 모듈이 로딩될 경로를 paths 속성의 키로 정의한 뒤 shim 속성에서 정의한 코드에 대한 의존성을 정의한다. 


"backbone" 키로 지정된 deps 속성에는 앞에서 했던 define 처럼 배열 형태로 의존성을 정의하고, exports 속성으로 팩토리 함수를 정의했다. 

이렇게 설정해두면 "backbone" 이라는 모듈 이름으로 RequireJS 모듈처럼 사용을 할 수 있다.
exports 속성에 문자열을 주면 그 문자열에 해당하는 전역의 속성이 define에서 팩토리 함수에서 리턴하는 객체가 되며, 함수를 줄 경우 반환되는 객체를 지정해줄 수 있다. 

exports에 함수를 지정하는 경우는 팩토리 함수와 동일하게 이 shim 모듈이 반환하는 모듈을 조정할 때 유용하다. 

가령 prototype.js와 jQuery를 같이  사용할 경우에는 $ 변수 충돌이 일어나므로 반드시 jQuery에서 prototype.js를 로딩하기 전에 jQuery.noConflict 를 호출해야 한다. 

이럴 경우 RequireJS에서는
"jquery": {
  exports: function() {
    var jQuery = jQuery.noConflict();
    return jQuery;
  }
},
"prototype": {
  deps: [ "jquery" ],
  exports: "$"
}
과 같은 방식을 적용할 수 있겠다. 두 모듈이 안전하게 로딩될 것이다.

 

마치며

JavaScript 는 사용하기 편한만큼 편함에 너무 의존하다보면 돌이킬 수 없는 스파게티코드나 중복 코드 발생이 많아질 수 있다. 

RequireJS는 이러한 경우의 한 대안이 될 수 있으며, 모듈 프로그래밍을 통해 좀 더 체계적인 프로그래밍을 가능하게 해 주며, 브라우저 지원도 IE6 이상부터 지원하는 괜찮은 호환율을 보여준다. 

지금 자신의 프로젝트를 보라. 

혹시 스파게티, 반복 코드가 보인다면, 바로 RequireJS를 한번 고려해볼 때다.



출처 - http://blog.javarouka.me/2013/04/requirejs-javascript.html






이 글은 @verekia 씨가 작성한 Build a simple client-side MVC app with RequireJS를 허락을 받아 번역한 글입니다.

웹 개발자라면 흔히 파일 하나로 자바스크립트 코드를 짜기 시작합니다. 그리고 그 코드가 점점 커져서 나중에는 수정하기가 정말 어려워지죠. 이런 문제의 해결하기 위해서 코드를 여러 파일로 쪼갤 수 있습니다. 하지만 그러면 script 태그가 많아지고 다른 파일에서 정의한 함수를 조회하기 위한 글로벌 변수가 많아집니다. 그래서 글로벌 네임스페이스는 지저분해지고, 추가한 js 파일들의 HTTP 요청이 네트워크 대역폭을 차지해서 정작 해당 페이지는 로딩이 느려집니다.

이런 일을 겪었다면 프런트 앤드 코드를 뭔가 다른 방법으로 관리해야 겠다는 필요성을 느끼게 됩니다. 특히 자바스크립트가 수천 라인이 넘는 대형 사이즈의 웹 앱을 제작해야 한다면 더욱 그렇습니다. 유지보수를 쉽게 할 수 있도록 이 모든 문제를 해결할 새로운 방법이 필요합니다. 스크립트 로더가 바로 이를 위한 새로운 기법입니다. 스크립트 로더들은 웹에서 쉽게 찾을 수 있지만 여기서는 그중에서도 RequireJS라는 라이브러리를 보겠습니다.

여러분은 단계별로 따라 하는 튜토리얼을 통해서 RequireJS 기반의 간단한 MVC(Model - View - Controller) 앱 제작법을 배울 겁니다. 스크립트 로더에 대한 사전 지식은 필요 없습니다. 이제 기초부터 살펴 봅시다.

개 요

RequestJS란 무엇이고 왜 좋은가

RequireJS는 AMD(Asynchronous Module Definition)의 구현체입니다. AMD란 모듈을 정의하는 방법과 모듈이 필요할 때 비동기로 로딩하는 방법을 정의한 API 입니다. 제임스 버크(James Burke)씨가 개발했는데, 2년간 개발해서 겨우 버전 1.0을 찍었습니다. 여러분은 RequireJS로 Javascript코드를 모듈화 할 수 있고 비동기로 관리하면서 여러파일을 병렬로 다운로드 할 수 있습니다. 스크립트 파일이 필요할 때만 병렬로 로딩되기 때문에, 페이지 로딩 속도는 빨라집니다. 그래서 이게 대단한거죠!

프런트 앤드를 위한 MVC?

MVC는 서버 사이트 코드를 구조화하고 모듈로 만들며 유지보수가 용의하도록 돕는 매우 잘 알려진 디자인 패턴입니다. 그러면 프런트 앤드에서 MVC를 사용하려면 어떻게 해야 할까요? 자바스크립트에서 이 디자인패턴을 적용할 수 있을 까요? 만약 여러분이 자바스크립트를 단지 애니메이션과 폼 유효성 검사, 100라인이 넘지 않는 간단한 처리를 위해서 사용한다면 MVC를 사용해서 여러분의 스크립트 파일을 구조화할 필요 없습니다. RequireJS도 사용할 필요 없을 겁니다. 하지만, 뷰(view)가 많은 리치 웹 앱을 제작 중이라면 반드시 필요합니다!

우리가 만들어 볼 앱

RequireJS를 사용해서 MVC 코드로 구조화하는 감을 잡기 위해서 뷰가 딱 2개인 정말 간단한 앱을 만들어 보겠습니다.

  • 사용자 목록을 보여주는 뷰. (name 속성으로 나타낸)
  • 사용자를 추가할 수 있는 뷰.

다음은 완성된 모습입니다.

비즈니스 로직이 정~말 간단하기 때문에 여러분은 다른건 신경쓰지 않고 코드 구조화를 이해하는데만 집중 할 수 있습니다. 또한 이 튜토리얼을 읽으면서 같이 짜보기를 강력히 추천합니다. 진짜 간단하거든요. 오래걸리지 않습니다. 여러분이 모듈화 프로그래밍을 해본적이 없거나 RequireJS를 사용해본 적이 없다면, 프로그래밍 실력을 늘리는데 도움이 될겁니다. 후회 안 할테니 꼭 해보세요.

HTML과 CSS 파일

예제에서 사용할 HTML 마크업입니다.

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title>A simple MVC structure</title>
    <link rel="stylesheet" href="css/style.css">
</head>
<body>
    <div id="container">
        <h1>My users</h1>
        <nav><a href="#list">List</a> - <a href="#add">Add</a></nav>
        <div id="app"></div>
    </div>
    <script data-main="js/main" src="js/require.js"></script>
</body>
</html>

nav 메뉴의 링크는 모든 페이지에서 유지할 앱의 네이게이션이고 #app div에서 MVC 애플리케이션의 마술이 일어납니다. body 끝에는 RequireJS를 추가했습니다. script 태그에 data-main="js/main" 이라는 특별한 속성을 추가했는데, 이 속성의 값은 RequireJS가 애플리케이션 전체의 경로 기준점으로 사용합니다.

기본적인 스타일쉬트도 추가해 봅시다.

#container{
    font-family:Calibri, Helvetica, serif;
    color:#444;
    width:200px;
    margin:100px auto 0;
    padding:30px;
    border:1px solid #ddd;
    background:#f6f6f6;
    -webkit-border-radius:4px;
       -moz-border-radius:4px;
            border-radius:4px;
}
 
h1, nav{
    text-align:center;
    margin:0 0 20px;
}

OOP 돌아보기. 모듈(module)이란?

자바스크립트 객체 지향 프로프래밍에는 모듈 패턴(Module Pattern)이라는 정말 많이 사용하는 디자인 패턴이 있습니다. 이 패턴은 글로벌 네임스페이스를 지저분하게 하지 않고 객체 안에 메서드와 속성을 캡슐화하기 위해서 사용합니다.(그래서 모듈이라고 합니다.) 그리고 자바나 PHP 같은 다른 OOP 언어의 클래스를 흉내내기 위해서 사용하기도 합니다. 다음은 main.js 파일에 정의한 MyMath라는 간단한 모듈입니다.

var MyMath = (function(){
 
    // Put your private variables and functions here
 
    return { // Here are the public methods
        add:function(a, b){
            return a + b;
        }
    };
})();
 
console.log(MyMath.add(1, 2));

공개 메서드를 리터럴 객체 형태로 정의했지만 불편합니다. 대신에 리빌링 모듈 패턴(Revealing Module Pattern)을 사용해서 비공개 속성과 메서드를 반환 합니다.

var MyMath = (function(){
 
    // With this pattern you can use the usual function notation:
 
    function add(a, b){
        return a + b;
    }
 
    return {
        add:add // But don't forget to declare it in the returned object!
    };
})();
 
console.log(MyMath.add(1, 2));

이 튜토리얼에서는 리빌링 모듈 패턴을 사용할 겁니다.

RequireJS

RequireJS로 모듈 정의하기

앞 절에서 우리는 변수 MyMath에 호출할 모듈을 정의했습니다. 모듈을 꼭 이렇게 정의해야 하는건 아닙니다. 이번에는 RequireJS를 사용해 보겠습니다. RequireJS는 유지보수가 용의하도록 자바스크립트 파일을 쪼개는 역할을 합니다. 그래서 main.js와 같은 폴더에 MyMath 모듈을 정의할 MyMath.js 파일을 생성하겠습니다.

define(function(){
 
    function add(a, b){
        return a + b;
    }
 
    return {
        add:add
    };
});

MyMath 변수는 사라졌고 define 함수의 파라미터로 모듈을 집어넣었습니다. define함수는 RequireJS가 제공하는 함수로 인데, 외부에서 모듈에 접근할 수 있도록 합니다.

main 파일에서 모듈 호출하기

그럼 다시 main.js파일로 돌아가겠습니다. RequireJS는 우리가 만든 MyMath 모듈을 호출하는데 사용할require라는 함수도 제공합니다. main.js는 이렇게 바뀝니다.

require(['MyMath'], function(MyMath){
 
    console.log(MyMath.add(1, 2));  
 
});

MyMath를 호출하는 부분은 다음 두 파라미터를 가진 require 함수안에 있습니다.

  • 첫 파라미터는 로딩할 모듈의 배열입니다. 모듈의 경로는 경로 기준점(HTML파일의 data-main 속성이 생각나나요?) 기준으로 정의됩니다. .js 확장자는 생략합니다.
  • 두번째 파라미터는 모듈이 로드될 때 호출할 함수입니다. 모듈은 이 함수의 파라미터로 전달됩니다. 그래서 그냥 모듈명을 파라미터 명으로 하면 됩니다.

자. 그럼 이제 페이지를 리로드 해봅시다... 와우! 축하해요! 여러분은 다른 파일의 모듈을 호출했어요! 그렇습니다. 이건 정말 쉽습니다. 그러면 이제 여러분은 그 무섭다는 MVC 아키텍처를 할 준비가 된겁니다. MVC는 여러분이 정의한 모듈과 정말 비슷하게 동작합니다. 그러므로 당연히 할 수 있습니다.

MVC 패턴의 구조

잠깐! : 이 튜토리얼에서는 서버 사이드 MVC처럼 컨트롤러 하나에 뷰 하나를 연결합니다. 하지만 프런트 앤드 개발에서는 한 컨트롤러에 여러 뷰를 연결하는건 정말 일반적이며 이런 경우 뷰는 버튼이나 입력필드 같은 시각적인 컨포넌트가 됩니다. Backbone 같은 자바스크립트 MVC 프레임워크가 이같은 다른 접근을 사용합니다만 그건 이 글이 목적하는 바와는 다릅니다. 여기서 제 목표는 실제 사용하는 MVC 프레임워크 전부를 만들자는게 아니라 여러분이 이미 잘 알고 있는 구조들을 통해서 RequireJS가 어떻게 동작하는지를 그려보자는 겁니다.

일단 간단하게 우리 프로젝트의 파일과 폴더부터 생성합시다. 데이터를 표현하는 모델을 사용하려 합니다. 비즈니스 로직은 컨트롤러에서 다루고 그 컨트롤러는 페이지를 렌더링할 특정 뷰를 호출할 겁니다. 그럼 어떻게 될까요? 폴더는 Models, Controllers, Views가 필요하고 두 개의 컨트롤러와 두 개의 뷰, 하나의 모델이 필요합니다.

자바스크립트 폴더구조는 다음과 같습니다.

* Controllers
    * AddController.js
    * ListController.js
* Models
    * User.js
* Views
    * AddView.js
    * ListView.js
* main.js
* require.js

전체 구조가 준비되었나요? 좋습니다! 가장 간단한 모델 부터 구현합시다.

모델: User.js

이 예제에서 User는 name 속성 하나를 가진 간단한 클래스입니다.

define(function(){
 
    function User(name){
        this.name = name || 'Default name';
    }
 
    return User;
});

main.js 파일로 돌아와서, require 메서드로 User를 사용하게 정의할 수 있습니다. 그리고 예제 목적에 부합하게 사용자 목록을 생성해 보겠습니다.

require(['Models/User'], function(User){
 
    var users = [new User('Barney'),
                 new User('Cartman'),
                 new User('Sheldon')];
 
    for (var i = 0, len = users.length; i < len; i++){
        console.log(users[i].name);
    }
 
    localStorage.users = JSON.stringify(users);
});

사용자 배열을 JOSN에서 시리얼라이즈 한 후에 데이터베이스처럼 접근하기 위해서 HTML5 로컬 스토리지에 저장합니다.

잠깐 : JSON을 시리얼라이즈하는 stringify와 디시리얼라이즈하는 parse를 IE7에서 사용하려면 폴리필(polyfill)이 필요합니다. 더글라스 클락포트씨의 Github 레파지토리에 보면json2.js라고 있습니다.

사용자 목록 출력

이제 이렇게 저장한 사용자를 출력할 차례입니다. 이 일을 할 ListController.js와 ListView.js를 만들겠습니다. 두 컴포넌트는 관련이 있습니다. 그래서 어떤 식으로든 연결이 되야 합니다. 많은 방법이 있습니다만 예제를 단순하게 하기 위해서 여기선 이렇게 하겠습니다. ListView는 render란 메서드를 갖고,ListController는 로컬 스토리지에서 가져온 사용자를 파라미터로 해서 ListView의 render 메서드를 호출합니다. 그렇습니다. ListController는 ListView에 의존합니다.

RequireJS에서는 require 메서드처럼 define 메서드에서도 의존성 있는 관련 모듈을 배열로 넘길 수 있습니다. 모듈안에서 해당 컨트롤러의 메인 동작으로 놓을 start 메서드를 만듭시다. (뭐, run이나 main같은 이름도 좋습니다.) ListController.js 를 봅시다.

define(['Views/ListView'], function(ListView){
 
    function start(){
        var users = JSON.parse(localStorage.users);
        ListView.render({users:users});
    }
 
    return {
        start:start
    };
});

여기서는 로컬스토리지의 users를 디시리얼라이징 합니다. 그리고 render메서드에 객체로 넘깁니다. 이제ListView.js의 render 메서드만 구현하면 끝이군요.

define(function(){
 
    function render(parameters){
        var appDiv = document.getElementById('app');
 
        var users = parameters.users;
 
        var html = '<ul>';
        for (var i = 0, len = users.length; i < len; i++){
            html += '<li>' + users[i].name + '</li>';
        }
        html += '</ul>';
 
        appDiv.innerHTML = html;
    }
 
    return {
        render:render
    };
});

render 메서드는 #app 문서요소에 집어넣을 HTML 문자열을 만들기 위해서 users를 순회작업 하는게 전부입니다.

잠깐 : 이런 식으로 자바스크립트 파일에서 HTML을 사용하는건 좋은 생각이 아닙니다. 나중에 수정할 때 너무 힘들거든요. 템플릿 사용을 고려해야 합니다. 템플릿은 HTML 마크업에 데이터를 넣는 훌륭한 방법입니다. 좋은 템플릿 시스템이 정말 많이 있습니다. 예를 들면 jQuery-tmpl이나 Mustache.js 가 있습니다. 하지만 이 글의 범위를 벗어나는 주제이고 지금의 구조를 복잡하게 만들겁니다. 여기선 단순하게 갑시다.

이제 ListController 모듈을 실행할 차례입니다. main.js 파일에서 require 메서드로ListController를 선언하고 ListController.start()를 호출합시다.

require(['Models/User', 'Controllers/ListController'], function(User, ListController){
 
    var users = [new User('Barney'),
                 new User('Cartman'),
                 new User('Sheldon')];
 
    localStorage.users = JSON.stringify(users);
 
    ListController.start();
});

페이지를 리로드 하면 멋진 사용자 목록을 볼 수 있습니다.

와~~! 이겁니다! 여러분도 같이 코딩했다면 축하합니다!

잠깐 : 지금은 라우팅 시스템이 없기때문에 실행하고 싶은 컨트롤러는 수동으로만 정의할 수 있습니다. 하지만 곧 정말 간단히 생성할 수 있게 됩니다. 기다리세요. ㅋ

사용자 등록하기

이제 목록에 사용자를 등록할수 있으면 좋겠군요. 간단한 텍스트 입력 필드와 버튼를 보여주고 버튼을 클릭하면 로컬 스토리지에 사용자를 추가하는 이벤트 핸들러를 등록할 예정입니다. 앞에서 했던 작업과 비슷한AddController에서 시작합시다. 이 파일은 뷰로 파라미터를 전혀 넘기지 않기때문에 무척 간단합니다.AddContoller.js를 보시죠.

define(['Views/AddView'], function(AddView){
 
    function start(){
        AddView.render();
    }
 
    return {
        start:start
    };
});

이어서 뷰 파일 AddView.js입니다.

define(function(){
 
    function render(parameters){
        var appDiv = document.getElementById('app');
        appDiv.innerHTML = '<input id="user-name" /><button id="add">Add this user</button>';
    }
 
    return {
        render:render
    };
});

이제 여러분의 main.js파일에서 AddController를 정의해서 다음 뷰를 무사히 가져올 start 메서드를 호출할 수 있습니다.

하지만 아직 버튼에 이벤트 연결을 하지 않았으므로 지금의 뷰는 별 의미는 없습니다. 이제 그 작업을 하기전에 한가지 질문을 해봅니다. 클릭 이벤트에 대한 이벤트 로직은 어디에 놓아야 할까요? 뷰일까요? 컨트롤러일까요? 이벤트 리스너 위치가 뷰라고 생각하고 이벤트 비즈니스 로직을 놓는건 상당히 안 좋은 습관입니다. 비록 컨트롤러에 뷰에 있는 div의 ID가 없는게 더 좋으니까 완벽하다 할 수는 없지만 컨트롤러에 이벤트 로직를 놓는게 더 좋습니다.

잠깐 : 가장 좋은 방법은 뷰에는 이벤트 리스너 함수가 있고, 그 함수가 컨트롤러나 이벤트 처리 전용 모듈에 있는 비즈니스 로직 메서드를 호출하는 겁니다. 이 방식이 어려운건 아니지만 이 때문에 예제가 복잡해져서 여러분이 포기할길 원치는 않습니다. 연습할 때 시도해도 됩니다.

말한 것 처럼, 이벤트 로직을 컨트롤러에 짜봅시다. AddController에 bindEvents 함수를 만듭니다. 그리고 뷰가 HTML 렌더링을 끝내면 이 함수를 호출합니다.

define(['Views/AddView', 'Models/User'], function(AddView, User){
 
    function start(){
        AddView.render();
        bindEvents();
    }
 
    function bindEvents(){
        document.getElementById('add').addEventListener('click', function(){
            var users = JSON.parse(localStorage.users);
            var userName = document.getElementById('user-name').value;
            users.push(new User(userName));
            localStorage.users = JSON.stringify(users);
            require(['Controllers/ListController'], function(ListController){
                ListController.start();
            });
        }, false);
    }
 
    return {
        start:start
    };
});

bindEvents에서는 #add 버튼의 클릭 이벤트에 이벤트 리스너를 추가합니다. (IE의 attachEvent를 위해서 만들어둔 함수가 있다면 그걸 써도 되고 jQuery를 써도 됩니다. ) 버튼을 클릭하면, 로컬 스토리지에서 사용자를 users 변수에 문자열을 가져와서 배열로 디시리얼라이즈하고 #user-name 입력 필드에 있는 이름으로 새로운 user를 넣습니다. 그리고 갱신된 users 배열을 로컬 스토리지에 다시 저장합니다. requireListController를 가져와서 start 메서드를 실행합니다. 그러면 다음 화면을 볼 수 있습니다.

멋지군요! 여기까지 예제를 같이 짰다면 이제 잠시 쉬어도 됩니다. 커피 한잔 타오고 계속하죠.

라우터로 뷰 화면간 이동하기

자 다시 합시다. 우리가 만든 앱은 상당히 멋져보이긴 하지만 실제로는 사용자를 한 명 더 추가하기 위해서 등록 뷰로 이동할 수 없으므로 영 별로입니다. 라우팅(routing) 시스템을 빼먹었습니다. 서버 사이드 MVC 프레임워크로 작업해본 적이 있다면 아마도 라우팅이 익숙할 겁니다. URL은 각자 다른 뷰를 불러옵니다. 하지만 우리는 클라이언트 사이드 작업을 하고 있고 서버 사이드와는 확실히 다릅니다. 이와 같은 자바스크립트 단일 페이지 인터페이스에서 해당 앱의 다른 부분으로 이동하려면 URL 해시(hash)를 사용합니다. 우리 경우에는 다음 URL을 입력할 때 두 개의 다른 뷰를 찾을 수 있어야 합니다.

  • http://yourlocalhostpath/#list
  • http://yourlocalhostpath/#add

이렇게해서 각 페이지를 쉽게 접근할 수 있고 북마크도 가능해 집니다.

잠깐 : 파이어폭스, 크롬, 오페라는 HTML5 히스토리 관리(pushState, popState, replaceState) 지원 기능이 있어서 해시를 다루지 않아도 됩니다.

브라우저 호환성과 응용

구형 브라우저를 지원해야 한다면 히스토리와 해시 이동 관리가 쉽지 않습니다. 지원하기로 정한 브라우저와 관련해서 여러분이 결정할 수 있는 몇가지 해법이 여기 있습니다.

이중에 구현이 간단한 수동 감시를 구현할 겁니다. n 밀리초 마다 hash가 변경되었는 지를 확인하는게 전부입니다. 변경이 확인되면 필요한 함수를 호출합니다.

잠깐 : 이 작업을 해주는 jQuery 플러그인도 있습니다.

라우터와 메인 라우터 순회

main.js 다음에는 라우팅 로직을 관리할 Router.js 파일을 만듭시다. Router.js에서는 경로를 정의하고 URL에 정의된게 없을 때 사용할 기본값을 정의해야 합니다. 우리는 인스턴스에 해시(hash)와 관련 컨트롤러(controller)를 가진 객체의 단순 배열을 사용할 수 있습니다. URL에 해시가 없을 때 사용할 기본값(defaultRoute)도 필요합니다.

define(function(){
 
    var routes = [{hash:'#list', controller:'ListController'},
                  {hash:'#add',  controller:'AddController'}];
    var defaultRoute = '#list';
    var currentHash = '';
 
    function startRouting(){
        window.location.hash = window.location.hash || defaultRoute;
        setInterval(hashCheck, 100);
    }
 
    return {
        startRouting:startRouting
    };
});

startRouting이 호출되면, URL에 기본 해시 값을 지정합니다. 그리고 hashCheck를 반복해서 호출하기 시작합니다. hashCheck는 아직 구현하지 않은 함수입니다. currentHash 변수는 해시 변경이 감지되면 해시의 현재 값을 저장할 때 사용합니다.

해시 변화 확인

이 함수가 hashCheck입니다. 100 밀리초마다 호출됩니다.

function hashCheck(){
    if (window.location.hash != currentHash){
        for (var i = 0, currentRoute; currentRoute = routes[i++];){
            if (window.location.hash == currentRoute.hash)
                loadController(currentRoute.controller);
        }
        currentHash = window.location.hash;
    }
}

hashCheck는 currentHash와 값 비교를 해서 해시가 변경됬는지 확인합니다. 그리고 라우터중 하나와 일치하면 관련 컨트롤러 명으로 loadController를 호출합니다.

적절한 컨트롤러 로딩

이제, loadController는 컨트롤러의 모듈을 로드할 require 를 호출해서 해당 모듈의 start 함수를 실행합니다.

function loadController(controllerName){
    require(['Controllers/' + controllerName], function(controller){
        controller.start();
    });
}

Router.js는 결국 이렇게 완성됩니다.

define(function(){
 
    var routes = [{hash:'#list', controller:'ListController'},
                  {hash:'#add',  controller:'AddController'}];
    var defaultRoute = '#list';
    var currentHash = '';
 
    function startRouting(){
        window.location.hash = window.location.hash || defaultRoute;
        setInterval(hashCheck, 100);
    }
 
    function hashCheck(){
        if (window.location.hash != currentHash){
            for (var i = 0, currentRoute; currentRoute = routes[i++];){
                if (window.location.hash == currentRoute.hash)
                    loadController(currentRoute.controller);
            }
            currentHash = window.location.hash;
        }
    }
 
    function loadController(controllerName){
        require(['Controllers/' + controllerName], function(controller){
            controller.start();
        });
    }
 
    return {
        startRouting:startRouting
    };
});

새로 만든 라우팅 시스템 적용하기

이제 남은 일은 main.js 파일에서 라우터 모듈을 require로 요청해서 startRouting 함수를 호출하는 일 뿐입니다.

require(['Models/User', 'Router'], function(User, Router){
 
    var users = [new User('Barney'),
                 new User('Cartman'),
                 new User('Sheldon')];
 
    localStorage.users = JSON.stringify(users);
 
    Router.startRouting();
});

우리가 만드는 앱의 한 컨트롤러에서 다른 곳으로 이동하려면, 새 컨트롤러의 해시 경로로 현재 window.hash를 교체하면 됩니다. 이런 경우 새로운 라우팅 시스템 대신에 아직 수동으로 AddControllerListController를 로딩하고 있습니다.

require(['Controllers/ListController'], function(ListController){
    ListController.start();
});

이 3줄을 해시 업데이트로 바꿔 버립시다.

window.location.hash = '#list';

자! 이겁니다. 우리 앱은 이제 응용 가능한 라우팅 시스템을 갖췄습니다. 한 뷰에서 다른 뷰로 이동할 수 있고, 반대로 URL에 원하는 해시를 넣을 수 있습니다. 그러면 정의된 라우터에 일치하는 해시를 찾아서 적절한 컨트롤러를 로딩합니다. 멋지죠?

동작하는 데모는 여기서 보세요.

결론

여러분은 프레임워크 없이 완전한 MVC 앱을 만들었으므로 자부심을 가져도 됩니다. 모듈 생성에 필요한 필수 요소만 있는 파일들을 연결하기 위해서 RequireJS를 사용했을 뿐이죠. 그럼 다음은 뭘 해야 할까요? 이 튜토리얼을 보고 여러분이 직접한 이 작은 작업이 맘에 든다면 앱에 기능을 추가해서 우리가 만든 작은 프레임워크를 확장할 수 있습니다. 그러려면 필요한 새로운 기법들이 있습니다. 시도해볼 만한 다음 단계에 대해 몇가지 아이디어가 있습니다.

  • 템플릿 시스템을 통합합니다.
  • 라우팅 시스템처럼 앱과 직접 관련 없으면서 다른 프로젝트에서 재사용 가능한 부분을 분리해서 작은 확장 라이브러리를 생성합니다.
  • 그 라이브러리에서 모델, 뷰, 컨트롤러를 객체로 정의합니다.
  • 다양한 소스의 데이터(RESTfull APS, localStorage, IndexedDB 등)를 다룰 새로운 추상 계층을 생성합니다.

이런 DIY 접근은 학습용으로는 훌륭하지만, 현시점에서 실 프로젝트에 적용하기에는 현재 프레임워크 상태가 그리 적합하지는 않습니다. 앞에 나열한 기능을 구현할만큼 부지런하지 않다면, 이미 존재하는 MVC 프레임워크 사용법을 배워 볼 수 있습니다. 가장 인기 있는 MVC 프레임워크입니다.

개인적으로는 최소화 버전이 5kb가 안되서 Backbone를 좋아합니다. 그래서 다음 튜토리얼은 RequireJS와 Backbone를 정말 멋지게 엮어서 사용하는 법에 대해 다룰 예정입니다. 이 내용을 알고 싶으면 @verekia를 팔로우 하세요. 그리고 애디 오스마니(Addy Osmani)씨의 대형 jQuery 애플리케이션과 자바스크립트 모듈에 대한 튜토리얼을 읽고 그를 팔로우 하고 RequireJS를 만든 제임스 퍼크(James Burke)씨도 팔로우하기를 추천합니다. 두 사람 모두 모듈 기반 자바스크립트 앱의 정보통입니다. 애디 오스마니씨는 TodoMVC라는 프로젝트도 시작했습니다. TodoMVC는 간단한 동일 웹 앱을 각기 다른 MVC 프레임워크를 사용해서 만드는 방법을 비교하는 프로젝트입니다. 여러분이 적절한 프레임워크를 선택하는데 도움이 될 겁니다.

오늘은 여기까집니다. 읽어주셔서 감사합니다.



출처 - http://zziuni.pe.kr/587




'Development > JavaScript' 카테고리의 다른 글

Backbone.js  (0) 2013.10.13
javascript - 라우팅  (0) 2013.10.13
javascript - 크롬에서 동적으로 로드된 js 파일 디버깅 방법  (0) 2013.07.28
javascript - forms 객체  (0) 2013.05.13
socket.io readme  (0) 2012.12.28
Posted by linuxism
,