前言
上篇从多线程打包
和缩小打包作用域
两个方面入手,对webpack打包层面做出优化。本篇描述从动态链接库思想
方面继续深入探究打包层面的深度优化。
动态链接库与DLLPlugin
动态链接库(Dynamic Link Library 或者 Dynamic-link Library,缩写为 DLL),是微软公司在微软Windows操作系统中,实现共享函数库概念的一种方式。这些库函数的扩展名是 ”.dll"、".ocx"(包含ActiveX控制的库)或者 ".drv"(旧式的系统驱动程序)。
当一段相同的子程序被多个程序调用时,为了减少内存消耗,可以将这段子程序存储为一个可执行文件,当被多个程序调用时只在内存中生成和使用同一个实例。
今天要介绍的主角“DLLPlugin”则借鉴了动态链接库的思路,对于第三方模块或者一些不常变化的模块预先进行编译和打包,然后再项目实际构建过程中直接取用。不过区别还是有的,DLLPlugin实际生成的文件是JS文件而不是动态链接库。在打包vendor的时候还会附加生成一份vendor的模块清单,这份清单将会在工程业务模块打包时起到链接和索引的作用。
1 vendor配置
首先需要为动态链接库单独创建一个Webpack配置文件,例如:webpack.vendor.config.js,注意要与webpack.config.js区分开来。
例:
// webpack.vendor.config.js
const path = require('path');
const webpack = require('webpack');
const dllAssetPath = path.join(__dirname, 'dll');
const dllLibraryName = 'dllExample';
module.exports = {
entry: ['react'],
output: {
path: dllAssetPath,
filename: 'vendor.js',
library: dllLibraryName
},
plugins: [
new webpack.DllPlugin({
name: dllLibraryName,
path: path.join(dllAssetPath, 'manifest.json')
})
]
}
其中,entry指定了将哪些模块打包为vendor,plugins的部分引入了DLLPlugin,并有如下配置:
- name: 导出的dll library的名字,需要与output.library的值对应;
- path: 资源清单的绝对路径,业务打包时将会使用这个清单进行模块索引;
2 vendor打包
接下来就要打包vendor并且生成资源清单。为后续方便操作,可以在package.json中配置一条运行指令:
// pachage.json
{
...
"scripts": {
...
"dll": "webpack --config webpack.vendor.config.js"
}
}
然后执行npm run dll
,会发现生成了一个dll目录,里面对应有两个文件:
- vendor.js: 库的代码
- manifest.json: 资源清单
感兴趣的可以打开这两个文件阅读一下。
3 链接到业务代码
试过之后,我们就要考虑将vendor链接到项目中去了。这里推荐与DLLPlugin配套的插件“DLLReferencePlugin”,它起到索引和链接作用。在工程的webpack配置文件中(注意是webpack.config.js,不是vendor的配置文件),通过DLLReferencePlugin来获取刚才打包好的资源清单,然后在页面中添加vendor.js就可以引用。
// webpack.cinfig.js
const path = require('path');
const webpack = require('webpack');
module.exports = {
...
plugins: [
new webpack.DllReferencePlugin({
manifest: require(path.join(__dirname, 'dll/manifest.json')
})
]
}
那么 index.html 引入会即可:
...
<body>
...
<script src="dll/vendor.js"></script>
<script src="dist/app.js"></script>
</body>
设置完毕后,当页面执行到vendor.js时,会声明全局变量dllExample,而manifest相当于注入app.js的资源地图,app.js会通过name字段找到名为DLLExample的library,再进一步获取其内部模块。
4 潜在问题
细心的小伙伴或许已经发现了,在当前配置中会存在一个问题:当打开manifest.json文件后,可以发现每个模块都会有一个id,其值是按照数字顺序递增的,而业务代码在引用vendor中模块时也是引用这个数字id,当我们更vendor时这个数字id也会随之发生改变。
现假设我们工程目录中有如下资源文件,并每个资源都加上了chunk hash:
- vendor@[hasn].js
- pageUser@[hasn].js
- pageIndex@[hasn].js
- util@[hasn].js
现在vendor中you一些模块,例如包含了react,其id为5.当尝试添加更多模块到vendor中的时候,那么重新进行Dll构建时,moment.js可能出现在react之前,此时react的id会变为6.而pageUser和pageIndex是通过id进行引用的,因此他们的文件内容也发生了改变。此时我们会面临如下情况:
- 两个页面的chunk hash均发生了改变。这是我们不希望看到的,因为他们本身并无变化,但是vendor的改变却驱使用户不得不重新下载所有资源。
- 两个页面chunk hash没有改变,但是这种情况更为糟糕:vendor中的模块id改变了,但是用户没有更新缓存,使用的还是旧版本的内容,而引用不到新的vendor模块,导致页面发生错误。并且对于开发者而言,这个错误却难以排查,因为开发环境下一切正常!
针对上述的问题2,解决方法是在打包vendor时添加上HashedModuleIdsPlugin,如下:
// webpack.vendor.config.js
module.exports = {
...
plugins: [
new webpack.DllPlugin({
name: dllLibraryName,
path: path.join(dllAssetPath, 'manifest.json')
}),
// 添加HashedModuleIdsPlugin
new webpack.HashedModuleIdsPlugin();
]
}
HashedModuleIdsPlugin是webpack3中被引入进来的,主要就是为了解决数字id的问题。HashedModuleIdsPlugin可以把id的生成算法修改为根据模块的引用路径生成一个字符串hash。
注:从webpack3开始,模块id不仅可以是数字,也可以是字符串。
小结
本篇从动态链接库思想着手,介绍了DLLPlugin与其配套插件DLLReferencePlugin使用,将第三方库与一些不常改动的模块编译打包,处理为类似于动态链接库的JS文件,以此来节约服务器资源。
下一篇介绍打包优化最后一个环节:死代码检测与去除。
网友评论