前言
春节放假前打开知乎整理整理文章,发现7个月前开源的知乎专栏文章LsLoader,对AMD模式和webpack1打包的移动端网站技术进行了详解,基本原理是利用对AMD语法的分析拆分请求,本地缓存,然后按需combo变化的代码。滴滴,美团,百度等都在用类似的思路解决问题。然而随着业务需求的增加和前端工具的升级,webpack+es6+Vue/React的复杂数据逻辑型业务越来越多,AMD格式通过改良虽可适配类似*.vue单文件组件话开发的需求,官方给出的demo还是webpack格式。如果lsLoader 直接套用webpack1的打包方式,出现了如下问题:
webpack1模块序号不稳定:
webpack1打包后生成的代码模块(简称chunks,下文统用该叫法)形如:
/* 0 *//***/function(module, exports, __webpack_require__){
模块0内容
},
/* 1 */
/***/function(module, exports, __webpack_require__){
模块1内容
}
按照webpack生成的数字序号进行编译,如果我们在应用的入口新加引用包,则编译出来的chunksID会全部变化。如果原来只有1,2,3模块,现在加入4以后原来1,2,3的模块编号也会变化,导致4个chunks在浏览器的缓存全部作废。
0、LsLoader支持webpack2拆分了
git clone 项目后
项目根目录下npm install
运行gulp webpack 源码预处理
再运行 webpack 打包
最后gulp addcombo 完成所有前端处理工作
运行node app.js 启动express
访问http://localhost:3000/webpack/index 即可看见打包后的webpack2代码
访问首页后,打开chrome dev tools
看到localStroage中缓存了首页入口文件,vue类库的源码,list.vue的单文件组件
开到network,再点击二级页面,
两个页面公用了vue库和一个list.vue组件,所以页面2的webpack只要加载一个入口文件就能运行了。
示例代码只是简单的演示了.js .vue文件,由于webpack require everything的特性,结合特定的webpack-loader,我们也可以拆分缓存react应用,babel-es6应用,大体积的logo图片等等各种静态资源
1、webpack2带来的变化
在webpack2的打包过程中,通过new webpack.HashedModuleIdsPlugin()插件,支持了对chunks按照md5进行模块标注,使用插件后,生成模块代码形如:
/***/ "2x7n":
/***/ (function(module, __webpack_exports__, __webpack_require__) {
模块0内容
},
/***/ "8CB3":
/***/ (function(module, exports) {
模块1内容
}
这样生成后,只要chunks内容不变,打包后的chunks代码就不会改变。
chunks已经稳定了,但是如果直接打包所有的代码都是一个bundle一坨代码,如何拆分优化呢?那就要借助webpack的comonchunksPlugin了
webpack.config.js中 plugin项目加入如下配置
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name:'模块A名称',
filename : 'common_[chunkhash].js', //生成的文件名
chunks : ["index","list"] //引用到的入口文件
}),
new webpack.optimize.CommonsChunkPlugin({
name:'模块B名称',
filename : 'common_[chunkhash].js', //生成的文件名
chunks : ["index","list"] //引用到的入口文件
})
]
把所有需要提取出来的模块都这么写到配置文件中,这样不管你在入口中引用的是.vue单文件,还是es6的js模块文件,抑或是css文件都可以拆分成单个文件加载/存储localStorage
2、工程化解决webpack2的配置修改以配合lsLoader
LsLoader只要提供一组js的文件名&线上md5路径就可以完成本地缓存,优化加载,顺序执行的工作。对于webpack2模块,我们需要最终在浏览器运行的是3块代码:
1、common代码
这里定义了webpack的模块调用,webpackJSONP函数
2、chunks代码,所有入口依赖的代码在这里按顺序运行,放在入口之前
3、上面两部分运行完成后运行入口js文件,调用之前定义的模块运行
在示例代码中,我展示了简单的几个配置步骤,如果你的项目要接入可以自己按照如下步骤处理或者按需调整:
第一步:维护入口文件webpack_entry.json路径
类似如下json ,key=入口文件名,value = 文件路径,告诉webpack要分析哪些入口
{
"list":"./dev/webpack2/js/pages/list.js",
"index":"./dev/webpack2/js/pages/index.js"
}
第二步:分析源码路径,把import模块遍历出来
webpack2 使用es6 import格式组织代码,由于uglifyJS暂时不支持es6的ast语法树分析,我暂时用正则匹配实现了一个简单的分析逻辑,即筛选出 file.match(/(import.*from.*)/g) 格式的依赖路径,然后把所有依赖到的模块结合webpack_entry.json的内容拼接到一起,我们管他叫webpack_lsloader_entry.json
{
"list": "./dev/webpack2/js/pages/list.js",
"index": "./dev/webpack2/js/pages/index.js",
"vueminjs": ["./dev/webpack2/js/lib/vue.min.js" ],
"listvue": ["./dev/webpack2/js/component/list.vue" ]
}
注意生成的入口文件是string类型,chunks文件是array类型
同时生成 commonChunksConfig.json 这个是用来标注chunks模块的配置,有了这些webpack打包才会拆分这些文件
[
{
"name": "vueminjs",
"filename": "vueminjs_[chunkhash].js",
"chunks": [
"list",
"index"
]
},
{
"name": "listvue",
"filename": "listvue_[chunkhash].js",
"chunks": [
"list",
"index"
]
}
]
第三步:分析源码的同时把入口-依赖对应关系写入模块表moduleMap.json
还是上步骤的分析,我们把对应的入口和依赖关系打印到一张表格中,这样方便页面渲染给前端代码使用,表明依赖关系
{
"index.js": { //入口名
"vueminjs": 1, //vueminjs 模块 引用一次
"listvue": 1
},
"list.js": {
"vueminjs": 1,
"listvue": 1
}
}
第四步,用webpack_lsloader_entry.json 中的入口列表以及commonChunksConfig.json的pe配置文件打包,执行webpack
这一部,如果看到我们配置的入口文件和chunks文件都单独打包出来,即说明我们的打包工作成功了

如果我们有cdn线上combo的话,需要在生成的webpack文件中加入分割符号(有的公司服务自带,即可省略)。同理,遍历build好的webpack文件,统一插入/*combojs*/做为文件分割符。
最后一步 前端页面调用
工程化处理完后,我们有源码名字-md5名字的manifest.json表,有所有入口模块对应依赖的moduleMap.json表,我们需要的就是把对应的关系,路径翻译成lsLoader能理解的格式,这个过程可以单独用gulp打包时候进行,如果前端可以写model层亦可放在模版渲染时处理。
示例demo中,我用express服务读取对应关系打印到前端页面,最终浏览器运行代码是:
<script>
lsloader.load("common.js","/webpack2/common_b13d8ba2eaa54cdcf63b.js")
</script>
加载common
<script>
(function(){
var relyModules = [];
relyModules.push({
name : 'vueminjs',
path : '/webpack2/vueminjs_563fcfec3a6d200ca9bc.js'
});
relyModules.push({
name : 'listvue',
path : '/webpack2/listvue_ee2e54f3faed72949254.js'
});
lsloader.loadCombo(relyModules);
})()
</script>
加载依赖模块,利用loadCombo合并请求
<script>
lsloader.load("index.js","/webpack2/page_index_62d29106ebc4fa7d88d1.js")
</script>
加载入口文件
如上述步骤,我们通过node.js脚步就自动化完成了对webpack2项目的拆分缓存。
3、实现意义
目前主流工程化框架例如FIS Scrat.js都使用自定义的require函数实现了AMD标准进行模块处理,但是对于webpack格式支持力度不够。而webpack2随着es6 vue2 的流行,不可避免需要引入部分项目。webpack本身的commonChunks不改动配置的话会对整个配置文件内的公用模块提取一个common.js 缓存命中率低,单文件尺寸较大。使用自定义的chunks配置可以合理配置文件荷载的同时不丢弃webpack的require anything的优点,无痛兼容react/vue 主流MVVM实践。拆分粒度过于细小也会引发webpack引入过多包装代码的问题,所以控制粒度需要结合自己的项目进行把控,一般建议一个单文件模块对应一个chunks,不要一个函数拆出一个chunks,就得不偿失了。
欢迎自己业务中试用这种思路,欢迎讨论各种前端业内解决方案