vite-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Vite Patterns

Vite 模式

Build tool and dev server patterns for Vite 8+ projects. Covers configuration, environment variables, proxy setup, library mode, dependency pre-bundling, and common production pitfalls.
适用于Vite 8+项目的构建工具与开发服务器模式,涵盖配置、环境变量、代理设置、库模式、依赖预打包以及常见生产环境陷阱。

When to Use

适用场景

  • Configuring
    vite.config.ts
    or
    vite.config.js
  • Setting up environment variables or
    .env
    files
  • Configuring dev server proxy for API backends
  • Optimizing build output (chunks, minification, assets)
  • Publishing libraries with
    build.lib
  • Troubleshooting dependency pre-bundling or CJS/ESM interop
  • Debugging HMR, dev server, or build errors
  • Choosing or ordering Vite plugins
  • 配置
    vite.config.ts
    vite.config.js
  • 设置环境变量或
    .env
    文件
  • 为API后端配置开发服务器代理
  • 优化构建输出(代码分割、压缩、资源处理)
  • 通过
    build.lib
    发布库
  • 排查依赖预打包或CJS/ESM互操作性问题
  • 调试HMR、开发服务器或构建错误
  • 选择或排序Vite插件

How It Works

工作原理

  • Dev mode serves source files as native ESM — no bundling. Transforms happen on-demand per module request, which is why cold starts are fast and HMR is precise.
  • Build mode uses Rolldown (v7+) or Rollup (v5–v6) to bundle the app for production with tree-shaking, code-splitting, and Oxc-based minification.
  • Dependency pre-bundling converts CJS/UMD deps to ESM once via esbuild and caches the result under
    node_modules/.vite
    , so subsequent starts skip the work.
  • Plugins share a unified interface across dev and build — the same plugin object works for both the dev server's on-demand transforms and the production pipeline.
  • Environment variables are statically inlined at build time.
    VITE_
    -prefixed vars become public constants in the bundle; everything unprefixed is invisible to client code.
  • 开发模式 以原生ESM形式提供源文件——无需打包。转换操作按需针对每个模块请求进行,这也是冷启动速度快、HMR精准的原因。
  • 构建模式 使用Rolldown(v7+)或Rollup(v5–v6)为生产环境打包应用,支持摇树优化、代码分割和基于Oxc的压缩。
  • 依赖预打包 通过esbuild将CJS/UMD依赖转换为ESM格式,并将结果缓存到
    node_modules/.vite
    下,后续启动时可跳过该步骤。
  • 插件 在开发和构建阶段共享统一接口——同一个插件对象可用于开发服务器的按需转换和生产流水线。
  • 环境变量 在构建时静态内联。前缀为
    VITE_
    的变量会成为包中的公共常量;无前缀的变量对客户端代码不可见。

Examples

示例

Config Structure

配置结构

Basic Config

基础配置

typescript
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: { '@': new URL('./src', import.meta.url).pathname },
  },
})
typescript
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: { '@': new URL('./src', import.meta.url).pathname },
  },
})

Conditional Config

条件配置

typescript
// vite.config.ts
import { defineConfig, loadEnv } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig(({ command, mode }) => {
  const env = loadEnv(mode, process.cwd())   // VITE_ prefixed only (safe)

  return {
    plugins: [react()],
    server: command === 'serve' ? { port: 3000 } : undefined,
    define: {
      __API_URL__: JSON.stringify(env.VITE_API_URL),
    },
  }
})
typescript
// vite.config.ts
import { defineConfig, loadEnv } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig(({ command, mode }) => {
  const env = loadEnv(mode, process.cwd())   // 仅加载VITE_前缀的变量(安全)

  return {
    plugins: [react()],
    server: command === 'serve' ? { port: 3000 } : undefined,
    define: {
      __API_URL__: JSON.stringify(env.VITE_API_URL),
    },
  }
})

Key Config Options

核心配置选项

KeyDefaultDescription
root
'.'
Project root (where
index.html
lives)
base
'/'
Public base path for deployed assets
envPrefix
'VITE_'
Prefix for client-exposed env vars
build.outDir
'dist'
Output directory
build.minify
'oxc'
Minifier (
'oxc'
,
'terser'
, or
false
)
build.sourcemap
false
true
,
'inline'
, or
'hidden'
键名默认值描述
root
'.'
项目根目录(
index.html
所在位置)
base
'/'
部署资源的公共基础路径
envPrefix
'VITE_'
客户端可访问环境变量的前缀
build.outDir
'dist'
输出目录
build.minify
'oxc'
压缩工具(
'oxc'
'terser'
false
build.sourcemap
false
可选值
true
'inline'
'hidden'

Plugins

插件

Essential Plugins

必备插件

Most plugin needs are covered by a handful of well-maintained packages. Reach for these before writing your own.
PluginPurposeWhen to use
@vitejs/plugin-react-swc
React HMR + Fast Refresh via SWCDefault for React apps (faster than Babel variant)
@vitejs/plugin-react
React HMR + Fast Refresh via BabelOnly if you need Babel plugins (emotion, MobX decorators)
@vitejs/plugin-vue
Vue 3 SFC supportVue apps
vite-plugin-checker
Runs
tsc
+ ESLint in worker thread with HMR overlay
Any TypeScript app — Vite does NOT type-check during
vite build
vite-tsconfig-paths
Honors
tsconfig.json
paths
aliases
Any time you already have aliases in
tsconfig.json
vite-plugin-dts
Emits
.d.ts
files in library mode
Publishing TypeScript libraries
vite-plugin-svgr
Imports SVGs as React componentsReact apps using SVGs as components
rollup-plugin-visualizer
Bundle treemap/sunburst reportPeriodic bundle size audits (use
enforce: 'post'
)
vite-plugin-pwa
Zero-config PWA + WorkboxOffline-capable apps
Critical callout:
vite build
transpiles but does NOT type-check. Type errors silently ship to production unless you add
vite-plugin-checker
or run
tsc --noEmit
in CI.
大多数插件需求都可通过少数维护良好的包满足,优先使用这些插件而非自行编写。
插件用途适用场景
@vitejs/plugin-react-swc
通过SWC实现React HMR + Fast RefreshReact应用默认选择(比Babel版本更快)
@vitejs/plugin-react
通过Babel实现React HMR + Fast Refresh仅当需要Babel插件(如emotion、MobX装饰器)时使用
@vitejs/plugin-vue
Vue 3单文件组件支持Vue应用
vite-plugin-checker
在工作线程中运行
tsc
+ ESLint,并通过HMR覆盖层展示结果
所有TypeScript应用——Vite在
vite build
过程中不进行类型检查
vite-tsconfig-paths
遵循
tsconfig.json
中的
paths
别名
当你已在
tsconfig.json
中配置别名时使用
vite-plugin-dts
在库模式下生成
.d.ts
文件
发布TypeScript库
vite-plugin-svgr
将SVG作为React组件导入使用SVG作为组件的React应用
rollup-plugin-visualizer
生成包的树形图/旭日图报告定期进行包大小审计(需设置
enforce: 'post'
vite-plugin-pwa
零配置PWA + Workbox支持离线访问的应用
重要提示:
vite build
仅进行转译,不做类型检查。除非添加
vite-plugin-checker
或在CI中运行
tsc --noEmit
,否则类型错误会被静默地带到生产环境。

Authoring Custom Plugins

自定义插件开发

Authoring is rare — most needs are covered by existing plugins. When you do need one, start inline in
vite.config.ts
and only extract if reused.
typescript
// vite.config.ts — minimal inline plugin
function myPlugin(): Plugin {
  return {
    name: 'my-plugin',                       // required, must be unique
    enforce: 'pre',                           // 'pre' | 'post' (optional)
    apply: 'build',                           // 'build' | 'serve' (optional)
    transform(code, id) {
      if (!id.endsWith('.custom')) return
      return { code: transformCustom(code), map: null }
    },
  }
}
Key hooks:
transform
(modify source),
resolveId
+
load
(virtual modules),
transformIndexHtml
(inject into HTML),
configureServer
(add dev middleware),
hotUpdate
(custom HMR — replaces deprecated
handleHotUpdate
in v7+).
Virtual modules use the
\0
prefix convention —
resolveId
returns
'\0virtual:my-id'
so other plugins skip it. User code imports
'virtual:my-id'
.
For full plugin API, see vite.dev/guide/api-plugin. Use
vite-plugin-inspect
during development to debug the transform pipeline.
自定义插件开发并不常见——大多数需求已有现成插件覆盖。当确实需要时,先在
vite.config.ts
中内联编写,仅在需要复用再提取为独立插件。
typescript
// vite.config.ts — 极简内联插件
function myPlugin(): Plugin {
  return {
    name: 'my-plugin',                       // 必填,必须唯一
    enforce: 'pre',                           // 可选值 'pre' | 'post'
    apply: 'build',                           // 可选值 'build' | 'serve'
    transform(code, id) {
      if (!id.endsWith('.custom')) return
      return { code: transformCustom(code), map: null }
    },
  }
}
核心钩子:
transform
(修改源码)、
resolveId
+
load
(虚拟模块)、
transformIndexHtml
(注入HTML)、
configureServer
(添加开发中间件)、
hotUpdate
(自定义HMR——替代v7+中已弃用的
handleHotUpdate
)。
虚拟模块 使用
\0
前缀约定——
resolveId
返回
'\0virtual:my-id'
,以便其他插件跳过处理。用户代码通过
'virtual:my-id'
导入。
完整插件API请参考 vite.dev/guide/api-plugin。开发期间可使用
vite-plugin-inspect
调试转换流程。

HMR API

HMR API

Framework plugins (
@vitejs/plugin-react
,
@vitejs/plugin-vue
, etc.) handle HMR automatically. Reach for
import.meta.hot
directly only when building custom state stores, dev tools, or framework-agnostic utilities that need to persist state across updates.
typescript
// src/store.ts — manual HMR for a vanilla module
if (import.meta.hot) {
  // Persist state across updates (must MUTATE, never reassign .data)
  import.meta.hot.data.count = import.meta.hot.data.count ?? 0

  // Cleanup side effects before module is replaced
  import.meta.hot.dispose((data) => clearInterval(data.intervalId))

  // Accept this module's own updates
  import.meta.hot.accept()
}
All
import.meta.hot
code is tree-shaken out of production builds — no guard removal needed.
框架插件(
@vitejs/plugin-react
@vitejs/plugin-vue
等)会自动处理HMR。仅在构建自定义状态存储、开发工具或跨框架工具(需要在更新时保留状态)时,才直接使用
import.meta.hot
typescript
// src/store.ts — 原生模块的手动HMR配置
if (import.meta.hot) {
  // 在更新时保留状态(必须修改属性,不可重新赋值.data)
  import.meta.hot.data.count = import.meta.hot.data.count ?? 0

  // 在模块被替换前清理副作用
  import.meta.hot.dispose((data) => clearInterval(data.intervalId))

  // 接受当前模块的更新
  import.meta.hot.accept()
}
所有
import.meta.hot
代码会在生产构建中被摇树移除——无需额外移除守卫代码。

Environment Variables

环境变量

Vite loads
.env
,
.env.local
,
.env.[mode]
, and
.env.[mode].local
in that order (later overrides earlier);
*.local
files are gitignored and meant for local secrets.
Vite按以下顺序加载
.env
.env.local
.env.[mode]
.env.[mode].local
(后续文件覆盖前面的);
*.local
文件会被git忽略,用于存储本地密钥。

Client-Side Access

客户端访问

Only
VITE_
-prefixed vars are exposed to client code:
typescript
import.meta.env.VITE_API_URL   // string
import.meta.env.MODE            // 'development' | 'production' | custom
import.meta.env.BASE_URL        // base config value
import.meta.env.DEV             // boolean
import.meta.env.PROD            // boolean
import.meta.env.SSR             // boolean
只有前缀为
VITE_
的变量可被客户端代码访问:
typescript
import.meta.env.VITE_API_URL   // 字符串类型
import.meta.env.MODE            // 可选值 'development' | 'production' | 自定义模式
import.meta.env.BASE_URL        // base配置值
import.meta.env.DEV             // 布尔类型
import.meta.env.PROD            // 布尔类型
import.meta.env.SSR             // 布尔类型

Using Env in Config

在配置中使用环境变量

typescript
// vite.config.ts
import { defineConfig, loadEnv } from 'vite'

export default defineConfig(({ mode }) => {
  const env = loadEnv(mode, process.cwd())          // VITE_ prefixed only (safe)
  return {
    define: {
      __API_URL__: JSON.stringify(env.VITE_API_URL),
    },
  }
})
typescript
// vite.config.ts
import { defineConfig, loadEnv } from 'vite'

export default defineConfig(({ mode }) => {
  const env = loadEnv(mode, process.cwd())          // 仅加载VITE_前缀的变量(安全)
  return {
    define: {
      __API_URL__: JSON.stringify(env.VITE_API_URL),
    },
  }
})

Security

安全注意事项

VITE_
Prefix is NOT a Security Boundary

VITE_
前缀并非安全边界

Any variable prefixed with
VITE_
is statically inlined into the client bundle at build time. Minification, base64 encoding, and disabling source maps do NOT hide it. A determined attacker can extract any
VITE_
var from the shipped JavaScript.
Rule: Only public values (API URLs, feature flags, public keys) go in
VITE_
vars. Secrets (API tokens, database URLs, private keys) MUST live server-side behind an API or serverless function.
任何前缀为
VITE_
的变量都会在构建时静态内联到客户端包中。压缩、Base64编码和禁用Source Map都无法隐藏这些变量。有决心的攻击者可以从发布的JavaScript中提取任何
VITE_
变量。
规则: 只有公共值(API地址、功能开关、公钥)可放入
VITE_
变量。密钥(API令牌、数据库地址、私钥)必须放在服务器端,通过API或无服务器函数提供。

The
loadEnv('')
Trap

loadEnv('')
陷阱

typescript
// BAD: passing '' as the third arg loads ALL env vars — including server secrets —
// and makes them available to inline into client code via `define`.
const env = loadEnv(mode, process.cwd(), '')

// GOOD: explicit prefix list
const env = loadEnv(mode, process.cwd(), ['VITE_', 'APP_'])
typescript
// 错误:传入''作为第三个参数会加载所有环境变量——包括服务器密钥——
// 并可通过`define`内联到客户端代码中。
const env = loadEnv(mode, process.cwd(), '')

// 正确:明确指定前缀列表
const env = loadEnv(mode, process.cwd(), ['VITE_', 'APP_'])

Source Maps in Production

生产环境中的Source Map

Production source maps leak your original source code. Disable them unless you upload to an error tracker (Sentry, Bugsnag) and delete locally afterward:
typescript
build: {
  sourcemap: false,                                  // default — keep it this way
}
生产环境的Source Map会泄露原始源码。除非要上传到错误追踪工具(如Sentry、Bugsnag)并在之后本地删除,否则应禁用:
typescript
build: {
  sourcemap: false,                                  // 默认值——保持此设置
}

.gitignore
Checklist

.gitignore
检查清单

  • .env.local
    ,
    .env.*.local
    — local secret overrides
  • dist/
    — build output
  • node_modules/.vite
    — pre-bundle cache (stale entries cause phantom errors)
  • .env.local
    ,
    .env.*.local
    — 本地密钥覆盖文件
  • dist/
    — 构建输出
  • node_modules/.vite
    — 预打包缓存(过期条目会导致莫名错误)

Server Proxy

服务器代理

typescript
// vite.config.ts — server.proxy
server: {
  proxy: {
    '/foo': 'http://localhost:4567',                    // string shorthand

    '/api': {
      target: 'http://localhost:8080',
      changeOrigin: true,                               // needed for virtual-hosted backends
      rewrite: (path) => path.replace(/^\/api/, ''),
    },
  },
}
For WebSocket proxying, add
ws: true
to the route config.
typescript
// vite.config.ts — server.proxy
server: {
  proxy: {
    '/foo': 'http://localhost:4567',                    // 字符串简写形式

    '/api': {
      target: 'http://localhost:8080',
      changeOrigin: true,                               // 虚拟主机后端需要此设置
      rewrite: (path) => path.replace(/^\/api/, ''),
    },
  },
}
如需代理WebSocket,在路由配置中添加
ws: true

Build Optimization

构建优化

Manual Chunks

手动代码分割

typescript
// vite.config.ts — build.rolldownOptions
build: {
  rolldownOptions: {
    output: {
      // Object form: group specific packages
      manualChunks: {
        'react-vendor': ['react', 'react-dom'],
        'ui-vendor': ['@radix-ui/react-dialog', '@radix-ui/react-popover'],
      },
    },
  },
}
typescript
// Function form: split by heuristic
manualChunks(id) {
  if (id.includes('node_modules/react')) return 'react-vendor'
  if (id.includes('node_modules')) return 'vendor'
}
typescript
// vite.config.ts — build.rolldownOptions
build: {
  rolldownOptions: {
    output: {
      // 对象形式:分组特定包
      manualChunks: {
        'react-vendor': ['react', 'react-dom'],
        'ui-vendor': ['@radix-ui/react-dialog', '@radix-ui/react-popover'],
      },
    },
  },
}
typescript
// 函数形式:按规则分割
manualChunks(id) {
  if (id.includes('node_modules/react')) return 'react-vendor'
  if (id.includes('node_modules')) return 'vendor'
}

Performance

性能优化

Avoid Barrel Files

避免桶文件

Barrel files (
index.ts
re-exporting everything from a directory) force Vite to load every re-exported file even when you import a single symbol. This is the #1 dev-server slowdown flagged by the official docs.
typescript
// BAD — importing one util forces Vite to load the whole barrel
import { slash } from '@/utils'

// GOOD — direct import, only the one file is loaded
import { slash } from '@/utils/slash'
桶文件(
index.ts
重新导出目录下所有内容)会迫使Vite加载所有被导出的文件,即使你只导入单个符号。这是官方文档指出的开发服务器变慢的头号原因。
typescript
// 错误——导入一个工具会迫使Vite加载整个桶文件
import { slash } from '@/utils'

// 正确——直接导入,仅加载单个文件
import { slash } from '@/utils/slash'

Be Explicit with Import Extensions

明确导入扩展名

Each implicit extension forces up to 6 filesystem checks via
resolve.extensions
. In large codebases, this adds up.
typescript
// BAD
import Component from './Component'

// GOOD
import Component from './Component.tsx'
Narrow
tsconfig.json
allowImportingTsExtensions
+
resolve.extensions
to only the extensions you actually use.
每个隐式扩展名会触发最多6次文件系统检查(通过
resolve.extensions
)。在大型代码库中,这会累积成显著的开销。
typescript
// 错误
import Component from './Component'

// 正确
import Component from './Component.tsx'
tsconfig.json
中缩小
allowImportingTsExtensions
+
resolve.extensions
的范围,仅保留实际使用的扩展名。

Warm-Up Hot-Path Routes

预热热点路由

server.warmup.clientFiles
pre-transforms known hot entries before the browser requests them — eliminating the cold-load request waterfall on large apps.
typescript
// vite.config.ts
server: {
  warmup: {
    clientFiles: ['./src/main.tsx', './src/routes/**/*.tsx'],
  },
}
server.warmup.clientFiles
会在浏览器请求前预先转换已知的热点条目——消除大型应用的冷加载请求瀑布。
typescript
// vite.config.ts
server: {
  warmup: {
    clientFiles: ['./src/main.tsx', './src/routes/**/*.tsx'],
  },
}

Profiling Slow Dev Servers

排查缓慢的开发服务器

When
vite dev
feels slow, start with
vite --profile
, interact with the app, then press
p+enter
to save a
.cpuprofile
. Load it in Speedscope to find which plugins are eating time — usually
buildStart
,
config
, or
configResolved
hooks in community plugins.
vite dev
运行缓慢时,先执行
vite --profile
,与应用交互,然后按
p+enter
保存
.cpuprofile
文件。在 Speedscope 中加载该文件,找出耗时的插件——通常是社区插件中的
buildStart
config
configResolved
钩子。

Library Mode

库模式

When publishing an npm package, use
build.lib
. Two footguns matter more than config detail:
  1. Types are not emitted — add
    vite-plugin-dts
    or run
    tsc --emitDeclarationOnly
    separately.
  2. Peer dependencies MUST be externalized — unlisted peers get bundled into your library, causing duplicate-runtime errors in consumers.
typescript
// vite.config.ts
build: {
  lib: {
    entry: 'src/index.ts',
    formats: ['es', 'cjs'],
    fileName: (format) => `my-lib.${format}.js`,
  },
  rolldownOptions: {
    external: ['react', 'react-dom', 'react/jsx-runtime'],  // every peer dep
  },
}
发布npm包时,使用
build.lib
。有两个陷阱比配置细节更重要:
  1. 不会生成类型文件——需添加
    vite-plugin-dts
    或单独运行
    tsc --emitDeclarationOnly
  2. 必须外部化peer依赖——未列出的peer依赖会被打包到库中,导致消费者出现重复运行时错误。
typescript
// vite.config.ts
build: {
  lib: {
    entry: 'src/index.ts',
    formats: ['es', 'cjs'],
    fileName: (format) => `my-lib.${format}.js`,
  },
  rolldownOptions: {
    external: ['react', 'react-dom', 'react/jsx-runtime'],  // 所有peer依赖
  },
}

SSR Externals

SSR外部依赖

Bare
createServer({ middlewareMode: true })
setups are framework-author territory. Most apps should use Nuxt, Remix, SvelteKit, Astro, or TanStack Start instead. What you will tweak as a framework user is the externals config when deps break in SSR:
typescript
// vite.config.ts — ssr options
ssr: {
  external: ['node-native-package'],           // keep as require() in SSR bundle
  noExternal: ['esm-only-package'],            // force-bundle into SSR output (fixes most SSR errors)
  target: 'node',                              // 'node' or 'webworker'
}
createServer({ middlewareMode: true })
设置属于框架开发者的范畴。大多数应用应使用Nuxt、Remix、SvelteKit、Astro或TanStack Start。作为框架用户,你需要调整的是当依赖在SSR中出现问题时的外部依赖配置:
typescript
// vite.config.ts — ssr选项
ssr: {
  external: ['node-native-package'],           // 在SSR包中保留为require()形式
  noExternal: ['esm-only-package'],            // 强制打包到SSR输出中(修复大多数SSR错误)
  target: 'node',                              // 可选值 'node' 或 'webworker'
}

Dependency Pre-Bundling

依赖预打包

Vite pre-bundles dependencies to convert CJS/UMD to ESM and reduce request count.
typescript
// vite.config.ts — optimizeDeps
optimizeDeps: {
  include: [
    'lodash-es',                              // force pre-bundle known heavy deps
    'cjs-package',                            // CJS deps that cause interop issues
    'deep-lib/components/**',                 // glob for deep imports
  ],
  exclude: ['local-esm-package'],             // must be valid ESM if excluded
  force: true,                                // ignore cache, re-optimize (temporary debugging)
}
Vite预打包依赖以将CJS/UMD转换为ESM并减少请求数量。
typescript
// vite.config.ts — optimizeDeps
optimizeDeps: {
  include: [
    'lodash-es',                              // 强制预打包已知的重型依赖
    'cjs-package',                            // 存在互操作性问题的CJS依赖
    'deep-lib/components/**',                 // 深层导入的glob匹配
  ],
  exclude: ['local-esm-package'],             // 排除的依赖必须是有效的ESM
  force: true,                                // 忽略缓存,重新优化(临时调试用)
}

Common Pitfalls

常见陷阱

Dev Does Not Match Build

开发与构建结果不一致

Dev uses esbuild/Rolldown for transforms; build uses Rolldown for bundling. CJS libraries can behave differently between the two. Always verify with
vite build && vite preview
before deploying.
开发模式使用esbuild/Rolldown进行转换;构建模式使用Rolldown进行打包。CJS库在两者中的表现可能不同。部署前务必用
vite build && vite preview
验证。

Stale Chunks After Deployment

部署后出现陈旧代码块

New builds produce new chunk hashes. Users with active sessions request old filenames that no longer exist. Vite has no built-in solution. Mitigations:
  • Keep old
    dist/assets/
    files live for a deployment window
  • Catch dynamic import errors in your router and force a page reload
新构建会生成新的代码块哈希值。有活跃会话的用户会请求已不存在的旧文件名。Vite没有内置解决方案,可通过以下方式缓解:
  • 在部署窗口期保留旧的
    dist/assets/
    文件
  • 在路由中捕获动态导入错误并强制页面刷新

Docker and Containers

Docker与容器

Vite binds to
localhost
by default, which is unreachable from outside a container:
typescript
// vite.config.ts — Docker/container setup
server: {
  host: true,                                  // bind 0.0.0.0
  hmr: { clientPort: 3000 },                   // if behind a reverse proxy
}
Vite默认绑定到
localhost
,容器外部无法访问:
typescript
// vite.config.ts — Docker/容器设置
server: {
  host: true,                                  // 绑定到0.0.0.0
  hmr: { clientPort: 3000 },                   // 若在反向代理后
}

Monorepo File Access

单仓库文件访问

Vite restricts file serving to the project root. Packages outside root are blocked:
typescript
// vite.config.ts — monorepo file access
server: {
  fs: {
    allow: ['..'],                             // allow parent directory (workspace root)
  },
}
Vite限制文件服务到项目根目录,根目录外的包会被阻止:
typescript
// vite.config.ts — 单仓库文件访问
server: {
  fs: {
    allow: ['..'],                             // 允许访问父目录(工作区根目录)
  },
}

Anti-Patterns

反模式

typescript
// BAD: Setting envPrefix to '' exposes ALL env vars (including secrets) to the client
envPrefix: ''

// BAD: Assuming require() works in application source code — Vite is ESM-first
const lib = require('some-lib')                // use import instead

// BAD: Splitting every node_module into its own chunk — creates hundreds of tiny files
manualChunks(id) {
  if (id.includes('node_modules')) {
    return id.split('node_modules/')[1].split('/')[0]   // one chunk per package
  }
}

// BAD: Not externalizing peer deps in library mode — causes duplicate runtime errors
// build.lib without rolldownOptions.external

// BAD: Using deprecated esbuild minifier
build: { minify: 'esbuild' }                  // use 'oxc' (default) or 'terser'

// BAD: Mutating import.meta.hot.data by reassignment
import.meta.hot.data = { count: 0 }           // WRONG: must mutate properties, not reassign
import.meta.hot.data.count = 0                 // CORRECT
Process anti-patterns:
  • vite preview
    is NOT a production server
    — it is a smoke test for the built bundle. Deploy
    dist/
    to a real static host (NGINX, Cloudflare Pages, Vercel static) or use a multi-stage Dockerfile.
  • Expecting
    vite build
    to type-check
    — it only transpiles. Type errors silently ship to production. Add
    vite-plugin-checker
    or run
    tsc --noEmit
    in CI.
  • Shipping
    @vitejs/plugin-legacy
    by default
    — it bloats bundles ~40%, breaks source-map bundle analyzers, and is unnecessary for the 95%+ of users on modern browsers. Gate it on real analytics, not assumption.
  • Hand-rolling 30+
    resolve.alias
    entries that duplicate
    tsconfig.json
    paths
    — use
    vite-tsconfig-paths
    instead. Observed in Excalidraw and PostHog; avoid in new projects.
  • Leaving stale
    node_modules/.vite
    after dep changes
    — pre-bundle cache causes phantom errors. Clear it when switching branches or after patching deps.
typescript
// 错误:将envPrefix设置为''会暴露所有环境变量(包括密钥)给客户端
envPrefix: ''

// 错误:假设require()可在应用源码中工作——Vite优先支持ESM
const lib = require('some-lib')                // 使用import替代

// 错误:将每个node_modules包拆分为单独的代码块——会生成数百个小文件
manualChunks(id) {
  if (id.includes('node_modules')) {
    return id.split('node_modules/')[1].split('/')[0]   // 每个包一个代码块
  }
}

// 错误:在库模式下未外部化peer依赖——会导致重复运行时错误
// build.lib未配置rolldownOptions.external

// 错误:使用已弃用的esbuild压缩工具
build: { minify: 'esbuild' }                  // 使用'oxc'(默认)或'terser'

// 错误:通过重新赋值修改import.meta.hot.data
import.meta.hot.data = { count: 0 }           // 错误:必须修改属性,不可重新赋值
import.meta.hot.data.count = 0                 // 正确
流程反模式:
  • vite preview
    不是生产服务器
    ——它只是构建包的冒烟测试工具。将
    dist/
    部署到真正的静态主机(NGINX、Cloudflare Pages、Vercel静态托管)或使用多阶段Dockerfile。
  • 期望
    vite build
    进行类型检查
    ——它仅进行转译。类型错误会被静默地带到生产环境。添加
    vite-plugin-checker
    或在CI中运行
    tsc --noEmit
  • 默认启用
    @vitejs/plugin-legacy
    ——它会使包体积膨胀约40%,破坏Source Map包分析工具,且对于95%以上使用现代浏览器的用户来说不必要。根据实际分析数据启用,而非主观假设。
  • 手动编写30+个
    resolve.alias
    条目来复制
    tsconfig.json
    paths
    ——使用
    vite-tsconfig-paths
    替代。在Excalidraw和PostHog中曾出现此类情况,新项目应避免。
  • 依赖变更后保留陈旧的
    node_modules/.vite
    ——预打包缓存会导致莫名错误。切换分支或更新依赖后清理该目录。

Quick Reference

快速参考

PatternWhen to Use
defineConfig
Always — provides type inference
loadEnv(mode, root, ['VITE_'])
Access env vars in config (explicit prefix)
vite-plugin-checker
Any TypeScript app (fills the type-check gap)
vite-tsconfig-paths
Instead of hand-rolled
resolve.alias
optimizeDeps.include
CJS deps causing interop issues
server.proxy
Route API requests to backend in dev
server.host: true
Docker, containers, remote access
server.warmup.clientFiles
Pre-transform hot-path routes
build.lib
+
external
Publishing npm packages
manualChunks
(object)
Vendor bundle splitting
vite --profile
Debug slow dev server
vite build && vite preview
Smoke-test prod bundle locally (NOT a prod server)
模式适用场景
defineConfig
始终使用——提供类型推断
loadEnv(mode, root, ['VITE_'])
在配置中访问环境变量(明确前缀)
vite-plugin-checker
所有TypeScript应用(填补类型检查空白)
vite-tsconfig-paths
替代手动编写的
resolve.alias
optimizeDeps.include
存在互操作性问题的CJS依赖
server.proxy
开发环境中将API请求路由到后端
server.host: true
Docker、容器、远程访问场景
server.warmup.clientFiles
预热热点路由
build.lib
+
external
发布npm包
manualChunks
(对象形式)
依赖包代码分割
vite --profile
调试缓慢的开发服务器
vite build && vite preview
本地冒烟测试生产包(不是生产服务器)

Related Skills

相关技能

  • frontend-patterns
    — React component patterns
  • docker-patterns
    — containerized dev with Vite
  • nextjs-turbopack
    — alternative bundler for Next.js
  • frontend-patterns
    — React组件模式
  • docker-patterns
    — Vite容器化开发
  • nextjs-turbopack
    — Next.js的替代打包工具