关于 vite-bundle-optimization
面向 Vite + React 开发者的构建优化参考,适用于生产配置以及排查构建缓慢或包体积过大。内容涵盖导入方式、代码拆分、资源加载和 Vite 构建配置。
主要功能
- 01避免使用聚合导出导入
- 02配置手动代码分块
- 03通过动态导入拆分路由
- 04延迟加载首屏以下组件
- 05延后加载第三方脚本
- 06分析构建包内容
适用场景
- 01减少初始 JavaScript 负载
- 02排查意外膨胀的构建包
- 03改善 Vite 开发服务器启动
- 04审查生产构建配置
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ patternsdev/ skills --skill vite-bundle-optimization安装后在 Claude Code 中输入/vite-bundle-optimization确认已加载。
详细介绍
Vite 打包优化
目录
适用于生产环境的模式,用于优化 Vite + React 应用的打包体积和构建性能。这些模式利用 Vite 的架构(开发时使用原生 ESM,生产环境使用 Rollup),实现更小、更快的打包产物。
适用场景
在以下情况下参考这些模式:
- 为生产环境搭建新的 Vite + React 项目
- 使用
npx vite-bundle-visualizer分析打包体积 - 构建时间较长或打包产物异常庞大
- 从 webpack/CRA 迁移到 Vite
- 优化 Core Web Vitals(LCP、FID/INP、CLS)
说明
- 在项目设置、构建配置和打包体积审查期间应用这些模式。发现打包产物庞大或构建缓慢时,使用
npx vite-bundle-visualizer进行诊断,并应用相应模式。
详情
概述
Vite 使用 esbuild 进行依赖预打包和开发转换,并使用 Rollup 进行生产构建。理解这种双重架构是有效优化的关键。以下模式按影响程度排序。
1. 避免从 Barrel 文件导入
影响:关键 — 可能导致启动时间增加 200-800ms,开发服务器启动时间增加 2-4s。
Barrel 文件(从多个模块重新导出的 index.ts)会迫使打包工具加载整个模块图,即使你只使用了一个导出项。这是 React 应用中最常见的打包体积问题。
避免 — 通过 Barrel 文件导入整个库:
import { Button, TextField } from '@/components'
// Loads ALL components in the barrel, even unused ones
import { Check, X, Menu } from 'lucide-react'
// Loads all 1,500+ icons (~2.8s in dev)
推荐 — 直接导入:
import { Button } from '@/components/Button'
import { TextField } from '@/components/TextField'
import Check from 'lucide-react/dist/esm/icons/check'
import X from 'lucide-react/dist/esm/icons/x'
import Menu from 'lucide-react/dist/esm/icons/menu'
使用 vite-plugin-barrel 自动修复:
// vite.config.ts
import barrel from 'vite-plugin-barrel'
export default defineConfig({
plugins: [
react(),
barrel({
packages: ['lucide-react', '@mui/material', '@mui/icons-material'],
}),
],
})
该插件会在构建时将 Barrel 导入转换为直接导入,让你既能使用简洁的语法,又能获得直接导入的性能。
常受影响的库: lucide-react、@mui/material、@mui/icons-material、@tabler/icons-react、react-icons、@radix-ui/react-*、lodash、date-fns、rxjs。
2. 配置手动分块
影响:高 — 改善缓存和并行加载,并减小初始打包体积。
Vite 默认会将所有供应商代码放入一个文件。对代码进行拆分,这样频繁变化的应用代码就不会使供应商缓存失效。
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
build: {
rollupOptions: {
output: {
manualChunks: {
// Core React — rarely changes
'vendor-react': ['react', 'react-dom'],
// Router — changes infrequently
'vendor-router': ['react-router-dom'],
// Data layer — changes occasionally
'vendor-query': ['@tanstack/react-query'],
// UI framework — changes with design updates
'vendor-ui': ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu'],
},
},
},
},
})
如需根据模块路径进行更动态的拆分:
manualChunks(id) {
if (id.includes('node_modules')) {
if (id.includes('react-dom')) return 'vendor-react'
if (id.includes('react-router')) return 'vendor-router'
if (id.includes('@tanstack')) return 'vendor-query'
return 'vendor' // everything else
}
},
3. 使用动态导入进行路由级代码拆分
影响:高 — 仅加载当前页面所需的代码。
使用 React.lazy() 和动态导入,将每个路由拆分到单独的代码块中。
import { lazy, Suspense } from 'react'
import { BrowserRouter, Routes, Route } from 'react-router-dom'
const Home = lazy(() => import('./pages/Home'))
const Dashboard = lazy(() => import('./pages/Dashboard'))
const Settings = lazy(() => import('./pages/Settings'))
function App() {
return (
<BrowserRouter>
<Suspense fallback={<PageSkeleton />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
</BrowserRouter>
)
}
Vite 会自动为每个延迟导入创建单独的代码块。为其命名以便调试:
const Dashboard = lazy(() =>
import(/* webpackChunkName: "dashboard" */ './pages/Dashboard')
)
4. 延迟加载首屏以下的重型组件
影响:高 — 减小初始打包体积,加快 LCP。
首次加载时不可见的组件(模态框、图表、编辑器、地图)应采用延迟加载。
import { lazy, Suspense, useState } from 'react'
const RichTextEditor = lazy(() => import('./components/RichTextEditor'))
const ChartPanel = lazy(() => import('./components/ChartPanel'))
function ArticlePage() {
const [editing, setEditing] = useState(false)
return (
<article>
<h1>Article Title</h1>
<p>Content visible immediately...</p>
{editing && (
<Suspense fallback={<EditorSkeleton />}>
<RichTextEditor />
</Suspense>
)}
<Suspense fallback={<ChartSkeleton />}>
<ChartPanel />
</Suspense>
</article>
)
}
5. 延迟加载第三方脚本
影响:高 — 分析、跟踪和小组件不应阻塞渲染。
在页面可交互后加载非关键第三方脚本。
避免 — 阻塞初始渲染:
// main.tsx
import * as Sentry from '@sentry/react'
import posthog from 'posthog-js'
Sentry.init({ dsn: '...' })
posthog.init('...')
推荐 — 在 hydration/mount 后加载:
// main.tsx — defer to idle time
function initThirdParty() {
import('@sentry/react').then(Sentry => {
Sentry.init({ dsn: import.meta.env.VITE_SENTRY_DSN })
})
import('posthog-js').then(({ default: posthog }) => {
posthog.init(import.meta.env.VITE_POSTHOG_KEY)
})
}
if ('requestIdleCallback' in window) {
requestIdleCallback(initThirdParty)
} else {
setTimeout(initThirdParty, 2000)
}
对于外部脚本标签,使用 defer 或动态注入:
function loadScript(src: string) {
const script = document.createElement('script')
script.src = src
script.async = true
document.body.appendChild(script)
}
6. 根据用户意图预加载关键资源
影响:中 — 消除导航时的感知延迟。
当用户表示出意图(悬停、聚焦)时,就开始加载路由代码,而不是等到点击后再加载。
function NavLink({ to, children }: { to: string; children: React.ReactNode }) {
const preload = () => {
// Vite creates a module preload for dynamic imports
switch (to) {
case '/dashboard':
import('./pages/Dashboard')
break
case '/settings':
import('./pages/Settings')
break
}
}
return (
<Link to={to} onMouseEnter={preload} onFocus={preload}>
{children}
</Link>
)
}
如需在 HTML head 中使用 <link rel="modulepreload">:
<link rel="modulepreload" href="/assets/Home-abc123.js" />
Vite 会自动为入口代码块添加 <link rel="modulepreload">。对于确定用户接下来会访问的路由,可手动添加预加载。
7. 配置依赖预打包
影响:中 — 加快开发服务器启动和页面加载。
Vite 使用 esbuild 对 node_modules 中的依赖进行预打包。配置预打包以处理边缘情况。
// vite.config.ts
export default defineConfig({
optimizeDeps: {
// Force pre-bundle these (useful for CJS deps or deep imports)
include: [
'react',
'react-dom',
'react-router-dom',
'@tanstack/react-query',
'date-fns/format',
'date-fns/parseISO',
],
// Skip pre-bundling for these (already ESM, or causes issues)
exclude: ['@vite-pwa/assets-generator'],
},
})
如果开发环境中有许多小请求,导致页面加载缓慢,通常是因为某个依赖未被预打包。将其添加到 include。
8. 启用压缩
影响:中——传输文件体积可缩小 60%–80%。
Vite 默认不启用压缩。为生产环境添加压缩插件。
// vite.config.ts
import viteCompression from 'vite-plugin-compression'
export default defineConfig({
plugins: [
react(),
viteCompression({ algorithm: 'gzip' }),
viteCompression({ algorithm: 'brotliCompress' }),
],
})
此操作会在资源文件旁生成 .gz 和 .br 文件。配置服务器(Nginx、Cloudflare、Vercel)来提供这些文件。
9. 定期分析打包产物
影响:信息性——在体积回归问题发布前及时发现。
每次依赖项有重大变更后,都运行打包产物可视化工具。
npx vite-bundle-visualizer
也可以将它添加到构建脚本中:
{
"scripts": {
"build": "vite build",
"analyze": "vite build && npx vite-bundle-visualizer"
}
}
需要关注的事项:
- 任何单个分块经 gzip 压缩后大于 200KB——考虑拆分
- 多个分块中加载了重复的库
- 仅使用少数几个函数,却加载了整个库
- 可以动态导入的
node_modules代码
10. 使用 import.meta.env 消除无用代码
影响:低至中——移除生产环境中未使用的代码路径。
Vite 会在构建时替换 import.meta.env.*,使 Rollup 能够通过摇树优化移除无用分支。
// This code is completely removed in production
if (import.meta.env.DEV) {
console.log('Debug info:', data)
window.__DEBUG_DATA__ = data
}
// Feature flags eliminated at build time
if (import.meta.env.VITE_FEATURE_NEW_DASHBOARD === 'true') {
// Only included when flag is set
initNewDashboard()
}
在 .env 文件中定义自定义环境变量:
# .env.production
VITE_FEATURE_NEW_DASHBOARD=true
VITE_API_URL=https://api.example.com
11. 优化图像和静态资源
影响:中——图像通常是体积最大的资源。
在 Vite 中配置资源处理:
// vite.config.ts
export default defineConfig({
build: {
assetsInlineLimit: 4096, // Inline assets < 4KB as base64
rollupOptions: {
output: {
assetFileNames: (assetInfo) => {
// Organize assets by type
if (/\.(png|jpe?g|gif|svg|webp|avif)$/.test(assetInfo.name ?? '')) {
return 'images/[name]-[hash][extname]'
}
if (/\.(woff2?|ttf|eot)$/.test(assetInfo.name ?? '')) {
return 'fonts/[name]-[hash][extname]'
}
return 'assets/[name]-[hash][extname]'
},
},
},
},
})
使用 vite-plugin-image-optimizer 自动压缩图像:
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
export default defineConfig({
plugins: [
react(),
ViteImageOptimizer({
png: { quality: 80 },
jpeg: { quality: 80 },
webp: { quality: 80 },
}),
],
})
12. 配置开发服务器代理以便开发 API
影响:中——消除 CORS 问题,简化本地开发。
Vite 单页应用通常会与独立的后端 API 通信。配置 server.proxy,在开发期间转发 API 请求,避免 CORS 问题,并匹配生产环境的 URL 模式。
// vite.config.ts
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:3001',
changeOrigin: true,
},
'/auth': {
target: 'http://localhost:3001',
changeOrigin: true,
},
// WebSocket support for real-time features
'/ws': {
target: 'ws://localhost:3001',
ws: true,
},
},
},
})
在应用代码中使用相对路径(fetch('/api/users'))——请求会发送到 Vite 开发服务器,再由其代理到后端。在生产环境中,配置反向代理(Nginx、Caddy)执行相同的路由。
13. 使用 vite-plugin-pwa 添加 PWA 支持
影响:中——为 Vite 单页应用提供离线能力、可安装性和缓存资源。
对于需要离线支持或可安装性的单页应用,vite-plugin-pwa 可处理 Service Worker 生成、预缓存和清单创建。
// vite.config.ts
import { VitePWA } from 'vite-plugin-pwa'
export default defineConfig({
plugins: [
react(),
VitePWA({
registerType: 'autoUpdate',
includeAssets: ['favicon.svg', 'robots.txt'],
manifest: {
name: 'My App',
short_name: 'App',
theme_color: '#ffffff',
icons: [
{ src: '/icon-192.png', sizes: '192x192', type: 'image/png' },
{ src: '/icon-512.png', sizes: '512x512', type: 'image/png' },
],
},
workbox: {
globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'],
runtimeCaching: [
{
urlPattern: /^https:\/\/api\./,
handler: 'NetworkFirst',
options: { cacheName: 'api-cache', expiration: { maxEntries: 50 } },
},
],
},
}),
],
})
对于需要静默更新的应用,使用 registerType: 'autoUpdate'。使用 registerType: 'prompt' 可向用户显示更新通知。
14. 选择 CSS 策略
影响:中——Vite 支持多种 CSS 方案,且无需配置。
Vite 开箱即支持 CSS Modules、PostCSS 和预处理器。根据需求选择:
CSS Modules——作用域样式、无运行时开销,内置于 Vite:
// Button.module.css → automatically scoped
import styles from './Button.module.css'
function Button({ children }: { children: React.ReactNode }) {
return <button className={styles.primary}>{children}</button>
}
Tailwind CSS——实用优先,可与 Vite 的 PostCSS 支持配合使用:
// vite.config.ts — no plugin needed, Tailwind uses PostCSS
// Just install tailwindcss and add postcss.config.js
// postcss.config.js
export default {
plugins: {
'@tailwindcss/postcss': {},
},
}
CSS-in-JS 注意事项: styled-components 和 Emotion 等库会增加运行时开销。对于优先考虑性能的 Vite 单页应用,建议使用 CSS Modules 或 Tailwind。如果需要 CSS-in-JS,可考虑 Vanilla Extract 或 Panda CSS 等零运行时方案。
15. 将 React Compiler 配置为 Vite 插件
影响:高——自动记忆化可省去手动使用 useMemo、useCallback 和 React.memo。
React Compiler 会分析组件并自动插入记忆化代码。在 Vite 项目中,将其添加为 Babel 插件:
npm install -D babel-plugin-react-compiler
// vite.config.ts
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [
react({
babel: {
plugins: ['babel-plugin-react-compiler'],
},
}),
],
})
启用后,可以逐步移除手动添加的 useMemo、useCallback 和 React.memo 调用——编译器会自动处理。启用后运行测试套件,确认行为保持不变。
编译器需要 React 19。它默认选择性启用;如果希望逐步采用,可以在每个文件中使用 'use memo' 指令启用。
来源
内容取自 patterns.dev——面向更广泛的 React Web 工程社区的 Vite 专属优化指南。