webpack 优化前端性能
随着前端的项目逐渐扩大,必然会带来性能问题。
项目在完成后会通过 webpack 进行打包,利用 webpack optimization 对前端项目性能优化是一个十分重要的环节。
通过 webpack 优化前端的手段有:
- JS 代码压缩
- CSS 代码压缩
- HTML 代码压缩
- 文件大小压缩
- 图片压缩
- Tree Shaking
- 代码分离
一、JS 代码压缩
TerserWebpackPlugin 插件使用 terser 来压缩 JavaScript。
在 production 模式下,webpack 默认用 TerserPlugin 来处理代码。如果要进行自定义配置,需要执行以下步骤:
安装 terser-webpack-plugin:
yarn add terser-webpack-plugin -D
设置 webpack 配置文件:
const TerserPlugin = require('terser-webpack-plugin')
module.exports = {
...
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true // 电脑 cpu 核数 -1
})
]
}
}
TerserPlugin 属性说明:
- test:用来匹配需要压缩的文件,默认为
/\.m?js(\?.*)?$/i
- include:匹配参与压缩的文件,默认为
undefined
- exclude:匹配不需要压缩的文件,默认为
undefined
- parallel:使用多进程并发运行提高构建的速度,默认为
true
,并发运行的默认数量为os.cpus().length - 1
- minify:允许自定义压缩函数,默认为
TerserPlugin.terserMinify
,使用 terser 库; - terserOptions:设置 terser 配置项;
- extractComments:是否将注释抽取到单独的文件中,默认为 true,开发阶段可设置为 false,不保留注释。
二、CSS 代码压缩
CSS 压缩通常是去除无用的空格等,可以使用 css-minimizer-webpack-plugin 插件来实现:
yarn add css-minimizer-webpack-plugin -D
配置如下:
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
module.exports = {
// ...
optimization: {
minimize: true,
minimizer: [
new CssMinimizerPlugin({
parallel: true
})
]
}
}
三、HTML 代码压缩
使用 HtmlWebpackPlugin 插件来生成 HTML 模板时,通过配置属性 minify 进行 HTML 优化:
yarn add html-webpack-plugin -D
配置如下:
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
// ...
plugin: [
new HtmlwebpackPlugin({
...
minify: {
minifyCSS: false, // 是否压缩 CSS
collapseWhitespace: false, // 是否折叠空格
removeComments: true // 是否移除注释
}
})
]
}
设置了 minify,实际会使用另一个插件 html-minifier-terser
四、文件大小压缩
对文件的大小进行压缩,减少 HTTP 传输过程中宽带的损耗:
yarn add compression-webpack-plugin -D
配置如下:
const CompressionPlugin = require("compression-webpack-plugin");
module.exports = {
// ...
plugins: [
new ComepressionPlugin({
test: /\.(css|js)$/, // 哪些文件需要压缩
threshold: 500, // 设置文件多大开始压缩
minRatio: 0.7, // 至少压缩的比例
algorithm: 'gzip' // 采用的压缩算法
})
],
};
五、图片压缩
一般在打包后,图片文件的大小远比 js 或 css 文件大,所以图片压缩较为重要,可以用 image-webpack-loader 来实现。
yarn add image-webpack-loader -D
配置如下:
module: {
rules: [
{
test: /\.(gif|png|jpe?g|svg)$/i,
use: [
'file-loader',
{
loader: 'image-webpack-loader',
options: {
// 压缩 jpeg 的配置
mozjpeg: {
progressive: true,
quality: 65
},
// 使用 imagemin**-optipng 压缩 png,enable: false 为关闭
optipng: {
enabled: false
},
// 使用 imagemin-pngquant 压缩 png
pngquant: {
quality: '65-90',
speed: 4
},
// 压缩 gif 的配置
gifsicle: {
interlaced: false
},
// 开启 webp,会把 jpg 和 png 图片压缩为 webp 格式
webp: {
quality: 75
}
}
}
]
}
]
}
六、Tree Shaking
Tree Shaking 表示消除死代码(JS 上下文中未引用的代码),依赖于 ES Module 的静态语法分析(不执行任何的代码,可以明确知道模块的依赖关系)
webpack 中实现 Tree Shaking 有两种方式:
usedExports
:通过标记某些函数是否被使用,之后通过 terser 来进行优化;sideEffects
:跳过整个模块/文件,直接查看该文件是否有副作用。
1、usedExports
usedExports 配置只需将 usedExports 设为 true
:
module.exports = {
// ...
optimization: {
usedExports
}
}
配置后,未被引用的代码在 webpack 打包中会加入 unused harmony export mul 注释,用来告知 Terser 在优化时,可以删除掉这段代码,terser 在优化时会将该函数去掉。
/* unused harmony export mul */
function sum(num1, num2) {
return num1 + num2
}
2、sideEffects
sideEffects 用于告知 webpack compiler 哪些模块时有副作用,sideEffects 的方式更为有效,因为它允许跳过整个模块/文件和整个文件子树。
这里的副作用是在导入时会执行特殊行为的代码,而不是仅暴露一个 export 或多个 export。例如 polyfill,它影响全局作用域,并且通常不提供 export。
配置方法:
在 package.json 中设置 sideEffects 属性:
{
"name": "your-project",
"sideEffects": false
}
设为 false 即告知 webpack 可以安全的删除未引用的 exports。
如果有些文件需要保留,可以设置为数组的形式:
{
"name": "your-project",
"sideEffects": [
"./src/util/format.js",
"*.css" // 所有的 css 文件
]
}
除了 JS,css 也能实现 tree shaking。
3、css tree shaking
css 进行 tree shaking 优化可以安装 PurgeCss 插件:
yarn add purgecss-plugin-webpack -D
配置如下:
const PurgeCssPlugin = require('purgecss-webpack-plugin')
module.exports = {
...
plugins: [
new PurgeCssPlugin({
path: glob.sync(`${path.resolve('./src')}/**/*`),
{
nodir: true
} // src 里面的所有文件
satelist: function () {
return {
standard: ["html"]
}
}
})
]
}
其中,paths 表示要检测哪些目录下的内容需要被分析,配合使用 glob。
默认情况下,Purgecss 会将 HTML 标签的样式移除掉,如果希望保留,可以添加 safelist 的属性。
七、代码分离
默认情况下,所有的 JS 代码在首页会全部加载,会影响首页的加载速度,可以将代码分离到不同的 bundle 中,然后按需加载,提高代码的加载性能。
可以通过 webpack 自带的 splitChunksPlugin 来实现。
默认配置如下:
module.exports = {
//...
optimization: {
splitChunks: {
chunks: 'async', // 对同步代码还是异步代码进行处理
minSize: 20000, // 生成 chunk 的最小体积
minRemainingSize: 0,
minChunks: 1, // 被引入的次数
maxAsyncRequests: 30,
maxInitialRequests: 30,
enforceSizeThreshold: 50000,
cacheGroups: {
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
}
}