webpack高级概念,Dllplugin打包性能配置(系列十八)
为什么要使用Dll
通常来说,我们的代码都可以至少简单区分成业务代码和第三方库。如果不做处理,每次构建时都需要把所有的代码重新构建一次,耗费大量的时间。然后大部分情况下,很多第三方库的代码并不会发生变更(除非是版本升级),这时就可以用到dll:把复用性较高的第三方模块打包到动态链接库中,在不升级这些库的情况下,动态库不需要重新打包,每次构建只重新打包业务代码。
使用dll时,可以把构建过程分成dll构建过程和主构建过程(实质也就是如此),所以需要两个构建配置文件,例如叫做webpack.config.js
和webpack.dll.config.js
。
1. 使用DLLPlugin
打包需要分离到动态库的模块
DllPlugin
是webpack
内置的插件,不需要额外安装,直接配置webpack.dll.config.js
文件:
我们先配置一个用于打包 dll
文件的 webpack
配置文件,生成打包后的 js
文件与描述动态链接库的 manifest.json
// webpack.dll.config.js
const path = require('path') const webpack = require('webpack') module.exports = { entry: { vendors: ['jquery', 'lodash'] // 要打包进vendor的第三方库,输出的文件名vendors }, output: {
//输出的动态链库的文件名称,[name]代表当前动态连接库的名称 filename: '[name].dll.js', // 打包后的文件名,就是映射entey中的vendor path: path.resolve(__dirname, '../dll'), // 打包后存储的位置 library: '[name]_[hash]' // 挂载到全局变量的变量名,这里要注意 这里的library的name一定要与DllPlugin中的name一致 }, plugins: [
//接入Dllplugin new webpack.DllPlugin({ name: '[name]_[hash]', // 引用output打包出的模块变量名,切记这里必须与output.library一致 path: path.join(__dirname, '../dll', '[name].manifest.json') // 描述动态链接库的 manifest.json 文件输出时的文件名称 }) ] }
配置下 package.json
文件的 scripts
:
"scripts": { "dev-build": "webpack --config ./build/webpack.dev.js", "dev": "webpack-dev-server --config ./build/webpack.dev.js", "build": "webpack --config ./build/webpack.prod.js", "build:dll": "webpack --config ./build/webpack.dll.js" },
执行下 npm run build:dll, 此时第三方库打包在dll中的vendors.dll.js
我们先来看看,这一步到底做了什么
vendors.dll.js
文件里是使用数组保存的第三方的模块,索引值就作为id;vendors.manifest.json
文件里,是用来描述对应的dll文件里保存的模块
2. 在主构建配置文件使用动态库文件
在webpack.config.js
中使用dll要用到DllReferencePlugin
,这个插件通过引用 dll 的 manifest 文件来把依赖的名称映射到模块的 id 上,之后再在需要的时候通过内置的 webpack_require 函数来 require 他们. webpack.config.js配置
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('../dll/vendors.manifest.json') //引入描述第三方库打包文件
}),
第一步产出的manifest
文件就用在这里,给主构建流程作为查找dll的依据:DllReferencePlugin去 manifest.json 文件读取 name 字段的值,把值的内容作为在从全局变量中获取动态链接库中内容时的全局变量名,因此:在 webpack_dll.config.js 文件中,DllPlugin 中的 name 参数必须和 output.library 中保持一致。
3. 在入口index.html文件引入dll文件。
在运行npm run build,发现打包后,index.html中src标签没有引入第三方vendors.dll.js文件
我们如果每次自己手动引入的话会比较麻烦,如果 dll
文件非常多的话,就难以想象了,这个时候就需要借助 add-asset-html-webpack-plugin
这个包了
在主webpack.config.js中配置
constAddAssetHtmlPlugin=require('add-asset-html-webpack-plugin')newAddAssetHtmlPlugin({ filepath: path.resolve(__dirname,'../dll/vendors.dll.js') //引入打包好的第三方库文件})
通过这包, webpack
会将 dll
打包出来的vendors.dll. js
文件通过 script
标签引入到 index.html
文件中
这个时候你在 npm run build
,就引入了
总结:没有配置DllPlugin插件,npm run build 打包花费的事件是1100多ms,
使用了DllPlugin插件,打包话费的事件是700ms, 节约了400ms的打包速度,当你抽离的第三方模块越多,这个效果就越明显。
注意事项
从前面可以看到dll带来的优点,但并不意味着我们就应该把除业务代码外的所有代码全部都丢到dll中,举一个例子:
1.对于lodash
这种第三方库,正确的用法是只去import
所需的函数(用什么引什么),例如:
// 正确用法
import isPlainObject from 'lodash/isPlainObject'
//错误用法
import { isPlainObject } from 'lodash'
这两种写法的差别在于,打包时webpack会根据引用去打包依赖的内容,所以第一种写法,webpack只会打包lodash的isPlainObject库,第二种写法却会打包整个lodash
。现在假设在项目中只是用到不同模块对lodash里的某几个函数并且没有对于某个函数重复使用非常多次,那么这时候把lodash
添加到dll中,带来的收益就并不明显,反而导致2个问题:
- 由于打包了整个
lodash
,而导致打包后的文件总大小(注意是总大小)比原先还要大 - 在dll打包太多内容也需要耗费时间,虽然我们一般只在第三方模块更新之后才进行重新预编译(就是dll打包的过程),但是如果这个时间太长的话体验也不好、
实践与反思
放一张自己在一个比较大的项目中单纯使用dll之后的收益,提取的内容是 react相关的第三方库,和fish
组件,构建时间从120s降低到80s左右(当然这个时间还是有点恐怖),构建前appjs的大小是680kb,拆分业务代码和第三方代码分别是400kb和380kb(这就是拆分后大小大于拆分前大小的例子),从这一点来看,对于常见第三方库是否要放进dll可能比较明确(比如react系列打包一般肯定不亏),但是还有一些就要结合具体的项目内容来进行判断和取舍。(强烈推荐使用webpack-bundle-analyzer
插件进行性能分析)
// 输出的动态链接库的文件名称,[name] 代表当前动态链接库的名称