摘要:雖然這些東西都是非常棒的,但是它們都不是實現延遲加載所必需的東西。我們通過的配置對象中的屬性就可以實現延遲加載。單元測試的技巧把改成是全局依賴并不意味著你應該從控制器中刪除它。因為在單元測試中,你只會加載這一個控制器而非整個應用模塊。
原文鏈接:http://michalzalecki.com/lazy-load-angularjs-with-webpack/
隨著你的單頁應用擴大,其下載時間也越來越長。這對提高用戶體驗不會有好處(提示:但用戶體驗正是我們開發單頁應用的原因)。更多的代碼意味著更大的文件,直到代碼壓縮已經不能滿足你的需求,你唯一能為你的用戶做的就是不要再讓他一次性下載整個應用。這時,延遲加載就派上用場了。不同于一次性下載所有文件,而是讓用戶只下載他現在需要的文件。
所以。如何讓你的應用程序實現延遲加載?它基本上是分成兩件事情。把你的模塊拆分成小塊,并實施一些機制,允許按需加載這些塊。聽起來似乎有很多工作量,不是嗎?如果你使用 Webpack 的話,就不會這樣。它支持開箱即用的代碼分割特性。在這篇文章中我假定你熟悉 Webpack,但如果你不會的話,這里有一篇介紹 。為了長話短說,我們也將使用 AngularUI Router 和 ocLazyLoad 。
代碼可以在 GitHub 上。你可以隨時 fork 它。
Webpack 的配置沒什么特別的,真的。實際上從你可以直接從文檔中復制然后粘貼,唯一的區別是采用了 ng-annotate ,以讓我們的代碼保持簡潔,以及采用 babel 來使用一些 ECMAScript 2015 的魔法特性。如果你對 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 ,因為我們需要實現嵌套視圖。我們有幾個使用案例。我們可以加載整個模塊(包括子狀態控制器)或每個 state 加載一個控制器(不去考慮對父級 state 的依賴)。
加載整個模塊當用戶輸入 /home 路徑,瀏覽器就會下載 home 模塊。它包括兩個控制器,針對 home 和 home.about 這兩個state。我們通過 state 的配置對象中的 resolve 屬性就可以實現延遲加載。得益于 Webpack 的 require.ensure 方法,我們可以把 home 模塊創建成第一個代碼塊。它就叫做 1.bundle.js 。如果沒有 $ocLazyLoad.load,我們會發現得到一個錯誤 Argument "HomeController" is not a function, got undefined,因為在 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); }) }); } } }) ...
我相信在這里沒有什么特別的,規則可以保持不變。
加載視圖(Views)現在,讓我們暫時放開控制器而去關注一下視圖。正如你可能已經注意到的,我們把視圖嵌入到了路由配置里面。如果我們沒有把里面所有的路由配置放進 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);當心重復的依賴
讓我們來看看 messages.all.controller 和 messages.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 這一個服務,來實現控制器之間的消息共享。此服務在兩個包中都存在。在 messages.all.controller 中有一個,在 messages.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", 8]); controller = $controller("MessagesAllController", { msgStore: msgStoreMock }); })); it("saves msgStore.all() in msgs", () => { expect(msgStoreMock.all).toHaveBeenCalled(); expect(controller.msgs).toEqual(["foo", 8]); }); });
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/78768.html
學習的過程中收藏了這些優秀教程和的項目,希望對你有幫助。 github地址, 有不錯的就更新 官方文檔 中文指南 初級教程 webpack-howto 作者:Pete Hunt Webpack 入門指迷 作者:題葉 webpack-demos 作者:ruanyf 一小時包教會 —— webpack 入門指南 作者:VaJoy Larn webpack 入門及實踐 作者:...
摘要:第一種方式是使用模塊加載器,如果你使用加載器的話,路由在加載子路由模塊時也是用的作為模塊加載器。還需注意的是,想要使用還需像這樣去注冊它你當然可以在里使用任何標識,不過路由模塊使用標識,所以最好也使用相同。 原文鏈接:Here is what you need to know about dynamic components in?Angular showImg(https://se...
摘要:什么是單頁面應用單頁面應用是指用戶在瀏覽器加載單一的頁面,后續請求都無需再離開此頁目標旨在用為用戶提供了更接近本地移動或桌面應用程序的體驗。流程第一次請求時,將導航頁傳輸到客戶端,其余請求通過獲取數據實現數據的傳輸通過或遠程過程調用。 什么是單頁面應用(SPA)? 單頁面應用(SPA)是指用戶在瀏覽器加載單一的HTML頁面,后續請求都無需再離開此頁 目標:旨在用為用戶提供了更接近本地...
摘要:什么是單頁面應用單頁面應用是指用戶在瀏覽器加載單一的頁面,后續請求都無需再離開此頁目標旨在用為用戶提供了更接近本地移動或桌面應用程序的體驗。流程第一次請求時,將導航頁傳輸到客戶端,其余請求通過獲取數據實現數據的傳輸通過或遠程過程調用。 什么是單頁面應用(SPA)? 單頁面應用(SPA)是指用戶在瀏覽器加載單一的HTML頁面,后續請求都無需再離開此頁 目標:旨在用為用戶提供了更接近本地...
閱讀 2980·2021-11-16 11:45
閱讀 5124·2021-09-22 10:57
閱讀 1763·2021-09-08 09:36
閱讀 1584·2021-09-02 15:40
閱讀 2508·2021-07-26 23:38
閱讀 1184·2019-08-30 15:55
閱讀 923·2019-08-30 15:54
閱讀 1213·2019-08-29 14:06