前两篇文章介绍了如何使用 webpack5 搭建一个 Vue3 的 开发环境 和 生产环境,这篇文章继续介绍在打包时如何做一些优化。
可以从 GitHub 获取本文所对应的代码
打包分析工具 webpack-bundle-analyzer
要做打包优化,首先我们得知道自己打出来的包的情况,我们使用 webpack-bundle-analyzer 来做打包分析工具。
安装:
npm install --save-dev webpack-bundle-analyzer
在 webpack.config.prod.js 中进行配置:
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const prodWebpackConfig = merge(baseWebpackConfig, {
plugins: [
// 打包分析插件
new BundleAnalyzerPlugin()
]
});
重新执行 npm run build 命令,在打包结束之后,会自动打开浏览器,加载http://127.0.0.1:8888 地址,显示当前的打包情况。
这里显示的是打包出来的 js 文件的情况。
打包优化时,我们的目的是控制 js 文件的大小和数量,主要从以下几个方面来优化。
使用 SplitChunksPlugin
webpack4 之后,内置了 SplitChunksPlugin 用来提取公用 chunk,我们直接配置即可。
默认情况下,它只会影响到按需加载的 chunks。
webpack 将根据以下条件自动拆分 chunks:
- 新的 chunk 可以被共享,或者模块来自于
node_modules文件夹 - 新的 chunk 体积大于 20kb(在进行 min+gz 之前的体积)
- 当按需加载 chunks 时,并行请求的最大数量小于或等于 30
- 当加载初始化页面时,并发请求的最大数量小于或等于 30
当尝试满足最后两个条件时,最好使用较大的 chunks。
在 webpack 中的默认配置如下:
module.exports = {
//...
optimization: {
splitChunks: {
chunks: 'async',
minSize: 20000,
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,
},
},
},
},
};
提取公共 chunk
chunks 默认为 async,只对异步导入的模块进行提取公共 chunk,而同步引入的,不会提取,像我们在项目中使用的 vue、vue-router,它在打包时,直接打入了main 里面。
chunks还有两个可选值:
initial- 同步引入的库进行分离;all- 所有引入的库进行分离;
在打包时,推荐使用 all。
项目开发中,我们对有些模块,比如 Util.js 文件,会在多个文件里面引入并使用,如果不做提取,会在每个文件打包时,都将 Util.js 的内容复制一份,最终打出来的包体积会较大,而这些重复的代码又是没必要的。
我们可以通过配置 cacheGroups 来控制公共 chunk 的提取。
在 webpack 的默认配置中,已经有了 defaultVendors 和 cacheGroups.default 两个配置。
defaultVendors 里面配置了 test 字段,后面是一个正则表达式,表示将那些从 node_modules 引入的模块,提取到一个公共 chunk 中;
default 里面没有配置 test,表示这个规则会应用到所有模块上,minChunks: 2 表示当某个文件至少被2个 chuns 使用时,将其提取到公共 chunk。
当然,我们也可以根据自己的需要,继续在 cacheGroups 中添加配置,将一些模块打包进一个单独的 chunk,比如下面的配置,会将 vue-router 和 vuex 打包进一个名为 vue_lib 的 chunk 中:
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vue_lib: {
test: /[\\/]node_modules[\\/](vue-router|vuex)/,
name: 'vue_lib'
},
}
}
}
在配置 splitChunks 时,需要注意以下几个配置字段:
-
minSize,生成 chunk 的最小体积(以 bytes 为单位),即使我们在cacheGroups里添加了新的 chunk 配置,但是如果这些模块抽取出来生成的新 chunk 的大小,还未达到miniSize设置的值,是不会生成新 chunk 的; -
maxSize,告诉 webpack ,尝试将大于maxSize个字节的 chunk 分割成较小的部分。这些较小的部分在体积上至少为minSize(仅次于maxSize)。maxSize只是一个提示,当模块大于maxSize或者拆分不符合minSize时可能会被违反,换句话说,我们设置了maxSize并不意味着当 chunk 大于这个值时,就一定会被拆分成多个 chunk,因为还有很多其它的条件在。 -
cacheGroups中配置 chunk 时,可以在里面覆盖splitChunks里面的配置。比如我们可以vue_lib的配置里面,重新设置minSize。optimization: { splitChunks: { chunks: 'all', minSize: 20000, cacheGroups: { vue_lib: { test: /[\\/]node_modules[\\/](vue-router|vuex)/, name: 'vue_lib', minSize: 10000, // 覆盖 splitChunks.minSize 配置 }, } } } -
minChunks,只有当某个模块被其它 chunk 使用的次数大于等于这个数值时,才会被抽取到新 chunk 中。
以上这些都是从 webpack 的配置入手,对已有的代码进行分包,它的能力也是有限的。
我们在写代码的时候,就要注意对模块的引用方式,对于那些不需要在一开始就加载的模块,使用异步引入的方式。打包的时候,这些异步引入的模块也会被打进单独的 chunk 中,在浏览器加载时,让这些模块按需加载。
gzip 压缩
文件大小是直接影响页面打开速度的因素,默认情况下,webpack 对打包出来的文件是没有压缩的。我们通过 webpack-bundle-analyzer 查看打包文件情况时,左侧的选项中有一个 Gzipped,选中之后,显示的文件大小,就是被压缩之后的文件大小。
Stat- 表示原始文件大小;
Parsed- 表示 webpack 打包之后的文件大小;
Gzipped- 表示压缩后的文件大小。
文档链接
可以看到,压缩前和压缩后,文件大小的差距还是很大的。
使用 compression-webpack-plugin
安装 compression-webpack-plugin 插件:
npm install compression-webpack-plugin --save-dev
在 webpack.config.prod.js 中配置:
const CompressionPlugin = require("compression-webpack-plugin");
module.exports = {
plugins: [
new CompressionPlugin({
test: /.(js|css)(\?.*)?$/, // 只压缩 js 和 css 文件
algorithm: 'gzip', // 压缩算法,默认为 gzip
threshold: 10240, // 文件大于 10 KB 时才压缩,默认为 0
minRatio: 0.8, // 只有当文件压缩后的比率小于这个值时,文件才会被压缩,默认值 0.8
deleteOriginalAssets: false, // 是否删除原文件,默认为 false
}),
],
};
重新打包,在 dist/js 和 dist/css 文件夹下面,都会生成以 .gz 为后缀名的文件,这些就是压缩过的文件。
到这里只是完成了第一步,当项目部署到服务器之后,需要后端开启 gzip,否则还是返回的未压缩的文件。当后端开启 gzip 后,在请求 js 文件时,响应头中会有 Content-Encoding: gzip。
我们可以在本地起一个服务来测试,这里需要安装一下 http-server。
在 dist 文件夹下,直接执行 http-server 命令启动服务,默认情况下是没有开启 gzip 的,
我们访问 http://localhost:8080,在调试工具的 network 中查看文件请求:
然后重新使用 http-server -g 命令启动服务,开启 gzip:
在浏览器中重新加载:
此时加载的就是压缩过的文件,在响应头中可以看到 Content-Encoding 字段,且值为 gzip。
前两篇文章:
从0到1:webpack5 搭建 Vue3 项目——开发环境
从0到1:webpack5 搭建 Vue3 项目——生产环境