RequireJS란

웹의 발전과 병행하여 javascript도 많은 발전이 있었습니다. 특히 최근에는 node.js가 트렌드가 되면서 더욱더 진일보 하고 있습니다. 그러다보니 javascript의 덩치도 커지고, 웹페이지를 처음에 띄울때 이렇게 커진 스크립트를 로딩하는데 시간이 많이 소요되는 문제가 발생하고 있습니다. 다음과 같은 여러가지 기법들을 사용하여 해결하려고 하지만 쉽지 않은 것 같습니다.


1. cdn을 이용하여 library로 사용되는 스크립트를 빠르게 로딩

 - 주요 라이브러리들은 빠르게 로딩할 수 있지만, app자체의 스크립트가 커지면 결국 시간이 많이 소요됩니다.

2. 스크립트의 로딩 시점을 html의 뒷 부분에 배치

 - 이렇게 하면 실제 뷰는 빠르게 로딩되지만 결국 사용자의 반응에 응답하기까지는 동일한 시간이 소요됩니다.


그래서 최근에 화두가 되고 있는 기법이 CommonJS에서 발단이 된 AMD(Asynchronous Module Definition)입니다. 의존관계를 설정하여 필요한 스크립트만 로드를 하고, web-app이 동작 중에 필요한 스크립트를 추가로 로드를 할 수도 있는 기법입니다. 이 AMD를 잘 구현해둔 패키지가 RequireJS(http://www.requirejs.org/) 입니다.


실습준비

우선 node를 사용하여 실습을 해보도록 하겠습니다. node가 설치되어있는 환경에서

# npm install -g bower
# npm install -g express-generator

를 실행하여 실습에 필요한 패키지들을 설치합니다. bower에 대해서는 http://forum.falinux.com/zbxe/index.php?document_srl=796129&mid=lecture_tip를 참고하시면 됩니다.


작업할 폴더로 이동한 뒤에

root@4a7ce17cefa1:/backup/requirejs# express .
destination is not empty, continue? y

   create : .
   create : ./package.json
   create : ./app.js
   create : ./public
   create : ./public/javascripts
   create : ./public/images
   create : ./public/stylesheets
   create : ./public/stylesheets/style.css
   create : ./routes
   create : ./routes/index.js
   create : ./routes/users.js
   create : ./views
   create : ./views/index.jade
   create : ./views/layout.jade
   create : ./views/error.jade
   create : ./bin
   create : ./bin/www

   install dependencies:
     $ cd . && npm install

   run the app:
     $ DEBUG=requirejs ./bin/www


Seed가 만들어졌으면 다음과 같이 필요한 패키지를 설치합니다.

root@4a7ce17cefa1:/backup/requirejs# npm install
debug@2.0.0 node_modules/debug
└── ms@0.6.2

cookie-parser@1.3.3 node_modules/cookie-parser
├── cookie@0.1.2
└── cookie-signature@1.0.5

morgan@1.3.2 node_modules/morgan
├── basic-auth@1.0.0
├── depd@0.4.5
└── on-finished@2.1.0 (ee-first@1.0.5)

serve-favicon@2.1.6 node_modules/serve-favicon
├── ms@0.6.2
├── fresh@0.2.4
└── etag@1.5.0 (crc@3.0.0)

express@4.9.8 node_modules/express
├── utils-merge@1.0.0
├── merge-descriptors@0.0.2
├── fresh@0.2.4
├── cookie@0.1.2
├── escape-html@1.0.1
├── range-parser@1.0.2
├── cookie-signature@1.0.5
├── vary@1.0.0
├── media-typer@0.3.0
├── methods@1.1.0
├── parseurl@1.3.0
├── finalhandler@0.2.0
├── serve-static@1.6.4
├── path-to-regexp@0.1.3
├── depd@0.4.5
├── on-finished@2.1.1 (ee-first@1.1.0)
├── qs@2.2.4
├── etag@1.4.0 (crc@3.0.0)
├── proxy-addr@1.0.3 (forwarded@0.1.0, ipaddr.js@0.1.3)
├── send@0.9.3 (destroy@1.0.3, ms@0.6.2, on-finished@2.1.0, mime@1.2.11)
├── type-is@1.5.2 (mime-types@2.0.2)
└── accepts@1.1.2 (negotiator@0.4.9, mime-types@2.0.2)

body-parser@1.8.4 node_modules/body-parser
├── bytes@1.0.0
├── raw-body@1.3.0
├── media-typer@0.3.0
├── depd@0.4.5
├── on-finished@2.1.0 (ee-first@1.0.5)
├── qs@2.2.4
├── iconv-lite@0.4.4
└── type-is@1.5.2 (mime-types@2.0.2)

jade@1.6.0 node_modules/jade
├── character-parser@1.2.0
├── commander@2.1.0
├── void-elements@1.0.0
├── mkdirp@0.5.0 (minimist@0.0.8)
├── monocle@1.1.51 (readdirp@0.2.5)
├── constantinople@2.0.1 (uglify-js@2.4.15)
├── transformers@2.1.0 (promise@2.0.0, css@1.0.8, uglify-js@2.2.5)
└── with@3.0.1 (uglify-js@2.4.15)

이렇게 하시면 express를 사용하여 간단한 웹서비스를 할 수 있는 환경이 갖추어집니다.


Bower를 사용할 준비도 해봅시다.

root@4a7ce17cefa1:/backup/requirejs# bower --allow-root init
? name: requirejs
? version: 0.0.0
? description:
? main file:
? what types of modules does this package expose?:
? keywords:
? authors:
? license: MIT
? homepage:
? set currently installed components as dependencies?: Yes
? add commonly ignored files to ignore list?: Yes
? would you like to mark this package as private which prevents it from being accidentally published to the registry?: No

{
  name: 'requirejs',
  version: '0.0.0',
  license: 'MIT',
  ignore: [
    '**/.*',
    'node_modules',
    'bower_components',
    'public/bower_components',
    'test',
    'tests'
  ]
}

? Looks good?: Yes


그리고 requirejs를 설치합니다.

root@4a7ce17cefa1:/backup/requirejs# bower --allow-root --save install requirejs
bower requirejs#*               cached git://github.com/jrburke/requirejs-bower.git#2.1.15
bower requirejs#*             validate 2.1.15 against git://github.com/jrburke/requirejs-bower.git#*
bower requirejs#~2.1.15        install requirejs#2.1.15

requirejs#2.1.15 public/bower_components/requirejs


node를 실행하여 모든 준비가 끝이 났는지 확인해봅시다.

root@4a7ce17cefa1:/backup/requirejs# npm start

> requirejs@0.0.0 start /backup/requirejs
> node ./bin/www


웹브라우저로 node application이 동작하고 있는 서버의 3000번 포트로 접속하면 

express-init.png

이런 간단한 웹어플리케이션이 동작하고 있는 것을 확인할 수 있습니다.


실습

No require

우선 일반적인 형식으로 작성을 먼저 해보겠습니다.

public/javascripts 폴더 밑에 다음과 같은 alerter.js 파일을 생성합니다.

alert('hello');


그리고 views/index.jade에 다음을 추가합니다.

extends layout

block content
  h1= title
  p Welcome to #{title}

  script(src='javascripts/alerter.js')


이렇게 수정을 한 후에 npm start로 다시 시작한 후 웹브라우저로 확인하면 경고창이 뜨면서 hello라고 표시됩니다.


Require basic

이렇게 한 것을 requirejs를 사용하도록 해보겠습니다.



먼저 작성한 public/javascripts/alerter.js를 다음과 같이 수정합니다.

define(function() {
    return function(message) {
        alert(message);
    }
});

alerter 모듈을 설정하는 내용입니다.


우선 public/javascripts 폴더에 main.js를 추가합니다.

require(['alerter'], function(alerter) {
    alerter('hello');
});

배열안의 문자열로 적혀있는 'alerter'는 alerter.js를 의미하며, 이 스크립트를 로딩해서 alerter로 받는 것에 대한 내용을 기술한 것입니다. 즉, 먼저 작성한 alerter.js에서 리턴하는 closure가 function의 인자인 alerter에 들어가게 됩니다.


views/index.jade도 다음과 같이 수정합니다.

extends layout

block content
  h1= title
  p Welcome to #{title}

  script(data-main='javascripts/main' src='bower_components/requirejs/require.js')

requirejs를 호출할 때는 data-main이라는 attribute가 필요하며, 이 attribute의 값이 entry point로 동작하게 됩니다. 이 때 .js는 생략합니다. 즉, 위와 같이 적었을 경우 javascripts/main.js를 맨 처음에 로드하게 됩니다.


수정이 완료되었으면 npm start로 다시 실행을 하고 웹브라우저로 확인합니다. 이전에 만든것과 동일하게 hello 경고창이 뜨겠지만 내부적으로는 AMD 기법을 사용하여 작성이 되었습니다.


의존성

이번에는 조금 더 발전한 형태인 dependency를 걸어서 사용해보도록 하겠습니다.


public/javascripts 폴더에 app.js를 작성합니다.


define(function(require) {
    var alerter = require('alerter');
    alerter('Message from app');
});

app 모듈을 작성하였습니다. app 모듈은 로드되면 alerter 모듈을 로드해서 경고창을 띄우도록 합니다.


public/javascripts/main.js를 수정합니다.

require(['app'], function(app) {
});

main.js는 requirejs가 처음에 로드하는 모듈이며, app 모듈을 로드합니다.


수정을 다 했으면 npm start로 다시 실행하고, 브라우저로 확인합니다. 이번에는 Message from app이라는 경고창이 뜹니다.


이번 예제에서는 require.js가 main.js를 로드하고, main.js가 app.js를 로드하고, app.js가 alerter.js를 로드하는 과정으로 진행이 됩니다. 즉, 각 스크립트간에 의존관계가 형성이 되었죠.


Lazy loading

이번에는 조금 더 발전된 형태인 lazy loading을 해보도록 하겠습니다.


public/javascripts/app.js를 다음과 같이 수정합니다.


define(function(require) {
    var button = document.createElement('button');
    button.onclick = function() {

        var alerter = require(['alerter'], function(alerter) {
            alerter('Message from app');
        });
    };

    button.textContent = 'Click me';

    document.body.appendChild(button);
});

npm start로 수정하고 브라우저로 확인하면 다음과 같습니다.

lazy-loading-01.png


이 때 개발자 도구로 확인하면 다음과 같이 alerter.js가 아직 로드되지 않은 상태입니다.

lazy-loading-02.png


여기서 Click me 버튼을 누르면 Message from app이라는 경고창이 뜹니다. 이제서야 alerter.js가 로딩되는 것을 확인할 수 있습니다.

lazy-loading-03.png




여기까지 해서 requirejs 수박 겉핥기를 해보았습니다. 괜히 더 복잡해지는 것이 아닌가 라는 생각이 들 수도 있지만, 프로젝트의 규모가 커지고, 스크립트 파일이 많아지면 관리가 힘들어집니다. 동적 로딩 및 의존성 관리를 쉽게 하기 위해서는 이런 도구의 사용에 익숙해져야 할 것 같습니다.