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 做的事

  1. bare import → 有效 URLreact/node_modules/.vite/deps/react.js?v=f3sf2ebd

  2. CommonJS → ESM:老库的 module.exports 转成 export

  3. 模块合并: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 状态

  • Sveltevite-plugin-svelte —— 组件级热更新

  • Solidvite-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"
  }
}

两次构建

  1. build:client —— 客户端 bundle(给浏览器)

  2. 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.hot HMR 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 是未来
       从写代码到部署,一条链路打通

参考链接