性能优化-构建与监控
构建优化
1. 打包分析
bash
# Vite
vite build --mode analyze
# Webpack
webpack --profile --json > stats.json
npx webpack-bundle-analyzer stats.json# Vite
vite build --mode analyze
# Webpack
webpack --profile --json > stats.json
npx webpack-bundle-analyzer stats.json2. Tree Shaking
js
// ✅ 支持 tree-shaking
import { debounce } from 'lodash-es'
import { createApp } from 'vue'
// ❌ 不支持 tree-shaking
import _ from 'lodash'// ✅ 支持 tree-shaking
import { debounce } from 'lodash-es'
import { createApp } from 'vue'
// ❌ 不支持 tree-shaking
import _ from 'lodash'3. 代码压缩
js
// Vite 默认使用 esbuild(构建)和 terser(生产)
// vite.config.js
export default {
build: {
minify: 'esbuild', // 快速
// minify: 'terser', // 更小体积
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules')) {
if (id.includes('vue')) return 'vendor-vue'
if (id.includes('lodash')) return 'vendor-lodash'
return 'vendor'
}
},
},
},
},
}// Vite 默认使用 esbuild(构建)和 terser(生产)
// vite.config.js
export default {
build: {
minify: 'esbuild', // 快速
// minify: 'terser', // 更小体积
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules')) {
if (id.includes('vue')) return 'vendor-vue'
if (id.includes('lodash')) return 'vendor-lodash'
return 'vendor'
}
},
},
},
},
}4. 图片压缩与转换
js
// vite-plugin-imagemin
import viteImagemin from 'vite-plugin-imagemin'
export default {
plugins: [
viteImagemin({
gifsicle: { optimizationLevel: 7 },
mozjpeg: { quality: 80 },
optipng: { optimizationLevel: 7 },
webp: { quality: 80 },
}),
],
}// vite-plugin-imagemin
import viteImagemin from 'vite-plugin-imagemin'
export default {
plugins: [
viteImagemin({
gifsicle: { optimizationLevel: 7 },
mozjpeg: { quality: 80 },
optipng: { optimizationLevel: 7 },
webp: { quality: 80 },
}),
],
}运行时监控
1. Performance Observer
js
// 监控 long tasks
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.warn('长任务:', entry.duration, 'ms')
// 上报到监控系统
reportMetric('long-task', entry.duration)
}
})
observer.observe({ entryTypes: ['longtask'] })
// 监控资源加载
const resourceObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.duration > 1000) {
reportMetric('slow-resource', {
name: entry.name,
duration: entry.duration,
})
}
}
})
resourceObserver.observe({ entryTypes: ['resource'] })// 监控 long tasks
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.warn('长任务:', entry.duration, 'ms')
// 上报到监控系统
reportMetric('long-task', entry.duration)
}
})
observer.observe({ entryTypes: ['longtask'] })
// 监控资源加载
const resourceObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.duration > 1000) {
reportMetric('slow-resource', {
name: entry.name,
duration: entry.duration,
})
}
}
})
resourceObserver.observe({ entryTypes: ['resource'] })2. 错误监控
js
// 全局 JS 错误
window.onerror = (message, source, line, col, error) => {
reportError({ message, source, line, col, error })
}
// 未捕获的 Promise 错误
window.addEventListener('unhandledrejection', (event) => {
reportError({ reason: event.reason })
})
// 资源加载错误
window.addEventListener('error', (event) => {
if (event.target.tagName === 'IMG' || event.target.tagName === 'SCRIPT') {
reportError({ type: 'resource-load', src: event.target.src })
}
}, true)// 全局 JS 错误
window.onerror = (message, source, line, col, error) => {
reportError({ message, source, line, col, error })
}
// 未捕获的 Promise 错误
window.addEventListener('unhandledrejection', (event) => {
reportError({ reason: event.reason })
})
// 资源加载错误
window.addEventListener('error', (event) => {
if (event.target.tagName === 'IMG' || event.target.tagName === 'SCRIPT') {
reportError({ type: 'resource-load', src: event.target.src })
}
}, true)3. 性能打点
js
// 手动打点
performance.mark('fetch-start')
await fetchData()
performance.mark('fetch-end')
performance.measure('fetch-duration', 'fetch-start', 'fetch-end')
const measure = performance.getEntriesByName('fetch-duration')[0]
console.log('接口耗时:', measure.duration)
// 清理
performance.clearMarks()
performance.clearMeasures()// 手动打点
performance.mark('fetch-start')
await fetchData()
performance.mark('fetch-end')
performance.measure('fetch-duration', 'fetch-start', 'fetch-end')
const measure = performance.getEntriesByName('fetch-duration')[0]
console.log('接口耗时:', measure.duration)
// 清理
performance.clearMarks()
performance.clearMeasures()性能预算
json
{
"performanceBudget": {
"bundleSize": {
"total": "200kb",
"vendor": "100kb",
"app": "80kb"
},
"lighthouse": {
"performance": 90,
"accessibility": 90,
"bestPractices": 85,
"seo": 90
},
"webVitals": {
"LCP": 2500,
"FID": 100,
"CLS": 0.1
}
}
}{
"performanceBudget": {
"bundleSize": {
"total": "200kb",
"vendor": "100kb",
"app": "80kb"
},
"lighthouse": {
"performance": 90,
"accessibility": 90,
"bestPractices": 85,
"seo": 90
},
"webVitals": {
"LCP": 2500,
"FID": 100,
"CLS": 0.1
}
}
}常用工具
| 工具 | 用途 |
|---|---|
| Lighthouse CI | 自动化性能审计 |
| bundlesize | 包体积预算检查 |
| webpack-bundle-analyzer | 打包分析 |
| why-did-you-render | React 不必要渲染检测 |
| Chrome DevTools | 实战调试分析 |
| Sentry | 错误与性能监控 |
Cao Note