一:作用
Webpack官网中对这两个插件的介绍:DLLPlugin 和 DLLReferencePlugin 能实现了拆分 bundles,同时还大大提升了构建的速度。实际上就是可以事先将常用的基础模块(如React、react-dom)等抽离出来,打包到一个个单独的动态链接库(dll)中去,后面再打包业务代码时若遇到需要导入的模块存在于前面打包好的某个动态链接库中时,就跳过相关的依赖的打包,直接使用动态链接库中的代码,以此来缩短webpack构建时间,提升构建速度。
二:项目背景
当前所在项目是一个集成了很多事项服务的一户通App,其中包含门户主系统以及根据不同的事项拆分出的多个系统,一个事项对应一个前端工程。为提高加载速度,增强用户体验,考虑将H5构建产物打进离线包,但因事项数量会不断增加,将所有事项打入离线包会导致离线包大小不断膨胀。虽然将事项全部代码打入离线包不可行,但各个事项工程有公共的依赖,如React、antd-mobile、项目组内封装的公共的组件库、jsApi、以及ahooks、query-string等,可以考虑将公共依赖从bundles中拆分出来,只将公共依赖加入离线包,能最高性价比地控制离线包大小,并能减少加载事项服务时的网络延迟,提高加载速度。
三:使用介绍
Webpack 内置了对动态链接库的支持,需要通过 2 个内置插件访问。DllPlugin:在一个额外的webpack配置中设置,用于将指定模块抽离,打包出动态链接库文件;DLLReferencePlugin:用于在webpack主配置文件中配置如何引入已经打包好的动态链接库文件。
下面以基本的 Demo 为例,介绍 DllPlugin和DLLReferencePlugin的使用。
第一步:构建出动态链接库文件
假设项目中需要将react、react-dom、antd、ahooks拆分出来,打包成动态链接库文件。我们首先需要新建一个webpack配置文件,因为动态链接库相关产物需要由一份独立的构建输出。
1.1 在根目录下新建Webpack配置文件vendors.webpack.config.js,配置主要内容如下:
const webpack = require('webpack');
const path = require('path');
module.exports = {
mode: 'production',
entry: {
// 把 React 相关模块的放到一个单独的动态链接库
react: ['react', 'react-dom'],
antd: ['antd'],
vendors: ['ahooks'],
},
output: {
// 输出的动态链接库的文件名称,[name] 代表当前动态链接库的名称,
// 也就是 entry 中配置的 react 、 antd 和 vendors
path: path.join(__dirname, 'dist'),
filename: `[name].dll.js`,
// 存放动态链接库的全局变量名称
library: '[name]_dll',
},
plugins: [
new webpack.DllPlugin({
path: path.join(__dirname, `dist/[name].manifest.json`),
name: '[name]_dll',
}),
],
};
1.2 执行构建命令
npx webpack --config vendors.webpack.config.js
1.3 构建产物
├── dist
├── antd.dll.js
├── antd.manifest.json
├── react.dll.js
├── react.manifest.json
├── vendors.dll.js
└── vendors.manifest.json
其中包含三个动态链接库文件,分别是antd.dll.js、react.dll.js、vendors.dll.js,以 react.dll.js 文件为例,其文件内容大致如下:
var react_dll = (function(e) {
// ...
}({
0: function(e, t, n) {
// 模块 ID 为 0 的模块对应的代码
},
141: function(e, t, n) {
// 模块 ID 为 141 的模块对应的代码
},
// ... 此处省略剩下的模块对应的代码
}));
产物中react_dll.js中包含了模块的代码,模块存放在一个对象中,用对象的key作为 ID。 并且还通过
react_dll 变量把自己暴露在了全局中,也就是可以通过 window.react_dll(ID) 可以访问到它里面包含的模块。
再看另一种产物 *.manifest.json, 以react.manifest.json为例子:
{
"name": "react_dll",
"content": {
"./node_modules/react/index.js": { "id": 0, "buildMeta": { "providedExports": true } },
"./node_modules/react-dom/index.js": { "id": 43, "buildMeta": { "providedExports": true } },
"./node_modules/object-assign/index.js": {
"id": 141,
"buildMeta": { "providedExports": true }
},
"./node_modules/react/cjs/react.production.min.js": {
"id": 201,
"buildMeta": { "providedExports": true }
},
"./node_modules/react-dom/cjs/react-dom.production.min.js": {
"id": 369,
"buildMeta": { "providedExports": true }
},
"./node_modules/scheduler/index.js": { "id": 370, "buildMeta": { "providedExports": true } },
"./node_modules/scheduler/cjs/scheduler.production.min.js": {
"id": 371,
"buildMeta": { "providedExports": true }
}
}
}
可见 manifest.json 文件清楚地描述了与其对应的 dll.js 文件中包含了哪些模块,以及每个模块的路径和 ID。
第二步:在主webpack文件中使用动态链接库文件
2.1 在根目录下Webpack主配置文件webpack.config.js,配置主要内容如下:
const path = require('path');
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin');
module.exports = {
entry: {
main: './main.js'
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist'),
},
plugins: [
// 告诉 Webpack 使用了哪些动态链接库
new DllReferencePlugin({
manifest: require('./dist/antd.manifest.json'),
}),
new DllReferencePlugin({
manifest: require('./dist/react.manifest.json'),
}),
new DllReferencePlugin({
manifest: require('./dist/vendors.manifest.json'),
}),
],
};
2.2 结合umi使用的场景及构建产物(即在umi中实现2.1的webpack配置)
import { defineConfig } from 'umi';
export default defineConfig({
// ... umi其他配置
chainWebpack(config, { webpack }) {
config.plugin('DllReferencePlugin-0').use(
new webpack.DllReferencePlugin({
manifest: require(`./dist/antd.manifest.json`),
}),
);
config.plugin('DllReferencePlugin-1').use(
new webpack.DllReferencePlugin({
manifest: require(`./dist/react.manifest.json`),
}),
);
config.plugin('DllReferencePlugin-2').use(
new webpack.DllReferencePlugin({
manifest: require(`./dist/vendors.manifest.json`),
}),
);
},
});
产物:
DONE Compiled successfully in 34147ms
File Size Gzipped
dist/umi.d7332489.js 2.0 MB 691.9 KB
dist/umi.26e98a26.css 185.3 KB 33.3 KB
效果对比:
若是去掉DllPlugin和DLLReferencePlugin相关的配置,构建出的产物:
DONE Compiled successfully in 58566ms
File Size Gzipped
dist/umi.e3d6af5a.js 3.2 MB 1.0 MB
dist/umi.26e98a26.css 185.3 KB 33.3 KB
可见使用DllPlugin和DLLReferencePlugin后在线包构建产物的体积已经减少50%,时间上也加快了40%,上面的例子只是提取了react、antd-mobile等4个公共依赖,若是抽取全部的公共依赖,这个效果会更好。
第三步:在index.html中引入*.dll.js
最后我们需要在index.html中引入*.dll.js以及umi.js才能使项目正常运转。可以使用umi中的addHTMLHeadScripts配置,其他方法在此就不再详细介绍。
最终生成的index.html: