Webpack:动态链接库与 DllPlugin
我正在参加「掘金·启航计划」
动态链接库
动态链接库是早期 Windows 系统由于受限于当时计算机内存空间较小的问题而出现的一种内存优化方法。当一段相同的子程序被多个程序调用时,为了减少内存消耗,可以将这段子程序存储为一个可执行文件,当被多个程序调用时只在内存中生成和使用同一个实例。
DllPlugin 的思想
DllPlugin 借鉴了动态链接库的这种思路,对于第三方模块或者一些不常变化的模块,可以将他们预先编译和打包,然后在项目实际构建过程中直接取用即可。当然,通过 DllPlugin 实际生成的还是 JS 文件而不是动态链接库,取这个名字只是由于方法类似罢了。在打包 vendor 的时候还会附加生成一份 vendor 的模块清单(manifest),这份清单将会在工程业务模块打包时起到链接和索引的作用。
DllPlugin 和 Code Splitting 的区别
DllPlugin 和 Code Splitting(代码分片)有点类似,都可以用来提取公共模块,但本质上有一些区别。Code Splitting 的思路是设置一些特定的规则并在打包的过程中根据这些规则提取模块;DllPlugin 则是将 vendor 完全拆出来,有自己的一整套 Webpack 配置并独立打包,在实际工程构建时就不用再对它进行任何处理,直接取用即可。因此,理论上来说,DllPlugin 会比 Code Splitting 在打包速度上更胜一筹,但也相应地增加了配置,以及资源管理的复杂度。
DllPlugin 的配置
项目目录
build
├─ dll # 存放打包的 dll 文件和资源清单
│ ├─ manifest.json
│ └─ dll.js
├─ webpack.base.conf.js
├─ webpack.dev.conf.js
├─ webpack.prod.conf.js
└─ webpack.dll.conf.js
当然,目录可以根据自己项目更改,注意路径不要写错。
配置单独的 config 文件
首先要为动态链接库单独创建一个 Webpack 配置文件,比如命名为 Webpack.dll.config.js
。
const path = require('path');
const webpack = require('webpack');
var shell = require('shelljs');
// 重新构建前,清除上一次的数据
shell.rm('-rf', path.join(__dirname, './dll'));
module.exports = {
mode: 'production',
// 指定打包哪些模块
entry: {
vendor: [
'react',
'react-dom',
'react-redux',
'react-router-dom',
'@reduxjs/toolkit',
'axios',
// ....其他需要单独打包的库
],
},
resolve: {
extensions: ['.js', '.jsx'],
},
output: {
// 打包出的 dll js文件名
filename: '[name]-[hash:6].dll.js',
path: path.join(__dirname, '/dll/'),
library: '[name][hash:6]',
},
plugins: [
new webpack.DllPlugin({
name: '[name][hash:6]',
context: process.cwd(),
// 打包出的资源清单(manifest.json)的绝对路径地址以及名字
path: path.join(__dirname, '/dll/[name]-manifest.json')
}),
],
};
其中,webpack.DllPlugin 配置项说明如下:
name
:导出的 dll library 的名字,需要与 output.library 的值对应;context
:manifest 缓存文件的请求上下文(默认为 Webpack 执行环境上下文);path
:资源清单(manifest.json)的绝对路径,业务代码打包时将会使用到这个清单进行模块索引。
添加 script 脚本执行命令
为了后续运行方便,可以在 package.json
中配置一条 npm script:
// package.json
{
"scripts": {
"dll": "webpack --config build/webpack.dll.conf.js",
}
}
运行 npm run dll
后,便会在 build
目录中生成一个 dll
目录,里面会有两个文件:
前者包含了库的代码,后者则是资源清单。
可以预览一下生成的 dll.js,它以一个立即执行函数表达式的声明开始:
var vendorabba1e = (function (e) {
// ......
})([
function (e, t, n) {},
function (e, t, n) {},
// ......
])
上面的 vendorabba1e 函数名正是我们在 webpack.dll.conf.js 中指定的 library
的名字。
接着打开 vendor-manifest.json,其大体内容如下:
{
"name": "vendorabba1e",
"content": {
"./node_modules/react/index.js": {
"id": 0,
"buildMeta": {
"providedExports": true
}
},
"./node_modules/react-router/esm/react-router.js": {
// ......
}
}
}
其中的 name 字段就是我们通过 DllPlugin 中的 name 配置项指定的。
链接到业务代码
链接到项目很简单,使用与 DllPlugin 配套的插件 DllReferencePlugin,它起到一个索引和链接的作用。
// webpack.base.conf.js
const path = require('path');
const webpack = require('webpack');
module.exports = {
// ......
plugins: [
new webpack.DllReferencePlugin({
manifest: require(path.join(__dirname, '/dll/vendor-manifest.json')),
}),
]
}
HTML 自动引入 Dll 资源
最后,我们要将生成的 dll 资源引入到 HTML 中。这里,我们用到了一个很好用的插件 add-asset-html-webpack-plugin
,它会将我们配置的资源自动导入到打包后的 HTML 文件中。前提是,我们使用的是 html-webpack-plugin
插件来管理我们的 HTML。
// webpack.base.conf.js
const path = require('path');
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
module.exports = {
// ......
plugins: [
new AddAssetHtmlPlugin([
{
filepath: path.join(__dirname, '/dll/*.js'),
// 输出到 dist 目录下的 js 文件夹
outputPath: 'js',
// 脚本加载的资源路径
publicPath: `/js`,
hash: false,
includeSourcemap: false,
},
]),
]
}
我们执行 npm run build
,可以看到打包后的 dist
目录中,index.html
已经导入了我们的 dll 文件:
当页面执行到 vendor-abba1e.dll.js
时,会声明 vendorabba1e 全局变量。而 manifest 相当于我们注入 main.js 的地图资源,main.js 会先通过 name 字段找到名为 vendorabba1e 的 library,再进一步获取其内部模块。这就是我们在 webpack.dll.conf.js
中给 DllPlugin 的 name 和 output.library
赋相同值的原因。
打包分析
我们使用 webpack-bundle-analyzer
来看下使用 dll 前后的打包效果:
-
不使用 dll
-
使用 dll
可以看到,有一些库从 main.js 中剥离出来了,体积也减少了 0.6M 左右,符合我们的预期。 大家快动手试试吧~!
想要完整配置文件的,可以猛戳这里☞☞
参考资料
转载自:https://juejin.cn/post/7235212712631058489