尝试通过AngularJS模块按需加载搭建大型应用(上)

没有合适的轮子,只好自己造( ╯□╰ )

模块化

模块化在当前大中型web项目开发中已经成为共识,从浏览器端AMD的requirejs到node.js遵循的commonjs规范,再到ES6开放了import关键字来管理模块都是最好的说明。其优势就是方便多人协作开发,代码可移植性可测试性高。
angular本身自带模块定义和依赖管理功能,这个模块管理一般是怎么使用的?
我们找个网站分析一波~
作为爱智求真的小伙伴,我们就来看看 知乎 旗下的 知乎专栏
先打开调试工具,截个图慢慢看~

点开这个带有hash戳被压缩的js文件,可以看到如下几个模块:

1
2
3
4
5
6
7
8
9
10
11
(function() {
angular.module("auth", ["auth.interceptor"])
}
).call(this),
...
function() {
angular.module("auth.interceptor", []).
}.call(this)
...
var app = angular.module("columnWebApp", ["auth", "blueimp.fileupload", "placeholderShim", "angularytics", "ngAnimate", "ngRoute", "ngTouch", "ngResource", "ngSanitize"])
...

这里声明了3个模块:

  • auth模块依赖了auth.interceptor模块。
  • auth.interceptor模块无依赖。
  • columnWebApp模块是个花心大萝卜,和一堆的模块有依赖关系。

弱弱地吐槽一波:用匿名函数做闭包来避免全局污染的是常见的传统做法,这里初步判断应该是由多个js文件合并而成,这里起码有两点是可以改进的。 一是windowangular这种全局变量完全可以传参到闭包内部便于代码压缩,例如:

1
2
3
4
5
6
7
8
//压缩前
(function(window, angular){
angular.module('auth', []);
}(window, angular));
//压缩后可能是
(function(window, a){
a.module('auth', []);
}(window, angular));

二是这种压缩打包的方式还是比较粗糙的,建议开发人员有时间还是可以将多余的模块引用去掉,变成链式调用,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 合并前
angular.module('auth').service('xxx', function(){
...
});
angular.module('auth').controller('xxCtrl', ['$scope', function($scope){
...
}]);
// 合并后
angular.module('auth').service('xxx', function(){
...
}).controller('xxCtrl', ['$scope', function($scope){
...
}]);

另外严格模式也建议开启。
吐槽 end~


从上面的例子可以看出,Angular项目通用的做法是:开发的时候按照业务逻辑写成不同的js文件,然后把这些js文件压缩合并成一个或多个,在用户第一次访问的时候全部返回给浏览器做预加载。

这种处理方式在小型网站还好,性能要求不高的中型网站也勉强可用,但是对于对于大型网站影响,随着js文件变多用户首次访问的加载时间会跟着增加,看看那些PV上千万、上亿的网站为了首页优化各种技术层出不穷。而且这种处理方式也是很不合理的,用户可能只想看看首页或者只使用某个功能模块,为毛要把所有的代码都家再过来浪费用户时间?

张鑫旭写过一篇 《基于用户行为的图片等资源预加载》 说的也是类似问题。这个解决思路很有意思,分析用户的操作行为,按需进行预加载。有兴趣大家可以看一看。
既然有了这个需求,我们看看目前有什么现成的解决方案~

按需加载

目前angular项目普遍使用ui.router模块已经成了不争的事实了,用它来管理每个“页面(视图)”和“页面对应的业务逻辑(控制器)”,依照大多数人的直觉思维。将按需加载理解为按页面加载,于是便出现了一些解决方案,基本思路都是一致的:

在“页面(视图)跳转”的时候,按需加载业务逻辑(控制器)。而恰好ui.router模块在配置路由的时候提供了一个resovle属性,可以在页面逻辑(控制器)加载之前进行一些操作。略有不同的便是有的使用了oclazyload,这个还比较好,起码是遵循angular框架的第三方模块,有的使用了requirejs,这种感觉就像是阿玛尼西装配阿迪达斯球鞋,虽然都是“阿”字系的,但想去甚远,做angular开发的时候还是尽量不要应用不按照angular规范开发的第三方插件,毕竟angular这种强框架不像requirejs有shim,所以还是避免给自己挖坑。

可这真的就是完美的解决方案吗?起码有以下几个问题:

  1. 每次“页面跳转”都要额外请求js并加载,浪费带宽增加页面加载时间,基本抛弃了预加载。
  2. 每一个路由都需要配置resolve属性,太low。
  3. 模块化程度太低,不利于以后代码移植和维护。

模块按需加载

将前面提到的模块化和按需加载结合起来看,比较好的解决方案应该是:按照业务功能划分模块,当用户点击某个功能模块的时候,按需加载该功能所需的文件。

举个不太恰当但容易理解的例子,当用户点击百度音乐的时候,从用户行为角度分析,这个用户很有可能会进行一些相关操作:比如搜歌、听歌等等。同时很有可能用户当前并不会访问贴吧或者新闻。所以此时按需加载音乐模块的代码逻辑,对于后续的搜歌、听歌操作实现了预加载。

这样的实现方式还会带来其它好处,下回详述~

同时也欢迎读者提供好的想法或者其它解决方案~ (●’◡’●)


一部由众多技术专家推荐, 帮你成为具有全面能力和全局视野工程师的进阶利器—— 《了不起的JavaScript工程师》出版了! 点击下方链接即刻踏上进阶之路!


亚里士朱德 wechat
更多WEB技术分享请订阅微信公众号“WEB学习社”