> ## Content Index
> Fetch the complete content index at: https://blog.vercanti.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Vite 高级指南
- URL: https://blog.vercanti.com/vite-gao-ji-zhi-nan/
- Published: 2026-08-28T14:35:24.000Z
- Updated: 2026-08-28T14:58:45.000Z
- Description: 1. 插件开发 API(#%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91-api) 2. SSR（服务端渲染）(#ssr%EF%BC%88%E6%9C%8D%E5%8A%A1%E7%AB%AF%E6%B8%B2%E6%9F%93%EF%BC%89) 3. 中间件模式(#%E4%B8%AD%E9%97%B4%E4%BB%B6%E6%A8%A1%E5%BC%8F) 4. JavaScript API(#javascript-api) 5. 性能优化(#%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96) 6.
- Author: yellowdog
- Tags: 前端开发, Vite

> 官方文档：<https://vite.dev/guide/>  
> 适用版本：Vite 6.x（2026-05-08 核实）

## 目录

1. [插件开发 API](#%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91-api)
2. [SSR（服务端渲染）](#ssr%EF%BC%88%E6%9C%8D%E5%8A%A1%E7%AB%AF%E6%B8%B2%E6%9F%93%EF%BC%89)
3. [中间件模式](#%E4%B8%AD%E9%97%B4%E4%BB%B6%E6%A8%A1%E5%BC%8F)
4. [JavaScript API](#javascript-api)
5. [性能优化](#%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96)
6. [环境变量进阶与多环境部署](#%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F%E8%BF%9B%E9%98%B6%E4%B8%8E%E5%A4%9A%E7%8E%AF%E5%A2%83%E9%83%A8%E7%BD%B2)
7. [Preview Options 完整配置](#preview-options-%E5%AE%8C%E6%95%B4%E9%85%8D%E7%BD%AE)
8. [Worker Options 完整配置](#worker-options-%E5%AE%8C%E6%95%B4%E9%85%8D%E7%BD%AE)
9. [打包与部署](#%E6%89%93%E5%8C%85%E4%B8%8E%E9%83%A8%E7%BD%B2)
10. [Monorepo 配置](#monorepo-%E9%85%8D%E7%BD%AE)
11. [高级插件示例：自定义 Mock 插件](#%E9%AB%98%E7%BA%A7%E6%8F%92%E4%BB%B6%E7%A4%BA%E4%BE%8B%EF%BC%9A%E8%87%AA%E5%AE%9A%E4%B9%89-mock-%E6%8F%92%E4%BB%B6)
12. [踩坑与最佳实践清单](#%E8%B8%A9%E5%9D%91%E4%B8%8E%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%E6%B8%85%E5%8D%95)

---

## 插件开发 API

### 插件结构

Vite 插件使用工厂函数模式，函数接收用户选项，返回一个 Plugin 对象（或 Plugin 数组）。

**命名规范：**

- Vite 专用插件：`vite-plugin-xxx`
- Rollup 兼容插件：`rollup-plugin-xxx`
- 框架专用插件：`vite-plugin-vue-xxx`

**Plugin 对象顶层控制字段：**

| 参数名     | 类型                 | 默认值       | 说明             |                                 |
| ------- | ------------------ | --------- | -------------- | ------------------------------- |
| name    | string             | 必填        | 插件名称，用于错误信息和日志 |                                 |
| enforce | 'pre' \| 'post'    | undefined | undefined      | 插件执行顺序。pre 在核心插件之前，post 在核心插件之后 |
| apply   | 'build' \| 'serve' | function  | 两者都运行          | 控制插件在何时生效。传入函数可实现更精细的条件判断       |

**执行顺序（从先到后）：**

1. enforce: 'pre' 的插件
2. 无 enforce 的插件（核心 Vite 插件穿插其中）
3. enforce: 'post' 的插件

**基本插件骨架：**

```javascript
// vite-plugin-example/index.js
export default function vitePluginExample(options = {}) {
  const { prefix = 'example' } = options

  return {
    name: 'vite-plugin-example',
    enforce: 'pre',          // 可选
    apply: 'build',          // 可选，仅构建时启用

    // ... 钩子函数
  }
}

```

**apply 使用函数进行条件判断：**

```javascript
export default function myPlugin() {
  return {
    name: 'vite-plugin-conditional',
    apply(config, { command, mode }) {
      // 仅在生产构建且 mode 为 staging 时启用
      return command === 'build' && mode === 'staging'
    },
  }
}

```

---

### 通用钩子（与 Rollup 共享）

这些钩子与 Rollup 插件 API 完全兼容，在 Vite 中的行为与 Rollup 一致。

**钩子总览：**

| 钩子          | 触发时机      | 异步支持 | 说明            |
| ----------- | --------- | ---- | ------------- |
| options     | 服务器/构建启动前 | 是    | 修改 Rollup 选项  |
| buildStart  | 每次构建开始    | 是    | 初始化构建状态       |
| resolveId   | 模块导入解析    | 是    | 自定义模块 ID 解析逻辑 |
| load        | 模块加载      | 是    | 自定义模块内容读取     |
| transform   | 模块内容转换    | 是    | 转换模块源代码       |
| buildEnd    | 构建结束      | 是    | 构建完成或出错时触发    |
| closeBundle | bundle 关闭 | 是    | 清理资源          |

#### options(options)

**参数：**

| 参数名     | 类型            | 说明             |
| ------- | ------------- | -------------- |
| options | RollupOptions | 当前 Rollup 配置对象 |

**返回值：** `RollupOptions | null`。返回修改后的配置对象或 null（不修改）。

```javascript
options(options) {
  // 追加外部依赖
  return {
    ...options,
    external: [...(options.external || []), 'some-lib'],
  }
}

```

#### buildStart(options)

**参数：**

| 参数名     | 类型                     | 说明                |
| ------- | ---------------------- | ----------------- |
| options | NormalizedInputOptions | 已规范化的 Rollup 输入选项 |

**返回值：** void

```javascript
buildStart(options) {
  console.log('构建开始，入口文件：', options.input)
  // 初始化缓存、计时器等
  this.cache = new Map()
}

```

#### resolveId(source, importer, options)

**参数：**

| 参数名                | 类型                     | 默认值 | 说明                         |
| ------------------ | ---------------------- | --- | -------------------------- |
| source             | string                 | 必填  | 被导入的模块标识符（原始字符串）           |
| importer           | string \| undefined    | 必填  | 发起导入的模块路径。入口模块时为 undefined |
| options.isEntry    | boolean                | —   | 是否为入口模块                    |
| options.assertions | Record<string, string> | —   | 导入断言（import assertions）    |

**返回值：** `string | false | null | { id, external?, moduleSideEffects?, resolvedBy? }`

- 返回字符串：作为解析后的模块 ID
- 返回 false：将该模块标记为外部模块（external）
- 返回 null：交给其他插件或默认解析逻辑处理

```javascript
resolveId(source, importer) {
  if (source === 'virtual:config') {
    // 虚拟模块，返回特殊 ID
    return '\0virtual:config'
  }
  return null // 其他模块交给默认处理
}

```

#### load(id)

**参数：**

| 参数名 | 类型     | 说明                      |
| --- | ------ | ----------------------- |
| id  | string | 模块 ID（由 resolveId 返回的值） |

**返回值：** `string | null | { code, map?, moduleSideEffects?, syntheticNamedExports? }`

```javascript
load(id) {
  if (id === '\0virtual:config') {
    return `export const config = ${JSON.stringify({ version: '1.0.0' })}`
  }
  return null
}

```

#### transform(code, id)

**参数：**

| 参数名  | 类型     | 说明    |
| ---- | ------ | ----- |
| code | string | 模块源代码 |
| id   | string | 模块 ID |

**返回值：** `string | null | { code, map? }`

```javascript
transform(code, id) {
  if (!id.endsWith('.vue')) return null

  // 对 .vue 文件进行处理
  const transformed = code.replace(/console\.log\(.*?\)/g, '')
  return {
    code: transformed,
    map: null, // 若有 source map 请提供
  }
}

```

#### buildEnd(error?)

**参数：**

| 参数名   | 类型                 | 默认值       | 说明           |
| ----- | ------------------ | --------- | ------------ |
| error | Error \| undefined | undefined | 若构建出错，则为错误对象 |

```javascript
buildEnd(error) {
  if (error) {
    console.error('构建失败：', error.message)
  } else {
    console.log('构建成功')
  }
}

```

#### closeBundle()

无参数。用于清理资源、关闭数据库连接等。在 `buildEnd` 之后触发。

```javascript
async closeBundle() {
  await db.close()
  console.log('资源清理完毕')
}

```

---

### Vite 特有钩子

这些钩子仅在 Vite 中有效，Rollup 构建时会被忽略。

**钩子总览：**

| 钩子                     | 异步 | 顺序 | 说明                 |
| ---------------------- | -- | -- | ------------------ |
| config                 | 是  | 串行 | 修改 Vite 配置（深度合并方式） |
| configResolved         | 是  | 并行 | 读取最终配置，只读，不可修改     |
| configureServer        | 否  | 串行 | 配置开发服务器，添加自定义中间件   |
| configurePreviewServer | 否  | 串行 | 配置预览服务器            |
| transformIndexHtml     | 是  | 串行 | 转换 index.html      |
| handleHotUpdate        | 否  | 串行 | 自定义 HMR 更新处理逻辑     |

#### config(config, env)

**参数：**

| 参数名         | 类型                 | 说明                                        |
| ----------- | ------------------ | ----------------------------------------- |
| config      | UserConfig         | 用户配置（合并前）                                 |
| env.command | 'build' \| 'serve' | 当前运行的命令                                   |
| env.mode    | string             | 当前 mode（development/production/staging 等） |

**返回值：** `Partial<UserConfig> | null`。返回的对象将与现有配置深度合并。

```javascript
config(config, { command, mode }) {
  if (command === 'serve') {
    return {
      define: {
        __DEV__: true,
      },
    }
  }
  return {
    build: {
      sourcemap: mode !== 'production',
    },
  }
}

```

#### configResolved(config)

**参数：**

| 参数名    | 类型             | 说明              |
| ------ | -------------- | --------------- |
| config | ResolvedConfig | 最终合并解析后的完整配置，只读 |

```javascript
let resolvedConfig

configResolved(config) {
  // 保存配置引用供其他钩子使用
  resolvedConfig = config
  console.log('最终 base：', config.base)
}

```

#### configureServer(server)

**参数：**

| 参数名    | 类型            | 说明      |
| ------ | ------------- | ------- |
| server | ViteDevServer | 开发服务器实例 |

**返回值：** `(() => void) | void`。若返回函数，该函数将在内置中间件安装完毕后执行（可用于在内置中间件之后插入中间件）。

```javascript
configureServer(server) {
  // 在内置中间件之前添加
  server.middlewares.use('/api/hello', (req, res) => {
    res.end(JSON.stringify({ message: 'hello from plugin' }))
  })

  // 若要在内置中间件之后添加，返回一个函数
  return () => {
    server.middlewares.use((req, res, next) => {
      // 后置中间件
      next()
    })
  }
}

```

#### configurePreviewServer(server)

**参数：**

| 参数名    | 类型            | 说明      |
| ------ | ------------- | ------- |
| server | PreviewServer | 预览服务器实例 |

与 configureServer 用法相同，但作用于 `vite preview` 命令启动的预览服务器。

#### transformIndexHtml(html, ctx)

**参数：**

| 参数名          | 类型                         | 说明                 |
| ------------ | -------------------------- | ------------------ |
| html         | string                     | 原始 HTML 内容         |
| ctx.path     | string                     | HTML 文件路径          |
| ctx.filename | string                     | HTML 文件绝对路径        |
| ctx.server   | ViteDevServer \| undefined | 开发模式下的服务器实例        |
| ctx.bundle   | OutputBundle \| undefined  | 构建模式下的 bundle 信息   |
| ctx.chunk    | OutputChunk \| undefined   | 与当前 HTML 对应的 chunk |

**返回值：** `string | HtmlTagDescriptor[] | { html, tags }`

```javascript
transformIndexHtml(html, ctx) {
  // 方式一：直接返回修改后的 HTML 字符串
  return html.replace(
    '<title>App</title>',
    `<title>${process.env.npm_package_name}</title>`
  )
}

// 方式二：返回标签描述符数组（推荐，更安全）
transformIndexHtml() {
  return [
    {
      tag: 'meta',
      attrs: { name: 'build-time', content: new Date().toISOString() },
      injectTo: 'head',
    },
    {
      tag: 'script',
      attrs: { src: '/analytics.js', defer: true },
      injectTo: 'body',
    },
  ]
}

```

**HtmlTagDescriptor 字段：**

| 字段       | 类型                                | 说明             |                |      |
| -------- | --------------------------------- | -------------- | -------------- | ---- |
| tag      | string                            | HTML 标签名       |                |      |
| attrs    | Record<string, string \| boolean> | 标签属性           |                |      |
| children | string \| HtmlTagDescriptor\[\]   | 子内容            |                |      |
| injectTo | 'head' \| 'body'                  | 'head-prepend' | 'body-prepend' | 注入位置 |

#### handleHotUpdate(ctx)

**参数（ctx 对象）：**

| 参数名       | 类型                      | 说明             |
| --------- | ----------------------- | -------------- |
| file      | string                  | 发生变化的文件绝对路径    |
| timestamp | number                  | 变化发生的时间戳       |
| modules   | ModuleNode\[\]          | 受此变化影响的模块列表    |
| read      | () => string \| Promise | 读取文件内容的函数（带缓存） |
| server    | ViteDevServer           | 开发服务器实例        |

**返回值：** `ModuleNode[] | void`。返回需要热更新的模块列表；返回空数组则不触发 HMR；返回 undefined 则使用默认逻辑。

```javascript
async handleHotUpdate({ file, modules, server }) {
  if (file.endsWith('.json')) {
    // 自定义：JSON 文件变化时，通知客户端刷新数据
    server.ws.send({
      type: 'custom',
      event: 'json-update',
      data: { file },
    })
    return [] // 阻止默认 HMR
  }

  // 过滤掉不需要热更新的模块
  return modules.filter(m => !m.url.includes('vendor'))
}

```

---

### 虚拟模块

虚拟模块允许插件向构建系统提供不存在于磁盘上的模块内容。

**约定：**

- 模块 ID 以 `virtual:` 前缀开头（面向用户的 ID）
- 在 `resolveId` 返回时，使用 `\0` 前缀（Rollup 约定，防止其他插件误处理）

```javascript
// vite-plugin-virtual-config.js
export default function vitePluginVirtualConfig(appConfig = {}) {
  const VIRTUAL_ID = 'virtual:app-config'
  const RESOLVED_ID = '\0virtual:app-config'

  return {
    name: 'vite-plugin-virtual-config',

    resolveId(id) {
      if (id === VIRTUAL_ID) {
        return RESOLVED_ID
      }
    },

    load(id) {
      if (id === RESOLVED_ID) {
        return `
          export const config = ${JSON.stringify(appConfig)};
          export default config;
        `
      }
    },
  }
}

```

**使用方式：**

```javascript
// vite.config.js
import vitePluginVirtualConfig from './vite-plugin-virtual-config'

export default {
  plugins: [
    vitePluginVirtualConfig({
      apiBaseUrl: 'https://api.example.com',
      version: '2.0.0',
    }),
  ],
}

```

```javascript
// src/app.js
import { config } from 'virtual:app-config'

console.log(config.apiBaseUrl) // 'https://api.example.com'

```

**TypeScript 类型声明：**

```typescript
// src/env.d.ts
declare module 'virtual:app-config' {
  export const config: {
    apiBaseUrl: string
    version: string
  }
  export default config
}

```

---

### 插件通信（Client-Server）

Vite 提供了开发服务器与浏览器客户端之间通过 WebSocket 通信的机制。

#### 服务端发送事件

```javascript
// 插件中（服务端）
configureServer(server) {
  server.watcher.on('change', (file) => {
    server.ws.send({
      type: 'custom',
      event: 'file-changed',
      data: { file, timestamp: Date.now() },
    })
  })
}

```

**server.ws.send 参数：**

| 参数名   | 类型       | 说明                            |
| ----- | -------- | ----------------------------- |
| type  | 'custom' | 类型固定为 'custom'（内置类型由 Vite 管理） |
| event | string   | 自定义事件名称                       |
| data  | any      | 传递给客户端的数据（需可 JSON 序列化）        |

#### 客户端监听事件

```javascript
// 客户端代码（会被 tree-shaking 掉，仅开发时有效）
if (import.meta.hot) {
  import.meta.hot.on('file-changed', (data) => {
    console.log('文件发生变化：', data.file)
    // 执行自定义更新逻辑
  })
}

```

#### 客户端发送事件

```javascript
// 客户端发送
if (import.meta.hot) {
  import.meta.hot.send('client-event', { payload: 'hello server' })
}

```

#### 服务端监听客户端事件

```javascript
configureServer(server) {
  server.ws.on('client-event', (data, client) => {
    console.log('收到客户端消息：', data.payload)
    // client 是发送消息的 WebSocket 连接
  })
}

```

#### TypeScript 扩展 CustomEventMap

```typescript
// src/env.d.ts
/// <reference types="vite/client" />

interface ImportMetaHot {
  on(event: 'file-changed', cb: (data: { file: string; timestamp: number }) => void): void
  on(event: 'build-complete', cb: (data: { duration: number }) => void): void
}

```

或使用推荐的 CustomEventMap 扩展方式：

```typescript
declare module 'vite/types/customEvent' {
  interface CustomEventMap {
    'file-changed': { file: string; timestamp: number }
    'build-complete': { duration: number }
    'client-event': { payload: string }
  }
}

```

---

### 完整插件示例：vite-plugin-auto-routes

自动扫描 `pages/` 目录，生成 Vue Router 路由配置的完整插件：

```javascript
// vite-plugin-auto-routes/index.js
import fs from 'node:fs'
import path from 'node:path'

const VIRTUAL_ID = 'virtual:auto-routes'
const RESOLVED_ID = '\0virtual:auto-routes'

/**
 * 递归扫描目录，生成路由配置
 * @param {string} dir - 扫描目录绝对路径
 * @param {string} base - 路由 base path
 * @returns {Array} 路由配置数组
 */
function scanRoutes(dir, base = '') {
  const routes = []

  if (!fs.existsSync(dir)) return routes

  const entries = fs.readdirSync(dir, { withFileTypes: true })

  for (const entry of entries) {
    const fullPath = path.join(dir, entry.name)

    if (entry.isDirectory()) {
      // 递归处理子目录
      const children = scanRoutes(fullPath, `${base}/${entry.name}`)
      if (children.length > 0) {
        routes.push({
          path: `${base}/${entry.name}`,
          children,
        })
      }
    } else if (entry.isFile() && /\.(vue|jsx|tsx)$/.test(entry.name)) {
      const name = entry.name.replace(/\.(vue|jsx|tsx)$/, '')
      const routePath = name === 'index'
        ? base || '/'
        : `${base}/${name}`

      // 处理动态路由：[id].vue -> :id
      const normalizedPath = routePath.replace(/\[(\w+)\]/g, ':$1')

      routes.push({
        path: normalizedPath,
        component: fullPath,
        name: normalizedPath.replace(/\//g, '-').replace(/^-/, '') || 'home',
      })
    }
  }

  return routes
}

/**
 * 将路由配置转换为可执行的模块代码
 */
function generateRouteCode(routes) {
  const imports = []
  const routeObjects = []

  function processRoutes(routeList, level = 0) {
    return routeList.map((route) => {
      if (route.component) {
        const importName = `Page_${imports.length}`
        imports.push(
          `const ${importName} = () => import(${JSON.stringify(route.component)})`
        )
        return `{
          path: ${JSON.stringify(route.path)},
          name: ${JSON.stringify(route.name)},
          component: ${importName},
        }`
      } else {
        const children = processRoutes(route.children || [])
        return `{
          path: ${JSON.stringify(route.path)},
          children: [${children.join(',')}],
        }`
      }
    })
  }

  const routeStrings = processRoutes(routes)

  return `
${imports.join('\n')}

export const routes = [
  ${routeStrings.join(',\n  ')}
]

export default routes
  `.trim()
}

export default function vitePluginAutoRoutes(options = {}) {
  const {
    pagesDir = 'src/pages',
    extensions = ['vue', 'jsx', 'tsx'],
  } = options

  let pagesAbsPath
  let viteConfig

  return {
    name: 'vite-plugin-auto-routes',
    enforce: 'pre',

    configResolved(config) {
      viteConfig = config
      pagesAbsPath = path.resolve(config.root, pagesDir)
    },

    resolveId(id) {
      if (id === VIRTUAL_ID) {
        return RESOLVED_ID
      }
    },

    load(id) {
      if (id !== RESOLVED_ID) return null

      const routes = scanRoutes(pagesAbsPath)
      return generateRouteCode(routes)
    },

    configureServer(server) {
      // 监听 pages 目录变化，使虚拟模块失效
      server.watcher.add(pagesAbsPath)
      server.watcher.on('add', invalidate)
      server.watcher.on('unlink', invalidate)
      server.watcher.on('addDir', invalidate)
      server.watcher.on('unlinkDir', invalidate)

      function invalidate(file) {
        if (!file.startsWith(pagesAbsPath)) return

        const mod = server.moduleGraph.getModuleById(RESOLVED_ID)
        if (mod) {
          server.moduleGraph.invalidateModule(mod)
          server.ws.send({ type: 'full-reload' })
        }
      }
    },
  }
}

```

**使用方式：**

```javascript
// vite.config.js
import autoRoutes from './vite-plugin-auto-routes'

export default {
  plugins: [
    autoRoutes({ pagesDir: 'src/pages' }),
  ],
}

```

```javascript
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import { routes } from 'virtual:auto-routes'

export const router = createRouter({
  history: createWebHistory(),
  routes,
})

```

---

## SSR（服务端渲染）

### SSR 项目结构

```
project/
├── src/
│   ├── main.js          # 通用代码，导出 createApp 工厂函数
│   ├── entry-client.js  # 客户端入口：挂载到 DOM
│   ├── entry-server.js  # 服务端入口：返回渲染结果
│   └── App.vue
├── server.js            # Express 服务器
├── index.html           # HTML 模板
└── vite.config.js

```

**src/main.js（通用代码）：**

```javascript
import { createApp } from 'vue'
import App from './App.vue'
import { createRouter } from './router'

// 每次请求都需要创建全新的实例，避免状态污染
export function createVueApp() {
  const app = createApp(App)
  const router = createRouter()
  app.use(router)
  return { app, router }
}

```

**src/entry-client.js：**

```javascript
import { createVueApp } from './main'

const { app, router } = createVueApp()

// 等待路由就绪后再挂载，确保 hydration 正确
router.isReady().then(() => {
  app.mount('#app')
})

```

**src/entry-server.js：**

```javascript
import { renderToString } from 'vue/server-renderer'
import { createVueApp } from './main'

export async function render(url, manifest) {
  const { app, router } = createVueApp()

  await router.push(url)
  await router.isReady()

  const ctx = {}
  const html = await renderToString(app, ctx)

  // 提取预加载链接（需要 manifest）
  const preloadLinks = manifest
    ? renderPreloadLinks(ctx.modules, manifest)
    : ''

  return { html, preloadLinks }
}

function renderPreloadLinks(modules, manifest) {
  let links = ''
  const seen = new Set()
  modules.forEach((id) => {
    const files = manifest[id]
    if (files) {
      files.forEach((file) => {
        if (!seen.has(file)) {
          seen.add(file)
          const ext = file.split('.').pop()
          if (ext === 'js') {
            links += `<link rel="modulepreload" crossorigin href="${file}">`
          } else if (ext === 'css') {
            links += `<link rel="stylesheet" href="${file}">`
          }
        }
      })
    }
  })
  return links
}

```

---

### 开发环境 SSR 配置

**server.js（开发 + 生产合一）：**

```javascript
import fs from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import express from 'express'

const __dirname = path.dirname(fileURLToPath(import.meta.url))
const isProd = process.env.NODE_ENV === 'production'

async function createExpressServer() {
  const app = express()

  let vite
  let template
  let render

  if (!isProd) {
    // 开发模式：使用 Vite 中间件
    const { createServer } = await import('vite')
    vite = await createServer({
      root: __dirname,
      appType: 'custom',          // 不使用 Vite 的 HTML 处理逻辑
      server: {
        middlewareMode: true,     // 中间件模式，不启动内置 HTTP 服务器
      },
    })

    // 挂载 Vite 中间件（处理模块热更新等）
    app.use(vite.middlewares)
  } else {
    // 生产模式：服务静态文件
    app.use(express.static(path.resolve(__dirname, 'dist/client'), {
      index: false, // 不自动提供 index.html，由 SSR 处理
    }))
  }

  app.use('*', async (req, res) => {
    const url = req.originalUrl

    try {
      if (!isProd) {
        // 开发模式：每次请求都读取最新的 HTML 和模块
        template = fs.readFileSync(path.resolve(__dirname, 'index.html'), 'utf-8')
        template = await vite.transformIndexHtml(url, template)
        // ssrLoadModule 自动处理 ESM 和热更新
        const serverEntry = await vite.ssrLoadModule('/src/entry-server.js')
        render = serverEntry.render
      } else {
        // 生产模式：缓存 template 和 render
        if (!template) {
          template = fs.readFileSync(
            path.resolve(__dirname, 'dist/client/index.html'),
            'utf-8'
          )
        }
        if (!render) {
          render = (await import('./dist/server/entry-server.js')).render
        }
      }

      const manifest = isProd
        ? JSON.parse(fs.readFileSync(
            path.resolve(__dirname, 'dist/client/.vite/ssr-manifest.json'),
            'utf-8'
          ))
        : {}

      const { html: appHtml, preloadLinks } = await render(url, manifest)

      const finalHtml = template
        .replace('<!--preload-links-->', preloadLinks)
        .replace('<!--app-html-->', appHtml)

      res.status(200).set({ 'Content-Type': 'text/html' }).end(finalHtml)
    } catch (e) {
      // 让 Vite 修复错误堆栈（映射回源码）
      if (vite) vite.ssrFixStacktrace(e)
      console.error(e)
      res.status(500).end(e.message)
    }
  })

  return app
}

createExpressServer().then((app) => {
  app.listen(3000, () => {
    console.log('SSR 服务器启动：http://localhost:3000')
  })
})

```

---

### 生产环境 SSR 构建

**package.json scripts：**

```json
{
  "scripts": {
    "dev": "node server.js",
    "build": "npm run build:client && npm run build:server",
    "build:client": "vite build --outDir dist/client",
    "build:server": "vite build --outDir dist/server --ssr src/entry-server.js",
    "preview": "cross-env NODE_ENV=production node server.js"
  }
}

```

**vite.config.js（SSR 项目）：**

```javascript
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  build: {
    // SSR manifest 用于生成 preload links
    ssrManifest: true,
  },
  ssr: {
    // 强制将这些包打包进 SSR bundle（不外部化）
    noExternal: ['some-lib-with-css', /^@my-org\//],
  },
})

```

---

### SSR Options 参数表

| 参数名                            | 类型                    | 默认值        | 说明                                     |      |   |                                     |
| ------------------------------ | --------------------- | ---------- | -------------------------------------- | ---- | - | ----------------------------------- |
| ssr.target                     | 'node' \| 'webworker' | 'node'     | SSR 构建目标环境                             |      |   |                                     |
| ssr.format                     | 'esm' \| 'cjs'        | 'esm'      | SSR 产物格式                               |      |   |                                     |
| ssr.noExternal                 | string \| RegExp      | (string    | RegExp)\[\]                            | true | — | 强制将指定依赖打包进 SSR bundle。true 表示打包所有依赖 |
| ssr.external                   | string\[\] \| true    | —          | 强制将指定依赖外部化（不打包）。true 表示外部化所有依赖         |      |   |                                     |
| ssr.resolve.conditions         | string\[\]            | —          | 解析依赖的 package.json exports 条件，叠加到默认条件上 |      |   |                                     |
| ssr.resolve.externalConditions | string\[\]            | \['node'\] | 解析外部化 SSR 依赖时使用的条件                     |      |   |                                     |
| ssr.optimizeDeps.enabled       | boolean               | —          | 是否为 SSR 启用依赖优化                         |      |   |                                     |

---

## 中间件模式

中间件模式允许 Vite 作为现有 HTTP 服务器的中间件，而非独立服务器。

### server.middlewareMode 配置

| 参数名                   | 类型             | 默认值      | 说明                        |                               |
| --------------------- | -------------- | -------- | ------------------------- | ----------------------------- |
| server.middlewareMode | boolean        | false    | 启用中间件模式，Vite 不创建 HTTP 服务器 |                               |
| appType               | 'spa' \| 'mpa' | 'custom' | 'spa'                     | 指定应用类型。custom 表示完全自定义 HTML 处理 |

### 与 Express 集成

```javascript
import express from 'express'
import { createServer as createViteServer } from 'vite'

const app = express()

const vite = await createViteServer({
  server: { middlewareMode: true },
  appType: 'custom',
})

// 挂载 Vite 中间件（必须在自定义路由之前）
app.use(vite.middlewares)

// 自定义 API 路由
app.get('/api/data', (req, res) => {
  res.json({ data: 'from server' })
})

// 兜底：所有非 API 路由交给 Vite/SSR 处理
app.use('*', async (req, res) => {
  // ... SSR 逻辑
})

app.listen(3000)

```

### 与 Koa 集成

```javascript
import Koa from 'koa'
import { createServer as createViteServer } from 'vite'
import { createReadStream } from 'node:fs'
import { resolve } from 'node:path'

const app = new Koa()

const vite = await createViteServer({
  server: { middlewareMode: true },
  appType: 'custom',
})

// 将 Connect 风格的中间件转换为 Koa 中间件
app.use(async (ctx, next) => {
  await new Promise((resolve, reject) => {
    vite.middlewares(ctx.req, ctx.res, (err) => {
      if (err) reject(err)
      else resolve()
    })
  })
  await next()
})

// Koa 自定义路由
app.use(async (ctx) => {
  if (ctx.path.startsWith('/api')) {
    ctx.body = { message: 'API response' }
    return
  }

  // SSR 处理
  const template = await vite.transformIndexHtml(
    ctx.url,
    '<html><body><!--app--></body></html>'
  )
  ctx.type = 'text/html'
  ctx.body = template
})

app.listen(3000, () => {
  console.log('Koa + Vite 服务器启动：http://localhost:3000')
})

```

---

## JavaScript API

### createServer(inlineConfig)

以编程方式创建 Vite 开发服务器。

**参数：**

| 参数名          | 类型           | 默认值 | 说明                 |
| ------------ | ------------ | --- | ------------------ |
| inlineConfig | InlineConfig | {}  | Vite 配置（与配置文件深度合并） |

**返回值：** `Promise<ViteDevServer>`

#### ViteDevServer 完整 API

| 属性/方法                           | 类型                               | 说明                                |
| ------------------------------- | -------------------------------- | --------------------------------- |
| config                          | ResolvedConfig                   | 最终解析后的 Vite 配置                    |
| httpServer                      | http.Server \| null              | Node.js HTTP 服务器实例。中间件模式下为 null   |
| watcher                         | FSWatcher                        | Chokidar 文件监听器实例                  |
| ws                              | WebSocketServer                  | WebSocket 服务器，用于 HMR 通信           |
| moduleGraph                     | ModuleGraph                      | 模块依赖图，追踪模块间的导入关系                  |
| pluginContainer                 | PluginContainer                  | 插件容器，可调用插件钩子                      |
| transformRequest(url, options?) | Promise<TransformResult \| null> | 对指定 URL 的模块执行 Vite 管道（解析、加载、转换）   |
| ssrLoadModule(url, options?)    | Promise<Record<string, any>>     | 加载 SSR 模块（Node.js 环境中执行）          |
| ssrFixStacktrace(e)             | void                             | 修复 SSR 错误的堆栈信息，映射回源码位置            |
| listen(port?, isRestart?)       | Promise                          | 启动 HTTP 服务器监听                     |
| close()                         | Promise                          | 关闭服务器，停止文件监听和 WebSocket           |
| restart(forceOptimize?)         | Promise                          | 重启服务器。forceOptimize=true 强制重新优化依赖 |
| printUrls()                     | void                             | 打印服务器地址信息                         |
| bindCLIShortcuts(options?)      | void                             | 绑定 CLI 快捷键（q 退出，r 重启等）            |

**编程方式使用示例：**

```javascript
import { createServer } from 'vite'

const vite = await createServer({
  root: process.cwd(),
  server: {
    port: 5173,
    host: true,
  },
})

await vite.listen()
vite.printUrls()

// 手动触发模块转换
const result = await vite.transformRequest('/src/main.js')
console.log(result?.code)

// 关闭服务器
process.on('SIGINT', async () => {
  await vite.close()
  process.exit(0)
})

```

---

### build(inlineConfig)

以编程方式执行 Vite 构建。

**参数：**

| 参数名          | 类型           | 默认值 | 说明                 |
| ------------ | ------------ | --- | ------------------ |
| inlineConfig | InlineConfig | {}  | Vite 配置（与配置文件深度合并） |

**返回值：** `Promise<RollupOutput | RollupOutput[] | RollupWatcher>`

```javascript
import { build } from 'vite'

// 基础构建
const result = await build({
  root: process.cwd(),
  build: {
    outDir: 'dist',
  },
})

// result 是 RollupOutput 数组，包含所有输出文件信息
console.log('输出文件：', result.output.map(f => f.fileName))

// watch 模式（返回 RollupWatcher）
const watcher = await build({
  build: {
    watch: {},
  },
})

watcher.on('event', (event) => {
  if (event.code === 'BUNDLE_END') {
    console.log('构建完成，耗时：', event.duration, 'ms')
  }
})

```

---

### preview(inlineConfig)

以编程方式启动预览服务器（服务 build 产物）。

**参数：**

| 参数名          | 类型           | 说明      |
| ------------ | ------------ | ------- |
| inlineConfig | InlineConfig | Vite 配置 |

**返回值：** `Promise<PreviewServer>`

```javascript
import { preview } from 'vite'

const previewServer = await preview({
  preview: {
    port: 4173,
    open: true,
  },
})

previewServer.printUrls()

```

---

### resolveConfig(inlineConfig, command, mode)

解析 Vite 配置（合并配置文件与 inlineConfig），不启动服务器。

**参数：**

| 参数名          | 类型                 | 说明       |
| ------------ | ------------------ | -------- |
| inlineConfig | InlineConfig       | 内联配置     |
| command      | 'build' \| 'serve' | 模拟的命令    |
| mode         | string             | 模拟的 mode |

**返回值：** `Promise<ResolvedConfig>`

```javascript
import { resolveConfig } from 'vite'

const config = await resolveConfig({}, 'build', 'production')
console.log('解析后的 base：', config.base)
console.log('所有插件：', config.plugins.map(p => p.name))

```

---

### loadConfigFromFile(configEnv, configFile?)

加载 Vite 配置文件。

**参数：**

| 参数名               | 类型                 | 默认值  | 说明                           |
| ----------------- | ------------------ | ---- | ---------------------------- |
| configEnv.command | 'build' \| 'serve' | 必填   | 当前命令                         |
| configEnv.mode    | string             | 必填   | 当前 mode                      |
| configFile        | string             | 自动查找 | 配置文件路径，默认自动查找 vite.config.\* |

**返回值：** `Promise<{ path, config, dependencies } | null>`

```javascript
import { loadConfigFromFile } from 'vite'

const result = await loadConfigFromFile(
  { command: 'build', mode: 'production' },
  './vite.config.ts'
)

if (result) {
  console.log('配置文件路径：', result.path)
  console.log('配置内容：', result.config)
  console.log('依赖的文件：', result.dependencies)
}

```

---

## 性能优化

### 开发服务器优化

#### server.warmup 预热关键文件

在服务器启动时提前转换关键模块，减少首次访问延迟：

```javascript
export default {
  server: {
    warmup: {
      // 预热客户端模块（路由入口、核心组件等）
      clientFiles: [
        './src/main.js',
        './src/components/Layout.vue',
        './src/router/index.js',
      ],
      // 预热 SSR 模块
      ssrFiles: [
        './src/entry-server.js',
      ],
    },
  },
}

```

| 参数名                       | 类型         | 说明                    |
| ------------------------- | ---------- | --------------------- |
| server.warmup.clientFiles | string\[\] | 预热的客户端模块路径（支持 glob）   |
| server.warmup.ssrFiles    | string\[\] | 预热的 SSR 模块路径（支持 glob） |

#### optimizeDeps 减少首次启动扫描

```javascript
export default {
  optimizeDeps: {
    // 明确指定需要预构建的依赖（避免 Vite 全量扫描）
    include: [
      'vue',
      'vue-router',
      'pinia',
      'axios',
      'lodash-es',
      // 对于深层导入也要声明
      'some-lib > nested-dep',
    ],
    // 排除不需要预构建的依赖
    exclude: ['@vite/client', '@vite/env'],
    // 对需要转换的依赖进行 esbuild 配置
    esbuildOptions: {
      target: 'esnext',
    },
    // 强制重新构建（调试时使用）
    force: false,
  },
}

```

**optimizeDeps 参数表：**

| 参数名                            | 类型                   | 默认值   | 说明                         |
| ------------------------------ | -------------------- | ----- | -------------------------- |
| optimizeDeps.entries           | string \| string\[\] | —     | 指定扫描入口，默认扫描 index.html     |
| optimizeDeps.exclude           | string\[\]           | \[\]  | 排除预构建的依赖列表                 |
| optimizeDeps.include           | string\[\]           | \[\]  | 强制预构建的依赖列表（即使被 exclude 排除） |
| optimizeDeps.esbuildOptions    | EsbuildOptions       | —     | 传递给 esbuild 的选项            |
| optimizeDeps.force             | boolean              | false | 忽略缓存，强制重新预构建               |
| optimizeDeps.holdUntilCrawlEnd | boolean              | true  | 是否等待爬取结束再发送预构建模块           |

---

### 构建产物优化

#### 代码分割策略（manualChunks）

```javascript
export default {
  build: {
    rollupOptions: {
      output: {
        manualChunks(id) {
          // Vue 相关库单独打包
          if (id.includes('/node_modules/vue') ||
              id.includes('/node_modules/@vue')) {
            return 'vue-vendor'
          }

          // UI 组件库单独打包
          if (id.includes('/node_modules/element-plus') ||
              id.includes('/node_modules/@element-plus')) {
            return 'element-plus'
          }

          // 工具库单独打包
          if (id.includes('/node_modules/lodash-es') ||
              id.includes('/node_modules/dayjs')) {
            return 'utils'
          }

          // 其余第三方依赖合并为 vendor
          if (id.includes('/node_modules/')) {
            return 'vendor'
          }

          // 业务代码按模块分割
          if (id.includes('/src/views/admin/')) {
            return 'admin'
          }
        },
      },
    },
  },
}

```

**对象形式（静态声明）：**

```javascript
export default {
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          'vue-vendor': ['vue', 'vue-router', 'pinia'],
          'ui': ['element-plus'],
          'utils': ['lodash-es', 'dayjs', 'axios'],
        },
      },
    },
  },
}

```

#### build.rollupOptions.output 参数表

| 参数名                  | 类型                                     | 默认值                                   | 说明                     |                |      |      |
| -------------------- | -------------------------------------- | ------------------------------------- | ---------------------- | -------------- | ---- | ---- |
| chunkFileNames       | string \| function                     | 'assets/\[name\]-\[hash\].js'         | 非入口 chunk 的文件名模板       |                |      |      |
| entryFileNames       | string \| function                     | 'assets/\[name\]-\[hash\].js'         | 入口 chunk 的文件名模板        |                |      |      |
| assetFileNames       | string \| function                     | 'assets/\[name\]-\[hash\]\[extname\]' | 静态资源文件名模板              |                |      |      |
| manualChunks         | Record<string, string\[\]> \| function | —                                     | 自定义 chunk 分割策略         |                |      |      |
| format               | 'es' \| 'cjs'                          | 'umd'                                 | 'iife'                 | 'system'       | 'es' | 输出格式 |
| globals              | Record<string, string>                 | —                                     | UMD/IIFE 格式中外部依赖的全局变量名 |                |      |      |
| inlineDynamicImports | boolean                                | false                                 | 是否将动态导入内联（不分割 chunk）   |                |      |      |
| sourcemap            | boolean \| 'inline'                    | 'hidden'                              | false                  | 是否生成 sourcemap |      |      |

**文件名模板可用占位符：**

| 占位符         | 说明         |
| ----------- | ---------- |
| \[name\]    | chunk 名称   |
| \[hash\]    | 基于内容的 hash |
| \[format\]  | 输出格式       |
| \[extname\] | 文件扩展名（含点号） |

---

### build 核心参数表

| 参数名                         | 类型                   | 默认值       | 说明                                |              |
| --------------------------- | -------------------- | --------- | --------------------------------- | ------------ |
| build.target                | string \| string\[\] | 'modules' | 浏览器兼容目标，传 esbuild target 格式       |              |
| build.outDir                | string               | 'dist'    | 构建输出目录                            |              |
| build.assetsDir             | string               | 'assets'  | 静态资源子目录名                          |              |
| build.assetsInlineLimit     | number               | 4096      | 小于此字节的资源转为 base64 内联              |              |
| build.cssCodeSplit          | boolean              | true      | 启用 CSS 代码分割                       |              |
| build.sourcemap             | boolean \| 'inline'  | 'hidden'  | false                             | 生成 sourcemap |
| build.rollupOptions         | RollupOptions        | {}        | 传递给 Rollup 的配置                    |              |
| build.lib                   | LibraryOptions       | —         | 构建库模式配置                           |              |
| build.ssr                   | boolean \| string    | —         | 启用 SSR 构建，传字符串时指定入口               |              |
| build.ssrManifest           | boolean \| string    | false     | 生成 SSR manifest 文件                |              |
| build.reportCompressedSize  | boolean              | true      | 在终端输出 gzip 压缩后的大小                 |              |
| build.chunkSizeWarningLimit | number               | 500       | chunk 大小警告阈值（KB）                  |              |
| build.minify                | boolean \| 'terser'  | 'esbuild' | 'esbuild'                         | 压缩工具选择       |
| build.terserOptions         | TerserOptions        | —         | Terser 压缩选项（minify: 'terser' 时有效） |              |
| build.emptyOutDir           | boolean              | true      | 构建前清空输出目录                         |              |
| build.copyPublicDir         | boolean              | true      | 是否将 public 目录文件复制到 outDir         |              |

---

### Tree Shaking 优化

**在 package.json 中声明无副作用：**

```json
{
  "name": "my-lib",
  "sideEffects": false
}

```

或精确声明有副作用的文件：

```json
{
  "sideEffects": [
    "*.css",
    "./src/polyfills.js"
  ]
}

```

**避免 import \* 的写法：**

```javascript
// 不推荐：整个 lodash 都会被打包
import _ from 'lodash'
import * as _ from 'lodash'

// 推荐：只打包使用的函数
import { debounce, throttle } from 'lodash-es'

// 推荐：按需导入（lodash-es 支持 tree shaking）
import debounce from 'lodash-es/debounce'

```

**在 vite.config.js 中配合 optimizeDeps：**

```javascript
export default {
  optimizeDeps: {
    include: ['lodash-es'],
  },
}

```

---

### 大包分析

**使用 rollup-plugin-visualizer：**

```bash
npm install -D rollup-plugin-visualizer

```

```javascript
import { defineConfig } from 'vite'
import { visualizer } from 'rollup-plugin-visualizer'

export default defineConfig({
  plugins: [
    visualizer({
      open: true,           // 构建后自动打开浏览器
      filename: 'stats.html', // 分析报告文件名
      gzipSize: true,       // 显示 gzip 后的大小
      brotliSize: true,     // 显示 brotli 后的大小
      template: 'treemap',  // 可选 'sunburst', 'network', 'raw-data', 'list'
    }),
  ],
  build: {
    // 调整 chunk 大小警告阈值
    chunkSizeWarningLimit: 1000, // KB
    // 关闭压缩大小报告（大型项目可加快构建速度）
    reportCompressedSize: false,
  },
})

```

---

## 环境变量进阶与多环境部署

### loadEnv() 在配置文件中使用

`loadEnv` 允许在 `vite.config.js` 中读取 `.env` 文件中的变量：

```javascript
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig(({ command, mode }) => {
  // 第三个参数为前缀过滤，'' 表示加载所有变量（包括 VITE_ 前缀和无前缀）
  const env = loadEnv(mode, process.cwd(), '')

  return {
    plugins: [vue()],

    define: {
      // 将 Node.js 环境变量注入到客户端代码
      __APP_VERSION__: JSON.stringify(env.npm_package_version),
      __BUILD_TIME__: JSON.stringify(new Date().toISOString()),
    },

    server: {
      proxy: {
        '/api': {
          target: env.VITE_API_BASE_URL || 'http://localhost:8080',
          changeOrigin: true,
          rewrite: (path) => path.replace(/^\/api/, ''),
        },
      },
    },

    build: {
      sourcemap: env.VITE_SOURCEMAP === 'true',
    },
  }
})

```

**loadEnv 参数：**

| 参数名      | 类型                   | 默认值      | 说明                                    |
| -------- | -------------------- | -------- | ------------------------------------- |
| mode     | string               | 必填       | 环境名（development/production/staging 等） |
| envDir   | string               | 必填       | .env 文件所在目录，通常为 process.cwd()         |
| prefixes | string \| string\[\] | 'VITE\_' | 变量前缀过滤，传 '' 加载所有变量                    |

---

### 多环境配置示例

**目录结构：**

```
.env                    # 所有环境共享的基础变量
.env.development        # 本地开发
.env.staging            # 测试环境
.env.production         # 生产环境

```

**.env（基础）：**

```bash
VITE_APP_NAME=My App
VITE_APP_VERSION=1.0.0

```

**.env.development：**

```bash
VITE_API_BASE_URL=http://localhost:8080
VITE_SOURCEMAP=true
VITE_MOCK_ENABLED=true
VITE_LOG_LEVEL=debug

```

**.env.staging：**

```bash
VITE_API_BASE_URL=https://staging-api.example.com
VITE_SOURCEMAP=true
VITE_MOCK_ENABLED=false
VITE_LOG_LEVEL=info

```

**.env.production：**

```bash
VITE_API_BASE_URL=https://api.example.com
VITE_SOURCEMAP=false
VITE_MOCK_ENABLED=false
VITE_LOG_LEVEL=error

```

**package.json scripts：**

```json
{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "build:staging": "vite build --mode staging",
    "build:production": "vite build --mode production",
    "preview": "vite preview"
  }
}

```

**TypeScript ImportMetaEnv 扩展：**

```typescript
// src/env.d.ts
/// <reference types="vite/client" />

interface ImportMetaEnv {
  // 基础（Vite 内置）
  readonly MODE: string
  readonly BASE_URL: string
  readonly PROD: boolean
  readonly DEV: boolean
  readonly SSR: boolean

  // 自定义变量
  readonly VITE_APP_NAME: string
  readonly VITE_APP_VERSION: string
  readonly VITE_API_BASE_URL: string
  readonly VITE_SOURCEMAP: string
  readonly VITE_MOCK_ENABLED: string
  readonly VITE_LOG_LEVEL: 'debug' | 'info' | 'warn' | 'error'
}

interface ImportMeta {
  readonly env: ImportMetaEnv
}

```

**在代码中使用：**

```typescript
// 有类型提示和自动补全
const apiUrl = import.meta.env.VITE_API_BASE_URL
const isMockEnabled = import.meta.env.VITE_MOCK_ENABLED === 'true'
const appName = import.meta.env.VITE_APP_NAME

```

---

## Preview Options 完整配置

`preview` 选项用于配置 `vite preview` 命令启动的预览服务器（用于本地预览构建产物）。

| 参数名                  | 类型                           | 默认值         | 说明                             |
| -------------------- | ---------------------------- | ----------- | ------------------------------ |
| preview.host         | string \| boolean            | 'localhost' | 监听的主机名。true 或 '0.0.0.0' 监听所有地址 |
| preview.allowedHosts | string\[\] \| true           | —           | 允许访问的主机名列表。true 表示允许所有主机       |
| preview.port         | number                       | 4173        | 监听端口号                          |
| preview.strictPort   | boolean                      | false       | 端口占用时是否直接退出（false 则自动递增）       |
| preview.https        | https.ServerOptions          | —           | 启用 HTTPS，传入证书配置                |
| preview.open         | boolean \| string            | false       | 启动后自动打开浏览器。字符串时指定打开的 URL 路径    |
| preview.proxy        | Record<string, ProxyOptions> | —           | 代理配置，格式与 server.proxy 相同       |
| preview.cors         | boolean \| CorsOptions       | —           | 配置 CORS                        |
| preview.headers      | OutgoingHttpHeaders          | —           | 指定响应头                          |

```javascript
export default {
  preview: {
    host: '0.0.0.0',
    port: 4173,
    strictPort: true,
    https: {
      key: fs.readFileSync('./certs/key.pem'),
      cert: fs.readFileSync('./certs/cert.pem'),
    },
    open: true,
    proxy: {
      '/api': {
        target: 'https://api.example.com',
        changeOrigin: true,
      },
    },
    cors: true,
    headers: {
      'X-Custom-Header': 'my-value',
      'Cache-Control': 'no-cache',
    },
  },
}

```

---

## Worker Options 完整配置

`worker` 选项用于配置 Web Worker 的打包方式。

| 参数名                  | 类型               | 默认值    | 说明                                          |
| -------------------- | ---------------- | ------ | ------------------------------------------- |
| worker.format        | 'es' \| 'iife'   | 'iife' | Worker bundle 的输出格式。iife 兼容性更好，es 支持 import |
| worker.plugins       | () => Plugin\[\] | —      | 应用于 Worker bundle 的 Vite 插件（工厂函数形式）         |
| worker.rollupOptions | RollupOptions    | —      | Worker bundle 的 Rollup 配置                   |

```javascript
export default {
  worker: {
    // 使用 ES 模块格式（支持在 Worker 中使用 import）
    format: 'es',

    // Worker 专用插件（使用工厂函数避免实例共享）
    plugins: () => [myWorkerPlugin()],

    rollupOptions: {
      output: {
        // Worker 产物命名规则
        entryFileNames: 'workers/[name]-[hash].js',
      },
    },
  },
}

```

**在代码中使用 Worker：**

```javascript
// 使用 ?worker 后缀导入
import MyWorker from './my.worker.js?worker'

const worker = new MyWorker()
worker.postMessage({ type: 'start', data: [1, 2, 3] })
worker.onmessage = (e) => {
  console.log('Worker 结果：', e.data)
}

// 内联 Worker（打包进主 bundle）
import InlineWorker from './my.worker.js?worker&inline'

// SharedWorker
import SharedWorkerClass from './shared.worker.js?sharedworker'
const sharedWorker = new SharedWorkerClass()

```

**Worker 文件示例（my.worker.js）：**

```javascript
// Worker 内不能访问 window、document 等 DOM API
self.onmessage = (e) => {
  const { type, data } = e.data

  if (type === 'start') {
    // 执行计算密集型任务
    const result = data.reduce((sum, n) => sum + n, 0)
    self.postMessage({ result })
  }
}

```

---

## 打包与部署

### Docker 多阶段构建

```dockerfile
# Stage 1：构建阶段
FROM node:22-alpine AS build

WORKDIR /app

# 先复制依赖文件，利用 Docker 层缓存
COPY package.json package-lock.json ./
RUN npm ci --prefer-offline

# 复制源代码并构建
COPY . .
RUN npm run build

# Stage 2：生产阶段（仅包含静态文件）
FROM nginx:1.25-alpine AS production

# 复制构建产物
COPY --from=build /app/dist /usr/share/nginx/html

# 复制自定义 Nginx 配置
COPY nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

```

**nginx.conf（SPA 路由 + 性能优化）：**

```nginx
server {
    listen 80;
    server_name _;
    root /usr/share/nginx/html;
    index index.html;

    # 开启 gzip 压缩
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_proxied expired no-cache no-store private auth;
    gzip_types
        text/plain
        text/css
        text/xml
        text/javascript
        application/javascript
        application/json
        application/xml+rss
        image/svg+xml;

    # 静态资源长期缓存（Vite 产物带 hash，可永久缓存）
    location /assets/ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        access_log off;
    }

    # index.html 不缓存（确保用户获取最新版本）
    location / {
        try_files $uri $uri/ /index.html;
        add_header Cache-Control "no-cache, no-store, must-revalidate";
        add_header Pragma "no-cache";
        add_header Expires "0";
    }

    # 安全响应头
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
}

```

**.dockerignore：**

```
node_modules
dist
.git
.gitignore
*.md
.env*

```

**构建和运行：**

```bash
docker build -t my-vite-app .
docker run -p 80:80 my-vite-app

```

---

### GitHub Actions CI/CD

**部署到 GitHub Pages：**

```yaml
# .github/workflows/deploy.yml
name: Deploy to GitHub Pages

on:
  push:
    branches: [main]
  workflow_dispatch:

# 设置 GITHUB_TOKEN 权限
permissions:
  contents: read
  pages: write
  id-token: write

# 防止并发部署
concurrency:
  group: pages
  cancel-in-progress: false

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '22'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Build
        run: npm run build
        env:
          VITE_API_BASE_URL: ${{ secrets.VITE_API_BASE_URL }}
          NODE_ENV: production

      - name: Setup Pages
        uses: actions/configure-pages@v4

      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: dist

  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    needs: build
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4

```

**部署到 VPS（通过 SSH）：**

```yaml
# .github/workflows/deploy-vps.yml
name: Deploy to VPS

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '22'
          cache: 'npm'

      - name: Install and Build
        run: |
          npm ci
          npm run build
        env:
          VITE_API_BASE_URL: ${{ secrets.VITE_API_BASE_URL }}

      - name: Deploy to VPS via SCP
        uses: appleboy/scp-action@v0.1.7
        with:
          host: ${{ secrets.VPS_HOST }}
          username: ${{ secrets.VPS_USER }}
          key: ${{ secrets.VPS_SSH_KEY }}
          source: 'dist/*'
          target: '/var/www/my-app'
          strip_components: 1

      - name: Restart Nginx
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.VPS_HOST }}
          username: ${{ secrets.VPS_USER }}
          key: ${{ secrets.VPS_SSH_KEY }}
          script: nginx -s reload

```

---

### CDN 部署（base 配置）

**部署到子路径：**

```javascript
export default {
  base: '/my-app/',  // 所有资源路径前缀
}

```

**部署到 CDN：**

```javascript
export default defineConfig(({ command }) => ({
  base: command === 'build'
    ? 'https://cdn.example.com/assets/my-app/'
    : '/',
}))

```

**动态 base（运行时注入）：**

```javascript
// vite.config.js
export default {
  experimental: {
    renderBuiltUrl(filename, { hostId, hostType, type }) {
      if (type === 'asset') {
        return `https://cdn.example.com/${filename}`
      }
      // 返回 undefined 使用默认处理
    },
  },
}

```

---

## Monorepo 配置

### 目录结构

```
my-monorepo/
├── package.json          # 根 package.json（workspaces 配置）
├── pnpm-workspace.yaml   # pnpm workspaces 配置（若用 pnpm）
├── packages/
│   ├── ui/               # 组件库
│   │   ├── package.json
│   │   ├── vite.config.js
│   │   └── src/
│   ├── app/              # 主应用
│   │   ├── package.json
│   │   ├── vite.config.js
│   │   └── src/
│   └── shared/           # 共享工具代码
│       ├── package.json
│       └── src/
└── node_modules/

```

**根 package.json（npm workspaces）：**

```json
{
  "name": "my-monorepo",
  "private": true,
  "workspaces": [
    "packages/*"
  ]
}

```

**pnpm-workspace.yaml：**

```yaml
packages:
  - 'packages/*'

```

---

### resolve.dedupe 解决 Monorepo 依赖问题

在 Monorepo 中，不同包可能各自安装了同一依赖的不同副本，导致问题（如 Vue 实例不唯一）。

```javascript
// packages/app/vite.config.js
import { defineConfig } from 'vite'
import { resolve } from 'path'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],

  resolve: {
    // 强制使用根 node_modules 中的单一副本
    dedupe: ['vue', 'vue-router', 'pinia', 'react', 'react-dom'],

    // 若使用 pnpm，symlink 指向真实路径，设为 false 避免路径解析问题
    preserveSymlinks: false,

    alias: {
      // 将 workspace 包别名指向源码（避免引用编译后版本）
      '@my-org/ui': resolve(__dirname, '../ui/src'),
      '@my-org/shared': resolve(__dirname, '../shared/src'),
    },
  },

  optimizeDeps: {
    // workspace 包的源码中如果有未预构建的依赖，需要显式包含
    include: [
      'vue',
      'pinia',
      // 对于 workspace 包中的深层依赖
      '@my-org/ui > some-dep',
    ],
    // 排除 workspace 包本身（它们会被 Vite 直接处理）
    exclude: ['@my-org/ui', '@my-org/shared'],
  },
})

```

---

### 完整 Monorepo vite.config.js 示例

**packages/ui/vite.config.js（库模式）：**

```javascript
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'

export default defineConfig({
  plugins: [vue()],

  build: {
    // 库模式构建
    lib: {
      entry: resolve(__dirname, 'src/index.js'),
      name: 'MyUI',
      formats: ['es', 'cjs'],
      fileName: (format) => `my-ui.${format}.js`,
    },
    rollupOptions: {
      // 外部化 peer dependencies
      external: ['vue'],
      output: {
        globals: { vue: 'Vue' },
        // ES 模块保留目录结构（方便 tree shaking）
        preserveModules: true,
        preserveModulesRoot: 'src',
      },
    },
    // 生成类型声明
    emptyOutDir: true,
  },
})

```

**packages/app/vite.config.js（应用模式）：**

```javascript
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'

export default defineConfig({
  plugins: [vue()],

  resolve: {
    dedupe: ['vue', 'vue-router', 'pinia'],
    alias: {
      '@': resolve(__dirname, 'src'),
      '@my-org/ui': resolve(__dirname, '../ui/src'),
      '@my-org/shared': resolve(__dirname, '../shared/src'),
    },
  },

  optimizeDeps: {
    include: [
      'vue',
      'vue-router',
      'pinia',
      // 处理 workspace 包内的 CommonJS 依赖
      '@my-org/ui > some-cjs-lib',
    ],
  },

  server: {
    fs: {
      // 允许访问 packages/ 目录（跨 workspace 引用时需要）
      allow: ['../..'],
    },
  },
})

```

---

## 高级插件示例：自定义 Mock 插件

完整可运行的 `vite-plugin-mock` 实现：

```javascript
// vite-plugin-mock/index.js
import fs from 'node:fs'
import path from 'node:path'
import { createRequire } from 'node:module'

const require = createRequire(import.meta.url)

/**
 * 解析请求体
 */
async function parseBody(req) {
  return new Promise((resolve) => {
    let body = ''
    req.on('data', (chunk) => { body += chunk.toString() })
    req.on('end', () => {
      try {
        resolve(body ? JSON.parse(body) : {})
      } catch {
        resolve({})
      }
    })
  })
}

/**
 * 从 mock 目录加载所有 mock 文件
 */
function loadMockFiles(mockDir) {
  const mocks = []

  if (!fs.existsSync(mockDir)) return mocks

  const files = fs.readdirSync(mockDir).filter(f => f.endsWith('.js'))

  for (const file of files) {
    const filePath = path.join(mockDir, file)
    try {
      // 清除 require 缓存，支持热重载
      delete require.cache[filePath]
      const mockModule = require(filePath)
      const mockList = Array.isArray(mockModule) ? mockModule : [mockModule]
      mocks.push(...mockList)
    } catch (e) {
      console.error(`[vite-plugin-mock] 加载 mock 文件失败：${file}`, e.message)
    }
  }

  return mocks
}

/**
 * 匹配 mock 路由
 */
function matchMock(mocks, url, method) {
  return mocks.find((mock) => {
    const methodMatch = !mock.method || mock.method.toUpperCase() === method.toUpperCase()
    const urlMatch = typeof mock.url === 'string'
      ? mock.url === url.split('?')[0]
      : mock.url instanceof RegExp
        ? mock.url.test(url)
        : false
    return methodMatch && urlMatch
  })
}

export default function vitePluginMock(options = {}) {
  const {
    mockDir = 'mock',      // mock 文件目录
    enable = true,         // 是否启用
    logger = true,         // 是否打印日志
  } = options

  return {
    name: 'vite-plugin-mock',
    apply: 'serve',        // 仅在开发服务器中启用

    configureServer(server) {
      if (!enable) return

      const mockAbsDir = path.resolve(process.cwd(), mockDir)
      let mocks = loadMockFiles(mockAbsDir)

      // 监听 mock 目录变化，自动重新加载
      server.watcher.add(mockAbsDir)
      server.watcher.on('change', (file) => {
        if (file.startsWith(mockAbsDir)) {
          mocks = loadMockFiles(mockAbsDir)
          if (logger) {
            console.log(`[vite-plugin-mock] mock 文件已更新：${path.basename(file)}`)
          }
        }
      })

      // 在内置中间件之前插入 mock 中间件
      server.middlewares.use(async (req, res, next) => {
        const matched = matchMock(mocks, req.url, req.method)

        if (!matched) return next()

        if (logger) {
          console.log(`[vite-plugin-mock] ${req.method} ${req.url}`)
        }

        // 支持延迟响应
        if (matched.delay && matched.delay > 0) {
          await new Promise(resolve => setTimeout(resolve, matched.delay))
        }

        const body = await parseBody(req)

        // 支持函数形式的响应（动态响应）
        let responseData
        if (typeof matched.response === 'function') {
          responseData = await matched.response({
            url: req.url,
            method: req.method,
            body,
            headers: req.headers,
            query: Object.fromEntries(new URL(req.url, 'http://localhost').searchParams),
          })
        } else {
          responseData = matched.response
        }

        const statusCode = matched.statusCode || 200

        res.writeHead(statusCode, {
          'Content-Type': 'application/json',
          'Access-Control-Allow-Origin': '*',
          ...(matched.headers || {}),
        })

        res.end(JSON.stringify(responseData))
      })
    },
  }
}

```

**Mock 文件示例（mock/user.js）：**

```javascript
// mock/user.js
module.exports = [
  {
    url: '/api/users',
    method: 'GET',
    delay: 200, // 模拟 200ms 延迟
    response: ({ query }) => {
      const page = parseInt(query.page) || 1
      const pageSize = parseInt(query.pageSize) || 10

      const users = Array.from({ length: pageSize }, (_, i) => ({
        id: (page - 1) * pageSize + i + 1,
        name: `用户 ${(page - 1) * pageSize + i + 1}`,
        email: `user${i + 1}@example.com`,
        role: i % 3 === 0 ? 'admin' : 'user',
      }))

      return {
        code: 0,
        message: 'success',
        data: {
          list: users,
          total: 100,
          page,
          pageSize,
        },
      }
    },
  },

  {
    url: '/api/users/login',
    method: 'POST',
    delay: 500,
    response: ({ body }) => {
      const { username, password } = body

      if (username === 'admin' && password === '123456') {
        return {
          code: 0,
          message: '登录成功',
          data: {
            token: 'mock-jwt-token-' + Date.now(),
            userInfo: { id: 1, name: 'Admin', role: 'admin' },
          },
        }
      }

      return {
        code: 401,
        message: '用户名或密码错误',
        data: null,
      }
    },
  },

  {
    url: /^\/api\/users\/\d+$/,  // 正则匹配
    method: 'GET',
    response: ({ url }) => {
      const id = url.split('/').pop()
      return {
        code: 0,
        data: { id: parseInt(id), name: `用户 ${id}`, email: `user${id}@example.com` },
      }
    },
  },
]

```

**vite.config.js 使用：**

```javascript
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import mockPlugin from './vite-plugin-mock'

export default defineConfig(({ command }) => ({
  plugins: [
    vue(),
    mockPlugin({
      mockDir: 'mock',
      enable: command === 'serve', // 仅开发环境启用
      logger: true,
    }),
  ],
}))

```

---

## 踩坑与最佳实践清单

### SSR Hydration 不匹配问题

**问题描述：** 服务端渲染的 HTML 与客户端 Vue 重新渲染的结果不一致，导致 hydration 失败，出现闪烁或警告。

**常见原因及解决方案：**

```javascript
// 错误：在服务端和客户端使用不一致的随机值
const id = Math.random() // 每次调用结果不同

// 错误：直接使用 Date.now()
const timestamp = Date.now()

// 正确：使用可预测的、基于请求的数据
// 在 SSR 中通过 provide/inject 传递，确保客户端复水时一致

// 错误：服务端访问了 window 对象
if (window !== undefined) { ... }

// 正确：使用平台判断
if (typeof window !== 'undefined') { ... }
// 或在 onMounted 中处理（仅在客户端运行）
onMounted(() => {
  // 这里的代码只在客户端执行
})

```

---

### build.target 和 define 组合使用时的注意事项

**问题：** 当 `build.target` 低于某些语法支持版本时，`define` 替换后的代码可能无法正确压缩。

```javascript
// 问题示例
export default {
  build: {
    target: 'es2015', // 目标 ES2015
  },
  define: {
    // 这里替换的是字面量，esbuild 会进行常量折叠
    __DEV__: false,
    // 注意：不要 define 对象或数组，只 define 基本类型
    __CONFIG__: JSON.stringify({ key: 'value' }), // 正确
  },
}

// 陷阱：define 的值在替换时是直接字符串替换，不是 AST 转换
// 错误：这会替换成 undefined（字符串）
define: {
  'process.env.NODE_ENV': undefined, // 错误
}

// 正确：始终用 JSON.stringify 包裹字符串值
define: {
  'process.env.NODE_ENV': JSON.stringify('production'), // 正确
}

```

---

### manualChunks 循环依赖导致 chunk 爆炸

**问题：** 错误的 manualChunks 配置可能导致模块被重复打包到多个 chunk 中，造成产物体积暴增。

```javascript
// 危险：如果 A 依赖 B，B 依赖 A，强行拆分会导致循环
// 正确做法：使用函数形式，让 Rollup 帮助处理依赖关系

// 推荐：先分析依赖关系，再配置（用 visualizer 先可视化）
build: {
  rollupOptions: {
    output: {
      manualChunks(id) {
        // 不要把相互依赖的模块拆分到不同 chunk
        if (id.includes('/node_modules/')) {
          // 安全做法：按大的包名分组，不要过细拆分
          const match = id.match(/node_modules\/([^/]+)/)
          if (match) {
            const pkg = match[1]
            // 只对体积大的包单独分割
            if (['lodash-es', 'echarts', 'monaco-editor'].includes(pkg)) {
              return pkg
            }
            return 'vendor'
          }
        }
      },
    },
  },
}

```

---

### 插件 enforce 顺序问题

**执行顺序总览：**

```
enforce: 'pre' 插件
  → Vite 核心插件（HTML、alias 等）
    → 无 enforce 插件
      → Vite 构建插件
        → enforce: 'post' 插件

```

**常见误区：**

```javascript
// 错误：在无 enforce 插件中尝试处理原始 import alias
// 此时 alias 已被 Vite 核心插件处理，拿到的已经是解析后的路径

// 正确：若需要在 alias 解析前处理，使用 enforce: 'pre'
{
  name: 'my-resolver',
  enforce: 'pre',
  resolveId(source) {
    // 此时 source 是原始导入字符串，alias 尚未替换
  }
}

// transform 插件的顺序同样受 enforce 影响
// 若需要在 Vue/React 编译前处理源码，使用 enforce: 'pre'
// 若需要在编译后处理，使用 enforce: 'post'

```

---

### server.fs.deny 拦截 .env 文件

**默认行为：** Vite 默认会拦截对 `.env` 文件的直接 HTTP 请求，防止敏感信息泄露。

```javascript
// 注意：Vite 默认拒绝访问 .env 文件
// 如果你的 API 代理路径包含 '.env' 字符串，会被误拦截

// 错误的代理配置（路径含 .env）
server: {
  proxy: {
    '/api/.env-config': 'http://backend.com', // 会被 Vite 拦截！
  }
}

// 查看默认 fs.deny 规则：
server: {
  fs: {
    deny: ['.env', '.env.*', '*.{crt,pem}'] // Vite 默认值
  }
}

// 若确实需要允许某类文件（谨慎操作）：
server: {
  fs: {
    // 移除 .env 拦截（不推荐）
    deny: ['*.{crt,pem}'],
  }
}

```

---

### Worker 中不能使用 window

```javascript
// 错误：Worker 中没有 window 对象
self.onmessage = (e) => {
  window.location.href // ReferenceError: window is not defined
  document.querySelector('.app') // ReferenceError: document is not defined
  localStorage.getItem('key') // Worker 中不可用
}

// 正确：Worker 中使用 self 代替 window，且只能访问受限 API
self.onmessage = (e) => {
  // 可用：self, postMessage, fetch, XMLHttpRequest, WebSocket
  // 可用：setTimeout, setInterval, Promise, crypto
  // 可用：IndexedDB（通过 self.indexedDB）
  // 不可用：window, document, localStorage, sessionStorage, alert

  fetch('/api/data').then(r => r.json()).then(data => {
    self.postMessage(data)
  })
}

```

---

### 其他最佳实践

**避免在 vite.config.js 中进行大量文件系统操作：**

```javascript
// 不推荐：在配置中使用大量 glob（每次启动都会执行）
import { glob } from 'glob'
const entries = glob.sync('src/pages/**/*.vue') // 每次启动都扫描

// 推荐：将扫描逻辑放到插件的 buildStart 钩子中（仅构建时执行一次）
// 或放到 configureServer 中（仅开发时执行）

```

**正确使用 this.emitFile 生成额外文件：**

```javascript
// 在插件中生成额外的输出文件
{
  name: 'my-plugin',
  generateBundle(options, bundle) {
    // 生成额外文件
    this.emitFile({
      type: 'asset',
      fileName: 'manifest.json',
      source: JSON.stringify({ version: '1.0.0' }),
    })
  }
}

```

**CSS Modules 命名冲突：**

```javascript
// vite.config.js
export default {
  css: {
    modules: {
      // 开发模式：保留原始类名方便调试
      generateScopedName: process.env.NODE_ENV === 'development'
        ? '[name]__[local]'
        : '[hash:base64:8]',
      // 避免不同模块的类名哈希冲突
      hashPrefix: 'my-app',
    },
  },
}

```

**避免在生产构建中包含开发工具：**

```javascript
// 正确做法：使用 import.meta.env.DEV 进行条件导入
if (import.meta.env.DEV) {
  // 这段代码和导入会在生产构建中被 tree-shaking 移除
  const { setupDevtools } = await import('./devtools')
  setupDevtools(app)
}

```

---

## 最佳实践

**插件的 `enforce` 字段明确执行时机**：插件不声明 `enforce` 时默认在 Vite 核心插件之后运行。需要在 `resolve` 之前介入（如路径别名插件）用 `enforce: 'pre'`；需要在 `build` 输出之后处理（如 HTML 注入）用 `enforce: 'post'`。混淆时机是插件 Bug 最常见的根源。

**SSR 插件用 `apply` 字段区分 serve/build**：部分插件（如热更新辅助）只在 `serve` 阶段有意义；另一些（如 CDN 替换）只在 `build` 阶段需要。用 `apply: 'serve'` 或 `apply: 'build'` 缩小插件作用范围，避免 SSR 构建时加载无用插件。

**`resolveId` 返回虚拟模块时加 `\0` 前缀**：Vite/Rollup 约定以 `\0` 开头的 ID 为虚拟模块，不会被其他插件（如 node\_modules 解析）拦截处理，避免路径冲突。

```ts
resolveId(id) {
  if (id === 'virtual:my-module') return '\0virtual:my-module'
},
load(id) {
  if (id === '\0virtual:my-module') return `export const msg = 'hello'`
},

```

**`createServer` 的 `middlewareMode` 用于嵌入现有 HTTP 框架**：将 Vite 作为中间件集成到 Express/Fastify 时，设置 `server: { middlewareMode: true }`，不让 Vite 启动自己的 HTTP 服务器，避免端口冲突和双重请求处理。

**环境变量文件按优先级顺序覆盖**：`.env.production.local > .env.production > .env.local > .env`，本地覆盖文件（`.local`）加入 `.gitignore`，不提交到仓库，团队共享变量放 `.env.production`。

---

## 常见陷阱

### 陷阱：SSR 构建产物在 Node.js 运行时报 `ReferenceError: window is not defined`

**现象：** SSR 模式下启动服务端渲染，某个第三方库在导入时直接访问 `window`/`document`，构建通过但运行时崩溃。  
**原因：** Vite SSR 构建输出的是 Node.js 可执行的 CommonJS/ESM 包，但部分前端库假设浏览器环境，在模块顶层读取 `window`。  
**解决：** 在 `vite.config.ts` 中把问题包加入 `ssr.noExternal`（强制打包并让 Vite 注入 shim），或在动态 import 中延迟加载该库，或在服务端环境提供 `global.window = {}` 占位。

### 陷阱：自定义插件的 `transform` 在生产构建中不触发

**现象：** 开发服务器下插件生效，`vite build` 后功能缺失。  
**原因：** 部分开发者在 `transform` 钩子中写了提前返回逻辑（如 `if (!this.server) return`），或插件依赖开发服务器特有的 API（如 `server.moduleGraph`），导致生产构建路径跳过了转换。  
**解决：** 在 `configResolved` 钩子中保存 `config`，区分 `command === 'serve'` 和 `'build'` 两条路径，确保 `transform` 在两种模式下都执行必要逻辑。

### 陷阱：`build.rollupOptions.output.manualChunks` 导致循环依赖警告

**现象：** 配置 `manualChunks` 将某些模块合并后，Rollup 输出大量 circular dependency 警告，或运行时出现变量未定义错误。  
**原因：** `manualChunks` 强制将模块分组，若分组破坏了 Rollup 自动确定的模块加载顺序，会引入循环引用。  
**解决：** 优先使用 `manualChunks` 的函数形式而非对象形式，让 Rollup 仍能分析依赖关系；避免把有相互依赖的模块拆到不同 chunk；出现问题时先移除 `manualChunks` 验证是否是它引起的。

---

## 参见

[Vite初级指南](https://blog.vercanti.com/vite-chu-ji-zhi-nan/)  
[Vite中级指南](https://blog.vercanti.com/vite-zhong-ji-zhi-nan/)  
[TypeScript完全指南](https://blog.vercanti.com/typescript-wan-quan-zhi-nan/)