文章目录
主要讲解一些常见的提高webpack打包速度的方法供大家参考探讨~
跟上技术的迭代
随时升级webpack及其相关管理工具的版本,包括npm,node,yarn等。
每个版本都会有优化,同时webpack建立在node环境之上,node的升级也会有一定的效率提升
同样安装较新的npm和yarn工具,可以提高相互依赖的模块间的解析速度
在尽可能少的模块上应用loader
loader的解析和处理较为耗时的,有可能的话我们可以使用exclude和include字段来配置loader忽略与仅应用的文件。
例如我们可以配置exclude来让babel-loader不去处理node_modules文件夹下的内容,或者只处理src目录下内容:
{
test:/\.js$/,
exclude:/node_modules/,
include:path.resolve(__dirname,'../src'),
use:[{
loader:'babel-loader'
}]
}
上述两种方式可以减少loader涉入,降低执行频率提高打包速度
Plugin尽可能精简并可靠
之前笔记中上面我们使用了一个插件进行CSS的压缩,但是实际上在开发环境下是不需要压缩的,可以不需要这个插件,可以提高开发过程中的打包速度。
此外尽量使用webpack官网推荐的插件和使用方式,尽量不要使用第三方和自己创建的不可靠的插件,有可能存在副作用
resolve参数的合理配置
resolve这个参数之前没怎么提到过,但是有效的配置它可以简化我们的import写法。
有时候我们在react中可以看到这样的引入:
import xxx from './child/child'
这里其实是需要resolve配置才能加载到./child/child.js上的:
resolve:{
extensions:['.js'.'.jsx']
}
这样写之后webpack打包时会先去目录下找JS文件,找不到再找JSX文件,然后才会报错。
此外他还可以根据from的目录,来默认挑选一些文件(根据文件名):
resolve:{
mainFiles:['index','child']
}
此时webpack会先尝试寻找index文件,不存在再去找child文件
最后我们resolve还支持任意字符串去映射一个模块,相当于给模块起个别名。
例如我们希望import一个来自/a/b/c/child文件,我们可以如下配置resolve来起一个路径别名:
resolve:{
extensions:[...],
alias:{
child:path.resolve(__dirname,'../src/a/b/c/child')
}
}
// import Child from './a/b/c/child'
// 可以改写为
import Child from 'child';
是不是很方便?同时若路径也变动,只需要修改resolve配置项就行,不需要每一个from都进行修改
但是这种配置多了会存在性能问题,无论是extension还是mainFiles都建议不要配置太多,否则存在额外查询耗时
使用DllPlugin提高打包速度
不知道你们是否还记得前述笔记我们通过splitChunks实现代码分割把node_modules下面的文件打包到了vendors文件中。
但是其实上述打包过程中这些不变的第三方模块每一次都会跟着业务代码重新打包,如果可以复用这些的话是不是就可以提高打包速度?
这里我们新建一个webpack.dll.js配置文件,用它来对第三方模块进行针对性打包(我们希望把3个工具库打包到dll目录下面):
...
module.exports={
mode:'production',
entry:{
vendors:['react','react-dom','lodash']
},
output:{
filename:'[name].dll.js',
path:path.resolve(__dirname,'../dll')
}
}
然后配置一个script来运行:
"build:dll":"webpack --config ./build/webpack.dll.js"
最后在dll目录下会生成vendros.dll.js文件
打包好的dll如何使用呢,首先我们在dll的config文件中通过一个全局变量将其暴露出来:
output:{
...,
library:'[name]'
}
接着我们就可以通过vendors变量来引用
然后可使用add-asset-html-webpack-plugin(记得安装)帮助我们在html文件上增加一些静态资源:
plugins:[
...,
new AddAssetHtmlWebpackPlugin({
filepath:path.resolve(__dirname,'../dll/vendros.dll.js')
})
]
通过以上操作针对第三方模块就可以进行单独的打包与引用,不需要重复打包
但是目前还有一个问题,我们只是把打包好了库的JS文件并能够引用,代码里面import的仍然是node_modules,并没有真正用到打包的JS文件。
这里就需要用到标题的插件DLLPlugin了,它可以做一个映射:
plugins:[
new webpack.DllPlugin({
name:'[name]',
path:path.resolve(__dirname,'../dll/[name].manifest.json')
})
]
之后打包的时候会生成了manifest映射文件,结合全局变量和映射文件来对源代码进行分析,如果符合就可以进行映射。
然打开common的配置文件,加一个:
webpack.DllReferencePlugin({
manifest:path.resolve(__dirname,'../dll/vendors.dll.js')
})
使用Dll插件,打包时候如果发现第三方模块在manifest.json时就不会在去node_modules里面打包了。
总结一下:
- 第三方模块单独打包,生成打包结果
- 使用library暴露为全局变量
- 借助dll插件来生成manifest映射文件,从dll文件夹里面拿到打包后的模块(借助dllReference插件)就不用重复打包了
- 中间进行了很多分析的过程,最后决定要不要再去分析node_modules内容
最后补充一下你也可以给dll的配置文件设置多个entry下的字段来进行拆分打包,例如:
entry:{
vendors:['lodash'],
react:['react']
}
...
这样的话AddAsset插件和DLLReference插件都要多配置一份。
这里我们其实可以把插件的配置内容写到一个数组中,通过node工具来动态生成Plugin:
const files = fs.readdirSync(path.resolve(__dirname,'../dll'))
files.forEach(file =>{
if(/.*\.dll.js/.test(file)){
plugins.push(new AddAssetHtmlWebpackPlugin ({
filepath:path.resolve(__dirname,'../dll',file)
})
}
if(/.*\.manifest.json/.test(file)){
plugins.push(new webpack.DllRefrencePlugin)({
manifest:path.resolve(__dirname,'../dll',file)
})
}
})
去除冗余引用
有时候我们写的代码经常会import一些没有用到的模块,如果没有配置tree-shaking就会有很多冗余代码。
所以编程时候注意去掉没有使用的包,或者通过tree-shaking去掉
也可以通过splitChunksPlugin进行拆分,把大文件拆成几个小文件,提高打包速度
多进程打包
利用node的多进程,利用多个cpu进行项目打包,这里感兴趣可以自己尝试(thread-loader,parallel-webpack,happypack)
合理使用SourceMap
根据不同环境进行合适的sourceMap打包,可以回头复习下相关笔记
结合stats.json文件分析打包结果
借助一些打包分析工具可以分析打包情况,例如哪个模块打包慢,通过流程分析发现问题,进行优化
开发环境无用插件需要剔除
例如开发环境下mode的配置,一些压缩插件的使用等等,需要具体根据环境区分配置