단일 페이지 앱이 성장함에 따라 다운로드 시간도 늘어납니다. 이는 사용자 경험에 별로 좋지 않습니다(힌트: 그러나 사용자 경험은 우리가 단일 페이지 앱을 개발하는 이유입니다). 더 많은 코드는 더 큰 파일을 의미하며, 코드 압축이 더 이상 요구 사항을 충족하지 못할 때까지 사용자를 위해 할 수 있는 유일한 일은 전체 애플리케이션을 한 번에 다운로드하도록 요청하는 것을 중단하는 것입니다. 게으른 로딩이 유용한 곳입니다. 모든 파일을 한 번에 다운로드하는 대신 사용자는 지금 필요한 파일만 다운로드할 수 있습니다.
그래요. 애플리케이션을 지연 로딩하는 방법은 무엇입니까? 기본적으로 두 가지로 나누어집니다. 모듈을 작은 덩어리로 나누고 요청 시 이러한 덩어리를 로드할 수 있는 메커니즘을 구현하십시오. 많은 일을 하는 것 같지 않나요? Webpack을 사용하는 경우에는 그렇지 않습니다. 기본적으로 코드 분할 기능을 지원합니다. 이 기사에서는 여러분이 Webpack에 익숙하다고 가정하지만, 그렇지 않은 경우 여기에 소개합니다. 간단히 말해서 AngularUI Router와 ocLazyLoad도 사용하겠습니다.
코드는 GitHub에서 확인할 수 있습니다. 언제든지 포크할 수 있습니다.
웹팩 구성
사실 특별한 건 없습니다. 실제로 문서에서 복사하여 붙여넣기만 하면 됩니다. 유일한 차이점은 코드를 깨끗하게 유지하기 위해 ng-annotate를 사용하고 ECMAScript 2015 마법 중 일부를 사용하기 위해 babel을 사용한다는 것입니다. ES6에 관심이 있다면 이전 게시물을 확인해 보세요. 이러한 것들은 모두 훌륭하지만 지연 로딩을 구현하는 데 필요한 것은 없습니다.
// webpack.config.js var config = { entry: { app: ['./src/core/bootstrap.js'], }, output: { path: __dirname + '/build/', filename: 'bundle.js', }, resolve: { root: __dirname + '/src/', }, module: { noParse: [], loaders: [ { test: /\.js$/, exclude: /node_modules/, loader: 'ng-annotate!babel' }, { test: /\.html$/, loader: 'raw' }, ] } }; module.exports = config;
신청
애플리케이션 모듈은 모든 페이지에서 다운로드해야 하는 Bundle.js에 포함되어야 하는 기본 파일입니다. 보시다시피, 전역 종속성을 제외하고 복잡한 것은 로드하지 않습니다. 컨트롤러를 로드하는 것과 달리 라우팅 구성만 로드합니다.
// app.js 'use strict'; export default require('angular') .module('lazyApp', [ require('angular-ui-router'), require('oclazyload'), require('./pages/home/home.routing').name, require('./pages/messages/messages.routing').name, ]);
라우팅 구성
모든 지연 로딩은 경로 구성에서 구현됩니다. 앞서 말했듯이 우리는 중첩된 뷰를 구현해야 하기 때문에 AngularUI Router를 사용하고 있습니다. 우리에게는 몇 가지 사용 사례가 있습니다. 전체 모듈(하위 상태 컨트롤러 포함)을 로드하거나 (상위 상태에 대한 종속성과 관계없이) 상태당 하나의 컨트롤러를 로드할 수 있습니다.
전체 모듈 로드
사용자가 /home 경로를 입력하면 브라우저가 홈 모듈을 다운로드합니다. 여기에는 home과 home.about의 두 가지 상태에 대한 두 개의 컨트롤러가 포함되어 있습니다. 상태 구성 객체의 해결 속성을 통해 지연 로딩을 구현할 수 있습니다. Webpack의 require.ensure 메소드 덕분에 홈 모듈을 첫 번째 코드 블록으로 생성할 수 있습니다. 1.bundle.js 라고 합니다. $ocLazyLoad.load가 없으면 Argument 'HomeController' is not a function, got unundefined 오류가 발생하는 것을 발견하게 됩니다. 왜냐하면 Angular의 디자인에서는 애플리케이션을 시작한 후 파일을 로드하는 것이 불가능하기 때문입니다. 그러나 $ocLazyLoad.load를 사용하면 시작 중에 모듈을 등록하고 로드된 후에 사용할 수 있습니다.
// home.routing.js 'use strict'; function homeRouting($urlRouterProvider, $stateProvider) { $urlRouterProvider.otherwise('/home'); $stateProvider .state('home', { url: '/home', template: require('./views/home.html'), controller: 'HomeController as vm', resolve: { loadHomeController: ($q, $ocLazyLoad) => { return $q((resolve) => { require.ensure([], () => { // load whole module let module = require('./home'); $ocLazyLoad.load({name: 'home'}); resolve(module.controller); }); }); } } }).state('home.about', { url: '/about', template: require('./views/home.about.html'), controller: 'HomeAboutController as vm', }); } export default angular .module('home.routing', []) .config(homeRouting);
컨트롤러는 모듈 종속성으로 처리됩니다.
// home.js 'use strict'; export default angular .module('home', [ require('./controllers/home.controller').name, require('./controllers/home.about.controller').name ]);
로드 컨트롤러만
우리가 하고 있는 일은 첫 번째 단계이며, 다음 단계로 넘어가겠습니다. 이번에는 큰 모듈은 없고 간소화된 컨트롤러만 있을 것입니다.
// messages.routing.js 'use strict'; function messagesRouting($stateProvider) { $stateProvider .state('messages', { url: '/messages', template: require('./views/messages.html'), controller: 'MessagesController as vm', resolve: { loadMessagesController: ($q, $ocLazyLoad) => { return $q((resolve) => { require.ensure([], () => { // load only controller module let module = require('./controllers/messages.controller'); $ocLazyLoad.load({name: module.name}); resolve(module.controller); }) }); } } }).state('messages.all', { url: '/all', template: require('./views/messages.all.html'), controller: 'MessagesAllController as vm', resolve: { loadMessagesAllController: ($q, $ocLazyLoad) => { return $q((resolve) => { require.ensure([], () => { // load only controller module let module = require('./controllers/messages.all.controller'); $ocLazyLoad.load({name: module.name}); resolve(module.controller); }) }); } } })
여기에는 특별한 것이 없으며 규칙은 그대로 유지될 수 있다고 생각합니다.
뷰 로드 중
이제 컨트롤러를 잠시 놓고 뷰에 집중해 보겠습니다. 눈치채셨겠지만 우리는 라우팅 구성에 뷰를 포함시켰습니다. 모든 라우팅 구성을 Bundle.js 내부에 넣지 않았다면 문제가 되지 않았지만 이제는 그렇게 해야 합니다. 이 경우는 지연 로딩 경로 구성이 아니라 뷰에 관한 것이므로 Webpack을 사용하여 구현하면 매우 간단합니다.
// messages.routing.js ... .state('messages.new', { url: '/new', templateProvider: ($q) => { return $q((resolve) => { // lazy load the view require.ensure([], () => resolve(require('./views/messages.new.html'))); }); }, controller: 'MessagesNewController as vm', resolve: { loadMessagesNewController: ($q, $ocLazyLoad) => { return $q((resolve) => { require.ensure([], () => { // load only controller module let module = require('./controllers/messages.new.controller'); $ocLazyLoad.load({name: module.name}); resolve(module.controller); }) }); } } }); } export default angular .module('messages.routing', []) .config(messagesRouting);
중복 종속성 주의
message.all.controller와 message.new.controller의 내용을 살펴보겠습니다.
// messages.all.controller.js 'use strict'; class MessagesAllController { constructor(msgStore) { this.msgs = msgStore.all(); } } export default angular .module('messages.all.controller', [ require('commons/msg-store').name, ]) .controller('MessagesAllController', MessagesAllController); // messages.all.controller.js 'use strict'; class MessagesNewController { constructor(msgStore) { this.text = ''; this._msgStore = msgStore; } create() { this._msgStore.add(this.text); this.text = ''; } } export default angular .module('messages.new.controller', [ require('commons/msg-store').name, ]) .controller('MessagesNewController', MessagesNewController);
문제의 원인은 require('commons/msg-store').name 입니다. 컨트롤러 간 메시지 공유를 실현하려면 msgStore 서비스가 필요합니다. 이 서비스는 두 패키지 모두에 존재합니다. message.all.controller에 하나가 있고 message.new.controller에 또 하나가 있습니다. 이제 최적화할 여지가 없습니다. 어떻게 해결하나요? msgStore를 애플리케이션 모듈의 종속성으로 추가하기만 하면 됩니다. 이것이 완벽하지는 않지만 대부분의 경우 충분합니다.
// app.js 'use strict'; export default require('angular') .module('lazyApp', [ require('angular-ui-router'), require('oclazyload'), // msgStore as global dependency require('commons/msg-store').name, require('./pages/home/home.routing').name, require('./pages/messages/messages.routing').name, ]);
单元测试的技巧
把 msgStore 改成是全局依赖并不意味着你应该从控制器中删除它。如果你这样做了,在你编写测试的时候,如果没有模拟这一个依赖,那么它就无法正常工作了。因为在单元测试中,你只会加载这一个控制器而非整个应用模块。
// messages.all.controller.spec.js 'use strict'; describe('MessagesAllController', () => { var controller, msgStoreMock; beforeEach(angular.mock.module(require('./messages.all.controller').name)); beforeEach(inject(($controller) => { msgStoreMock = require('commons/msg-store/msg-store.service.mock'); spyOn(msgStoreMock, 'all').and.returnValue(['foo', ]); controller = $controller('MessagesAllController', { msgStore: msgStoreMock }); })); it('saves msgStore.all() in msgs', () => { expect(msgStoreMock.all).toHaveBeenCalled(); expect(controller.msgs).toEqual(['foo', ]); }); });
以上内容是小编给大家分享的Webpack 实现 AngularJS 的延迟加载,希望对大家有所帮助!