Webpack 实现 AngularJS 的延迟加载
随着你的单页应用扩大,其下载时间也越来越长。这对提高用户体验不会有好处(提示:但用户体验正是我们开发单页应用的原因)。更多的代码意味着更大的文件,直到代码压缩已经不能满足你的需求,你唯一能为你的用户做的就是不要再让他一次性下载整个应用。这时,延迟加载就派上用场了。不同于一次性下载所有文件,而是让用户只下载他现在需要的文件。
所以。如何让你的应用程序实现延迟加载?它基本上是分成两件事情。把你的模块拆分成小块,并实施一些机制,允许按需加载这些块。听起来似乎有很多工作量,不是吗?如果你使用 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', ]); controller = $controller('messagesallcontroller', { msgstore: msgstoremock }); })); it('saves msgstore.all() in msgs', () => { expect(msgstoremock.all).tohavebeencalled(); expect(controller.msgs).toequal(['foo', ]); }); });
以上内容是小编给大家分享的webpack 实现 angularjs 的延迟加载,希望对大家有所帮助!
推荐阅读
-
PHP中使用虚代理实现延迟加载技术
-
基于WebUploader实现单图片和多图片上传,上传回显,编辑加载,图片删除,位置切换以及基于PhotoSwipe框架的图片预览功能
-
asp.net ext treepanel 动态加载XML的实现方法
-
jQuery向下滚动即时加载内容实现的瀑布流效果,jquery向下滚动_PHP教程
-
angularjs和微信配合初次数据加载的问题
-
PHP框架如何实现自动加载类和类常驻内存的?
-
php-网上的文件下载和视频预加载是如何实现的?
-
jQuery向下滚动即时加载内容实现的瀑布流效果_php实例
-
angularjs实现柱状图动态加载的示例
-
AngularJS实现动态切换样式的方法分析