Webpack 实现 AngularJS 的延迟加载


Posted in Javascript onMarch 02, 2016

随着你的单页应用扩大,其下载时间也越来越长。这对提高用户体验不会有好处(提示:但用户体验正是我们开发单页应用的原因)。更多的代码意味着更大的文件,直到代码压缩已经不能满足你的需求,你唯一能为你的用户做的就是不要再让他一次性下载整个应用。这时,延迟加载就派上用场了。不同于一次性下载所有文件,而是让用户只下载他现在需要的文件。

所以。如何让你的应用程序实现延迟加载?它基本上是分成两件事情。把你的模块拆分成小块,并实施一些机制,允许按需加载这些块。听起来似乎有很多工作量,不是吗?如果你使用 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 的延迟加载,希望对大家有所帮助!

Javascript 相关文章推荐
JavaScript 选中文字并响应获取的实现代码
Aug 28 Javascript
游览器中javascript的执行过程(图文)
May 20 Javascript
select标记美化--JS式插件、后期加载
Apr 01 Javascript
js一般方法改写成面向对象方法的无限级折叠菜单示例代码
Jul 04 Javascript
jQuery检测返回值的数据类型
Jul 13 Javascript
JavaScript类型系统之基本数据类型与包装类型
Jan 06 Javascript
Bootstrap Fileinput文件上传组件用法详解
May 10 Javascript
浅谈PDF.js使用心得
Jun 07 Javascript
在vue中获取token,并将token写进header的方法
Sep 26 Javascript
原生js实现针对Dom节点的CRUD操作示例
Aug 26 Javascript
Vue实现渲染数据后控制滚动条位置(推荐)
Dec 09 Javascript
vue实现移动端H5数字键盘组件使用详解
Aug 25 Javascript
浅谈JS原型对象和原型链
Mar 02 #Javascript
jquery单击事件和双击事件冲突解决方案
Mar 02 #Javascript
jQuery实现只允许输入数字和小数点的方法
Mar 02 #Javascript
jQuery Mobile开发中日期插件Mobiscroll使用说明
Mar 02 #Javascript
javascript求日期差的方法
Mar 02 #Javascript
基于JavaScript实现表单密码的隐藏和显示出来
Mar 02 #Javascript
jQuery判断浏览器并动态调整select宽度的方法
Mar 02 #Javascript
You might like
php中几种常见安全设置详解
2010/04/06 PHP
简单的php文件上传(实例)
2013/10/27 PHP
详解PHP对象的串行化与反串行化
2016/01/24 PHP
laravel5.4生成验证码的实例讲解
2017/08/05 PHP
php+lottery.js实现九宫格抽奖功能
2019/07/21 PHP
心扬JS分页函数代码
2010/09/10 Javascript
jQuery UI 实现email输入提示实例
2013/08/15 Javascript
js的alert样式如何更改如背景颜色
2014/01/22 Javascript
浅谈JavaScript 框架分类
2014/11/10 Javascript
基于JavaScript代码实现兼容各浏览器的设为首页和加入收藏
2016/01/07 Javascript
Bootstrap学习笔记之环境配置(1)
2016/12/07 Javascript
js原生Ajax的封装和原理详解
2017/03/11 Javascript
AngularJs+Bootstrap实现漂亮的计算器
2017/08/10 Javascript
详解Vue.js v-for不支持IE9的解决方法
2018/12/29 Javascript
详解微信小程序-获取用户session_key,openid,unionid - 后端为nodejs
2019/04/29 NodeJs
浅谈element中InfiniteScroll按需引入的一点注意事项
2020/06/05 Javascript
Python获取Linux系统下的本机IP地址代码分享
2014/11/07 Python
Python使用sorted排序的方法小结
2017/07/28 Python
python多线程socket编程之多客户端接入
2017/09/12 Python
Python测试网络连通性示例【基于ping】
2018/08/03 Python
Python使用正则表达式分割字符串的实现方法
2019/07/16 Python
python二分法查找算法实现方法【递归与非递归】
2019/12/06 Python
Jupyter Notebook折叠输出的内容实例
2020/04/22 Python
美术师范毕业生自荐信
2013/11/16 职场文书
给实习单位的感谢信
2014/02/01 职场文书
怎么写好自荐书
2014/03/02 职场文书
《骑牛比赛》教后反思
2014/04/22 职场文书
读书伴我成长演讲稿
2014/05/07 职场文书
2015年教师自我评价范文
2015/03/04 职场文书
2015年感恩节演讲稿(优选篇)
2015/03/20 职场文书
2016个人先进事迹材料范文
2016/03/01 职场文书
Vue实现导入Excel功能步骤详解
2021/07/03 Vue.js
详解JSON.parse和JSON.stringify用法
2022/02/18 Javascript
一条 SQL 语句执行过程
2022/03/17 MySQL
淡雅古典唯美少女娇媚宁静迷人写真
2022/03/21 杂记
Java实现简单小画板
2022/06/10 Java/Android