阅读 641

使用 LsLoader 优化 webpack2 应用加载性能,实现按 chunks 加载 web 应用

Demo git仓库: GitHub - sexdevil/LSLoader: localStorage loader to increase mobile webapp speed 欢迎clone 欢迎star

前言

春节放假前打开知乎整理整理文章,发现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文件都单独打包出来,即说明我们的打包工作成功了


第五步 (可选) combo配置

如果我们有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,就得不偿失了。

欢迎自己业务中试用这种思路,欢迎讨论各种前端业内解决方案