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

Publication

Category

Recent Post

2015. 4. 16. 17:50 My Services/Smart Analytics

AngularJS SPA 형태 개발을 진행할 때 AMD(Asychronous Module Definition)에 대한 고려를 하지 않고 있다. 계속해서 확장 되어야 하는 애플리케이션을 개발한다면 AMD를 염두에 두자. 하지만 AMD가 아니라 앵귤러 스러운 모듈단위의 동적 로딩을 하고 싶을 경우 어떻게 하는지 Smart Analytics 관점에서 컨셉을 테스트해 보자. 



개념 


  - CommonJS : exports, require, module을 통한 서버사이드 자바스크립트 모듈화 스팩이고 Node.js에서 많이 사용함

  - AMD : 브라우져에서 모듈화에 대한 스팩. 가장 많이 쓰는 구현체 require.js  를 통한 자세한 사용예(NHN)

    + 특징 : 동적 로딩, 의존성 관리, 모듈화 

    + define을 통한 모듈 패턴을 적용하여 모듈을 만든다

    + require를 통해 의존관계 모듈들을 주입받아 사용한다. 

  - AngularJS는 모듈화와 DI 즉, 의존성 주입을 가능하게 하고, RequireJS는 JS 파일의 의존성을 관리하고 동적 로딩을 통한 성능 최적화를 얻는다. (참조)





Smart Analytics에서 바로 보는 동적 로딩 시나리오

  

  ElasticSearch의 Kibana는 엘라스틱서치의 데이터를 자유롭게 표현할 수 있는 대시보드 소프트웨어이다. AngularJS + Require.js를 사용하고 있다. 하지만 좀 더 AngularJS 스러운 방식으로 앵귤러 모듈을 필요한 시점에 동적으로 할 수는 없을까? 화면이 라우팅될 때 또는 필요한 시점에 필요한 모듈만 로딩할 수 있도록 지원하는 앵귤러 컴포넌트로 ocLazyLoad 모듈이 있고 이를 이용하여 대시보드 화면에 국한하여 시나리오를 써보자


  - 사용자가 로그인을 하면 사용자마다 할당 된 대시보드 목록을 가져오고 첫번째 대시보드를 브라우저 영역 전체화면으로 보여준다.

  - 대시보드에는 사용자가 직접 제작한 차트가 보이고 해당 차트는 별도의 파일로 존재하고 앵귤러의 독립 모듈로 존재한다. 따라서 대시보드안에 들어가는 차트들은 플로그인 방식으로 동적으로 로딩되어 대시보드 일부분에 포함되어 들어가야 한다. 

  - 그러나 사용자가 직접 제작하지 않고  저작도구를 통해 제작한 차트는 별도의 파일로 존재하지 않고 환경값을 서버로 부터 받아 차트 컨테이너에 자동으로 해석되어 표현된다. 


  여기서 동적으로 로딩되어 표현할 것은 직접 별도로 제작된 차트를 대상으로 하고, 별도의 파일로 존재한다. 해당 파일은 로컬 또는 네트워크를 통해 로딩 될 수 있어서 다양한 차트 화면을 대시보드로 수용할 수 있다. 마치 플러그인 방식으로 대시보드 운영 서버의 재기동 없이도 플러그인을 설치하기만 하면 자동으로 대시보드의 특정위치에 차트가 표현되는 것이다. 

  

   - chart-config.json + chart-container를 통해 차트를 자동으로 표현하거나

   - 로컬에서 사용자가 직접 제작한 bizChart01.js 파일을 동적으로 로딩해서 표현하거나, bizGrid01.js를 원격에서 동적 로딩하는 개념이다. 







ocLazyLoad 기반 앵귤러 모듈의 동적 로딩


  ocLazyLoad 모듈을 이용해 파일 로딩하는 예를 보자. 설치하고 모듈 의존성 설정

// survey-gorilla 설문조사 오픈소스를 위해 만들어 놓은 제너레이터로서 generator-angular-fullstack을 기반으로 수정한 것이다. 

$ npm install generator-sg --save

$ mkdir prototyping & cd prototyping


// 애플리케이션 생성 : mongodb는 선택안하고, ui-bootstrap과 sass를 선택함

$ yo sg SAProto 

// oclazyload를 설치함 

$ bower install oclazyload --save

bower install       oclazyload#0.6.3


  ocLazyLoad 모듈을 위한 별도의 서비스를 /app/components/base/pluginer 폴더에 만들어 보자. pluginer 앵귤러 서비스를 base 모듈에 포함시키기 위해 먼저 base.module.js를 만든다. 

// base.module.js 를 만들고, oc.lazyLoad 모듈에 대한 의존관계를 설정한다. 

$ cd client/components

$ mkdir base & cd base

$ vi base.module.js 


(function() {

  'use strict';


angular 

    .module('sa.base', ['oc.lazyLoad']);


})();



// pluginer 서비스를 만들어 보자 

$ mkdir pluginer & cd pluginer

$ vi pluginer.service.js 


(function() {

  'use strict';


  angular

    .module('sa.base')

    .service('pluginer', pluginer);


  /* @ngInject */

  function pluginer($q, $ocLazyLoad) {

    this.load = load;


    function load(moduleName, filePath) {

      var deferred = $q.defer();


      $ocLazyLoad

        .load({

          name: moduleName,

          files: [ filePath ]

        })

        .then(function() {

          console.log('loaded plugin: ' + moduleName + ' successfully.');

          deferred.resolve('ok');

        }, function() {

          console.log('failed plugin: ' + moduleName + ' successfully.');

          deferred.reject('fail');

        });


      return deferred.promise;

    }

  }


})();


// 최종 폴더 구조 


  pluginer.service.js는 $ocLazyLoad를 통해 지정한 파일의 모듈을 로딩해서 사용할 수 있고 promise 패턴을 통해 비동기적인 상황에 대응한다. 다음으로 플러그인을 하나 만들어 보자. 우선 client 밑에 plugins 폴더를 만들고 index.html에 포함되지 않는 *.js / *.css / *.jpg(png) 등을 놓는다. plugin.bizchart01이라는 별도의 모듈을 만들고 bizchart01 서비스를 만든다. 

// client 밑에 plugins 폴더를 생성한다.

$ mkdir plugins & cd plugins

$ mkdir bizchart01 & cd bizchart01

$ vi bizchart01.js 


(function() {

  'use strict';


  angular

    .module('plugin.bizchart01', [])

    .service('bizchart01', bizchart01);


  function bizchart01() {

    this.draw = draw;


    function draw() {

      return 'drawing....';

    }

  }


})();


// plugins 폴더 

 

  이제 pluginer 서비스를 통해 plugin.bizchart01 모듈을 필요한 시점에 로딩을 하고 bizchart01 서비스의 draw() 를 호출해서 출력해 보자. sa.base 모듈을 app.js에 의존관계를 설정하고 app/main/main.html 를 바꿔보자. 

// /client/app/app.js 의 sa.base 모듈 의존 설정 

  angular

    .module('saprotoApp', [

 'ngCookies',

 'ngResource',

 'ngSanitize',

 'ui.router',

 'ui.bootstrap',

                  'sa.base'

])

    .config(config)

    .factory('authInterceptor', authInterceptor)

    .run(run);


// main.html 과 main.controller.js를 수정하여 테스트 코드 작성

// main.html 

 <li><a href="#" ng-click="lazyloading()">Load Module</a>: {{lazyloadingMsg}}</li>


// main.controller.js 

(function () {

  'use strict';


  angular

    .module('saprotoApp')

    .controller('MainCtrl', MainCtrl);


  /* @ngInject */

  function MainCtrl($scope, $injector, pluginer) {

    $scope.lazyloading = lazyloading;


    function lazyloading() {

      pluginer

        .load('plugin.bizchart01', 'plugins/bizchart01/bizchart01.js')

        .then(function() {

          var bizchart01 = $injector.get('bizchart01');

          $scope.lazyloadingMsg = 'lazyloading message: ' + bizchart01.draw();

          console.log($scope.lazyloadingMsg);

        });

    }    

  }


})();


  pluginer를 통해 "plugins/bizchart01/bizchart01.js" 에 있는 "plugin.bizchart01" 모듈을 로딩하고, $injector를 통해 "bizchart01" 서비스를 얻어와 draw()를 호출했다. 이를 확인할 수 있는 것은 크롬의 개발자 도구에서 브라우저에서 로딩 링크를 클릭했을 때 실제로 bizchart01.js 파일을 호출하는지 보면 된다. 


1) "Load Module"를 클릭한다. 



2) 크롬 개발자 도구의 Nework에서 "Load Module" 링크를 클릭시 bizchart01.js을 요청하는지 확인한다. 



이제 별도의 차트를 앵귤러 모듈로 만들어서 필요한 시점에 로딩할 수가 있다. 이외에도 ui-router를 통해 partial view가 변경될 때도 가능하고 다양한 예제는 ocLazyLoad를 참조한다. 


테스트 소스 : https://github.com/Smart-Analytics/prototyping/tree/feature/lazyloading




<참조>


  - CommonJS와 AMD 개념 비교 (NHN)

  - AMD : require.js 사용예

  - Node.js와 브라우져 사용예 (Mobicon)

  - ocLazyLoad GitHub

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. 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 윤영식
2013. 3. 16. 16:17 Backbone.js

모듈 프로그래밍은 큰사이즈의 애플리케이션을 작은 단위의 관리가능한 블록으로 만드는데 유용하다. 모듈기반 코딩은 유지보수 노력을 줄여주고 재사용을 높여준다. 그러나 모듈간의 의존관계를 관리하는 부분은 어려운 점들이 있다. 이런 의존 관계상의 문제를 해결하기 위하여 RequireJS 프레임워크가 나오게 되었다. 



1) Old Style Loading JavaScript 파일들

  - 큰사이즈 애플리케이션들은 여러 자바스크립트 파일을 <script> 태그로 로딩한다

  - 이때 각 자바스크립트 파일은 어떻게 로딩을 처리하는지 예제로 보자


purchase.js

function purchaseProduct(){

  console.log("Function : purchaseProduct");

  var credits = getCredits();

  if(credits > 0){

    reserveProduct();

    return true;

  }

  return false;

}


products.js

function reserveProduct(){

  console.log("Function : reserveProduct");

  return true;

}


credits.js

function getCredits(){

  console.log("Function : getCredits");

  var credits = "100";

  return credits;

}


  - 만일 main.js에서 이렇게 코딩을 하면 var result = purchaseProduct(); 

  - purchase.js는 credits.jsproducts.js 파일 의존관계가 된다. 따라서 이들은 purchaseProduct()이 호출되지 전에 먼저 로딩되어 져야 한다. 

  - 만약 다음 순서라면 에러가 발생할 것이다. (credits.js 가 먼저 로딩되어야 하는데 맨 뒤에 있으므로)

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

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

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

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


  

2) RequireJS 소개

  - http://requirejs.org/docs/download.html 에서 모듈을 다운로드하자 

  - 모든 자바스크립트 파일과 함께 require.js 파일을 같은 디렉토리에 둔다

    

  - index.html 안에 하기와 같이 script를 넣는다 

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


  - 코드안에 RequireJS를 사용하여 require code를 넣는다.

  - data-main 어트리뷰트 애플리케이션 최기화 시점으로 이때 RequireJS가 다른 스크립트 파일과 의존관계를 찾기위하여 scripts 디렉토리 밑에 있는 main.js를 사용하고 require.js를 src 값으로 지정한다. main.js가 해당 예에서는 같은 폴더에 모든 파일이 위치하지만 원하는 어느 폴더에 놓아도 무방하다. 


  - main.js 코드를 보자 

require(["purchase"],function(purchase){

  purchase.purchaseProduct();

});


  -  RequireJS는 require() 또는 define() 펑션으로 모든 코드를 감싼다. 펑션의 첫번째 파라미터로 의존관계에 있는 파일명을 넣으면 되고, .js는 생략가능하다. 두번째 파라미터는 무명함수(anonymous function)로 의존파일안의 함수를 호출한다. 

  - 다중의존관계 호출은 다음과 같다

require(["a","b","c"],function(a,b,c){

});



3) Define으로 모듈 생성하기

  - main.js 에서 사용될 모듈들을 define() 펑션을 이용하여 만든다

  - 모듈생성이 펑션널 프로그래밍 단위 즉 Task 단위로 생성이 되는 것이다. 

  - main.js 에서 필요한 모듈 즉 Task 를 로딩하여 사용하게 된다 

  - purchase.js를 바꾸어 보자

define(["credits","products"], function(credits,products) {

  console.log("Function : purchaseProduct");

  return {

    purchaseProduct: function() {

      var credit = credits.getCredits();

      if(credit > 0){

        products.reserveProduct();

        return true;

      }

      return false;

    }

  }

});


  - products.js 도 바꾸어 보자

define(function(products) {

  return {

    reserveProduct: function() {

      console.log("Function : reserveProduct");

      return true;

    }

  }

});


  - credits.js 도 바꾸어 보자 

define(function() {

  console.log("Function : getCredits");

  return {

    getCredits: function() {

      var credits = "100";

      return credits;

    }

  }

});



4) define()으로 모듈 만들고 require()로 필요한 모듈 사용하기 

  - require() : 모듈을 로딩하여 즉시 함수를 수행하는데 사용한다

  - define() : 모듈을 정의하는데 사용한다

  - purchaseProduct() 은 main.js 파일에서 함수를 즉시 실행한 것이다. 그러나 다른 파일은 재사용을 위하여 모듈화 하기 위하여 define()을 사용했다. 



5) 왜 RequireJS가 중요한가?

  - RequireJS는 펑션이 수행되기전에 모든 의존관계 파일이 로딩될 때까지 기다린다. 즉, 하나의 호출에 관여된 의존관계 파일을 로딩한 후에 펑션을 호출해 준다. 필요한 시점에 로딩하여 주고 Asynchronous Module Loading 이다. 

 


6) 의존관계 파일의 우선순위 관리 방법

  - RequireJS는 파일을 로딩할 때 Asynchronous Module Definition(AMD)을 사용한다.

  - Asynch로 로딩되기 때문에 순서적인 로딩을 보장받고 싶다면 shim 환경파일을 작성할 수 있다. 

  - 환경 shim 예

requirejs.config({

  shim: {

    'source1': ['dependency1','dependency2'],

    'source2': ['source1']

  }

});


  - RequireJS에서 제공하는 config() 펑션을 통해 shim 이라 불리우는 파라미터를 정의한다.

  - configuration 가이드는 http://requirejs.org/docs/api.html#config 를 참조한다. (Advanced User로 갈려면 꼭 익히자!)

    + jQuery config

    + Node config

    + Dojo config


  - 예로 하기와 같이 정의하지만 로딩 순서 지정을 위와 같이 shim으로 할 수 있는 것이다. 

define(["dependency1","dependency2","source1","source2"], function() {

);

  

  - source2는 source1을 의존한다. source1의 로딩이 끝나면 source2는 모든 의존파일이 로딩되었다 여긴다. 그러나 dependency1과 dependency2 는 여전히 로딩되고 있을지도 모른다. shim config를 사용하면 이러한 복잡성을 제거하여 로딩 순서를 지정하여 줄 수 있다. 


  - Backbone.js Boilerplate 블로깅의 index.html 파일을 파악해 보자



<참조>

  - 원문 : Understanding RequireJS for Effective JavaScript Module Loading

  - RequireJS 홈페이지

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

Node.js에서 모듈을 사용하는 방법과 브라우져에서 SPA(Singe page application) 기반으로 개발을 진행할 때 Javascript 모듈의 의존성 관계 관리를 위한 모듈 사용방법은 차이가 있다. 각각에 대해 간단히 알아보자 


1) Node.js 모듈 

  - CommonJS 패턴을 사용한다 : Synchronous 로딩이다 

  - require('dowon') 방식으로 module을 로딩한다 

  - module.exports 를 통하여 functionality를 노출시킨다. 모듈이라는 컴포넌트를 사용하기 위한 외부 Interface를 지정하는 것이다.


  - calc.js 모듈 파일 

var Calc = function(start) {
	var that = this;
	
	this.add = function(x) {
		start = start +x;
		return that;
	};

	this.multiply = function(x) {
		start = start * x;
		return that;
	};

	this.equals = function(callback) {
		callback(start);
		return that;
	};
}
// module.exports를 사용한다 
module.exports = {
	add: function(x, y) {
		return new Calc(x).add(y || 0);
	},
	multiply: function(x, y) {
		return new Calc(x).multiply(y || 1);	
	}
}


- app.js 모듈 파일 

// require를 호출하여 모듈 즉, 컴포넌트를 로딩한다
var Calc = require('./calc.js');

// 모듈에서 외부로 노출시킨 API를 호출한다
Calc.add(1, 2)
	.multiply(3)
	.equals(function (result) {
		console.log(result);
	});



2) 브라우져 기반 모듈

  - Require.js 를 -AMD(Asynchronous Module Definition)- 사용한다 : Asynchronous 로딩이다

  - CommonJS와 약간의 차이가 있다

  - require 를 통해 dependencies와 callback을 얻는다 : 주로 호출하는 Main 입장의 application에서 사용할 때 

  - define 을 통해 dependencies를 얻고 API를 노출한다 : 개별 모듈을 정의할 때  

  - RequireJS사용하기 블로그

  - 제품 리스트를 보여주는 메인 소스 

//require 사용
require(['jquery', './big-cart'], function($, bigCart) {  <=== big-cart.js 의 펑션을 호출한다. 즉, big-car.js에 의존관계
	$(document).ready(function() {
		bigCart.init();
	});
})


  - 쇼핑 카트 

// define 사용하여 정의
define(['./pubsub', 'jquery'], function(pubsub, $) {  <=== pubsub.js 의 펑션을 호출한다. 즉, pubsub.js에 의존관계
	var cart, count = 0;

	pubsub.sub('add-to-cart', function(itme) {
		count++;

		cart.find('h1').html(count);

		var li = $('< li >')
			.html(item.name)
			.data('key', item.id);

		cart.find('ul').append(li);

	});

	pubsub.sub('remove-from-cart', function(item) {
		count--;

		cart.find('h1').html(count);
		cart.find('li').filter(function() {
			return $(this).data('key') == item.id;
		}).remove();

	});

	return {
		init: function() {
			cart = $('.big-cart');
		}
	}
});


  - 카트 내역 관리 모델 

define(function() {  <=== 의존하는 것이 없다 
	var cache = {};
	return {
		pub: function(id) {
			var args = [].slice.call(arguments, 1);
			if(!cache[id]) {
				cache[id] = [];
			}

			for(var i=0, il=cache[i].length; i -1) {
					cache[id] = cache[id].slice(0, index).concat(cache[id].slice(index+1));
				}
			}
		}
	}
});



3) CommonJS와 Require.js 개념 정리


4) AMD 개념


5) AMD 동영상
  - 하나의 자바스크립트 파일의 내역을 재사용할 수 있도록 - DRY (Don't Repeatly Yourself) - 모듈로 분리함
    + 첫째는 단순히 <script> 태그를 사용하여 개발자가 순서대로 넣는 호랑이 담배피던 시절의 방식
    + 둘째는 define을 사용하여 모듈라이제이션하고 사용시에 require 한다 
  - part01
 


posted by 윤영식
2013. 3. 12. 13:55 Backbone.js

MVC 프레임워크를 사용하는 이유는 마틴 파울러 아저씨의 주장처럼 Clean Code를 생산하기 위해서이다. 클린 코드가 되면 클라이언트 입장에서 events와 callbacks의 유지보수 용이성이 높아지고, Testing이 쉬워진다. Backbone.js 는 그 중에서도 가장 간결하면서 여러 Third part 모듈을 붙일 수 있어서 확장성이 높다. 그러나 Backbone.js 만을 가지고 큰 규모의 확장성 있는 애플리케이션을 만들기에는 부족함이 존재한다. 이를 해결할 수 있는 방법을 알아보자 


1) MV* Framework : Backbone.js 

  - Selector : jQuery, YUI 등 선택

  - Template : Handlebar, Mustache 등 선택

  - MVC

    + Model : Data 유효성검사, 서버로부터 가져오고, 여러 화면에 공유하는 백본의 핵심 - Collection 개념으로 확장

    + View : 데이터의 표현, Cotroller 역할 하지 않고  EventListener를 통하여 Controller에게 위임한다 

    + Controller :  백본에선 진정한 Controller 는 없다. View & Router 가 controller와 유사하고, Router는 Model 이벤트의 요청에 대해 View 응답을 갖는다 

  - 백본은 SPA(Single Page Applications) 개발을 위한 기본적인 추상화 레이어(Abstraction Layer)일 뿐이다. 모든 것을 제공하진 않으니 필요한 요소를 잘 선택하여 모듈화해야 한다 



2) 좀 더 복잡한 SPA 개발하기 

  - 백본을 기반으로 Large Scale Application 개발을 위한 프레임워크들

  - http://chaplinjs.org/ 

  - http://thoraxjs.org/ : Backbone.js & Handlebar 

  - http://marionettejs.com/

  - Backbone.js LayoutManager

  - Aura : 위젯기반 프레임워크 



3) 채플린 배우기 

  - 백본은 저수준의 프레임워크이기 때문에 잘 구조화해서 사용하는 방법을 알아야 한다 

  - 백본 예제의 To do list 는 SPA 라고 할 수 없으며 좀 더 규모가 큰 구조화된 애플리케이션 개발의 가이드가 될 수 없다 

  - 예제 : 채플린 기반 Real world application 인 moviepilot

  - GitHub 저장소

  - 의존성 (SmartSolution에서 사용할 기준)

    + Backbone.js

    + Underscore.js

    + jQuery

    + Handlebar.js 

    + AMD (Requires.js)

    + CoffeeScript

  - 채플린 Boilerplate 소스



4) 채플린 Boilerplate 소스 돌려보기 

  - Git clone

$ git clone https://github.com/chaplinjs/chaplin-boilerplate.git

Cloning into 'chaplin-boilerplate'...

remote: Counting objects: 263, done.

remote: Compressing objects: 100% (204/204), done.

remote: Total 263 (delta 135), reused 178 (delta 50)

Receiving objects: 100% (263/263), 302.91 KiB | 148 KiB/s, done.

Resolving deltas: 100% (135/135), done.


  - static 수행 (블로깅)

static

serving "." at http://127.0.0.1:8080


// 브라우져 호출


// 브라우져 호출후 static console 출력 내용 

11:28:08 [200]: /

11:28:08 [200]: /js/vendor/require-2.1.1.js

11:28:08 [200]: /js/hello_world_application.js

11:28:08 [200]: /js/routes.js

11:28:08 [200]: /js/vendor/chaplin-0.6.0.js

11:28:08 [200]: /js/views/layout.js

11:28:08 [200]: /js/vendor/jquery-1.8.3.js

11:28:08 [200]: /js/vendor/underscore-1.4.3.js

11:28:09 [200]: /js/vendor/backbone-0.9.9.js

11:28:09 [200]: /js/controllers/hello_world_controller.js

11:28:09 [200]: /js/models/hello_world.js

11:28:09 [200]: /js/views/hello_world_view.js

11:28:09 [200]: /js/models/base/model.js

11:28:09 [200]: /js/views/base/view.js

11:28:09 [200]: /js/vendor/require-text-2.0.3.js

11:28:09 [200]: /js/lib/view_helper.js

11:28:09 [200]: /js/templates/hello_world.hbs

11:28:09 [200]: /js/vendor/handlebars-1.0.rc.1.js

11:28:09 [200]: /js/lib/utils.js

11:28:09 [404]: /favicon.ico


  - .coffee 소스 수정시 컴파일 방법

    + /coffee 디렉토리에 위치 /js 컴파일된 파일 

    + 컴파일 : coffee --bare --output js/ coffee/


  - 구조파악 

    + models, views, controllers, lib, vendor  디렉토리

    + vendor : backbone.js, jquery.js 등의 모듈 

    + views : Handlebar 템플릿 사용 -> js/templates/hello_world.hbs 

    + index.html : AMD 모듈 관리 (블로깅) -> coffee/hello_world_application.coffee 메인

   


5) 마리오네트

  - 다음 블로깅에서 마리오네트를 살펴보자 : 좀 더 활발하게 활동하고 있다는데 채플린 또는 마리오네트 아니면 코너스톤중 택일



<참조>

  - 채플린 (Chaplin)

  - JavaScript Framework 별 To Do List

  - 예제 : 와인셀러 (블러깅)

  - 채플린 Boilerplate 소스

  - 코너스톤 팀의 Backbone & CSS 를 위한 프레임워크 고도화


posted by 윤영식
prev 1 next