Vite 8 完全指南:从原理到架构,毕业级
开发服务器、HMR、Plugin 系统、Environment API、SSR、Rolldown、生产构建——12 章全覆盖
2026-06-03 · 基于 Vite 8(Rolldown-powered)· 毕业级深度
1. Vite 到底是什么
Vite(法语“快”,读 /viːt/)= ESM dev server \+ Rolldown 打包器
不是 webpack 的替代品。是从零重新思考“开发时代码应该怎么交给浏览器”。
传统 bundler(webpack):
启动 → 打包整个应用 → 浏览器拿到 bundle → 能跑
项目越大,启动越慢
Vite:
启动 → 浏览器直接请求 → 按需加载 → 能跑
项目多大都秒开
一句话:Vite 把浏览器变成了 bundler,自己只负责转译和缓存。
为什么要存在
| 问题 | webpack 的做法 | Vite 的做法 |
|---|---|---|
| 启动慢 | 打包整个依赖图 | 原生 ESM 按需加载 |
| HMR 慢 | 重新计算整个 bundle | 只替换改动的模块 |
| 配置复杂 | 需要大量 loader/plugin | 开箱即用 |
| dev ≠ prod | 开发和构建行为不一致 | Rolldown 统一工具链 |
谁在用
Nuxt、SvelteKit、Astro、React Router、SolidStart、Qwik City、Laravel、Ruby on Rails……几乎所有现代框架都选择 Vite 作为构建层。
尤雨溪的真正野心:不赢框架战争,赢基础设施层。
2. 核心架构全景
┌──────────────────────────────────────────────────────┐
│ Vite 8 架构 │
├────────────────────┬─────────────────────────────────┤
│ 开发时 (dev) │ 生产构建 (build) │
├────────────────────┼─────────────────────────────────┤
│ Vite Dev Server │ Rolldown │
│ (Connect/Node.js) │ (Rust 打包引擎) │
│ │ │
│ ┌────────────────┐ │ ┌───────────────────────────┐ │
│ │ 原生 ESM 按需 │ │ │ Tree-shaking (死代码消除) │ │
│ │ Oxc 实时转译 │ │ │ Code-splitting (代码分割) │ │
│ │ WebSocket HMR │ │ │ Chunk 优化 │ │
│ └────────────────┘ │ └───────────────────────────┘ │
│ │ │
│ 依赖预构建 │ 静态资源优化 │
│ (Rolldown, 一次性) │ CSS 提取/压缩 (Lightning CSS) │
├────────────────────┴─────────────────────────────────┤
│ 插件系统 (Plugin API) │
│ 兼容 Rollup/Rolldown 插件 + Vite 专有 Hook │
├──────────────────────────────────────────────────────┤
│ Environment API (Vite 8+) │
│ client / ssr / 自定义环境,各自独立模块图 │
├──────────────────────────────────────────────────────┤
│ Oxc (解析 + 转译引擎) │
│ 替代 esbuild,Rust 编写,全链路覆盖 │
└──────────────────────────────────────────────────────┘
两大运行模式
开发态:Vite Dev Server(原生 ESM,按需转译,Rolldown 只做依赖预构建)
↓
构建态:Rolldown 全量打包(tree-shaking + code-splitting + 压缩)
关键洞察(Vite 8\+):esbuild \+ Rollup 双轨制 → Rolldown 一统天下。同一套工具链从 dev 到 prod,行为一致。
3. 开发模式深度剖析
请求流程
1. 浏览器请求 http://localhost:5173/
↓
2. Vite dev server 返回 index.html
↓
3. index.html 里 <script type="module" src="/src/main.tsx">
↓
4. 浏览器发请求 → Vite 拦截
↓
5. Plugin Pipeline 处理:
resolveId → load → transform
↓
6. Oxc(Rust)实时转译 TSX → JS
↓
7. 返回原生 ESM 给浏览器
↓
8. 浏览器执行,遇到 import 再请求下一个模块
↓
9. 按需加载,懒执行
index.html 的特殊地位
Vite 项目里,index.html 在项目根目录(不是 public/)。它是应用的入口,是模块图的一部分。
project/
index.html ← 入口,Vite 作为 server 直接提供
src/
main.js
App.vue
HTML 里引用的所有资源都会被自动处理:
-
<script type="module" src="...">→ ESM 转译 -
<link href="...">→ CSS 处理 -
<img src="...">→ 资源哈希 \+ URL 重写
Vite Dev Server 内部
Node.js 进程
├── Connect 中间件栈
│ ├── 依赖预构建缓存(/node_modules/.vite/deps/)
│ ├── 模块转译管道(Plugin Container)
│ ├── WebSocket(HMR 通信)
│ └── 静态文件服务
├── Module Graph(模块依赖图)
├── 文件监听器(chokidar)
└── WebSocket Server
4. 依赖预构建
为什么需要
import React from 'react' // 浏览器不认识 bare import
import { debounce } from 'lodash-es' // lodash-es 有 600+ 个模块
浏览器不能直接 import 'react',需要转成有效 URL。
Vite 用 Rolldown 做的事
-
bare import → 有效 URL:
react→/node_modules/.vite/deps/react.js?v=f3sf2ebd -
CommonJS → ESM:老库的
module.exports转成export -
模块合并:600 个 lodash-es 小模块 → 1 个文件(减少 HTTP 请求)
缓存策略
预构建结果 → HTTP 强缓存(Cache-Control: max-age=31536000, immutable)
只有 package.json 依赖版本变了 → 重新预构建
手动清理
rm -rf node_modules/.vite
# 或
pnpm dev --force
5. HMR 完整机制
工作流程
你改了 App.tsx
↓
chokidar 检测到文件变化
↓
Vite 找到受影响的模块(Module Graph)
↓
WebSocket 发送 'update' 事件给浏览器
↓
浏览器只重新请求这一个模块
↓
Oxc 转译 → 返回新代码
↓
import.meta.hot.accept() 回调执行
↓
模块替换,保留应用状态
为什么 HMR 快
因为不打包。每个模块是独立的 ESM,改一个只更新一个。
webpack HMR:改文件 → 重新编译 chunk → 推送 → 浏览器执行
Vite HMR: 改文件 → WebSocket 通知 → 浏览器只请求那个模块 → 替换
客户端 HMR API
// 自定义 HMR 行为
if (import.meta.hot) {
// 接受当前模块的更新
import.meta.hot.accept((newModule) => {
// newModule 是新模块的导出
// 用新模块替换旧模块的逻辑
})
// 接受依赖模块的更新(当依赖变化时重新执行当前模块)
import.meta.hot.accept('./dependency.js', (newDep) => {
// 处理依赖更新
})
// 当模块被替换时清理副作用
import.meta.hot.dispose(() => {
// 清理定时器、事件监听器等
})
// 向服务端发送自定义事件
import.meta.hot.send('my-custom-event', { data: 123 })
// 监听服务端发送的自定义事件
import.meta.hot.on('server-event', (data) => {
console.log('Server says:', data)
})
}
框架级 HMR
-
Vue:
@vitejs/plugin-vue—— SFC(单文件组件)热更新,保留组件状态 -
React:
@vitejs/plugin-react—— Fast Refresh,只更新改动的组件,保留 hooks 状态 -
Svelte:
vite-plugin-svelte—— 组件级热更新 -
Solid:
vite-plugin-solid—— fine-grained reactivity
6. Plugin 系统深度剖析
插件本质
// 工厂函数 → 返回插件对象(约定)
export default function myPlugin(options = {}) {
return {
name: 'my-plugin', // 必须有 name
// ... hooks
}
}
Hook 生命周期(完整)
Server 启动
│
├── options ← 修改原始配置
├── buildStart ← 插件初始化
│
│ ┌─ 每个模块请求 ──────────────────────┐
│ │ resolveId(解析模块 ID) │
│ │ ↓ │
│ │ load(加载模块内容) │
│ │ ↓ │
│ │ transform(转译代码) │
│ └─────────────────────────────────────┘
│
├── buildEnd ← 构建结束
└── closeBundle ← 清理
Universal Hooks(dev 和 build 都生效)
| Hook | 类型 | 执行时机 | 作用 |
|---|---|---|---|
options |
async, sequential | 启动时 | 修改原始 Rollup/Rolldown 配置 |
buildStart |
async, sequential | 启动后 | 插件初始化,读取配置 |
resolveId |
async, sequential | 每个模块 | 自定义模块路径解析 |
load |
async, sequential | 每个模块 | 自定义模块内容加载 |
transform |
async, sequential | 每个模块 | 转译代码(最常用) |
buildEnd |
async | 构建结束 | 清理资源 |
closeBundle |
sync | 最后 | 最终清理 |
注意:moduleParsed 在 dev 模式下不会被调用(Vite 避免完整 AST 解析以提升性能)。
Vite 专有 Hook
| Hook | 类型 | 作用 |
|---|---|---|
config |
async, sequential | 修改 Vite 配置(在解析前) |
configResolved |
async, parallel | 读取最终配置 |
configureServer |
async, sequential | 配置 dev server(加中间件) |
configurePreviewServer |
async, sequential | 配置预览 server |
transformIndexHtml |
async, sequential | 转换 HTML 入口文件 |
handleHotUpdate |
async, sequential | 自定义 HMR 更新逻辑 |
配置相关 Hook
// config —— 在配置解析前修改
export default function myPlugin() {
return {
name: 'modify-config',
config(config, { mode, command }) {
// 返回部分配置,会深度合并
return {
resolve: {
alias: { '@': '/src' }
}
}
}
}
}
// configResolved —— 读取最终配置
export default function myPlugin() {
let resolvedConfig
return {
name: 'read-config',
configResolved(config) {
resolvedConfig = config
},
transform(code, id) {
if (resolvedConfig.command === 'serve') {
// dev 环境
} else {
// build 环境
}
}
}
}
configureServer —— Dev Server 中间件
export default function myPlugin() {
return {
name: 'server-middleware',
configureServer(server) {
// 在 Vite 内部中间件之前执行
server.middlewares.use((req, res, next) => {
if (req.url === '/api/health') {
res.end('OK')
} else {
next()
}
})
// 返回一个函数 = 在 Vite 内部中间件之后执行
return () => {
server.middlewares.use((req, res, next) => {
// 后置中间件
next()
})
}
}
}
}
transformIndexHtml —— HTML 增强
export default function myPlugin() {
return {
name: 'html-enhance',
transformIndexHtml(html) {
// 直接替换 HTML
return html.replace(
/<title>(.*?)<\/title>/,
'<title>My App</title>'
)
}
}
}
// 或者注入标签
export default function myPlugin() {
return {
name: 'inject-script',
transformIndexHtml: {
order: 'pre', // 在 HTML 处理前执行
handler(html) {
return {
html,
tags: [
{
tag: 'script',
attrs: { src: '/analytics.js' },
injectTo: 'body'
}
]
}
}
}
}
}
handleHotUpdate —— 自定义 HMR
export default function myPlugin() {
return {
name: 'custom-hmr',
handleHotUpdate({ file, modules, server, read }) {
// 只处理特定文件
if (file.endsWith('.md')) {
// 手动触发全量刷新
server.ws.send({ type: 'full-reload' })
return [] // 返回空数组 = 阻止默认 HMR
}
// 或者发送自定义事件
if (file.endsWith('.yaml')) {
server.ws.send({
type: 'custom',
event: 'config-change',
data: { file }
})
return []
}
// 返回受影响的模块列表(精确控制)
return modules.filter(m => !m.id?.includes('ignored'))
}
}
}
虚拟模块(Virtual Modules)
插件可以创建不存在于文件系统的模块:
export default function myPlugin() {
const virtualModuleId = 'virtual:config'
const resolvedVirtualModuleId = '\0' + virtualModuleId
return {
name: 'virtual-config',
resolveId(id) {
if (id === virtualModuleId) {
return resolvedVirtualModuleId // \0 前缀 = 虚拟模块
}
},
load(id) {
if (id === resolvedVirtualModuleId) {
return `export const config = ${JSON.stringify({
apiUrl: 'https://api.example.com',
debug: true
})}`
}
}
}
}
// 用户代码里直接用
import { config } from 'virtual:config'
console.log(config.apiUrl)
命名约定:
-
用户侧:
virtual:xxx前缀 -
插件内部:
\0前缀(防止其他插件处理) -
浏览器侧:编码为
/@id/__x00__xxx
插件命名规范
rolldown-plugin-xxx ← 通用(Vite + Rolldown 都能用)
vite-plugin-xxx ← Vite 专用
vite-plugin-vue-xxx ← Vue 专用
vite-plugin-react-xxx ← React 专用
调试插件
npm install -D vite-plugin-inspect
// vite.config.js
import inspect from 'vite-plugin-inspect'
export default defineConfig({
plugins: [inspect()]
})
访问 http://localhost:5173/__inspect/ 查看所有模块的转换链。
7. Environment API
⚠️ Release Candidate 阶段,API 可能在未来大版本变化。
为什么需要
Vite 5 之前只有两个环境:client(浏览器)和 ssr(Node.js)。用 ssr: boolean 区分。
但现代应用可能跑在:
-
浏览器(client)
-
Node.js(SSR)
-
Edge Runtime(Cloudflare Workers)
-
Web Worker(Vitest)
-
React Server Components(RSC)
Environment API 让框架定义自己的运行环境。
核心概念
Vite Dev Server
├── environments.client ← 浏览器环境
│ ├── moduleGraph(模块图)
│ ├── pluginContainer(插件管道)
│ └── hot(WebSocket 通信)
├── environments.ssr ← Node.js 环境
│ ├── moduleGraph(独立模块图)
│ ├── pluginContainer
│ └── hot
└── environments.rsc ← 自定义环境(如 RSC)
├── moduleGraph
├── pluginContainer
└── hot
每个环境有独立的模块图、独立的插件管道、独立的通信通道。
DevEnvironment 类
class DevEnvironment {
name: string // 环境标识符
hot: NormalizedHotChannel // 通信通道
moduleGraph: EnvironmentModuleGraph // 模块图
plugins: Plugin[] // 解析后的插件
pluginContainer: EnvironmentPluginContainer // 插件容器
config: ResolvedConfig // 配置
// 核心方法
async transformRequest(url: string): Promise<TransformResult | null>
async warmupRequest(url: string): Promise<void>
async fetchModule(id: string, importer?: string): Promise<FetchResult>
}
环境配置
// vite.config.js
export default defineConfig({
environments: {
client: {
// 浏览器环境配置
resolve: {
conditions: ['browser']
}
},
ssr: {
// Node.js SSR 环境
resolve: {
conditions: ['node']
},
dev: {
// dev 时的环境创建函数
createEnvironment(name, config) {
return new DevEnvironment(name, config, { /* ... */ })
}
}
},
rsc: {
// React Server Components 环境
resolve: {
conditions: ['react-server']
}
}
}
})
插件中访问环境
export default function myPlugin() {
return {
name: 'env-aware',
transform(code, id) {
// 通过 this.environment 访问当前环境
console.log(this.environment.name) // 'client' | 'ssr' | 'rsc'
// 根据环境做不同处理
if (this.environment.name === 'ssr') {
// SSR 特定转换
}
}
}
}
每个环境独立的插件
export default function myPlugin() {
return {
name: 'per-env',
// 控制插件在哪些环境生效
applyToEnvironment(environment) {
return environment.name === 'client' // 只在 client 环境生效
}
}
}
// 或者用 helper
import { perEnvironmentPlugin } from 'vite'
export default defineConfig({
plugins: [
perEnvironmentPlugin('my-plugin', (environment) => {
return {
name: 'my-plugin',
transform(code) {
// 每个环境有独立的插件实例和状态
}
}
})
]
})
ModuleRunner —— 运行时执行器
ModuleRunner 在目标运行时中执行 Vite 处理过的代码:
// 在 Node.js 中运行
import { ModuleRunner, ESModulesEvaluator, createNodeImportMeta } from 'vite/module-runner'
const runner = new ModuleRunner(
{
transport: yourTransport, // 与 Vite server 的通信通道
createImportMeta: createNodeImportMeta,
},
new ESModulesEvaluator()
)
await runner.import('/src/entry-server.js')
ModuleRunnerTransport —— 通信层
interface ModuleRunnerTransport {
connect?(handlers: ModuleRunnerTransportHandlers): Promise<void> | void
disconnect?(): Promise<void> | void
send?(data: HotPayload): Promise<void> | void
invoke?(data: HotPayload): Promise<{ result: any } | { error: any }>
}
不同运行时的通信方式:
| 运行时 | 通信方式 |
|---|---|
| 浏览器 | WebSocket \+ HTTP |
| Node.js(同进程) | 直接函数调用 |
| Worker Thread | parentPort.postMessage |
| Cloudflare Workers | 自定义 RPC |
环境工厂(Environment Factory)
框架/平台提供者可以创建环境工厂:
// 由 Cloudflare 等平台提供
function createWorkerdEnvironment(userConfig) {
return mergeConfig({
resolve: {
conditions: ['workerd']
},
dev: {
createEnvironment(name, config) {
return new DevEnvironment(name, config, {
hot: true,
transport: workerdTransport(),
})
}
},
build: {
createEnvironment(name, config) {
return new WorkerdBuildEnvironment(name, config)
}
}
}, userConfig)
}
// 用户配置
export default {
environments: {
ssr: createWorkerdEnvironment({ build: { outDir: '/dist/ssr' } }),
rsc: createWorkerdEnvironment({ build: { outDir: '/dist/rsc' } }),
}
}
8. SSR 深度解析
SSR 是什么
前端框架(React/Vue/Svelte)在 Node.js 上运行应用,预渲染成 HTML,然后在客户端“水合”(hydrate):
1. 服务器运行应用 → 生成 HTML 字符串
2. 发送给浏览器 → 用户立即看到内容(不用等 JS 加载)
3. JS 加载完成 → 水合(绑定事件监听器,让页面可交互)
Vite SSR 项目结构
project/
index.html
server.js ← 主应用服务器
src/
main.js ← 通用代码(环境无关)
entry-client.js ← 客户端入口(挂载到 DOM)
entry-server.js ← 服务端入口(用框架 SSR API 渲染)
Dev Server 设置
// server.js
import express from 'express'
import { createServer as createViteServer } from 'vite'
async function createServer() {
const app = express()
// Vite 中间件模式
const vite = await createViteServer({
server: { middlewareMode: true },
appType: 'custom' // 禁用 Vite 的 HTML 服务逻辑
})
app.use(vite.middlewares)
app.use('*all', async (req, res) => {
const url = req.originalUrl
// 1. 读取 index.html
let template = fs.readFileSync('index.html', 'utf-8')
// 2. Vite HTML 转换(注入 HMR client、插件转换)
template = await vite.transformIndexHtml(url, template)
// 3. 加载服务端入口(自动转译 ESM → Node.js 可用)
const { render } = await vite.ssrLoadModule('/src/entry-server.js')
// 4. 渲染 HTML
const appHtml = await render(url)
// 5. 注入到模板
const html = template.replace('<!--ssr-outlet-->', appHtml)
// 6. 返回
res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
})
app.listen(5173)
}
生产构建
{
"scripts": {
"dev": "node server",
"build:client": "vite build --outDir dist/client",
"build:server": "vite build --outDir dist/server --ssr src/entry-server.js"
}
}
两次构建:
-
build:client—— 客户端 bundle(给浏览器) -
build:server—— SSR bundle(给 Node.js)
SSR 外部化
SSR 时,依赖默认被“外部化”(不经过 Vite 转译,直接用 Node.js 原生 import):
export default defineConfig({
ssr: {
// 强制某些依赖经过 Vite 转译
noExternal: ['my-esm-library'],
// 强制某些依赖外部化
external: ['lodash-es'],
// 目标运行时
target: 'node' // 或 'webworker'
}
})
条件逻辑
if (import.meta.env.SSR) {
// 服务端专用代码(构建时静态替换,可以 tree-shake)
} else {
// 客户端专用代码
}
SSR Manifest(预加载指令)
vite build --ssrManifest
生成 .vite/ssr-manifest.json,映射模块 ID → chunk 文件。用于:
-
精确预加载当前页面需要的 JS/CSS
-
103 Early Hints 优化
预渲染 / SSG
如果路由和数据已知,可以预渲染成静态 HTML:
// prerender.js
import { render } from './entry-server.js'
import { writeFileSync } from 'fs'
const routes = ['/', '/about', '/blog/hello-world']
for (const route of routes) {
const html = await render(route)
writeFileSync(`dist/static${route === '/' ? '/index' : route}.html`, html)
}
9. TypeScript 处理
只转译,不检查
Vite(Oxc):TSX → JS(快,只关心语法,单文件处理)
tsc: TSX → 类型检查(慢,需要完整类型信息,全模块图)
原因:类型检查需要整个模块图,跟 Vite 的按需加载模型冲突。分离关注点。
配置要求
// tsconfig.json
{
"compilerOptions": {
"isolatedModules": true, // 必须!Oxc 不支持 const enum 等
"useDefineForClassFields": true,
"skipLibCheck": true // 跳过依赖的类型检查
}
}
开发时类型检查
# 单独进程运行类型检查
tsc --noEmit --watch
# 或用插件在浏览器中显示类型错误
npm install -D vite-plugin-checker
// vite.config.js
import checker from 'vite-plugin-checker'
export default defineConfig({
plugins: [checker({ typescript: true })]
})
客户端类型
// tsconfig.json
{
"compilerOptions": {
"types": ["vite/client"]
}
}
提供:
-
资源导入类型(
.svg,.png等) -
import.meta.env类型 -
import.meta.hotHMR API 类型
10. CSS 体系
开箱即用
import './styles.css' // 自动注入 <style> 标签,支持 HMR
import classes from './a.module.css' // CSS Modules
PostCSS
项目里有 postcss.config.js 就自动生效:
npm install -D autoprefixer postcss-nesting
CSS 预处理器
npm install -D sass-embedded # .scss/.sass
npm install -D less # .less
npm install -D stylus # .styl/.stylus
Vite 内置支持,不需要额外插件。
Lightning CSS
Vite 默认用 Lightning CSS 压缩生产 CSS。实验性支持完全替换 PostCSS:
export default defineConfig({
css: {
transformer: 'lightningcss',
lightningcss: {
// Lightning CSS 选项
}
}
})
CSS 注入控制
import './foo.css' // 注入到页面
import styles from './bar.css?inline' // 不注入,返回字符串
11. 生产构建与 Rolldown
构建命令
vite build # 生产构建
vite preview # 本地预览构建结果
Rolldown 演进
Vite 1-2:esbuild(dev)+ Rollup(build)← 双轨制,行为不一致
Vite 3-5:esbuild(dev)+ Rollup(build)← 但插件 API 统一了
Vite 8+: Rolldown 一统天下 ← Rust 编写,dev 和 build 用同一个工具
Rolldown 的优势:
-
Rust 编写,比 esbuild 更快
-
兼容 Rollup 插件 API(生态通用)
-
内置 Oxc 解析 \+ 转译 \+ 压缩
-
dev 和 build 行为一致
浏览器兼容
默认目标(Baseline Widely Available):
-
Chrome ≥ 111
-
Edge ≥ 111
-
Firefox ≥ 114
-
Safari ≥ 16.4
export default defineConfig({
build: {
target: 'es2015' // 最低支持 ES2015
}
})
代码分割(Chunking)
export default defineConfig({
build: {
rolldownOptions: {
output: {
codeSplitting: {
// 手动分割策略
manualChunks(id) {
if (id.includes('node_modules')) {
return 'vendor'
}
}
}
}
}
}
})
库模式(Library Mode)
export default defineConfig({
build: {
lib: {
entry: 'lib/main.js',
name: 'MyLib',
fileName: 'my-lib'
},
rolldownOptions: {
external: ['vue'], // 外部化依赖
output: {
globals: { vue: 'Vue' }
}
}
}
})
多页应用
export default defineConfig({
build: {
rolldownOptions: {
input: {
main: 'index.html',
admin: 'admin/index.html'
}
}
}
})
公共路径
export default defineConfig({
base: '/my-app/' // 部署在子路径下
})
12. 完整 Web 架构图谱
从开发到部署的完整链路
开发者写代码
↓
┌─────────────────────────────────┐
│ 开发环境 │
│ Vite Dev Server │
│ ├── 原生 ESM 按需加载 │
│ ├── Oxc 实时转译 │
│ ├── HMR(WebSocket) │
│ ├── Plugin Pipeline │
│ └── Vitest(单元测试) │
└─────────────────────────────────┘
↓ git push
┌─────────────────────────────────┐
│ CI(GitHub Actions) │
│ ├── pnpm install │
│ ├── pnpm lint(ESLint) │
│ ├── tsc --noEmit(类型检查) │
│ ├── pnpm test(Vitest) │
│ └── pnpm build(Rolldown) │
└─────────────────────────────────┘
↓ build 成功
┌─────────────────────────────────┐
│ CD(自动部署) │
│ ├── Docker build(多阶段构建) │
│ ├── 推送到容器 Registry │
│ ├── 拉取到服务器 │
│ ├── docker compose up │
│ └── Nginx 反向代理 │
└─────────────────────────────────┘
↓
用户访问
Docker 多阶段构建
# 阶段 1:构建
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
# 阶段 2:运行(最终镜像不含源码和 node_modules)
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
GitHub Actions CI
name: CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lint-test-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: corepack enable && pnpm install --frozen-lockfile
- run: pnpm lint
- run: pnpm build
deploy:
needs: lint-test-build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- run: docker build -t my-app .
- run: docker push registry/my-app:latest
- run: ssh deploy@server "docker pull registry/my-app:latest && docker compose up -d"
Monorepo 架构
monorepo/
package.json ← workspace 配置
apps/
web/ ← 主应用(Vite)
admin/ ← 管理后台(Vite)
packages/
ui/ ← 共享 UI 组件库
utils/ ← 共享工具函数
config/ ← 共享 Vite/TS 配置
// 根 package.json
{
"scripts": {
"dev": "pnpm -r --parallel run dev",
"build": "pnpm -r run build"
}
}
测试体系
┌─────────────────────────────────┐
│ 测试金字塔 │
├─────────────────────────────────┤
│ E2E 测试(Playwright/Cypress) │ ← 少量,覆盖关键路径
│ 集成测试(Vitest + Testing Lib)│ ← 中量,组件/API 测试
│ 单元测试(Vitest) │ ← 大量,工具函数/逻辑测试
└─────────────────────────────────┘
Vitest = 基于 Vite 的测试框架,共享 Vite 的转译管道和插件系统:
npm install -D vitest
// vitest.config.js
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
environment: 'jsdom', // 或 'happy-dom'
globals: true,
}
})
// math.test.ts
import { describe, it, expect } from 'vitest'
import { add } from './math'
describe('add', () => {
it('adds two numbers', () => {
expect(add(1, 2)).toBe(3)
})
})
核心一句话总结
Vite = ESM dev server + Rolldown 打包器
开发时按需加载(快),生产时全量打包(优)
插件系统是灵魂,Environment API 是未来
从写代码到部署,一条链路打通