vite-bundle-optimization

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Vite Bundle Optimization

Vite 打包优化

Production-ready patterns for optimizing bundle size and build performance in Vite + React applications. These patterns leverage Vite's architecture (native ESM in dev, Rollup in production) to deliver smaller, faster bundles.
适用于Vite + React应用的生产级打包体积与构建性能优化方案。这些方案充分利用Vite的架构特性(开发环境使用原生ESM,生产环境使用Rollup),以生成体积更小、加载更快的打包文件。

When to Use

适用场景

Reference these patterns when:
  • Setting up a new Vite + React project for production
  • Analyzing bundle size with
    npx vite-bundle-visualizer
  • Build times are slow or bundles are unexpectedly large
  • Migrating from webpack/CRA to Vite
  • Optimizing Core Web Vitals (LCP, FID/INP, CLS)
在以下场景中可参考这些方案:
  • 为生产环境搭建全新的Vite + React项目
  • 使用
    npx vite-bundle-visualizer
    分析打包体积
  • 构建速度缓慢或打包文件体积异常庞大
  • 从Webpack/CRA迁移至Vite
  • 优化Core Web Vitals(LCP、FID/INP、CLS)指标

Instructions

使用说明

  • Apply these patterns during project setup, build configuration, and bundle size reviews. When you see large bundles or slow builds, diagnose with
    npx vite-bundle-visualizer
    and apply the relevant pattern.
  • 在项目初始化、构建配置及打包体积审查阶段应用这些方案。当遇到打包体积过大或构建缓慢问题时,使用
    npx vite-bundle-visualizer
    进行诊断,然后应用对应的优化方案。

Details

详细内容

Overview

概述

Vite uses esbuild for dependency pre-bundling and development transforms, and Rollup for production builds. Understanding this dual architecture is key to optimizing effectively. The patterns below are ordered by impact.

Vite在依赖预打包和开发环境转换中使用esbuild,在生产环境构建中使用Rollup。理解这一双架构特性是实现有效优化的关键。以下方案按优化影响程度排序。

1. Avoid Barrel File Imports

1. 避免桶文件导入

Impact: CRITICAL — Can add 200-800ms to startup and 2-4s to dev server boot.
Barrel files (
index.ts
that re-export from many modules) force bundlers to load the entire module graph even when you only use one export. This is the #1 bundle size issue in React apps.
Avoid — imports entire library through barrel:
tsx
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)
Prefer — direct imports:
tsx
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'
Auto-fix with
vite-plugin-barrel
:
typescript
// vite.config.ts
import barrel from 'vite-plugin-barrel'

export default defineConfig({
  plugins: [
    react(),
    barrel({
      packages: ['lucide-react', '@mui/material', '@mui/icons-material'],
    }),
  ],
})
This transforms barrel imports into direct imports at build time, giving you ergonomic syntax with direct-import performance.
Commonly affected libraries:
lucide-react
,
@mui/material
,
@mui/icons-material
,
@tabler/icons-react
,
react-icons
,
@radix-ui/react-*
,
lodash
,
date-fns
,
rxjs
.

影响程度:关键 — 可能导致启动时间增加200-800ms,开发服务器启动时间增加2-4秒。
桶文件(即从多个模块重新导出的
index.ts
)会强制打包工具加载整个模块依赖图,即便你只使用其中一个导出项。这是React应用中排名第一的打包体积问题。
不推荐 — 通过桶文件导入整个库:
tsx
import { Button, TextField } from '@/components'
// 加载桶文件中的所有组件,包括未使用的组件

import { Check, X, Menu } from 'lucide-react'
// 加载全部1500+个图标(开发环境耗时约2.8秒)
推荐 — 直接导入:
tsx
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
自动修复:
typescript
// vite.config.ts
import barrel from 'vite-plugin-barrel'

export default defineConfig({
  plugins: [
    react(),
    barrel({
      packages: ['lucide-react', '@mui/material', '@mui/icons-material'],
    }),
  ],
})
该插件会在构建阶段将桶文件导入转换为直接导入,既保留了简洁的语法,又能获得直接导入的性能优势。
常受影响的库:
lucide-react
,
@mui/material
,
@mui/icons-material
,
@tabler/icons-react
,
react-icons
,
@radix-ui/react-*
,
lodash
,
date-fns
,
rxjs

2. Configure Manual Chunk Splitting

2. 配置手动代码分割

Impact: HIGH — Better caching, parallel loading, smaller initial bundle.
Vite's default chunking puts all vendor code into one file. Split it so that frequently-changing app code doesn't invalidate the vendor cache.
typescript
// 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'],
        },
      },
    },
  },
})
For more dynamic splitting based on module paths:
typescript
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
  }
},

影响程度:高 — 提升缓存效率、实现并行加载、减小初始打包体积。
Vite默认将所有第三方依赖代码打包到一个文件中。通过手动分割,可避免频繁变更的应用代码导致第三方依赖缓存失效。
typescript
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          // React核心库 — 极少变更
          'vendor-react': ['react', 'react-dom'],
          // 路由库 — 变更频率低
          'vendor-router': ['react-router-dom'],
          // 数据层库 — 偶尔变更
          'vendor-query': ['@tanstack/react-query'],
          // UI框架 — 随设计更新而变更
          'vendor-ui': ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu'],
        },
      },
    },
  },
})
基于模块路径实现更动态的分割:
typescript
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' // 其他所有依赖
  }
},

3. Dynamic Imports for Route-Level Code Splitting

3. 路由级代码分割的动态导入

Impact: HIGH — Load only the code needed for the current page.
Use
React.lazy()
with dynamic imports to split each route into its own chunk.
tsx
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 automatically creates separate chunks for each lazy import. Name them for easier debugging:
tsx
const Dashboard = lazy(() =>
  import(/* webpackChunkName: "dashboard" */ './pages/Dashboard')
)

影响程度:高 — 仅加载当前页面所需的代码。
使用
React.lazy()
结合动态导入,将每个路由分割为独立的代码块。
tsx
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会自动为每个懒导入创建独立的代码块。可为代码块命名以便调试:
tsx
const Dashboard = lazy(() =>
  import(/* webpackChunkName: "dashboard" */ './pages/Dashboard')
)

4. Lazy-Load Heavy Components Below the Fold

4. 懒加载首屏外的重型组件

Impact: HIGH — Reduces initial bundle for faster LCP.
Components that aren't visible on initial load (modals, charts, editors, maps) should be lazy-loaded.
tsx
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>
  )
}

影响程度:高 — 减小初始打包体积,提升LCP指标。
首屏不可见的组件(如模态框、图表、编辑器、地图)应采用懒加载。
tsx
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>文章标题</h1>
      <p>立即可见的内容...</p>

      {editing && (
        <Suspense fallback={<EditorSkeleton />}>
          <RichTextEditor />
        </Suspense>
      )}

      <Suspense fallback={<ChartSkeleton />}>
        <ChartPanel />
      </Suspense>
    </article>
  )
}

5. Defer Third-Party Scripts

5. 延迟加载第三方脚本

Impact: HIGH — Analytics, tracking, and widgets shouldn't block rendering.
Load non-critical third-party scripts after the page is interactive.
Avoid — blocks initial render:
tsx
// main.tsx
import * as Sentry from '@sentry/react'
import posthog from 'posthog-js'

Sentry.init({ dsn: '...' })
posthog.init('...')
Prefer — load after hydration/mount:
tsx
// 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)
}
For external script tags, use
defer
or dynamically inject them:
typescript
function loadScript(src: string) {
  const script = document.createElement('script')
  script.src = src
  script.async = true
  document.body.appendChild(script)
}

影响程度:高 — 分析工具、追踪脚本和组件不应阻塞页面渲染。
在页面交互完成后再加载非关键的第三方脚本。
不推荐 — 阻塞初始渲染:
tsx
// main.tsx
import * as Sentry from '@sentry/react'
import posthog from 'posthog-js'

Sentry.init({ dsn: '...' })
posthog.init('...')
推荐 — 在 hydration/挂载后加载:
tsx
// main.tsx — 延迟到空闲时间加载
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
属性或动态注入:
typescript
function loadScript(src: string) {
  const script = document.createElement('script')
  script.src = src
  script.async = true
  document.body.appendChild(script)
}

6. Preload Critical Assets on User Intent

6. 根据用户意图预加载关键资源

Impact: MEDIUM — Eliminates perceived latency on navigation.
Start loading a route's code when the user signals intent (hover, focus) rather than waiting for the click.
tsx
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>
  )
}
For
<link rel="modulepreload">
in the HTML head:
html
<!-- Preload critical route chunks -->
<link rel="modulepreload" href="/assets/Home-abc123.js" />
Vite automatically adds
<link rel="modulepreload">
for entry chunks. Add manual preloads for routes you know users will visit next.

影响程度:中 — 消除导航时的感知延迟。
当用户表现出导航意图(如悬停、聚焦)时,就开始加载目标路由的代码,而非等待点击事件触发。
tsx
function NavLink({ to, children }: { to: string; children: React.ReactNode }) {
  const preload = () => {
    // Vite会为动态导入创建模块预加载
    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头部使用
<link rel="modulepreload">
html
<!-- 预加载关键路由代码块 -->
<link rel="modulepreload" href="/assets/Home-abc123.js" />
Vite会自动为入口代码块添加
<link rel="modulepreload">
。对于已知用户接下来会访问的路由,可手动添加预加载。

7. Configure Dependency Pre-Bundling

7. 配置依赖预打包

Impact: MEDIUM — Faster dev server startup and page loads.
Vite pre-bundles
node_modules
dependencies using esbuild. Configure it to handle edge cases.
typescript
// 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'],
  },
})
If you see slow page loads in dev with many small requests, it's usually because a dependency isn't pre-bundled. Add it to
include
.

影响程度:中 — 加快开发服务器启动速度和页面加载速度。
Vite使用esbuild对
node_modules
中的依赖进行预打包。可通过配置处理特殊场景。
typescript
// vite.config.ts
export default defineConfig({
  optimizeDeps: {
    // 强制预打包这些依赖(适用于CJS依赖或深层导入)
    include: [
      'react',
      'react-dom',
      'react-router-dom',
      '@tanstack/react-query',
      'date-fns/format',
      'date-fns/parseISO',
    ],
    // 跳过这些依赖的预打包(已为ESM格式,或预打包会引发问题)
    exclude: ['@vite-pwa/assets-generator'],
  },
})
如果开发环境中页面加载缓慢且存在大量小请求,通常是因为某个依赖未被预打包。将其添加到
include
列表即可。

8. Enable Compression

8. 启用压缩

Impact: MEDIUM — 60-80% smaller transfer sizes.
Vite doesn't compress by default. Add the compression plugin for production.
typescript
// vite.config.ts
import viteCompression from 'vite-plugin-compression'

export default defineConfig({
  plugins: [
    react(),
    viteCompression({ algorithm: 'gzip' }),
    viteCompression({ algorithm: 'brotliCompress' }),
  ],
})
This generates
.gz
and
.br
files alongside your assets. Configure your server (Nginx, Cloudflare, Vercel) to serve them.

影响程度:中 — 传输体积减小60-80%。
Vite默认不启用压缩。为生产环境添加压缩插件。
typescript
// 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. Analyze Your Bundle Regularly

9. 定期分析打包文件

Impact: INFORMATIONAL — Catch size regressions before they ship.
Run the bundle visualizer after every significant dependency change.
bash
npx vite-bundle-visualizer
Or add it to your build script:
json
{
  "scripts": {
    "build": "vite build",
    "analyze": "vite build && npx vite-bundle-visualizer"
  }
}
What to look for:
  • Any single chunk > 200KB gzipped — consider splitting
  • Duplicate libraries loaded in multiple chunks
  • Full library loaded when only a few functions are used
  • node_modules
    code that could be dynamically imported

影响程度:信息性 — 在问题上线前发现体积回归。
每次重大依赖变更后运行打包可视化工具。
bash
npx vite-bundle-visualizer
或将其添加到构建脚本中:
json
{
  "scripts": {
    "build": "vite build",
    "analyze": "vite build && npx vite-bundle-visualizer"
  }
}
需要关注的点:
  • 单个代码块的gzip压缩后体积超过200KB — 考虑分割
  • 多个代码块中加载了重复的库
  • 加载了完整的库,但仅使用其中少数函数
  • node_modules
    中的代码可通过动态导入优化

10. Use
import.meta.env
for Dead Code Elimination

10. 使用
import.meta.env
实现死代码消除

Impact: LOW-MEDIUM — Removes unused code paths in production.
Vite replaces
import.meta.env.*
at build time, allowing Rollup to tree-shake dead branches.
typescript
// 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()
}
Define custom env variables in
.env
files:
env
undefined
影响程度:低-中 — 在生产环境中移除未使用的代码路径。
Vite会在构建时替换
import.meta.env.*
,使Rollup能够摇树(tree-shake)掉无用代码分支。
typescript
// 这部分代码在生产环境中会被完全移除
if (import.meta.env.DEV) {
  console.log('调试信息:', data)
  window.__DEBUG_DATA__ = data
}

// 功能标记会在构建时被消除
if (import.meta.env.VITE_FEATURE_NEW_DASHBOARD === 'true') {
  // 仅当标记开启时才会包含此代码
  initNewDashboard()
}
.env
文件中定义自定义环境变量:
env
undefined

.env.production

.env.production

VITE_FEATURE_NEW_DASHBOARD=true VITE_API_URL=https://api.example.com

---
VITE_FEATURE_NEW_DASHBOARD=true VITE_API_URL=https://api.example.com

---

11. Optimize Images and Static Assets

11. 优化图片与静态资源

Impact: MEDIUM — Images are typically the largest assets.
Configure asset handling in Vite:
typescript
// 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]'
        },
      },
    },
  },
})
Use
vite-plugin-image-optimizer
for automatic image compression:
typescript
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'

export default defineConfig({
  plugins: [
    react(),
    ViteImageOptimizer({
      png: { quality: 80 },
      jpeg: { quality: 80 },
      webp: { quality: 80 },
    }),
  ],
})

影响程度:中 — 图片通常是体积最大的资源。
在Vite中配置资源处理:
typescript
// vite.config.ts
export default defineConfig({
  build: {
    assetsInlineLimit: 4096, // 将小于4KB的资源内联为base64
    rollupOptions: {
      output: {
        assetFileNames: (assetInfo) => {
          // 按类型组织资源
          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
实现自动图片压缩:
typescript
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'

export default defineConfig({
  plugins: [
    react(),
    ViteImageOptimizer({
      png: { quality: 80 },
      jpeg: { quality: 80 },
      webp: { quality: 80 },
    }),
  ],
})

12. Configure Dev Server Proxy for API Development

12. 为API开发配置开发服务器代理

Impact: MEDIUM — Eliminates CORS issues and simplifies local development.
Vite SPAs typically talk to a separate backend API. Configure
server.proxy
to forward API requests during development, avoiding CORS and matching production URL patterns.
typescript
// 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,
      },
    },
  },
})
In your app code, use relative paths (
fetch('/api/users')
) — they hit Vite's dev server which proxies to your backend. In production, configure your reverse proxy (Nginx, Caddy) to do the same routing.

影响程度:中 — 消除CORS问题,简化本地开发。
Vite单页应用通常会与独立的后端API通信。配置
server.proxy
,在开发环境中转发API请求,避免CORS问题并匹配生产环境的URL模式。
typescript
// vite.config.ts
export default defineConfig({
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:3001',
        changeOrigin: true,
      },
      '/auth': {
        target: 'http://localhost:3001',
        changeOrigin: true,
      },
      // 为实时功能提供WebSocket支持
      '/ws': {
        target: 'ws://localhost:3001',
        ws: true,
      },
    },
  },
})
在应用代码中使用相对路径(如
fetch('/api/users')
)— 请求会先发送到Vite开发服务器,再由其代理到后端。在生产环境中,需配置反向代理(Nginx、Caddy)以实现相同的路由。

13. Add PWA Support with
vite-plugin-pwa

13. 使用
vite-plugin-pwa
添加PWA支持

Impact: MEDIUM — Offline capability, installability, and cached assets for Vite SPAs.
For SPAs that need offline support or installability,
vite-plugin-pwa
handles service worker generation, precaching, and manifest creation.
typescript
// 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 } },
          },
        ],
      },
    }),
  ],
})
Use
registerType: 'autoUpdate'
for apps that should silently update. Use
registerType: 'prompt'
to show users an update notification.

影响程度:中 — 为Vite单页应用提供离线能力、可安装性和资源缓存功能。
对于需要离线支持或可安装性的单页应用,
vite-plugin-pwa
可处理服务工作线程生成、预缓存和清单文件创建。
typescript
// vite.config.ts
import { VitePWA } from 'vite-plugin-pwa'

export default defineConfig({
  plugins: [
    react(),
    VitePWA({
      registerType: 'autoUpdate',
      includeAssets: ['favicon.svg', 'robots.txt'],
      manifest: {
        name: '我的应用',
        short_name: '应用',
        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. Choose a CSS Strategy

14. 选择合适的CSS策略

Impact: MEDIUM — Vite supports multiple CSS approaches with zero config.
Vite handles CSS Modules, PostCSS, and preprocessors out of the box. Choose based on your needs:
CSS Modules — scoped styles, no runtime cost, built into Vite:
tsx
// 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 — utility-first, works with Vite's PostCSS support:
typescript
// 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 considerations: Libraries like styled-components and Emotion add runtime overhead. For Vite SPAs prioritizing performance, prefer CSS Modules or Tailwind. If you need CSS-in-JS, consider zero-runtime options like Vanilla Extract or Panda CSS.

影响程度:中 — Vite原生支持多种CSS方案,无需额外配置。
Vite原生支持CSS Modules、PostCSS和预处理器。可根据需求选择:
CSS Modules — 样式作用域隔离,无运行时开销,Vite原生支持:
tsx
// Button.module.css → 自动添加作用域
import styles from './Button.module.css'

function Button({ children }: { children: React.ReactNode }) {
  return <button className={styles.primary}>{children}</button>
}
Tailwind CSS — 实用优先的CSS框架,与Vite的PostCSS支持兼容:
typescript
// vite.config.ts — 无需额外插件,Tailwind使用PostCSS
// 只需安装tailwindcss并添加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. Set Up the React Compiler as a Vite Plugin

15. 将React Compiler设置为Vite插件

Impact: HIGH — Automatic memoization eliminates manual
useMemo
,
useCallback
, and
React.memo
.
The React Compiler analyzes your components and auto-inserts memoization. In a Vite project, add it as a Babel plugin:
bash
npm install -D babel-plugin-react-compiler
typescript
// vite.config.ts
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [
    react({
      babel: {
        plugins: ['babel-plugin-react-compiler'],
      },
    }),
  ],
})
Once enabled, you can gradually remove manual
useMemo
,
useCallback
, and
React.memo
calls — the compiler handles them automatically. Verify behavior is preserved by running your test suite after enabling.
The compiler requires React 19. It's opt-in and can be enabled per-file with a
'use memo'
directive if you prefer incremental adoption.

影响程度:高 — 自动记忆化消除手动
useMemo
useCallback
React.memo
的需求。
React Compiler会分析组件并自动插入记忆化逻辑。在Vite项目中,可将其作为Babel插件添加:
bash
npm install -D babel-plugin-react-compiler
typescript
// 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'
指令为单个文件启用。

Source

来源

Patterns from patterns.dev — Vite-specific optimization guidance for the broader React web engineering community.
这些方案来自patterns.dev — 面向广大React前端开发者的Vite专属优化指南。