vite-patterns

vite-patterns

热门

Vite 构建工具实践指南,涵盖配置文件、插件开发、热更新 (HMR)、环境变量、代理设置、SSR、库模式 (Library Mode)、依赖预构建以及构建打包优化。当处理 vite.config.ts、Vite 插件或基于 Vite 开发的项目时激活此 Skill。

24万Star
3.6万Fork
更新于 2026/7/29
SKILL.md
只读
名称
vite-patterns
描述

Vite 构建工具实践指南,涵盖配置文件、插件开发、热更新 (HMR)、环境变量、代理设置、SSR、库模式 (Library Mode)、依赖预构建以及构建打包优化。当处理 vite.config.ts、Vite 插件或基于 Vite 开发的项目时激活此 Skill。

Vite 实践指南 (Vite Patterns)

适用于 Vite 8+ 项目的构建工具与开发服务器最佳实践指南。内容涵盖配置项解析、环境变量管理、开发代理设置、库模式打包、依赖预构建以及常见的生产环境踩坑避坑指南。

适用场景

  • 配置 vite.config.tsvite.config.js
  • 设置环境变量或 .env 文件时
  • 配置开发服务器代理(Dev Server Proxy)对接 API 后端时
  • 优化生产打包产物(分包 Chunk、代码压缩 Minification、静态资源 Asset 处理)时
  • 使用 build.lib 发布 npm 库时
  • 排查依赖预构建或 CJS/ESM 模块兼容性问题时
  • 调试热更新 (HMR)、开发服务器或构建报错时
  • 选择或配置 Vite 插件的加载与执行顺序时

工作原理

  • 开发模式 (Dev mode):直接将源码作为原生 ESM 提供服务——完全无需打包(Bundling)。模块转换按需随请求触发,这也是冷启动极快且 HMR 热更新精准的根本原因。
  • 构建模式 (Build mode):在 v7+ 版本中使用 Rolldown(v5–v6 版本使用 Rollup)进行生产环境打包,内置 Tree-shaking、代码分割(Code-splitting)以及基于 Oxc 的高效产物压缩。
  • 依赖预构建 (Dependency pre-bundling):通过 esbuild 首次运行将 CommonJS/UMD 依赖一次性转换为 ESM 格式,并将结果缓存到 node_modules/.vite,后续启动直接跳过该阶段。
  • 插件机制 (Plugins):在开发与构建阶段共享统一的插件接口——同一个插件对象既能处理开发服务器的按需转换,也能无缝接入生产打包流水线。
  • 环境变量 (Environment variables):在构建时会被静态硬编码(Inline)注入到代码中。带有 VITE_ 前缀的变量会编译为客户端打包产物中的公开常量;未加前缀的变量对客户端代码完全不可见。

经典示例

配置结构

基础配置
// 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 },
  },
})
条件配置(动态配置)
// 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),
    },
  }
})
核心配置项解析
配置项 默认值 说明
root '.' 项目根目录(index.html 所在的目录)
base '/' 部署静态资源时的公共基础路径
envPrefix 'VITE_' 暴露给客户端的环境变量前缀
build.outDir 'dist' 打包产物输出目录
build.minify 'oxc' 代码压缩工具(支持 'oxc''terser'false
build.sourcemap false SourceMap 配置(true'inline''hidden'

插件 (Plugins)

常用核心插件

绝大多数插件需求都可以通过社区成熟且维护良好的官方/第三方包解决,在尝试自己编写插件前建议优先使用以下包:

插件 功能说明 适用场景
@vitejs/plugin-react-swc 基于 SWC 提供 React HMR + Fast Refresh React 项目首选(编译速度显著高于 Babel 版本)
@vitejs/plugin-react 基于 Babel 提供 React HMR + Fast Refresh 仅在需要额外 Babel 插件(如 Emotion、MobX 装饰器)时使用
@vitejs/plugin-vue Vue 3 单文件组件 (SFC) 原生支持 Vue 项目
vite-plugin-checker 在 Worker 线程中运行 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 组件直接 import 需要将 SVG 当作组件调用的 React 项目
rollup-plugin-visualizer 生成打包体积分析图(Treemap/Sunburst 报告) 定期做打包体积审计时使用(建议配置 enforce: 'post'
vite-plugin-pwa 零配置生成 PWA + Workbox 支持 具备离线使用能力的 Web 应用

极其重要的坑点提醒: vite build 只负责语法转译(Transpile),完全不进行类型检查(Type-check)。如果不配置 vite-plugin-checker 或者在 CI 中追加 tsc --noEmit,类型报错也会被静默打包并直接上到生产环境!

编写自定义插件

编写自定义插件的情况其实非常少见——绝大多数需求已有现成插件。如果确实需要定制,建议先在 vite.config.ts 中以内联形式(Inline)编写,后续确认有复用需求时再抽离。

// 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 }
    },
  }
}

核心 Hook 说明: transform(源码修改)、resolveId + load(虚拟模块解析与加载)、transformIndexHtml(往 HTML 注入代码)、configureServer(扩展开发服务器中间件)、hotUpdate(自定义 HMR,在 v7+ 中替代了已废弃的 handleHotUpdate)。

虚拟模块(Virtual modules): 惯例使用 \0 前缀——resolveId 返回 '\0virtual:my-id' 从而避免被后续其他插件二次拦截处理。而在业务代码中正常使用 import 'virtual:my-id' 引入。

完整插件 API 详见 vite.dev/guide/api-plugin。开发调试转换流水线时,可搭配使用 vite-plugin-inspect 实时查看模块转换状态。

HMR API

框架级插件(如 @vitejs/plugin-react@vitejs/plugin-vue 等)已自动接管了 HMR 逻辑。只有在从零构建自定义状态管理库、开发调试工具、或者需要在代码更新时跨模块保持状态的非框架依赖工具时,才需要直接调用 import.meta.hot

// 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 相关代码在生产环境打包时会被自动 Tree-shaking 裁切掉,无需手动写防御判断。

环境变量

Vite 会按以下优先级依次加载:.env -> .env.local -> .env.[mode] -> .env.[mode].local(后加载的覆盖先加载的);*.local 文件应列入 .gitignore,专门用于保存本地私密配置。

客户端代码中读取环境变量

只有带 VITE_ 前缀的变量才会安全暴露给客户端代码:

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             // 布尔值,是否服务端渲染
在配置文件中使用环境变量
// 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),
    },
  }
})

安全规范

VITE_ 前缀绝非安全防护屏障

任何带有 VITE_ 前缀的环境变量,在构建阶段都会被直接静态硬编码(Inline)注入到客户端打包代码中。不管是代码压缩、Base64 编码还是关闭 Source Map,都无法阻止变量泄露。有经验的攻击者可以轻松从你发布的 JavaScript 静态资源中还原出所有 VITE_ 变量。

铁律: 只有公开信息(API 服务地址、功能开关 Feature Flag、公钥等)才能放到 VITE_ 变量中。敏感信息(API Token、数据库连接串、私钥等)必须保留在服务端,通过后台 API 或 Serverless 函数托管访问。

loadEnv('') 的安全陷阱
// 错误写法:第三个参数传空字符串 '' 会加载所有环境变量(包含服务端密钥!),
// 并且极易通过 `define` 不小心硬编码暴露到前端代码中。
const env = loadEnv(mode, process.cwd(), '')

// 正确写法:明确指定安全的前缀白名单列表
const env = loadEnv(mode, process.cwd(), ['VITE_', 'APP_'])
生产环境 Source Maps

生产环境输出的 Source Map 会直接暴露你的源代码细节。除非打包后会自动上传到错误监控平台(如 Sentry、Bugsnag)并在本地立即销毁,否则请始终关闭:

build: {
  sourcemap: false,                                  // 默认关闭 — 保持此默认配置
}
.gitignore 安全自查清单
  • .env.local, .env.*.local — 本地密钥重写文件
  • dist/ — 构建打包产物目录
  • node_modules/.vite — 依赖预构建缓存目录(残留的旧缓存极易引起诡异的报错)

服务端代理 (Server Proxy)

// 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 即可。

构建打包优化

手动分包 (Manual Chunks)
// vite.config.ts — build.rolldownOptions
build: {
  rolldownOptions: {
    output: {
      // 对象形式:将指定第三方包归类合并
      manualChunks: {
        'react-vendor': ['react', 'react-dom'],
        'ui-vendor': ['@radix-ui/react-dialog', '@radix-ui/react-popover'],
      },
    },
  },
}
// 函数形式:依据逻辑启发式分包
manualChunks(id) {
  if (id.includes('node_modules/react')) return 'react-vendor'
  if (id.includes('node_modules')) return 'vendor'
}

性能优化

规避 Barrel 文件(桶文件/统一导出文件)

Barrel 文件(即在 index.ts 中把某个目录下的所有模块集中重新导出)会导致 Vite 即便只需要引入单个函数,也必须强行加载并解析该目录下所有导出的模块。官方文档明确指出这是导致开发服务器变慢的第一大诱因

// 避坑写法 — 哪怕只用一个工具函数,也会迫使 Vite 加载整桶模块
import { slash } from '@/utils'

// 推荐写法 — 精确按需引入,只加载对应的文件
import { slash } from '@/utils/slash'
显式指定 import 路径扩展名

省去文件扩展名会导致 Vite 每次解析路径时执行多达 6 次文件系统查找(通过 resolve.extensions)。在大中型项目积累下来,性能开销非常巨大。

// 避坑写法
import Component from './Component'

// 推荐写法
import Component from './Component.tsx'

建议在 tsconfig.json 中收紧 allowImportingTsExtensions 并将 resolve.extensions 严格缩减至项目真正用到的扩展名。

预热关键路径路由 (Warm-Up Hot-Path Routes)

通过配置 server.warmup.clientFiles,让 Vite 在浏览器发起网络请求之前预先转换已知的高频热点入口模块——能有效消灭大型应用冷启动时的请求瀑布流(Waterfall)。

// vite.config.ts
server: {
  warmup: {
    clientFiles: ['./src/main.tsx', './src/routes/**/*.tsx'],
  },
}
排查卡顿的开发服务器

当感到 vite dev 运行迟缓时,可以通过运行 vite --profile 启动,并在页面上进行常规交互操作后,按下 p + Enter 保存 CPU 采样文件 .cpuprofile。将其拖入 Speedscope 进行可视化分析,排查是哪个插件拖累了速度——常见原因多出自第三方社区插件的 buildStartconfigconfigResolved 钩子。

库模式打包 (Library Mode)

当使用 Vite 打包发布 npm 组件库/工具库时,配置 build.lib 是核心操作。比起琐碎的配置参数,更需要警惕以下两大“天坑”:

  1. 默认不会生成类型声明文件 — 必须补充 vite-plugin-dts 插件,或单独运行 tsc --emitDeclarationOnly
  2. Peer Dependencies 必须手动配置 external 剔除 — 没在 external 中排除的同伴依赖会被强行打包进你的库代码中,导致使用方在运行时因出现多份同名依赖实例而报 Duplicate Runtime 错误。
// 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 外置依赖 (SSR Externals)

使用裸 createServer({ middlewareMode: true }) 进行极简搭建通常是应用级框架作者才需要接触的底层领域。绝大多数项目应当直接选择基于 Nuxt、Remix、SvelteKit、Astro 或 TanStack Start 开发。而作为框架使用者,当依赖在 SSR 阶段打不出期望结果时,你唯一需要手动调整的主要是 externals 配置:

//

<!-- truncated for translation batch; full body continues in source -->