原文链接: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]);
});
});