Webpack 實現(xiàn) AngularJS 的延遲加載
隨著你的單頁應(yīng)用擴大,其下載時間也越來越長。這對提高用戶體驗不會有好處(提示:但用戶體驗正是我們開發(fā)單頁應(yīng)用的原因)。更多的代碼意味著更大的文件,直到代碼壓縮已經(jīng)不能滿足你的需求,你唯一能為你的用戶做的就是不要再讓他一次性下載整個應(yīng)用。這時,延遲加載就派上用場了。不同于一次性下載所有文件,而是讓用戶只下載他現(xiàn)在需要的文件。
所以。如何讓你的應(yīng)用程序?qū)崿F(xiàn)延遲加載?它基本上是分成兩件事情。把你的模塊拆分成小塊,并實施一些機制,允許按需加載這些塊。聽起來似乎有很多工作量,不是嗎?如果你使用 Webpack 的話,就不會這樣。它支持開箱即用的代碼分割特性。在這篇文章中我假定你熟悉 Webpack,但如果你不會的話,這里有一篇介紹 。為了長話短說,我們也將使用 AngularUI Router 和 ocLazyLoad 。
代碼可以在 GitHub 上。你可以隨時 fork 它。
Webpack 的配置
沒什么特別的,真的。實際上從你可以直接從文檔中復(fù)制然后粘貼,唯一的區(qū)別是采用了 ng-annotate ,以讓我們的代碼保持簡潔,以及采用 babel 來使用一些 ECMAScript 2015 的魔法特性。如果你對 ES6 感興趣,可以看看 這篇以前的帖子 。雖然這些東西都是非常棒的,但是它們都不是實現(xiàn)延遲加載所必需的東西。
// 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;
應(yīng)用
應(yīng)用模塊是主文件,它必須被包括在 bundle.js 內(nèi),這是在每一個頁面上都需要強制下載的。正如你所看到的,我們不會加載任何復(fù)雜的東西,除了全局的依賴。不同于加載控制器,我們只加載路由配置。
// 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, ]);
路由配置
所有的延遲加載都在路由配置中實現(xiàn)。正如我所說,我們正在使用 AngularUI Router ,因為我們需要實現(xiàn)嵌套視圖。我們有幾個使用案例。我們可以加載整個模塊(包括子狀態(tài)控制器)或每個 state 加載一個控制器(不去考慮對父級 state 的依賴)。
加載整個模塊
當用戶輸入 /home 路徑,瀏覽器就會下載 home 模塊。它包括兩個控制器,針對 home 和 home.about 這兩個state。我們通過 state 的配置對象中的 resolve 屬性就可以實現(xiàn)延遲加載。得益于 Webpack 的 require.ensure 方法,我們可以把 home 模塊創(chuàng)建成第一個代碼塊。它就叫做 1.bundle.js 。如果沒有 $ocLazyLoad.load ,我們會發(fā)現(xiàn)得到一個錯誤 Argument 'HomeController' is not a function, got undefined ,因為在 Angular 的設(shè)計中,啟動應(yīng)用之后再加載文件的方式是不可行的。 但是 $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); }) }); } } })
我相信在這里沒有什么特別的,規(guī)則可以保持不變。
加載視圖(Views)
現(xiàn)在,讓我們暫時放開控制器而去關(guān)注一下視圖。正如你可能已經(jīng)注意到的,我們把視圖嵌入到了路由配置里面。如果我們沒有把里面所有的路由配置放進 bundle.js ,這就不會是一個問題,但現(xiàn)在我們需要這么做。這個案例不是要延遲加載路由配置而是視圖,那么當我們使用 Webpack 來實現(xiàn)的時候,這會非常簡單。
// 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);
當心重復(fù)的依賴
讓我們來看看 messages.all.controller 和 messages.new.controller 的內(nèi)容。
// 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 這一個服務(wù),來實現(xiàn)控制器之間的消息共享。此服務(wù)在兩個包中都存在。在 messages.all.controller 中有一個,在 messages.new.controller 中又有一個?,F(xiàn)在,它已經(jīng)沒有任何優(yōu)化的空間。如何解決呢?只需要把 msgStore 添加為應(yīng)用模塊的依賴。雖然這還不夠完美,在大多數(shù)情況下,這已經(jīng)足夠了。
// 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 改成是全局依賴并不意味著你應(yīng)該從控制器中刪除它。如果你這樣做了,在你編寫測試的時候,如果沒有模擬這一個依賴,那么它就無法正常工作了。因為在單元測試中,你只會加載這一個控制器而非整個應(yīng)用模塊。
// 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', ]); }); });
以上內(nèi)容是小編給大家分享的Webpack 實現(xiàn) AngularJS 的延遲加載,希望對大家有所幫助!
相關(guān)文章
詳解如何在Angular應(yīng)用中發(fā)起HTTP?302 redirect
這篇文章主要介紹了如何在Angular應(yīng)用中發(fā)起HTTP 302 redirect詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-12-12深入淺析angular和vue還有jquery的區(qū)別
vue是一個漸進式的框架, 是一個輕量級的框架而angular是一個mvc框架, 各有千秋,下面小編通過本文給大家介紹angular和vue還有jquery的區(qū)別,感興趣的朋友一起看看吧2018-08-08Angular實現(xiàn)的內(nèi)置過濾器orderBy排序與模糊查詢功能示例
這篇文章主要介紹了Angular實現(xiàn)的內(nèi)置過濾器orderBy排序與模糊查詢功能,涉及AngularJS過濾器、排序及字符串遍歷、查詢等相關(guān)操作技巧,需要的朋友可以參考下2017-12-12深究AngularJS如何獲取input的焦點(自定義指令)
本篇文章主要介紹了AngularJS如何獲取input的焦點(自定義指令),具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-06-06AngularJS使用$http配置對象方式與服務(wù)端交互方法
今天小編就為大家分享一篇AngularJS使用$http配置對象方式與服務(wù)端交互方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-08-08