项目性能优化
总结篇
重点
- 开发阶段开启缓存,比如针对一些性能开销比较大的 loader 使用 cache-loader 进行缓存
- 使用路由懒加载 + 异步组件(vue defineAsyncComponent 和 react lazy)
- 第三方组件库按需导入,第三方包如果有 esm 格式的更换 esm 格式的包 (lodash -> lodash-es, echarts),moment 更换为 dayjs
- js 文件使用 terserWebpackPlugin 进行压缩
- css 文件使用 mini-css-exctrat-plugin 提取成单独的文件,在使用 css-minimizer-webpack-plugin 插件进行压缩
- 小图片使用 base64 减少 http 请求,大图片使用 webp 格式进行无损压缩
- 开启多线程打包
- 第三方包使用 dllPlugin 进行打包,这部分代码基本是不变的,可以使用强缓存,有效期设置为 1 年,避免后续对第三方的打包
- 针对首屏优化还可以使用骨架屏
webpack 相关的优化
loader 指定范围,减少检索范围
javascript
const path = require('path')
module.exports = {
//...
module: {
rules: [
{
test: /\.js[x]?$/,
use: ['babel-loader'],
include: [path.resolve(__dirname, 'src')],
},
],
},
}
使用 cache-loader 对于一些性能开销大的 loader 进行缓存
javascript
// pnpm add cache-loader -D
module.exports = {
//...
module: {
//我的项目中,babel-loader耗时比较长,所以我给它配置了`cache-loader`
rules: [
{
test: /\.jsx?$/,
use: ['cache-loader', 'babel-loader'],
},
],
},
}
开启电脑多核处理 happypack
javascript
// pnpm add happypack -D
const Happypack = require('happypack')
module.exports = {
//...
module: {
rules: [
{
test: /\.js[x]?$/,
use: 'Happypack/loader?id=js',
include: [path.resolve(__dirname, 'src')],
},
{
test: /\.css$/,
use: 'Happypack/loader?id=css',
include: [path.resolve(__dirname, 'src'), path.resolve(__dirname, 'node_modules', 'bootstrap', 'dist')],
},
],
},
plugins: [
new Happypack({
id: 'js', //和rule中的id=js对应
//将之前 rule 中的 loader 在此配置
use: ['babel-loader'], //必须是数组
}),
new Happypack({
id: 'css', //和rule中的id=css对应
use: ['style-loader', 'css-loader', 'postcss-loader'],
}),
],
}
js 多进程压缩
webpack 5 默认使用 TerserWebpackPlugin, 即使默认就开启了多进程和缓存
中间缓存 HardSourceWebpackPlugin
首次构建时间没有太大变化,但是第二次开始,构建时间大约可以节约 80%。
javascript
// pnpm add hard-source-webpack-plugin -D
//webpack.config.js
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')
module.exports = {
//...
plugins: [new HardSourceWebpackPlugin()],
}
url-loader 图片压缩 base64
javascript
{
test: /.(jpg|jpeg|webp|png|svg|gif|woff|woff2|eot|ttf|otf)$/,
exclude: /(node_modules|bower_components)/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192,
name: '[path][name].[ext]',
},
},
],
}
开启 gzip 压缩
javascript
// pnpm add compression-webpack-plugin -D
// webpack.prod.config.js
const CompressionPlugin = require('compression-webpack-plugin')
const productionGzipExtensions = ['js', 'css']
module.exports = {
// ...
plugins: [
new CompressionPlugin({
filename: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp('\\.(' + productionGzipExtensions.join('|') + ')$'),
threshold: 10240,
minRatio: 0.8,
}),
],
}
第三方包单独拆分
dllPlugin 只适用于 webpack3, webpack4 之后对打包的优化效果已经不在需要这个插件了,vue-cli 和 cra 在升级 webpack4 之后都移除了这个插件
DllPLugin 只有第一次打包会打包第三方包,后续打包只会打包业务代码
javascript
// DLLPlugin
// webpack.dll.config.js
const path = require('path')
const { DllPlugin } = require('webpack')
const pathResolve = (url) => path.resolve(__dirname, url)
module.exports = {
mode: 'development',
entry: {
utils: ['isarray', 'is-promise'],
},
output: {
path: pathResolve('../dist'),
filename: 'utils.dll.js',
library: '_dll_utils',
},
plugins: [
new DllPlugin({
name: '_dll_utils',
path: path.join(__dirname, '../dist', 'utils.manifest.json'),
}),
],
}
//webbpack.config.js
const path = require('path')
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const pathResolve = (url) => path.resolve(__dirname, url)
module.exports = {
mode: 'development',
devtool: false,
entry: pathResolve('../src/index.js'),
output: {
path: pathResolve('../dist'),
filename: 'bundle.js',
},
plugins: [
new DllReferencePlugin({
manifest: require('../dist/utils.manifest.json'),
}),
new HtmlWebpackPlugin({
template: pathResolve('../public/index.html'),
}),
],
}
vue 相关的优化
路由懒加载
静态资源存放 cdn
图片使用 webp
组件库按需引入
异步组件
首屏优化
开启 http 缓存
使用骨架屏
script 标签使用 defer 、async
获取性能指标
performance api
performance
- performance
- eventCounts
- size
- memory: 显示此刻内存占用情况
- jsHeapSizeLimit number 可使用的内存
- totalJSHeapSize number 内存大小限制
- usedJSHeapSize number JS对象占用的内存数
注意: 如果 usedJSHeapSize > totalJSHeapSize, 那么极有可能是发生了内存泄漏
- navigate: 显示页面的来源信息
- redirectCount number 表示如果有重定向的话,页面通过几次重定向跳转而来,默认为0
- type number 表示页面打开的方式
- 0 正常进入
- 1 通过window.reload()刷新的页面
- 2 通过浏览器的前进后退按钮进入的页面
- 255 非以上方式进入的页面。
- timeOrigin: 时间戳 一系列时间点的基准点,精确到万分之一毫秒
- timing: 一系列关键时间点,包含网络、解析等一系列的时间数据。
- 。。。。太多了
- eventCounts
一下信息都是 timing 里面的字段计算出来的
重要
- 重定向耗时:redirectEnd - redirectStart
- DNS查询耗时:domainLookupEnd - domainLookupStart
- TCP链接耗时:connectEnd - connectStart
- HTTP请求耗时:responseEnd - responseStart
- 解析dom树耗时:domComplete - domInteractive
- 白屏时间:responseStart - navigationStart
- DOM ready时间:domContentLoadedEventEnd - navigationStart
- onload时间:loadEventEnd - navigationStart
web-vitals
ts
import {
onCLS,
onFCP,
onFID,
onINP,
onLCP,
onTTFB,
Metric
} from 'web-vitals'
import { performanceMapping } from './contants'
/**
* @param {function?} reportInfoToServer 上报性能指标给到后端
*/
const getSystemPerformance = (
reportInfoToServer: Function
) => {
// 网站性能核心指标
onLCP((e) => reportCallBack(e, reportInfoToServer)) // 用于判断页面视图是否大部分加载出来了,最好小于 2.5 秒
onFID((e) => reportCallBack(e, reportInfoToServer)) // 用于判断页面处于可交互性的时机, 最好小于 100 豪秒
onCLS((e) => reportCallBack(e, reportInfoToServer)) // 用于判断页面的视觉稳定性, 最好小于 0.1
// 网站性能其他指标
onINP((e) => reportCallBack(e, reportInfoToServer))
onFCP((e) => reportCallBack(e, reportInfoToServer))
onTTFB((e) => reportCallBack(e, reportInfoToServer))
}
/**
* @param e 性能指标相关的数据
* @param reportInfoToServer 上报函数
*/
const reportCallBack = (
e: Metric,
reportInfoToServer?: Function
) => {
console.log(`-------${performanceMapping[e.name] || e.name}-----`, e)
reportInfoToServer && reportInfoToServer()
}
export default getSystemPerformance