主题性能优化
性能优化是主题开发的重要环节。本文档将详细介绍如何优化 VitePress 主题的性能,包括加载优化、渲染优化和构建优化等方面。
版本说明
- 本文档基于 VitePress v1.0.0+ 和 Vite 5.0+ 编写
- 需要 Node.js 18.0.0 及以上版本
- 涉及 Web 性能优化基础知识
性能指标
Core Web Vitals
| 指标 | 说明 | 良好值 |
|---|---|---|
| LCP (Largest Contentful Paint) | 最大内容绘制时间 | ≤ 2.5s |
| FID (First Input Delay) | 首次输入延迟 | ≤ 100ms |
| CLS (Cumulative Layout Shift) | 累积布局偏移 | ≤ 0.1 |
| FCP (First Contentful Paint) | 首次内容绘制 | ≤ 1.8s |
| TTFB (Time to First Byte) | 首字节时间 | ≤ 600ms |
性能测试工具
bash
# 1. Lighthouse
npx lighthouse https://your-site.com --view
# 2. WebPageTest
# 访问 https://www.webpagetest.org/
# 3. Chrome DevTools
# F12 -> Lighthouse -> Generate report加载优化
1. 代码分割
自动代码分割
VitePress 和 Vite 会自动进行代码分割,但我们可以进一步优化:
typescript
// .vitepress/config.ts
export default defineConfig({
vite: {
build: {
rollupOptions: {
output: {
// 手动分割代码块
manualChunks: {
// Vue 核心库
'vue-vendor': ['vue'],
// VitePress 核心
'vitepress-vendor': ['vitepress'],
// 工具库
'utils': ['lodash-es', 'date-fns'],
// 图表库(按需)
'charts': ['echarts', 'mermaid']
}
}
},
// 提高 chunk 大小警告阈值
chunkSizeWarningLimit: 1000
}
}
})组件懒加载
vue
<script setup>
import { defineAsyncComponent } from 'vue'
// 懒加载大型组件
const HeavyChart = defineAsyncComponent(() =>
import('./components/HeavyChart.vue')
)
// 懒加载带选项
const HeavyComponent = defineAsyncComponent({
loader: () => import('./components/HeavyComponent.vue'),
loadingComponent: () => h('div', '加载中...'),
errorComponent: () => h('div', '加载失败'),
delay: 200,
timeout: 10000
})
</script>
<template>
<Suspense>
<template #default>
<HeavyChart />
</template>
<template #fallback>
<div class="loading">加载中...</div>
</template>
</Suspense>
</template>2. Tree Shaking
优化导入
typescript
// ❌ 不推荐:导入整个库
import _ from 'lodash'
// ✅ 推荐:按需导入
import { debounce, throttle } from 'lodash-es'
// ✅ 更好:使用专门的包
import debounce from 'lodash-es/debounce'配置 Tree Shaking
typescript
// package.json
{
"sideEffects": [
"**/*.css",
"**/*.vue"
]
}
// .vitepress/config.ts
export default defineConfig({
vite: {
build: {
// 启用 Tree Shaking
minify: 'esbuild',
rollupOptions: {
output: {
// 保留 ES 模块格式
format: 'es',
// 保留导入语句
exports: 'named'
}
}
}
}
})3. 资源优化
图片优化
vue
<script setup>
import { ref } from 'vue'
const imageUrl = ref('/images/hero.webp')
// 响应式图片
const srcset = computed(() => `
/images/hero-400.webp 400w,
/images/hero-800.webp 800w,
/images/hero-1200.webp 1200w
`)
</script>
<template>
<img
:src="imageUrl"
:srcset="srcset"
sizes="(max-width: 768px) 100vw, 50vw"
alt="Hero"
loading="lazy"
decoding="async"
/>
</template>typescript
// 使用 vite-plugin-image-optimizer
import { defineConfig } from 'vite'
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
export default defineConfig({
plugins: [
ViteImageOptimizer({
test: /\.(jpe?g|png|gif|webp|svg)$/i,
includePublic: true,
logStats: true
})
]
})字体优化
css
/* 使用 font-display: swap */
@font-face {
font-family: 'CustomFont';
src: url('/fonts/custom.woff2') format('woff2');
font-display: swap; /* 避免字体加载时的 FOIT */
font-weight: 400;
font-style: normal;
}
/* 预加载关键字体 */typescript
// .vitepress/config.ts
export default defineConfig({
head: [
// 预加载字体
['link', {
rel: 'preload',
href: '/fonts/custom.woff2',
as: 'font',
type: 'font/woff2',
crossorigin: 'anonymous'
}]
]
})4. 预加载和预获取
typescript
// .vitepress/config.ts
export default defineConfig({
head: [
// 预加载关键资源
['link', { rel: 'preload', href: '/fonts/custom.woff2', as: 'font', crossorigin: 'anonymous' }],
['link', { rel: 'preload', href: '/images/hero.webp', as: 'image' }],
// DNS 预解析
['link', { rel: 'dns-prefetch', href: 'https://fonts.googleapis.com' }],
// 预连接
['link', { rel: 'preconnect', href: 'https://fonts.googleapis.com' }],
['link', { rel: 'preconnect', href: 'https://fonts.gstatic.com', crossorigin: 'anonymous' }]
]
})vue
<!-- 组件中的预获取 -->
<script setup>
import { onMounted } from 'vue'
onMounted(() => {
// 预获取下一页资源
const link = document.createElement('link')
link.rel = 'prefetch'
link.href = '/next-page'
document.head.appendChild(link)
})
</script>渲染优化
1. 虚拟滚动
对于长列表,使用虚拟滚动减少 DOM 节点:
vue
<script setup>
import { ref } from 'vue'
import { useVirtualList } from '@vueuse/core'
const items = ref(Array.from({ length: 10000 }, (_, i) => ({
id: i,
title: `Item ${i}`
})))
const { list, containerProps, wrapperProps } = useVirtualList(
items,
{ itemHeight: 50 }
)
</script>
<template>
<div v-bind="containerProps" class="virtual-list">
<div v-bind="wrapperProps">
<div
v-for="{ data, index } in list"
:key="index"
class="item"
>
{{ data.title }}
</div>
</div>
</div>
</template>
<style scoped>
.virtual-list {
height: 500px;
overflow-y: auto;
}
.item {
height: 50px;
padding: 10px;
border-bottom: 1px solid #eee;
}
</style>2. 防抖和节流
vue
<script setup>
import { ref } from 'vue'
import { useDebounceFn, useThrottleFn } from '@vueuse/core'
const searchTerm = ref('')
// 防抖:搜索输入
const handleSearchDebounced = useDebounceFn((value) => {
console.log('搜索:', value)
// 执行搜索
}, 300)
// 节流:滚动事件
const handleScrollThrottled = useThrottleFn(() => {
console.log('滚动位置:', window.scrollY)
}, 100)
function handleInput(e) {
handleSearchDebounced(e.target.value)
}
onMounted(() => {
window.addEventListener('scroll', handleScrollThrottled)
})
onUnmounted(() => {
window.removeEventListener('scroll', handleScrollThrottled)
})
</script>3. 懒加载组件
vue
<script setup>
import { ref, onMounted } from 'vue'
const isVisible = ref(false)
const target = ref(null)
// IntersectionObserver 懒加载
onMounted(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
isVisible.value = true
observer.disconnect()
}
})
},
{ threshold: 0.1 }
)
if (target.value) {
observer.observe(target.value)
}
})
</script>
<template>
<div ref="target">
<HeavyComponent v-if="isVisible" />
<div v-else class="placeholder">加载中...</div>
</div>
</template>或使用 @vueuse/core:
vue
<script setup>
import { useIntersectionObserver } from '@vueuse/core'
const target = ref(null)
const isVisible = ref(false)
useIntersectionObserver(
target,
([{ isIntersecting }]) => {
if (isIntersecting) {
isVisible.value = true
}
},
{ threshold: 0.1 }
)
</script>
<template>
<div ref="target">
<HeavyComponent v-if="isVisible" />
</div>
</template>4. 计算属性缓存
vue
<script setup>
import { ref, computed } from 'vue'
const items = ref([])
// ✅ 使用计算属性(自动缓存)
const filteredItems = computed(() => {
return items.value.filter(item => item.active)
})
// ❌ 避免:方法调用(无缓存)
function getFilteredItems() {
return items.value.filter(item => item.active)
}
</script>5. v-once 和 v-memo
vue
<template>
<!-- 静态内容使用 v-once -->
<header v-once>
<h1>{{ siteTitle }}</h1>
<p>{{ siteDescription }}</p>
</header>
<!-- 复杂列表使用 v-memo -->
<div
v-for="item in items"
:key="item.id"
v-memo="[item.selected]"
>
<ExpensiveComponent :item="item" />
</div>
</template>构建优化
1. 压缩配置
typescript
// .vitepress/config.ts
export default defineConfig({
vite: {
build: {
// 使用 esbuild 压缩(更快)
minify: 'esbuild',
// 或使用 terser(压缩率更高)
// minify: 'terser',
// terserOptions: {
// compress: {
// drop_console: true,
// drop_debugger: true
// }
// },
// CSS 压缩
cssMinify: true
}
}
})2. 构建分析
bash
# 安装分析工具
npm install -D rollup-plugin-visualizertypescript
// .vitepress/config.ts
import { visualizer } from 'rollup-plugin-visualizer'
export default defineConfig({
vite: {
plugins: [
visualizer({
open: true,
gzipSize: true,
brotliSize: true,
filename: 'stats.html'
})
]
}
})3. 缓存优化
typescript
// .vitepress/config.ts
export default defineConfig({
vite: {
build: {
// 生成 sourcemap(可选)
sourcemap: false,
// 缓存目录
cacheDir: 'node_modules/.vite'
},
// 依赖预构建缓存
optimizeDeps: {
include: ['vue', 'vitepress']
}
}
})4. 外部化依赖
typescript
// .vitepress/config.ts
export default defineConfig({
vite: {
build: {
rollupOptions: {
// 外部化大型依赖
external: ['vue', 'vitepress'],
output: {
globals: {
vue: 'Vue',
vitepress: 'VitePress'
}
}
}
}
}
})网络优化
1. CDN 加速
typescript
// .vitepress/config.ts
export default defineConfig({
// 使用 CDN 基础路径
base: 'https://cdn.example.com/'
})2. 缓存策略
nginx
# nginx.conf
server {
# 静态资源长期缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# HTML 文件短期缓存
location ~* \.html$ {
expires 1h;
add_header Cache-Control "public, must-revalidate";
}
}3. Gzip 压缩
typescript
// .vitepress/config.ts
import viteCompression from 'vite-plugin-compression'
export default defineConfig({
vite: {
plugins: [
viteCompression({
algorithm: 'gzip',
ext: '.gz',
threshold: 10240, // 大于 10KB 才压缩
deleteOriginFile: false
}),
// Brotli 压缩(压缩率更高)
viteCompression({
algorithm: 'brotliCompress',
ext: '.br'
})
]
}
})4. HTTP/2 推送
nginx
# nginx.conf
server {
listen 443 ssl http2;
# HTTP/2 推送关键资源
http2_push /assets/index.js;
http2_push /assets/index.css;
http2_push /fonts/custom.woff2;
}运行时优化
1. 避免不必要的响应式
vue
<script setup>
import { ref, shallowRef, shallowReactive } from 'vue'
// ❌ 深层响应式(性能开销大)
const deepObject = ref({
nested: { data: [] }
})
// ✅ 浅层响应式(性能更好)
const shallowObject = shallowRef({
nested: { data: [] }
})
// ✅ 对于大型数据
const largeList = shallowReactive([])
</script>2. 使用 shallowRef 处理大型对象
vue
<script setup>
import { shallowRef, triggerRef } from 'vue'
const largeData = shallowRef({
items: Array(10000).fill(null).map((_, i) => ({ id: i }))
})
function updateItem(index, newValue) {
// 直接修改(不会触发更新)
largeData.value.items[index] = newValue
// 手动触发更新
triggerRef(largeData)
}
</script>3. 使用 markRaw 避免响应式转换
vue
<script setup>
import { markRaw } from 'vue'
// 标记为非响应式(如第三方库实例)
const chart = markRaw(new Chart(ctx, config))
</script>监控和分析
1. 性能监控
vue
<script setup>
import { onMounted } from 'vue'
onMounted(() => {
// 使用 Performance API
const timing = performance.timing
const loadTime = timing.loadEventEnd - timing.navigationStart
console.log('页面加载时间:', loadTime, 'ms')
// 使用 PerformanceObserver
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log('性能条目:', entry.name, entry.duration)
}
})
observer.observe({ entryTypes: ['measure', 'navigation', 'resource'] })
})
</script>2. 错误监控
typescript
// .vitepress/theme/index.ts
import { onMounted } from 'vue'
export default {
setup() {
onMounted(() => {
// 捕获全局错误
window.onerror = (message, source, lineno, colno, error) => {
console.error('全局错误:', { message, source, lineno, colno, error })
// 发送到错误监控服务
}
// 捕获未处理的 Promise 拒绝
window.onunhandledrejection = (event) => {
console.error('未处理的 Promise 拒绝:', event.reason)
}
})
}
}性能检查清单
加载性能
- [ ] 代码分割优化
- [ ] Tree Shaking 启用
- [ ] 图片优化和懒加载
- [ ] 字体优化
- [ ] 预加载关键资源
- [ ] Gzip/Brotli 压缩
- [ ] CDN 加速
渲染性能
- [ ] 虚拟滚动(长列表)
- [ ] 懒加载组件
- [ ] 防抖和节流
- [ ] 计算属性缓存
- [ ] v-once 和 v-memo
- [ ] 避免深层响应式
构建性能
- [ ] 压缩配置优化
- [ ] 构建分析
- [ ] 缓存优化
- [ ] 外部化依赖
网络性能
- [ ] 缓存策略
- [ ] HTTP/2
- [ ] 资源预加载
- [ ] 预连接第三方域名