常⽤的webpack优化⽅法
1. 前⾔
关于webpack,相信现在的前端开发⼈员⼀定不会陌⽣,因为它已经成为前端开发⼈员必不可少的⼀项技能,它的官⽅介绍如下:
webpack 是⼀个模块打包器。webpack的主要⽬标是将 JavaScript ⽂件打包在⼀起,打包后的⽂件⽤于在浏览器中使⽤,但它也能够胜任转换(transform)、打包(bundle)或包裹(package)任何资源(resource or asset)。
在⽇常开发⼯作中,我们除了会使⽤webpack以及会编写它的配置⽂件之外,我们还需要了解⼀些关于webpack性能优化的⽅法,这样在实际⼯作就能够如虎添翼,增强⾃⾝的竞争⼒。
关于webpack优化的⽅法我将其分为两⼤类,如下:
可以提⾼webpack打包速度,减少打包时间的优化⽅法
可以让Webpack打出来的包体积更⼩的优化⽅法
OK,废话不多说,接下来我们就来分别了解⼀下优化⽅法。
2. 提⾼ Webpack 打包速度
2.1 优化Loader搜索范围
对于Loader来说,影响打包效率⾸当其冲必属Babel了。因为Babel会将代码转为字符串⽣成AST,然后对AST继续进⾏转变最后再⽣成新的代码,项⽬越⼤,转换代码越多,效率就越低。当然了,我们是有办法优化的。
⾸先我们可以优化 Loader 的⽂件搜索范围,在使⽤loader时,我们可以指定哪些⽂件不通过loader处理,或者指定哪些⽂件通过loader处理。
module: {
rules: [
{
// js ⽂件才使⽤ babel
test: /\.js$/,
use: ['babel-loader'],
// 只处理src⽂件夹下⾯的⽂件
include: solve('src'),
// 不处理node_modules下⾯的⽂件
exclude: /node_modules/
}
]
}
}
对于Babel来说,我们肯定是希望只作⽤在JS代码上的,然后node_modules中使⽤的代码都是编译过的,所以我们也完全没有必要再去处理⼀遍。
另外,对于babel-loader,我们还可以将Babe l 编译过的⽂件缓存起来,下次只需要编译更改过的代码⽂件即可,这样可以⼤幅度加快打包时间。
loader: 'babel-loader?cacheDirectory=true'
2.2 cache-loader缓存loader处理结果
在⼀些性能开销较⼤的loader之前添加cache-loader,以将处理结果缓存到磁盘⾥,这样下次打包可以直接使⽤缓存结果⽽不需要重新打包。
module: {
rules: [
{
// js ⽂件才使⽤ babel
test: /\.js$/,
use: [
'cache-loader',
...loaders
],
}
]
}
}
那这么说的话,我给每个loder前⾯都加上cache-loader,然⽽凡事物极必反,保存和读取这些缓存⽂件会有⼀些时间开销,所以请只对性能开销较⼤的loader使⽤cache-loader。关于这个cache-loader更详细的使⽤⽅法请参照这⾥
2.3 使⽤多线程处理打包
受限于Node是单线程运⾏的,所以Webpack在打包的过程中也是单线程的,特别是在执⾏Loader的时候,长时间编译的任务很多,这样就会导致等待的情况。那么我们可以使⽤⼀些⽅法将Loader的同步执⾏转换为并⾏,这样就能充分利⽤系统资源来提⾼打包速度了。
2.3.1 HappyPack
,快乐的打包。⼈如其名,就是能够让Webpack把打包任务分解给多个⼦线程去并发的执⾏,⼦线程处理完后再把结果发送给主线程。module: {
rules: [
{
test: /\.js$/,
// 把对 .js ⽂件的处理转交给 id 为 babel 的 HappyPack 实例
use: ['happypack/loader?id=babel'],
exclude: solve(__dirname, 'node_modules'),
},
{
test: /\.css$/,
// 把对 .css ⽂件的处理转交给 id 为 css 的 HappyPack 实例
use: ['happypack/loader?id=css']
}
]
},
plugins: [
new HappyPack({
id: 'js', //ID是标识符的意思,ID⽤来代理当前的happypack是⽤来处理⼀类特定的⽂件的
threads: 4, //你要开启多少个⼦进程去处理这⼀类型的⽂件
loaders: [ 'babel-loader' ]
}),
new HappyPack({
id: 'css',
threads: 2,
loaders: [ 'style-loader', 'css-loader' ]
})
]
2.3.2 thread-loader
,在worker池(worker pool)中运⾏加载器loader。把thread-loader放置在其他loader之前,放置在这个thread-loader之后的loader就会在⼀个单独的worker池(worker pool)中运⾏。
module: {
rules: [
{
test: /\.js$/,
include: solve('src'),
use: [
{
loader: "thread-loader",
// 有同样配置的 loader 会共享⼀个 worker 池(worker pool)
options: {
/
/ 产⽣的 worker 的数量,默认是 cpu 的核⼼数
workers: 2,
// ⼀个 worker 进程中并⾏执⾏⼯作的数量
// 默认为 20
workerParallelJobs: 50,
// 额外的 node.js 参数
workerNodeArgs: ['--max-old-space-size', '1024'],
// 闲置时定时删除 worker 进程
// 默认为 500ms
// 可以设置为⽆穷⼤,这样在监视模式(--watch)下可以保持 worker 持续存在
poolTimeout: 2000,
/
/ 池(pool)分配给 worker 的⼯作数量
// 默认为 200
// 降低这个数值会降低总体的效率,但是会提升⼯作分布更均⼀
poolParallelJobs: 50,
// 池(pool)的名称
// 可以修改名称来创建其余选项都⼀样的池(pool)
name: "my-pool"
}
},
{
loader:'babel-loader'
}
]
}
]
}
}
同样,thread-loader也不是越多越好,也请只在耗时的 loader上使⽤。
2.3.3 webpack-parallel-uglify-plugin
在 Webpack3中,我们⼀般使⽤UglifyJS来压缩代码,但是这个是单线程运⾏的,也就是说多个js⽂件需要被压缩,它需要⼀个个⽂件进⾏压缩。所以说在正式环境打包压缩代码速度⾮常慢(因为压缩JS代码需要先把代码解析成AST语法树,再去应⽤各种规则分析和处理AST,导致这个过程耗时⾮常⼤)。为了加快效率,我们可以使⽤webpack-parallel-uglify-plugin插件,该插件会开启多个⼦进程,把对多
个⽂件压缩的⼯作分别给多个⼦进程去完成,但是每个⼦进程还是通过UglifyJS去压缩代码。⽆⾮就是变成了并⾏处理该压缩了,并⾏处理多个⼦任务,提⾼打包效率。来并⾏运⾏UglifyJS,从⽽提⾼效率。
在Webpack4中,我们就不需要以上这些操作了,只需要将mode设置为production就可以默认开启以上功能。代码压缩也是我们必做的性能优化⽅案,当然我们不⽌可以压缩 JS代码,还可以压缩 HTML、CSS代码,并且在压缩JS代码的过程中,我们还可以通过配置实现⽐如删除console.log这类代码的功能。
let ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
module: {},
plugins: [lodash有哪些方法
new ParallelUglifyPlugin({
workerCount:3,//开启⼏个⼦进程去并发的执⾏压缩。默认是当前运⾏电脑的cPU核数减去1
uglifyJs:{
output:{
beautify:false,//不需要格式化
comments:false,//不保留注释
},
compress:{
warnings:false,//在Uglify]s除没有⽤到的代码时不输出警告
drop_console:true,//删除所有的console语句,可以兼容ie浏览器
collapse_vars:true,//内嵌定义了但是只⽤到⼀次的变量
reduce_vars:true,//取出出现多次但是没有定义成变量去引⽤的静态值
}
},
})
]
}
关于该插件更加详细的⽤法请参照这⾥
2.4 DllPlugin&DllReferencePlugin
DllPlugin 可以将特定的类库提前打包成动态链接库,在⼀个动态链接库中可以包含给其他模块调⽤的函数和数据,把基础模块独⽴出来打包到单独的动态连接库⾥,当需要导⼊的模块在动态连接库⾥的时候,模块不⽤再次被打包,⽽是去动态连接库⾥获取。这种⽅式可以极⼤的减少打包类库的次数,只有当类库更新版本才有需要重新打包,并且也实现了将公共代码抽离成单独⽂件的优化⽅案。
这⾥我们可以先将react、react-dom单独打包成动态链接库,⾸先新建⼀个新的webpack配置⽂件:webpack.dll.js
const path = require('path');
const DllPlugin = require('webpack/lib/DllPlugin');
// 想统⼀打包的类库
entry:['react','react-dom'],
output:{
filename: '[name].dll.js',  //输出的动态链接库的⽂件名称,[name] 代表当前动态链接库的名称
solve(__dirname,'dll'),  // 输出的⽂件都放到 dll ⽬录下
library: '_dll_[name]',//存放动态链接库的全局变量名称,例如对应 react 来说就是 _dll_react
},
plugins:[
new DllPlugin({
/
/ 动态链接库的全局变量名称,需要和 output.library 中保持⼀致
// 该字段的值也就是输出的 manifest.json ⽂件中 name 字段的值
// 例如 react.manifest.json 中就有 "name": "_dll_react"
name: '_dll_[name]',
// 描述动态链接库的 manifest.json ⽂件输出时的⽂件名称
path: path.join(__dirname, 'dll', '[name].manifest.json')
})
]
}
然后我们需要执⾏这个配置⽂件⽣成依赖⽂件:
webpack --config webpack.dll.js --mode development
接下来我们需要使⽤DllReferencePlugin将依赖⽂件引⼊项⽬中
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin')
// ...省略其他配置
plugins: [
new DllReferencePlugin({
// manifest 就是之前打包出来的 json ⽂件
manifest:path.join(__dirname, 'dll', 'react.manifest.json')
})
]
}
2.5 noParse
module: {
noParse: /jquery|lodash/, // 正则表达式
// 或者使⽤函数
noParse(content) {
return /jquery|lodash/.test(content)
}
}
}
2.6 IgnorePlugin
IgnorePlugin⽤于忽略某些特定的模块,让 webpack不把这些指定的模块打包进去。
// ...省略其他配置
plugins: [
new webpack.IgnorePlugin(/^\.\/locale/,/moment$/)
]
}
webpack.IgnorePlugin()参数中第⼀个参数是匹配引⼊模块路径的正则表达式,第⼆个参数是匹配模块的对应上下⽂,即所在⽬录名。
2.7 打包⽂件分析⼯具
webpack-bundle-analyzer插件的功能是可以⽣成代码分析报告,帮助提升代码质量和⽹站性能。
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
plugins: [
new BundleAnalyzerPlugin({
generateStatsFile: true, // 是否⽣成stats.json⽂件
})
// 默认配置的具体配置项
// new BundleAnalyzerPlugin({
//  analyzerMode: 'server',
//  analyzerHost: '127.0.0.1',
//  analyzerPort: '8888',
/
/  reportFilename: 'report.html',
//  defaultSizes: 'parsed',
//  openAnalyzer: true,
//  generateStatsFile: false,
//  statsFilename: 'stats.json',
//  statsOptions: null,
//  excludeAssets: null,
//  logLevel: info
// })
]
}
使⽤⽅式:
"generateAnalyzFile": "webpack --profile --json > stats.json", // ⽣成分析⽂件
"analyz": "webpack-bundle-analyzer --port 8888 ./dist/stats.json" // 启动展⽰打包报告的http服务器
2.8 费时分析
speed-measure-webpack-plugin,打包速度测量插件。这个插件可以测量webpack构建速度,可以测量打包过程中每⼀步所消耗的时间,然后让我们可以有针对的去优化代码。
const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin');
const smw = new SpeedMeasureWebpackPlugin();
// ⽤smw.wrap()包裹webpack的所有配置项
module: {},
plugins: []
});
2.9 ⼀些⼩的优化点