webpack 性能优化
前言
webpack 性能优化无非是从时间和空间两个维度去分析。时间指的是打包时间尽可能快;空间指的是打包体积尽可能小。本文的 webpack 性能优化是基于 webpack 4.3.0 版本。本文主要是阐述 webpack 性能优化方面的内容,更多 webpack 知识可参考 wabpack 中 loader 和 plugins 的区别 和 webpack 4 新特性
1. 时间维度
1.1 缓存策略
1.1.1 缓存 babel-loader
配置示例:
{
test: /\.js$/,
loader: 'babel-loader?cacheDirectory=true',
include: [
resolve('src'),
],
exclude:/node_modules/
},
以上示例中配置了 babel-loader,其功能是将 ES6 语法转换为 ES5 语法,请注意babel-loader?cacheDirectory=true
,这行配置的意思是对已经转换过的未经改动的编译文件进行缓存,以此可以加快打包时间。注意需要排除 node_modules 文件夹,因为该文件所在的 JS 使用的是 ES5 语法,所以没必要再使用 Babel 转换。
1.1.2 缓存不常变动模块
不管是 React 框架还是 Vue 框架,有些模块比如 react 、react-router、vue、vue-router 等一般情况下(除非模块升级)都不会改动,所以在打包的时候希望这些资源能够被缓存下来。那么首先得把这些模块进行分割,然后缓存。所以这里有两步。
第一步:代码分割,使用 webpack 内置的 optimization.splitChunks 配置
示例如下:
optimization: {
concatenateModules:true,
splitChunks:{
chunks: "all",//async 按需加载的异步块 initial(初始块) all(所有块)
minSize: 30000, // 模块的最小体积
minChunks: 1, // 模块的最小被引用次数
maxAsyncRequests: 5, // 按需加载的最大并行请求数
maxInitialRequests: 3, // 一个入口最大并行请求数
automaticNameDelimiter: '~', // 文件名的连接符
name: true,
cacheGroups: { // 缓存组
vendor: {
test: /[\\/]node_modules[\\/]/,
name: "vendor",
priority: -10,
enforce: true
}
}
}},
在 webpack4 之前使用的是 CommonsChunkPlugin 进行代码分割。而 webpack4 之后就可以使用内置的 optimization.splitChunks 进行代码分割。代码分割的目的就是为了将一些不常更改的第三方库单独分离出来。
第二步:缓存,使用 hard-source-webpack-plugin 插件。
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')
plugins: [
new HardSourceWebpackPlugin()
]
以上配置中使用的是 hard-source-webpack-plugin 进行模块缓存。这样只有在第一次打包的时候花费的时间会长一些,但是从第二次之后,模块已经被缓存到 node_modules/.cache/hard-source 目录下,不会被打包进来,大大节省打包时间。
1.2 JS 与 CSS 并行加载
在 webpack 4 之前抽离 CSS 的插件是 extract-text-webpack-plugin 。之后采用的是 mini-css-extract-plugin 插件。其作用是用 JS 中将 CSS 单独抽离出来,并行加载,可以提升加载速度。
配置如下:
const MiniCssExtractPlugin=require('mini-css-extract-plugin');
plugins:[
// 提取单独的 CSS
new MiniCssExtractPlugin({
filename: utils.assetsPath('css/[name].[hash].css'),
allChunks: true,//也包含所有的 chunk。
}),
]
1.3 开启多线程
1.3.1 使用 HappyPack 开启多线程
除了以上直接使用缓存方案,还可以使用 HappyPack 插件对 Loader 进行多线程打包。
module: {
rules: [
{
test: /\.js$/,
use:['happypack/loader?id=babel'],
include: [
resolve('src'),
],
exclude:path.resolve(__dirname, 'node_modules')
},
]},
plugins:[
new Happypack({
id:"babel",
loaders:['babel-loader?cacheDirectory=true'],
threads:4 //开启4个线程转换loader
})
]
1.4 使用 DNS 加载静态数据
使用 DNS 加载静态数据会比较快,同时也可以减小公共模块的体积。
<script src="https://cdn.staticfile.org/vue/2.6.10/vue.min.js"></script>
<!-- 引入vue-router -->
<script src="https://cdn.staticfile.org/vue-router/3.0.1/vue-router.min.js"></script>
<!-- 引入组件库 -->
<script src="https://cdn.staticfile.org/element-ui/2.12.0/index.js"></script>
<!-- 引入样式 -->
<link href="https://cdn.staticfile.org/element-ui/2.12.0/theme-chalk/index.css" rel="stylesheet">
2. 空间维度
空间维度指的是通过对 CSS、JS 等静态资源进行压缩以减少资源体积。
2.1 压缩
2.1.1 CSS 压缩
optimize-css-assets-webpack-plugin 插件主要是用来压缩 CSS 文件的。
配置如下:
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin');
plugins:[
new OptimizeCSSPlugin({
cssProcessorOptions:{safe: true, map:{ inline: false }}
}),
]
2.1.2 JS 压缩
在 webpack 4 之前采用的 JS 压缩插件是 uglifyjs-webpack-plugin 。之后采用的是 terser-webpack-plugin 插件。其配置方式如出一辙。
uglifyjs-webpack-plugin 配置如下:
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
plugins:[
new UglifyJsPlugin({
uglifyOptions: {
compress: {
warnings: false, //buid 的时候不要报warnings
drop_console: true //no console
}
},
sourceMap: false,
parallel: true//是否开启多线程
}),
]
terser-webpack-plugin 配置如下:
const TerserPlugin = require('terser-webpack-plugin');
plugins:[
new TerserPlugin({
terserOptions: {
compress: {
warnings: false, //buid 的时候不要报warnings
drop_console: true //no console
}
},
sourceMap: false,
parallel: true//是否开启多线程
}),
]
可以看出他们的区别就在于 uglifyOptions 对象与 terserOptions 对象有所不同。
2.1.3 CSS 和 JS 压缩
compression-webpack-plugin 插件可以压缩选择的类型文件,其采用的是 gzip 格式进行压缩。
配置如下:
const CompressionWebpackPlugin = require('compression-webpack-plugin');
plugins:[
new CompressionWebpackPlugin({
asset: '[path].gz[query]',//目标资源名称
algorithm: 'gzip',//压缩格式
test: new RegExp('\\.(css|js)$'),//匹配压缩文件
threshold: 10240,//只压缩比这个大的资源10KB
minRatio: 0.8//压缩率小于0.8才会被压缩
})
]