性能优化实践 - 优化资源加载速度
“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第 3 篇文章,点击查看活动详情”
一、背景与目标
背景:商城移动端,首屏加载过慢,需要做性能优化。
目标:减少白屏时间,提高页面加载速度,提升用户体验。
⭐本文将重点放在对资源加载速度的优化。
二、优化思路

考虑到 webpack5 的一些新特性可以带来减少资源体积等作用,所以决定将项目中使用的 webpack4 升级到 webpack5。
⭐Webpack 5 发布 (2020-10-10) | webpack 中文文档
这个版本的重点在于以下几点。
- 尝试用持久性缓存来提高构建性能。
- 尝试用更好的算法和默认值来改进长期缓存。
- 尝试用更好的 Tree Shaking 和代码生成来改善包大小。
- 尝试改善与网络平台的兼容性。
- 尝试在不引入任何破坏性变化的情况下,清理那些在实现 v4 功能时处于奇怪状态的内部结构。
- 试图通过现在引入突破性的变化来为未来的功能做准备,使其能够尽可能长时间地保持在 v5 版本上。
⭐由于优化步骤是先升级 webpack,然后再进行相关优化,所以下文的组织方式也与之对应。
三、webpack 升级过程
升级指南
如何升级
方式一:手动修改 package.json 依赖包的版本号
方式二:通过工具升级,然后根据控制台报错解决问题
根据控制台报错去完善升级,会让自己处于很被动的状态,并且也会有一些奇怪的 bug 产生;
方式三:重新写 webpack.config.js
- 删除
package.json中devDependencies的相关loader和plugin依赖包 - 将
webpack和webpack-cli升级到最新稳定版 entry、output等路径相关的得参考以前的- 在重写配置的过程再去安装相应的
loader和plugin
会对 webpack 的掌握程度有一定要求,还需要检查以前的配置,哪些需要使用,哪些已经别的替代,哪些需要废弃
问题1-Error: Unknown option '--colors'
npm run build

原因
npx webpack --help
【webpack 5.x.x】

【webpack 4.x.x】

解决
package.json 中 --colors 替换为 --color
问题2-process is not defined
bootstrap:27 Uncaught ReferenceError: process is not defined
解决
new webpack.ProvidePlugin({ process: 'process/browser' }),
问题3-Autoprefixer

解决:去掉相关注释
.test {
/* ! autoprefixer: off */
...
/* ! autoprefixer: off */
}
问题4-CSS 写法

解决:按照提示
text-decoration-skip: ink; -> text-decoration-skip-ink: auto;
问题5-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
七、总结
性能优化可以从优化资源加载速度、优化运行性能等方面着手,采用适当的方式进行优化。本文从优化资源加载速度方面,针对项目存在的性能问题采取了相应的手段进行处理。
优化是无止尽的,关注每个阶段的目标,逐步优化;
需要量化优化的效果。
八、后续安排
建立性能优化知识体系
进一步优化
前端
-
优化加载性能
- 首页所加载的资源还可以拆分、体积还可以减小(合理、找到平衡)
-
优化运行性能
- 渲染层面
-
优化开发体验
- 提升构建速度
后端
- 接口拆分(各司其职)
九、参考
转载自:https://juejin.cn/post/7141685434102317069