“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第 3 篇文章,点击查看活动详情”
一、背景与目标
背景:商城移动端,首屏加载过慢,需要做性能优化。
目标:减少白屏时间,提高页面加载速度,提升用户体验。
⭐本文将重点放在对资源加载速度的优化。
二、优化思路
考虑到 webpack5
的一些新特性可以带来减少资源体积等作用,所以决定将项目中使用的 webpack4
升级到 webpack5
。
⭐Webpack 5 release (2020-10-10)
这个版本的重点在于以下几点。
- 尝试用持久性缓存来提高构建性能。
- 尝试用更好的算法和默认值来改进长期缓存。
- 尝试用更好的 Tree Shaking 和代码生成来改善包大小。
- 尝试改善与网络平台的兼容性。
- 尝试在不引入任何破坏性变化的情况下,清理那些在实现 v4 功能时处于奇怪状态的内部结构。
- 试图通过现在引入突破性的变化来为未来的功能做准备,使其能够尽可能长时间地保持在 v5 版本上。
⭐由于优化步骤是先升级 webpack
,然后再进行相关优化,所以下文的组织方式也与之对应。
三、webpack 升级过程
升级指南
升级过程
- 备份重要文件:在开始升级之前,备份了项目中的配置文件、代码和依赖项,以防止意外情况发生。
- 新建分支:创建一个新分支并基于该分支进行升级工作,以便与原始代码进行比较和备份。
- 阅读升级指南:通过阅读 Webpack 官方提供的从 v4 升级到 v5 的升级指南,了解新版本的重要变化和需要注意的事项。
- 升级 Webpack 和相关依赖:
- 更新项目中的
webpack
、webpack-cli
等相关依赖到最新版本,通过 npm 进行更新。 - 根据升级指南中的要求,更新其他相关依赖项,比如 loader、插件等。
- 更新项目中的
- 逐步调整配置文件:
- 根据升级指南中的配置变化,逐步调整项目中的 Webpack 配置文件。
- 检查废弃的配置项,并根据指南提供的替代方案进行修改。
- 运行构建命令:确保项目能够成功构建并正常运行。
- 优化配置:利用 Webpack5 的新特性,如持久性缓存等,优化构建过程和性能。
- 部署和监控:当确认升级无误后,先部署升级后的代码到测试环境,并持续监控项目的表现和性能,稳定一段时间后再部署到生产环境。
问题1-Error: Unknown option '--colors'
npm run build
原因
npx webpack --help
【webpack 5.x.x】
【webpack 4.x.x】
解决
package.json 中 --colors 替换为 --color
问题2-Autoprefixer
解决:去掉相关注释
.test {
/* ! autoprefixer: off */
...
/* ! autoprefixer: off */
}
问题3-CSS 写法
解决:按照提示
text-decoration-skip: ink; -> text-decoration-skip-ink: auto;
问题4-webpack.HotModuleReplacementPlugin
HotModuleReplacementPlugin | webpack 中文文档
webpack.HotModuleReplacementPlugin
是否需要配置,与 devServer.hot
对比
看一下 webpack-dev-server
源码
if (this.options.hot) {
const HMRPluginExists = compiler.options.plugins.find(
(p) => p.constructor === webpack.HotModuleReplacementPlugin
);
if (HMRPluginExists) {
this.logger.warn(
`"hot: true" automatically applies HMR plugin, you don't have to add it manually to your webpack configuration.`
);
} else {
// Apply the HMR plugin
const plugin = new webpack.HotModuleReplacementPlugin();
plugin.apply(compiler);
}
}
说明如果配置了 devServer.hot: true
就不需要配置 webpack.HotModuleReplacementPlugin
四、优化过程
减小资源体积
css 压缩
css-minimizer-webpack-plugin
(webpack5 推荐)
js 压缩
terser-webpack-plugin
Tree Shaking
官方文档:Tree Shaking | webpack 中文文档
Webpack
已经默认开启了这个功能,无需其他配置,但是使用这个会有条件
Tree Shaking
触发条件:
- 通过解构的方式获取方法,可以触发
Tree Shaking
- 调用的
npm
包必须使用ESM
- 同一文件的
Tree Shaking
有触发条件,条件就是mode=production
- 一定要注意使用解构来加载模块
// import { a } from 'xxx.js'
export function a(){}
// 引用 default 的没办法做 treeshaking
export default {
a(){},
b(){},
}
webpack 4.x 与 webpack 5.x Tree Shaking 的差异
图片压缩
开发如果项目中引用了较多图片,那么图片体积会比较大,将来请求速度比较慢,可以对图片进行任缩,减少图片体积。
ImageMinimizerWebpackPlugin
ImageMinimizerWebpackPlugin | webpack 中文文档
分析:
由于项目中图片大多是在线链接,引用过多本地静态图片才需要考虑是否需要压缩
所以不需要配置这个
Code Spliting
- 将代码分割成多个 js 文件,使单个文件体积更小,并行加载 js 速度更快。
- 通过
import
动态导入语法,实现按需加载。
介绍
开箱即用的 SplitChunksPlugin
对于大部分用户来说非常友好。
默认情况下,它只会影响到按需加载的 chunks
,因为修改 initial chunks
会影响到项目的 HTML
文件中的脚本标签。
webpack
将根据以下条件自动拆分 chunks
:
- 新的 chunk 可以被共享,或者模块来自于 node_modules 文件夹
- 新的 chunk 体积大于 20kb(在进行 min+gz 之前的体积)
- 当按需加载 chunks 时,并行请求的最大数量小于或等于 30
- 当加载初始化页面时,并发请求的最大数量小于或等于 30
当尝试满足最后两个条件时,最好使用较大的 chunks。
module.exports = {
//...
optimization: {
splitChunks: {
// chunks 用以告诉 splitChunks 的作用对象,其可选值有 async、 initial 和 all。默认值是 async,也就是默认只选取异步加载的chunk进行代码拆分
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,
},
},
},
},
};
实际应用
代码分割的原则:
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
name: 'chunk-vendors',
test: /[\/]node_modules[\/]/,
priority: 10,
chunks: 'initial'
},
echarts: {
name: 'chunk-echarts',
priority: 20,
test: /[\/]node_modules[\/]_?echarts|zrender(.*)/
},
commons: {
name: 'chunk-commons',
minChunks: 3, // minimum common number
priority: 5,
reuseExistingChunk: true
}
}
},
},
配置说明:
配置 runtimeChunk
runtimeChunk
用于保存文件的 hash 值和它们与文件关系,文件体积就比较小,所以变化重新请求的代价也小。
runtimeChunk: {
name: "runtime~single"
}
externals
背景
分析打包体积,发现 chunk-vendors
体积很大,说明第三方依赖包体积很大,可以将一些依赖包提取出来,通过 CDN
引入,不通过 webpack
打包。
实践
webpack
配置 externals
externals: {
'react': 'React',
'react-dom': 'ReactDOM',
'react-router-dom': 'ReactRouterDOM',
'react-router-redux':'ReactRouterRedux',
'redux': 'Redux',
'react-router': 'ReactRouter',
'react-redux': 'ReactRedux',
'redux-logger': 'reduxLogger',
'redux-thunk': 'ReduxThunk',
'prop-types': 'PropTypes',
'classnames': 'classNames',
'lodash': '_',
'immutable': 'Immutable',
'@babel/polyfill': '_babelPolyfill'
},
静态资源走 oss
考虑单独写一篇文章
五、优化结果
由于优化还在持续进行,等后续任务完成后再补上优化结果、优化前后的对比。
六、踩坑
lodash-es
为什么考虑使用 loadsh-es
- 由于
lodash
采用的是commonjs
规范,每次打包会把全部文件打包进去,不能按需引入,所以考虑使用lodash-es
出现问题
npm uninstall lodash && npm i-S lodash-es
之后,修改了项目中的相关引用方式,发现打包出来了的资源始终包含lodash
和lodash-es
,反而增加了文件的体积大小。
找到原因
- 后来找到原因,发现
server
端引用了lodash
; - 如果用
lodash-es
,即使使用了babel-node
也不行,因为lodash-es
属于第三方包,babel-node
会默认ignore node_modules
,而node
又只认识commonjs
,所以应用lodash-es
会报错
最终的处理方式
- 所以最终决定不使用
lodash-es
,还是使用lodash
七、总结
性能优化可以从优化资源加载速度、优化运行性能等方面着手,采用适当的方式进行优化。本文从优化资源加载速度方面,针对项目存在的性能问题采取了相应的手段进行处理。
优化是无止尽的,关注每个阶段的目标,逐步优化;
需要量化优化的效果。
八、后续安排
建立性能优化知识体系
进一步优化
前端
-
优化加载性能
- 首页所加载的资源还可以拆分、体积还可以减小(合理、找到平衡)
-
优化运行性能
- 渲染层面
-
优化开发体验
- 提升构建速度
后端
- 接口拆分(各司其职)