블로그 이미지
Peter Note
Web & LLM FullStacker, 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 Peter Note