构建层面优化
- 并行构建(thread-loader、happypack)
- 构建缓存(cache-loader、hard-source-webpack-plugin)
- 代码切割(splitChunks):按需加载,分离基础库和公共代码库。通过分割打包,可以将公共代码或外包依赖项单独打包,并从客户端缓存中受益。在此过程中,整个的包体积没有变小,且需要执行的请求可能会变多,但缓存的好处可以弥补这一成本。
- Hash 缓存
- 减少构建(external、DllPlugin)
- Tree Shaking
- 预渲染
- 分离样式:通过 css-loader、style-loader等一系列loader打包好了的css,如果是通过内联到js中,就会存在 css 无法缓存、增加了js文件体积和未样式化元素闪动(FOUC)问题。
- 静态资源、图片内联: 使用url-loader 内联资源,将小图像转换为base64形式的字符串,从而减少http请求。
性能好坏的测量
- 测量构建体积: webpack-bundle-analyzer (注:在上生产时注释掉该功能,不然会导致包已构建成功,但一直停留在构建中)
- 测量构建速度: speed-measure-webpack-plugin
如何优化
- 包体积优化:gzip压缩、图片压缩、静态文件压缩等;分包(splitchunks)按需加载,分离基础库和公共代码库。
- 构建速度优化:构建缓存(hard-source-webpack-plugin 插件做缓存,在webpack5 中已内置),
- 减少构建次数:对项目中依赖同一包的不同版本的,尽量统一到同一版本。
- 代码分隔以按需加载,分离基础库,提取公共代码。(SplitChunksPlugin)
- DllPlugin 和 DllReferencePlugin 用某种方式实现了拆分 bundles,同时还大幅度的提升了构建的速度。
Chunk 和 Bundle 的关系
- Chunk(块):指若干个 js Module的集合
- Bundle: 形式上是块的集合,是一个可以运行的整体。
注:
-
使用 html-webpack-externals-plugin 基础库分离,通过 CDN 引入,不打入 bundle 中。
-
使用 SplitChunksPlugin 不仅可以分离基础包,还可以提取公共带代码(推荐)。官方推荐将不需要立刻使用的代码通过异步的方式引入,这样才是加快网页速度的关键。
-
使用 DLLPlugin 预编译资源模块分离基础包(内置插件,更好的分包),减少不常更新的第三方包重复构建。
-
使用 hard-source-webpack-plugin 是 DLL 的更好替代者
实践
通过构建缓存 和 减少构建优化构建
-
使用构建缓存(hard-source-webpack-plugin 插件做缓存,在webpack5 中已内置)
-
分割代码(splitPlugins)按需加载,分离基础库和公共代码库,包体积不会减小,但可以减少依赖项的构建次数,从而减少构建时常;
调用 splitChunks 之后:
-
DllPlugin 和 DllReferencePlugin 用某种方式实现了拆分 bundles,同时还大幅度的提升了构建的速度。可以减小构建包的大小。
// vue.config.js
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')
const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const path = require('path')
const apiData = require('./config/api.json')
const webpack = require('webpack')
const smp = new SpeedMeasureWebpackPlugin()
module.exports = {
...
configureWebpack: smp.wrap({
entry: './src/main.js',
plugins: process.env.NODE_ENV === 'development' ? [
// new webpack.DllReferencePlugin({
// context: process.cwd(),
// name: 'vendor',
// manifest: require('./vendor-manifest.json')
// }),
// 开发环境复制配置文件到构建目录
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, './config/config.json'),
to: path.resolve(__dirname, './dist/solution/cdy/edge/config.json')
},
{
from: path.resolve(__dirname, './config/dev.config.json'),
to: path.resolve(__dirname, './dist/solution/cdy/edge/env.config.json')
}
]),
new BundleAnalyzerPlugin(),
new HardSourceWebpackPlugin() // 构建加速
] : [],
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
sfcloud: {
enforce: true,
test: /[\\/]node_modules[\\/]sf-cloud-el-ui[\\/]/
}
}
}
}
}),
...
}
优化前
优化前
–包体积:
优化前–非首次编译时常:
优化前–非首次构建时常:
优化前:
增加 **构建缓存(hard-source-webpack-plugin 插件)**后,二次构建(yarn serve)速度提升
splitChunkPlugin 分离公共代码库:
splitChunksPlugin 是webpack内置的插件,无需另外安装插件,可执行调用。
// vue.config.js
configureWebpack: {
optimization: {
splitChunks: { // 分离代码包:按需加载,将基础库和公共代码库。
chunks: 'all',
cacheGroups: {
sfcloud: {
enforce: true,
test: /[\\/]node_modules[\\/]sf-cloud-el-ui[\\/]/
}
}
}
}
}
体积优化策略
- scope Hoisting 原理:将所有模块的代码按照引用顺序放在一个函数的作用域里,然后适当的重命名一些变脸以防止变量名冲突。通过 scope hoisting 可以减少函数声明代码和内存开销。mode为 ‘production’ 时,默认开启。
1、将mode 设置为none, 时的包体积大小:
// vue.config.js
module.exports = {
...
configureWebpack: {
mode: 'none',
...
}
...
}
2. 生产环境打包(yarn build),明显包体积小了很多
项目实践最终结果:
非首次编译(yarn serve)时常:
非首次构建(yarn build)时常:
最终包体积:
优化后:
总结
指标 | 优化前 | 优化后 | 提升率 |
---|---|---|---|
非首次构建时长 | 55.41s | 38.01s | 31.4% |
非首次编译 | 19.83s | 3.5s | 82.3% |
包体积 | 3.64MB | 2.17MB | 40.4% |
性能指标 | 15 | 31 | 106.7% |
LCP | 10.8s | 7.9s | 17.6% |
FCP | 2.9s | 2.6s | 10.3% |