> ## 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-zhong-ji-zhi-nan/
- Published: 2026-08-28T14:35:23.000Z
- Updated: 2026-08-28T14:58:42.000Z
- Description: 本文覆盖 Vite 中级配置与使用模式，包括完整的配置项参考、多页面应用、库模式、环境变量进阶等内容。适合已掌握 Vite 基础的开发者。 需要加入 include 的场景： 1. 动态 import() 引入的 CommonJS 包（Vite 静态扫描时发现不了） 2. monorepo 中的本地包（链接包不走预构建，但其依赖可能是 CJS） 3. 某些通过插件间接引入的依赖 需要加入 exclude 的场景： 1. 已是纯 ESM 的包且体积小，无需合并 2. 包含 native 模块，esbuild 无法处理 3. 包含副作用，不适合被 esbui
- Author: yellowdog
- Tags: 前端开发, Vite

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

本文覆盖 Vite 中级配置与使用模式，包括完整的配置项参考、多页面应用、库模式、环境变量进阶等内容。适合已掌握 Vite 基础的开发者。

---

## 1\. 完整配置参考

### Shared Options 共享配置

| 参数名           | 类型                   | 默认值                                 | 可选值                                  | 说明                             |   |               |
| ------------- | -------------------- | ----------------------------------- | ------------------------------------ | ------------------------------ | - | ------------- |
| root          | string               | process.cwd()                       | 任意绝对/相对路径                            | 项目根目录，index.html 所在位置          |   |               |
| base          | string               | '/'                                 | 任意路径前缀字符串                            | 公共基础路径，所有静态资源和路由的前缀            |   |               |
| mode          | string               | 'development'（开发）/ 'production'（构建） | 任意字符串                                | 运行模式，影响 .env 文件加载              |   |               |
| define        | Record<string, any>  | {}                                  | —                                    | 全局常量替换，编译期替换（非运行时）             |   |               |
| plugins       | Plugin\[\]           | \[\]                                | —                                    | Vite/Rollup 插件数组               |   |               |
| publicDir     | string \| false      | 'public'                            | 任意路径或 false                          | 静态资源目录，构建时原样复制到 outDir         |   |               |
| cacheDir      | string               | 'node\_modules/.vite'               | 任意路径                                 | 预构建缓存目录                        |   |               |
| assetsInclude | string \| RegExp     | (string                             | RegExp)\[\]                          | —                              | — | 额外视为静态资源的文件类型 |
| logLevel      | string               | 'info'                              | 'info' / 'warn' / 'error' / 'silent' | 控制台日志级别                        |   |               |
| customLogger  | Logger               | —                                   | —                                    | 自定义日志对象，需实现 info/warn/error 方法 |   |               |
| clearScreen   | boolean              | true                                | true / false                         | 每次日志输出前是否清屏                    |   |               |
| envDir        | string               | root                                | 任意路径                                 | .env 文件所在目录                    |   |               |
| envPrefix     | string \| string\[\] | 'VITE\_'                            | 任意前缀字符串                              | 只有匹配前缀的环境变量才暴露给客户端             |   |               |
| appType       | string               | 'spa'                               | 'spa' / 'mpa' / 'custom'             | 应用类型，影响 HTML 中间件和 404 回退行为     |   |               |
| future        | object               | {}                                  | —                                    | 启用未来版本的 breaking change 兼容标志   |   |               |

```js
// vite.config.js 示例
import { defineConfig } from 'vite'

export default defineConfig({
  root: './src',
  base: '/my-app/',
  mode: 'development',
  define: {
    __APP_VERSION__: JSON.stringify('1.0.0'),
    __DEV__: true,
  },
  publicDir: 'public',
  cacheDir: 'node_modules/.vite',
  logLevel: 'info',
  clearScreen: false,
  envDir: '.',
  envPrefix: ['VITE_', 'APP_'],
  appType: 'spa',
})

```

---

### resolve 配置完整参数表

| 参数名                      | 类型                                                                    | 默认值                                                 | 可选值             | 说明                             |
| ------------------------ | --------------------------------------------------------------------- | --------------------------------------------------- | --------------- | ------------------------------ |
| resolve.alias            | Record<string, string> \| Array<{find, replacement, customResolver?}> | {}                                                  | —               | 路径别名，将导入路径映射到实际路径              |
| resolve.dedupe           | string\[\]                                                            | \[\]                                                | —               | 强制将指定包解析到同一副本，解决多副本问题          |
| resolve.conditions       | string\[\]                                                            | \`\['module', 'browser', 'development               | production'\]\` | —                              |
| resolve.mainFields       | string\[\]                                                            | \['browser', 'module', 'jsnext:main', 'jsnext'\]    | —               | package.json 主入口字段的解析顺序        |
| resolve.extensions       | string\[\]                                                            | \['.mjs','.js','.mts','.ts','.jsx','.tsx','.json'\] | —               | 导入省略扩展名时的自动补全顺序                |
| resolve.preserveSymlinks | boolean                                                               | false                                               | true / false    | 是否保留符号链接（不解析到真实路径），monorepo 常用 |

```js
import { defineConfig } from 'vite'
import path from 'path'

export default defineConfig({
  resolve: {
    // 对象写法（简单别名）
    alias: {
      '@': path.resolve(__dirname, './src'),
      '@components': path.resolve(__dirname, './src/components'),
      '@utils': path.resolve(__dirname, './src/utils'),
    },

    // 数组写法（支持自定义解析器）
    // alias: [
    //   {
    //     find: '@',
    //     replacement: path.resolve(__dirname, './src'),
    //     customResolver(source, importer, options) {
    //       // 自定义解析逻辑，返回 null 则走默认解析
    //       return null
    //     },
    //   },
    // ],

    dedupe: ['vue', 'vue-router'],

    conditions: ['module', 'browser', 'development'],

    mainFields: ['browser', 'module', 'main'],

    extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json'],

    preserveSymlinks: false,
  },
})

```

---

### CSS 配置完整参数表

#### css.modules 参数

| 参数名                | 类型                 | 默认值             | 可选值                                                     | 说明                        |
| ------------------ | ------------------ | --------------- | ------------------------------------------------------- | ------------------------- |
| localsConvention   | string \| function | 'camelCaseOnly' | 'camelCase' / 'camelCaseOnly' / 'dashes' / 'dashesOnly' | 导出的类名格式转换规则               |
| scopeBehaviour     | string             | 'local'         | 'local' / 'global'                                      | 默认作用域行为                   |
| globalModulePaths  | RegExp\[\]         | \[\]            | —                                                       | 不应用 CSS Modules 的文件路径匹配规则 |
| generateScopedName | string \| function | —               | —                                                       | 自定义生成作用域类名的函数或模板字符串       |
| hashPrefix         | string             | ''              | —                                                       | 生成哈希时的前缀，影响最终类名           |
| exportGlobals      | boolean            | false           | true / false                                            | 是否将 :global 类也导出          |
| autoModules        | boolean \| RegExp  | —               | —                                                       | 自动启用 CSS Modules 的文件匹配规则  |

#### css 顶层参数

| 参数名                        | 类型                      | 默认值       | 可选值                        | 说明                                       |
| -------------------------- | ----------------------- | --------- | -------------------------- | ---------------------------------------- |
| css.modules                | CSSModulesOptions       | —         | —                          | CSS Modules 配置对象                         |
| css.postcss                | string \| PostCSSConfig | —         | —                          | 内联 PostCSS 配置，或 postcss.config.js 所在目录路径 |
| css.preprocessorOptions    | Record<string, object>  | {}        | —                          | 传递给各 CSS 预处理器的选项（sass/less/stylus）       |
| css.preprocessorMaxWorkers | number \| true          | true      | 数字或 true                   | CSS 预处理器并行 Worker 数量，true 为 CPU 核心数减一    |
| css.devSourcemap           | boolean                 | false     | true / false               | 开发模式下是否生成 CSS source map                 |
| css.transformer            | string                  | 'postcss' | 'postcss' / 'lightningcss' | CSS 转换引擎选择                               |
| css.lightningcss           | LightningCSSOptions     | —         | —                          | 使用 lightningcss 时的配置项                    |

```js
import { defineConfig } from 'vite'

export default defineConfig({
  css: {
    // CSS Modules 配置
    modules: {
      localsConvention: 'camelCaseOnly',
      scopeBehaviour: 'local',
      globalModulePaths: [/global\.css$/],
      generateScopedName: '[name]__[local]___[hash:base64:5]',
      hashPrefix: 'my-app',
      exportGlobals: false,
    },

    // 内联 PostCSS 配置（也可以用 postcss.config.js 文件）
    postcss: {
      plugins: [
        // autoprefixer(),
        // postcssNested(),
      ],
    },

    // 预处理器选项
    preprocessorOptions: {
      scss: {
        // 全局注入变量/mixin（每个 scss 文件顶部自动追加）
        additionalData: `
          @use "@/styles/variables" as *;
          @use "@/styles/mixins" as *;
        `,
        // sass 编译器选项
        api: 'modern-compiler',
      },
      less: {
        modifyVars: {
          'primary-color': '#1890ff',
        },
        javascriptEnabled: true,
      },
      stylus: {
        define: {
          $primary: '#1890ff',
        },
      },
    },

    preprocessorMaxWorkers: true,
    devSourcemap: true,
    transformer: 'postcss',
  },
})

```

#### CSS Modules 完整用法

```css
/* Button.module.css */

/* 局部作用域（默认） */
.button {
  padding: 8px 16px;
  background: var(--color-primary);
}

/* 全局作用域 */
:global(.global-class) {
  color: red;
}

/* 组合（composes） */
.primaryButton {
  composes: button;
  background: blue;
}

/* 从其他模块组合 */
.iconButton {
  composes: icon from './Icon.module.css';
}

```

```jsx
// 在 React 中使用
import styles from './Button.module.css'

function Button({ children }) {
  return (
    <button className={styles.button}>
      {children}
    </button>
  )
}

```

---

### JSON 配置

| 参数名               | 类型                | 默认值    | 可选值                   | 说明                                                                              |
| ----------------- | ----------------- | ------ | --------------------- | ------------------------------------------------------------------------------- |
| json.namedExports | boolean           | true   | true / false          | 是否支持从 JSON 文件按名导入字段                                                             |
| json.stringify    | boolean \| 'auto' | 'auto' | true / false / 'auto' | 为 true 时将 JSON 序列化为字符串（JSON.parse('{...}')），体积更小但不支持按名导入；'auto' 会根据 JSON 大小自动选择 |

```js
export default defineConfig({
  json: {
    namedExports: true,
    stringify: 'auto',
  },
})

```

```js
// 按名导入（需要 namedExports: true）
import { version, name } from './package.json'

// 全量导入
import pkg from './package.json'

```

---

### esbuild 配置

| 参数名                 | 类型                      | 默认值                   | 可选值            | 说明                                     |
| ------------------- | ----------------------- | --------------------- | -------------- | -------------------------------------- |
| esbuild             | ESBuildOptions \| false | {}                    | 对象或 false      | 传递给 esbuild 的选项；设为 false 禁用 esbuild 转换 |
| esbuild.jsxFactory  | string                  | 'React.createElement' | 任意字符串          | JSX 工厂函数                               |
| esbuild.jsxFragment | string                  | 'React.Fragment'      | 任意字符串          | JSX Fragment 组件                        |
| esbuild.jsxInject   | string                  | —                     | —              | 自动注入到每个需要转换的文件头部的代码                    |
| esbuild.target      | string \| string\[\]    | —                     | 见 build.target | 单独为 esbuild 设置转换目标                     |
| esbuild.minify      | boolean                 | —                     | true / false   | esbuild 是否压缩（通常由 build.minify 控制）      |

```js
export default defineConfig({
  esbuild: {
    // React 17 之前的 JSX 转换
    jsxFactory: 'React.createElement',
    jsxFragment: 'React.Fragment',
    // 自动注入 React，省去每个文件手动 import
    jsxInject: `import React from 'react'`,

    // 也可以用于 Preact
    // jsxFactory: 'h',
    // jsxFragment: 'Fragment',
    // jsxInject: `import { h, Fragment } from 'preact'`,

    // 针对低版本浏览器时可单独设置
    target: 'es2015',
  },
})

```

---

### html 配置

| 参数名           | 类型     | 默认值 | 可选值      | 说明                                                                                   |
| ------------- | ------ | --- | -------- | ------------------------------------------------------------------------------------ |
| html.cspNonce | string | —   | 任意占位符字符串 | Content Security Policy nonce 占位符，Vite 会将该占位符替换为实际 nonce 值并注入到 <script> / <style> 标签 |

```js
export default defineConfig({
  html: {
    cspNonce: '%%NONCE%%',
  },
})

```

---

## 2\. Server 完整配置

| 参数名                        | 类型                           | 默认值                               | 可选值            | 说明                                       |
| -------------------------- | ---------------------------- | --------------------------------- | -------------- | ---------------------------------------- |
| server.host                | string \| boolean            | 'localhost'                       | 任意 IP / true   | 监听地址；true 或 '0.0.0.0' 表示监听所有网络接口         |
| server.allowedHosts        | string\[\] \| true           | \[\]                              | 域名数组或 true     | 允许访问开发服务器的主机名白名单；true 为全部允许（防 DNS 重绑定攻击） |
| server.port                | number                       | 5173                              | 任意端口号          | 开发服务器端口                                  |
| server.strictPort          | boolean                      | false                             | true / false   | 端口被占用时是否直接报错退出（而非自动换端口）                  |
| server.https               | https.ServerOptions          | —                                 | —              | 启用 TLS，传入证书配置对象                          |
| server.open                | boolean \| string            | false                             | true / 相对路径字符串 | 启动时自动打开浏览器；字符串则打开指定路径                    |
| server.proxy               | Record<string, ProxyOptions> | —                                 | —              | 代理规则，键为路径前缀，值为代理目标配置                     |
| server.cors                | boolean \| CorsOptions       | false                             | —              | 跨域资源共享配置                                 |
| server.headers             | OutgoingHttpHeaders          | —                                 | —              | 自定义响应头                                   |
| server.hmr                 | boolean \| HmrOptions        | true                              | —              | 热模块替换配置；false 禁用 HMR                     |
| server.warmup              | {clientFiles?, ssrFiles?}    | —                                 | —              | 预热文件列表，服务器启动时提前转换，加快首次访问速度               |
| server.watch               | WatchOptions \| null         | —                                 | —              | 文件监听配置，传给 chokidar                       |
| server.middlewareMode      | boolean                      | false                             | true / false   | 以中间件模式运行（不创建 HTTP 服务器，供自定义服务器集成）         |
| server.fs.strict           | boolean                      | true                              | true / false   | 限制只能访问工作区根目录范围内的文件                       |
| server.fs.allow            | string\[\]                   | —                                 | 路径数组           | 允许访问的额外目录（在 strict 模式下生效）                |
| server.fs.deny             | string\[\]                   | \['.env', '\*.pem', '.git/\*\*'\] | —              | 始终拒绝访问的敏感文件模式                            |
| server.origin              | string                       | —                                 | URL 字符串        | 资源 URL 的 origin，用于开发时生成绝对 URL            |
| server.sourcemapIgnoreList | false \| function            | —                                 | —              | 控制哪些文件的 source map 被标记为忽略（影响浏览器调试面板）     |

#### HMR 配置详细参数

| 参数名        | 类型      | 默认值          | 说明                  |
| ---------- | ------- | ------------ | ------------------- |
| protocol   | string  | 'ws' / 'wss' | WebSocket 协议        |
| host       | string  | server.host  | HMR 连接的主机名          |
| port       | number  | server.port  | HMR WebSocket 端口    |
| clientPort | number  | —            | 客户端连接使用的端口（反向代理场景用） |
| path       | string  | —            | WebSocket 路径        |
| timeout    | number  | 30000        | 连接超时时间（毫秒）          |
| overlay    | boolean | true         | 是否显示错误覆盖层           |

```js
export default defineConfig({
  server: {
    host: '0.0.0.0',
    port: 3000,
    strictPort: true,
    open: '/',
    allowedHosts: ['my-app.local', 'localhost'],

    // HTTPS 配置（配合 @vitejs/plugin-basic-ssl 或手动证书）
    // https: {
    //   key: fs.readFileSync('./certs/key.pem'),
    //   cert: fs.readFileSync('./certs/cert.pem'),
    // },

    // 代理配置
    proxy: {
      // 简单代理
      '/api': 'http://localhost:8080',

      // 带选项的代理
      '/api/v2': {
        target: 'http://api.example.com',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api\/v2/, ''),
      },

      // WebSocket 代理
      '/ws': {
        target: 'ws://localhost:8080',
        ws: true,
        changeOrigin: true,
      },

      // 正则代理
      '^/fallback/.*': {
        target: 'http://jsonplaceholder.typicode.com',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/fallback/, ''),
        // 自定义请求头
        headers: {
          'X-Custom-Header': 'foobar',
        },
        // 代理响应事件钩子
        configure: (proxy, options) => {
          proxy.on('error', (err, req, res) => {
            console.log('proxy error', err)
          })
          proxy.on('proxyReq', (proxyReq, req, res) => {
            console.log('Sending Request:', req.method, req.url)
          })
          proxy.on('proxyRes', (proxyRes, req, res) => {
            console.log('Received Response:', proxyRes.statusCode, req.url)
          })
        },
      },
    },

    cors: {
      origin: ['http://localhost:3001', 'https://my-app.com'],
      methods: ['GET', 'POST', 'PUT', 'DELETE'],
      credentials: true,
    },

    headers: {
      'X-Content-Type-Options': 'nosniff',
      'X-Frame-Options': 'DENY',
    },

    hmr: {
      protocol: 'ws',
      host: 'localhost',
      port: 5174,
      overlay: true,
    },

    warmup: {
      clientFiles: ['./src/components/**/*.vue', './src/pages/**/*.vue'],
      ssrFiles: ['./src/server/**/*.ts'],
    },

    watch: {
      // 忽略 node_modules 中的变化（默认行为）
      ignored: ['**/node_modules/**', '**/.git/**'],
      // 在 WSL/Docker 中可能需要轮询
      // usePolling: true,
      // interval: 100,
    },

    fs: {
      strict: true,
      allow: [
        // 允许访问项目根目录外的共享包
        '../shared-lib',
      ],
      deny: ['.env', '.env.*', '*.pem', '.git/**'],
    },

    origin: 'http://127.0.0.1:5173',
  },
})

```

---

## 3\. Build 完整配置

| 参数名                         | 类型                     | 默认值                         | 可选值                                       | 说明                                              |
| --------------------------- | ---------------------- | --------------------------- | ----------------------------------------- | ----------------------------------------------- |
| build.target                | string \| string\[\]   | 'baseline-widely-available' | 'es2015'\~'es2022'，'esnext'，'chrome100' 等 | 浏览器兼容目标，决定转换和 polyfill 范围                       |
| build.outDir                | string                 | 'dist'                      | 任意路径                                      | 构建输出目录                                          |
| build.assetsDir             | string                 | 'assets'                    | 任意路径                                      | 静态资源在 outDir 中的子目录名                             |
| build.assetsInlineLimit     | number \| function     | 4096（4KB）                   | 数字或函数                                     | 小于该大小的资源转为 base64 内联；函数形式可按文件名动态决定              |
| build.cssCodeSplit          | boolean                | true                        | true / false                              | 是否将 CSS 拆分为独立文件；false 则所有 CSS 合并进一个文件           |
| build.cssTarget             | string \| string\[\]   | —                           | 同 build.target                            | 单独为 CSS 设置兼容目标（与 JS target 可不同）                 |
| build.cssMinify             | boolean \| string      | true（esbuild）               | true / false / 'esbuild' / 'lightningcss' | CSS 压缩引擎                                        |
| build.sourcemap             | boolean \| string      | false                       | true / false / 'inline' / 'hidden'        | Source map 生成方式                                 |
| build.minify                | boolean \| string      | 'esbuild'                   | true / false / 'esbuild' / 'terser'       | JS 压缩引擎；false 禁用压缩                              |
| build.terserOptions         | TerserOptions          | —                           | —                                         | 使用 terser 时的配置（需先 npm install terser）           |
| build.modulePreload         | boolean \| object      | {polyfill: true}            | —                                         | 模块预加载配置；false 禁用                                |
| build.rollupOptions         | RollupOptions          | {}                          | —                                         | 直接传递给 Rollup 的配置（input/output/external/plugins） |
| build.commonjsOptions       | CommonjsOptions        | —                           | —                                         | 传给 @rollup/plugin-commonjs 的选项                  |
| build.lib                   | LibOptions \| false    | false                       | —                                         | 库模式配置                                           |
| build.manifest              | boolean \| string      | false                       | true / false / 文件名字符串                     | 是否生成 manifest.json（含文件名哈希映射）                    |
| build.ssrManifest           | boolean \| string      | false                       | true / false / 文件名字符串                     | SSR 资源清单                                        |
| build.ssr                   | boolean \| string      | false                       | true / 入口文件路径                             | SSR 构建模式                                        |
| build.write                 | boolean                | true                        | true / false                              | false 时不写磁盘，结果保留在内存（用于 API 调用场景）                |
| build.emptyOutDir           | boolean                | true                        | true / false                              | 构建前是否清空 outDir；outDir 在 root 外时默认 false         |
| build.copyPublicDir         | boolean                | true                        | true / false                              | 是否将 publicDir 中的文件复制到 outDir                    |
| build.reportCompressedSize  | boolean                | true                        | true / false                              | 是否显示 gzip 压缩后的体积报告（大型项目可关闭以加速构建）                |
| build.chunkSizeWarningLimit | number                 | 500                         | 数字（KB）                                    | 触发 chunk 体积警告的阈值（KB）                            |
| build.watch                 | WatcherOptions \| null | null                        | 配置对象或 null                                | 监听模式配置；非 null 时启用 watch 模式                      |

#### build.modulePreload 详细参数

| 参数名                 | 类型       | 默认值  | 说明                                               |
| ------------------- | -------- | ---- | ------------------------------------------------ |
| polyfill            | boolean  | true | 是否注入 modulepreload polyfill 脚本（支持不支持该特性的浏览器）     |
| resolveDependencies | function | —    | 自定义预加载依赖的解析函数 (url, deps, context) => string\[\] |

#### build.lib 详细参数

| 参数名      | 类型                   | 默认值                    | 说明                                              |        |
| -------- | -------------------- | ---------------------- | ----------------------------------------------- | ------ |
| entry    | string \| string\[\] | Record<string, string> | 必填                                              | 库的入口文件 |
| name     | string               | —                      | UMD/IIFE 格式下的全局变量名（使用 umd/iife 格式时必填）           |        |
| formats  | string\[\]           | \['es', 'umd'\]        | 输出格式数组，可选 'es' / 'cjs' / 'umd' / 'iife'         |        |
| fileName | string \| function   | 入口文件名                  | 输出文件名（不含扩展名）；函数形式 (format, entryName) => string |        |

```js
export default defineConfig({
  build: {
    target: ['es2020', 'chrome80', 'firefox75', 'safari13'],
    outDir: 'dist',
    assetsDir: 'assets',
    assetsInlineLimit: 4096,
    cssCodeSplit: true,
    sourcemap: false, // 生产环境不暴露 source map
    // sourcemap: 'hidden', // 生成但不引用（供错误追踪服务用）
    minify: 'esbuild',
    // minify: 'terser', // 需要 npm install terser
    // terserOptions: {
    //   compress: {
    //     drop_console: true,
    //     drop_debugger: true,
    //   },
    // },
    modulePreload: {
      polyfill: true,
    },
    rollupOptions: {
      // 多入口
      input: {
        main: './index.html',
        admin: './admin.html',
      },
      output: {
        // 分包策略
        manualChunks: (id) => {
          if (id.includes('node_modules')) {
            if (id.includes('vue')) return 'vue-vendor'
            if (id.includes('lodash')) return 'lodash-vendor'
            return 'vendor'
          }
        },
        // 输出文件名格式
        chunkFileNames: 'assets/js/[name]-[hash].js',
        entryFileNames: 'assets/js/[name]-[hash].js',
        assetFileNames: 'assets/[ext]/[name]-[hash].[ext]',
      },
      // 排除外部依赖（库模式常用）
      external: ['vue', 'react'],
    },
    manifest: true,
    emptyOutDir: true,
    copyPublicDir: true,
    reportCompressedSize: true,
    chunkSizeWarningLimit: 500,
  },
})

```

---

## 4\. Dependency Optimization 依赖预构建

| 参数名                            | 类型                   | 默认值   | 可选值          | 说明                                |
| ------------------------------ | -------------------- | ----- | ------------ | --------------------------------- |
| optimizeDeps.entries           | string \| string\[\] | —     | glob 模式字符串   | 自定义扫描入口，Vite 从这些入口分析需要预构建的依赖      |
| optimizeDeps.exclude           | string\[\]           | \[\]  | 包名数组         | 排除不需要预构建的包（如纯 ESM 包）              |
| optimizeDeps.include           | string\[\]           | \[\]  | 包名数组         | 强制将指定包纳入预构建范围                     |
| optimizeDeps.esbuildOptions    | ESBuildOptions       | —     | —            | 预构建时传给 esbuild 的额外选项              |
| optimizeDeps.force             | boolean              | false | true / false | 强制重新预构建，忽略缓存（等同于 \--force CLI 参数） |
| optimizeDeps.noDiscovery       | boolean              | false | true / false | 禁用自动依赖发现，只预构建 include 中列出的包       |
| optimizeDeps.holdUntilCrawlEnd | boolean              | true  | true / false | 等待所有静态导入抓取完成后再运行优化，避免首次请求时重新预构建   |
| optimizeDeps.needsInterop      | string\[\]           | \[\]  | 包名数组         | 强制对指定包应用 ESM 互操作处理                |

#### 何时需要 include / exclude

**需要加入 include 的场景：**

1. 动态 `import()` 引入的 CommonJS 包（Vite 静态扫描时发现不了）
2. monorepo 中的本地包（链接包不走预构建，但其依赖可能是 CJS）
3. 某些通过插件间接引入的依赖

**需要加入 exclude 的场景：**

1. 已是纯 ESM 的包且体积小，无需合并
2. 包含 native 模块，esbuild 无法处理
3. 包含副作用，不适合被 esbuild 处理

```js
export default defineConfig({
  optimizeDeps: {
    // 自定义扫描入口（默认扫描 index.html）
    entries: ['./src/**/*.{vue,js,ts,jsx,tsx}'],

    // 强制预构建（动态导入、CJS 包）
    include: [
      'lodash-es',
      'axios',
      // 处理通过动态 import 引入的 CJS 包
      'some-cjs-package',
      // monorepo 场景：链接的本地包中使用的 CJS 依赖
      'local-pkg > some-cjs-dep',
    ],

    // 排除无需预构建的纯 ESM 包
    exclude: [
      'vite',
      '@vitejs/plugin-vue',
    ],

    esbuildOptions: {
      // 处理某些包使用的 node 全局变量
      define: {
        global: 'globalThis',
      },
      // 处理 JSX
      plugins: [],
    },

    force: false,
    holdUntilCrawlEnd: true,
  },
})

```

---

## 5\. 多页面应用（MPA）

多页面应用（MPA）是指有多个独立 HTML 入口的应用，常见于后台管理系统中有多个独立子应用的场景。

### 项目结构

```
my-mpa/
├── vite.config.js
├── package.json
├── index.html          # 主页
├── admin.html          # 管理后台入口
├── login.html          # 登录页入口
└── src/
    ├── main.js         # 主页脚本
    ├── admin.js        # 管理后台脚本
    └── login.js        # 登录页脚本

```

### 配置示例

```js
// vite.config.js
import { defineConfig } from 'vite'
import path from 'path'

export default defineConfig({
  // MPA 模式：禁用 SPA 的 HTML 回退中间件（避免所有 404 都返回 index.html）
  appType: 'mpa',

  build: {
    rollupOptions: {
      input: {
        main: path.resolve(__dirname, 'index.html'),
        admin: path.resolve(__dirname, 'admin.html'),
        login: path.resolve(__dirname, 'login.html'),
      },
    },
  },
})

```

### HTML 入口文件

```html
<!-- admin.html -->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Admin</title>
  </head>
  <body>
    <div id="app"></div>
    <!-- 每个页面有自己的入口脚本 -->
    <script type="module" src="/src/admin.js"></script>
  </body>
</html>

```

### 开发时访问各页面

```
http://localhost:5173/           -> index.html（主页）
http://localhost:5173/admin.html -> admin.html（管理后台）
http://localhost:5173/login.html -> login.html（登录页）

```

注意：开发服务器中必须带 `.html` 后缀访问，但构建后可通过 nginx 等配置去掉后缀。

---

## 6\. 库模式（Library Mode）

库模式用于将 Vue/React 组件或工具函数打包为可供其他项目安装使用的 npm 包。

### 完整配置示例

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

export default defineConfig({
  plugins: [vue()],
  build: {
    lib: {
      // 库入口
      entry: path.resolve(__dirname, 'src/index.ts'),
      // UMD/IIFE 格式下挂载到 window 的变量名
      name: 'MyComponentLib',
      // 输出的格式
      formats: ['es', 'cjs', 'umd'],
      // 输出文件名（不含扩展名）
      fileName: (format) => `my-lib.${format}.js`,
    },
    rollupOptions: {
      // 排除 peer dependencies，不打包进库
      external: ['vue'],
      output: {
        // UMD 格式下，外部依赖对应的全局变量名
        globals: {
          vue: 'Vue',
        },
      },
    },
    // 库模式下通常关闭 CSS 代码分割
    cssCodeSplit: false,
  },
})

```

### 库的入口文件

```ts
// src/index.ts
export { default as MyButton } from './components/MyButton.vue'
export { default as MyInput } from './components/MyInput.vue'
export { default as MyModal } from './components/MyModal.vue'

// 导出类型
export type { ButtonProps } from './components/MyButton.vue'

```

### package.json 配置

```json
{
  "name": "my-component-lib",
  "version": "1.0.0",
  "type": "module",
  "files": ["dist"],
  "main": "./dist/my-lib.cjs.js",
  "module": "./dist/my-lib.es.js",
  "exports": {
    ".": {
      "import": "./dist/my-lib.es.js",
      "require": "./dist/my-lib.cjs.js"
    },
    "./style": "./dist/style.css"
  },
  "types": "./dist/index.d.ts",
  "peerDependencies": {
    "vue": "^3.0.0"
  },
  "devDependencies": {
    "vue": "^3.4.0",
    "vite": "^6.0.0",
    "@vitejs/plugin-vue": "^5.0.0",
    "typescript": "^5.0.0",
    "vue-tsc": "^2.0.0"
  }
}

```

### 用户使用库时需手动引入 CSS

```js
// 用户项目中
import { MyButton } from 'my-component-lib'
// 库模式下 CSS 不会自动注入，需手动引入
import 'my-component-lib/style'

```

---

## 7\. Glob 导入

Vite 支持通过 `import.meta.glob()` 批量导入文件，是实现自动路由、自动组件注册的核心工具。

### 基本用法

```js
// 懒加载（默认）：返回动态导入函数的 Map
const modules = import.meta.glob('./pages/*.vue')
// 等价于：
// const modules = {
//   './pages/Home.vue': () => import('./pages/Home.vue'),
//   './pages/About.vue': () => import('./pages/About.vue'),
// }

// 遍历使用
for (const path in modules) {
  modules[path]().then((mod) => {
    console.log(path, mod)
  })
}

```

### 参数说明

| 参数名    | 类型                               | 默认值   | 说明                                    |
| ------ | -------------------------------- | ----- | ------------------------------------- |
| eager  | boolean                          | false | true 时立即导入（静态导入），返回模块对象而非函数           |
| import | string                           | —     | 只导入模块的指定导出，如 'default'                |
| query  | string \| Record<string, string> | —     | 附加查询参数到每个匹配的导入路径                      |
| as     | string                           | —     | 以特定格式导入，如 'raw'（文本内容）/ 'url'（URL 字符串） |

```js
// eager: true 立即导入（静态）
const modules = import.meta.glob('./components/*.vue', { eager: true })
// 等价于：
// import * as mod from './components/MyButton.vue'
// const modules = { './components/MyButton.vue': mod }

// 只导入 default 导出
const pages = import.meta.glob('./pages/*.vue', {
  eager: true,
  import: 'default',
})

// 导入原始字符串内容
const texts = import.meta.glob('./locale/*.json', { as: 'raw' })

// 导入为 URL
const images = import.meta.glob('./assets/images/*', { as: 'url' })

// 附加 query 参数
const workers = import.meta.glob('./workers/*.js', {
  query: { worker: '' },
  import: 'default',
})

// 负向匹配（排除某些文件）
const modules = import.meta.glob([
  './components/**/*.vue',
  '!./components/**/__tests__/**',
  '!./components/**/index.vue',
])

```

### 实战示例：自动注册路由

```js
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'

// 批量导入 pages 目录下的所有 .vue 文件
const pageModules = import.meta.glob('../pages/**/*.vue')

const routes = Object.entries(pageModules).map(([path, component]) => {
  // 将文件路径转换为路由路径
  // '../pages/Home.vue' -> '/'
  // '../pages/user/Profile.vue' -> '/user/profile'
  const routePath = path
    .replace('../pages', '')
    .replace('.vue', '')
    .replace(/\/index$/, '/')
    .toLowerCase()

  return {
    path: routePath || '/',
    component, // 懒加载
  }
})

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

```

### 实战示例：自动注册全局组件

```js
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

// 导入所有 Base 开头的组件并全局注册
const components = import.meta.glob('./components/Base*.vue', { eager: true })

Object.entries(components).forEach(([path, module]) => {
  // './components/BaseButton.vue' -> 'BaseButton'
  const componentName = path
    .split('/')
    .pop()
    .replace('.vue', '')

  app.component(componentName, module.default)
})

app.mount('#app')

```

---

## 8\. Web Workers

Vite 提供多种方式在项目中使用 Web Worker。

### 方式一：构造函数 + import.meta.url（推荐）

```js
// 主线程
const worker = new Worker(new URL('./workers/heavy-task.js', import.meta.url), {
  type: 'module',
})

worker.postMessage({ data: [1, 2, 3, 4, 5] })

worker.onmessage = (event) => {
  console.log('Worker result:', event.data)
}

```

```js
// workers/heavy-task.js
self.onmessage = (event) => {
  const result = event.data.data.reduce((sum, n) => sum + n, 0)
  self.postMessage(result)
}

```

### 方式二：?worker 查询参数

```js
// 导入 worker 构造函数
import MyWorker from './workers/heavy-task.js?worker'

const worker = new MyWorker()
worker.postMessage({ data: 'hello' })
worker.onmessage = (e) => console.log(e.data)

```

### 方式三：?sharedworker

```js
import SharedWorker from './workers/shared.js?sharedworker'

const worker = new SharedWorker()
worker.port.start()
worker.port.postMessage('hello')
worker.port.onmessage = (e) => console.log(e.data)

```

### 方式四：?worker&inline（内联 worker）

将 worker 代码打包为 base64 字符串内联在主包中，无需单独请求 worker 文件。适合小型 worker。

```js
import InlineWorker from './workers/tiny-task.js?worker&inline'

const worker = new InlineWorker()

```

### 在 Worker 中使用 ES Module

```js
// workers/module-worker.js
// 可以在 worker 中使用 import（Vite 会处理）
import { computeHash } from '../utils/crypto.js'

self.onmessage = async (event) => {
  const hash = await computeHash(event.data)
  self.postMessage(hash)
}

```

---

## 9\. 模式与环境变量进阶

### .env 文件优先级

Vite 加载 `.env` 文件的顺序（后加载的优先级更高）：

```
.env                  # 所有模式通用
.env.local            # 所有模式通用（本地覆盖，不提交 git）
.env.[mode]           # 仅指定模式
.env.[mode].local     # 仅指定模式（本地覆盖，不提交 git）

```

示例：

```
.env
.env.development
.env.development.local  <- 优先级最高（开发模式下）
.env.production
.env.staging            <- 自定义模式

```

### 自定义模式

```bash
# 使用自定义模式运行
vite --mode staging

# 使用自定义模式构建
vite build --mode staging

```

```
# .env.staging
VITE_API_BASE_URL=https://staging-api.example.com
VITE_ENV_NAME=staging

```

### 在 vite.config.js 中使用 loadEnv

```js
// vite.config.js
import { defineConfig, loadEnv } from 'vite'

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

  return {
    define: {
      // 将服务端环境变量安全地注入（不要暴露敏感变量）
      __API_URL__: JSON.stringify(env.VITE_API_BASE_URL),
    },
    server: {
      proxy: {
        '/api': {
          target: env.VITE_API_BASE_URL,
          changeOrigin: true,
        },
      },
    },
  }
})

```

### TypeScript 类型扩展

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

interface ImportMetaEnv {
  readonly VITE_APP_TITLE: string
  readonly VITE_API_BASE_URL: string
  readonly VITE_ENABLE_MOCK: string
  // 在此处声明所有自定义环境变量
}

interface ImportMeta {
  readonly env: ImportMetaEnv
}

```

### 在代码中使用环境变量

```js
// 只有 VITE_ 前缀的变量会暴露给客户端
console.log(import.meta.env.VITE_API_BASE_URL)
console.log(import.meta.env.MODE)       // 当前模式
console.log(import.meta.env.BASE_URL)   // base 配置的值
console.log(import.meta.env.PROD)       // boolean，是否生产环境
console.log(import.meta.env.DEV)        // boolean，是否开发环境
console.log(import.meta.env.SSR)        // boolean，是否 SSR 环境

```

### NODE\_ENV 与 mode 的区别

| 概念        | 设置方式                  | 影响范围                                     | 说明                                              |
| --------- | --------------------- | ---------------------------------------- | ----------------------------------------------- |
| mode      | \--mode 参数            | Vite 的 .env 文件加载，import.meta.env.MODE    | 可以是任意字符串，用于区分不同部署环境（staging/testing/production） |
| NODE\_ENV | process.env.NODE\_ENV | Node.js 工具链（webpack/rollup/babel 等）的优化行为 | 通常只有 development / production / test 三个值        |

`vite build` 时，`NODE_ENV` 默认为 `production`，`mode` 默认为 `production`，但可以单独修改 mode 而不影响 NODE\_ENV：

```bash
# mode 为 staging，但 NODE_ENV 仍为 production（触发生产构建优化）
vite build --mode staging

```

---

## 10\. CSS 进阶

### PostCSS 配置

```js
// postcss.config.js（独立文件）或内联到 vite.config.js
export default {
  plugins: [
    // 自动添加浏览器前缀
    require('autoprefixer')({
      overrideBrowserslist: ['> 1%', 'last 2 versions', 'not dead'],
    }),
    // 支持嵌套 CSS（类似 Sass）
    require('postcss-nested'),
    // 支持 CSS 变量的静态回退
    require('postcss-custom-properties'),
  ],
}

```

```css
/* 使用 postcss-nested 后可以这样写 CSS */
.button {
  padding: 8px 16px;

  &:hover {
    background: blue;
  }

  &--primary {
    background: var(--color-primary);
  }

  .icon {
    margin-right: 4px;
  }
}

```

### Sass/SCSS 全局变量注入

```js
// vite.config.js
export default defineConfig({
  css: {
    preprocessorOptions: {
      scss: {
        // 每个 scss 文件顶部自动追加，无需手动 import
        additionalData: `
          @use "@/styles/variables" as *;
          @use "@/styles/mixins" as *;
          @use "@/styles/functions" as *;
        `,
      },
    },
  },
})

```

```scss
// src/styles/variables.scss
$primary-color: #1890ff;
$secondary-color: #52c41a;
$font-size-base: 14px;
$border-radius: 4px;

```

```scss
// 组件中无需 import 直接使用变量
.my-component {
  color: $primary-color;
  font-size: $font-size-base;
  border-radius: $border-radius;
}

```

### CSS 代码分割策略

```js
export default defineConfig({
  build: {
    // 默认 true：每个异步 chunk 有对应的 CSS 文件
    cssCodeSplit: true,

    rollupOptions: {
      output: {
        // 自定义 CSS 文件名
        assetFileNames: (assetInfo) => {
          if (assetInfo.name?.endsWith('.css')) {
            return 'assets/css/[name]-[hash][extname]'
          }
          return 'assets/[name]-[hash][extname]'
        },
      },
    },
  },
})

```

---

## 11\. 踩坑注意事项

### resolve.alias 必须使用绝对路径

```js
import path from 'path'

// 正确：使用 path.resolve 生成绝对路径
export default defineConfig({
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },
})

// 错误：相对路径在不同调用位置会解析到不同目录
// alias: {
//   '@': './src',  // 不要这样写
// }

```

### optimizeDeps.include 处理 CommonJS 包

某些包以 CommonJS 格式发布，Vite 不会自动处理动态 `import()` 引入的 CJS 包，需手动加入 `include`：

```js
export default defineConfig({
  optimizeDeps: {
    include: [
      // 通过动态 import 引入的 CJS 包
      'moment',
      'lodash',
      // 子路径导入
      'lodash/cloneDeep',
      // monorepo 场景：链接包中的 CJS 依赖
      '@my-org/shared > axios',
    ],
  },
})

```

### build.target 低于 es2015 时会报错

Vite 的 esbuild 转换最低支持到 `es2015`（即 ES6）。如果需要支持 IE11 等更低版本，需要使用 `@vitejs/plugin-legacy`：

```js
import legacy from '@vitejs/plugin-legacy'

export default defineConfig({
  plugins: [
    legacy({
      targets: ['defaults', 'IE 11'],
      additionalLegacyPolyfills: ['regenerator-runtime/runtime'],
    }),
  ],
  // 不要设置 build.target 为 'es5'，改用 legacy 插件
})

```

### cssCodeSplit: false 将所有 CSS 合并为一个文件

```js
// 设置为 false 后，所有组件的 CSS 都合并进 style.css
// 优点：减少 HTTP 请求
// 缺点：首屏加载所有样式，无法按需加载
export default defineConfig({
  build: {
    cssCodeSplit: false,
    // 此时所有 CSS 输出到单个文件：dist/assets/style-[hash].css
  },
})

```

### 库模式下 CSS 不会自动注入

打包为库时，CSS 会输出为独立的 `style.css` 文件，不会自动注入到 DOM 中。需要告知用户手动引入：

```js
// 库作者：在 package.json 中声明 CSS 导出路径
{
  "exports": {
    ".": {
      "import": "./dist/my-lib.es.js",
      "require": "./dist/my-lib.cjs.js"
    },
    "./style": "./dist/style.css"
  }
}

```

```js
// 用户使用库时必须手动引入 CSS
import { MyButton } from 'my-component-lib'
import 'my-component-lib/style'  // 必须，否则无样式

```

### server.fs.strict 导致访问工作区外文件报错

在 monorepo 中，链接的本地包文件在工作区根目录外，需配置 `allow`：

```js
export default defineConfig({
  server: {
    fs: {
      strict: true,
      allow: [
        // 允许访问 monorepo 根目录（自动检测通常能处理，但有时需手动配置）
        '../..',
        // 允许访问特定的共享包
        '../../packages/shared',
      ],
    },
  },
})

```

### define 配置的值需要 JSON.stringify

`define` 是编译期字符串替换，不是运行时赋值。如果值是字符串，必须包裹在 `JSON.stringify` 中：

```js
export default defineConfig({
  define: {
    // 正确：字符串值需要 JSON.stringify
    __APP_VERSION__: JSON.stringify('1.0.0'),
    __API_URL__: JSON.stringify('https://api.example.com'),

    // 正确：数字/布尔值直接写
    __ENABLE_ANALYTICS__: true,
    __MAX_RETRY__: 3,

    // 错误：字符串没有 JSON.stringify 会导致代码中出现裸的 1.0.0
    // __APP_VERSION__: '1.0.0',  // 会被替换为 1.0.0（无引号）
  },
})

```

### 热更新（HMR）在某些场景下失效

```js
// 手动接受模块热更新（用于非框架代码）
if (import.meta.hot) {
  // 接受自身更新
  import.meta.hot.accept((newModule) => {
    if (newModule) {
      // 用新模块重新初始化
      newModule.init()
    }
  })

  // 接受依赖模块的更新
  import.meta.hot.accept('./dep.js', (newDep) => {
    // 处理依赖更新
  })

  // 清理副作用（如定时器、事件监听）
  import.meta.hot.dispose((data) => {
    clearInterval(timer)
    document.removeEventListener('click', handler)
  })
}

```

---

## 最佳实践

**按需拆分 `vite.config.ts` 配置**：将 `plugins`、`build`、`server` 分别提取为独立函数或文件，避免单文件过长，方便 CI 中按环境合并配置。

**使用 `defineConfig` 的函数形式读取 `mode`**：需要在配置中判断 `development`/`production` 时，用 `export default defineConfig(({ mode }) => {...})` 而非在模块顶层读取 `process.env.NODE_ENV`，确保 Vite 内部的 `mode` 变量与配置保持一致。

```ts
// 正确：通过参数接收 mode
export default defineConfig(({ mode }) => ({
  define: { __DEV__: mode === 'development' },
}))

```

**库模式下声明 `external`**：打包为库时，把 `vue`、`react` 等宿主提供的依赖加入 `build.rollupOptions.external`，避免将框架代码打入产物。

```ts
build: {
  lib: { entry: 'src/index.ts', formats: ['es', 'cjs'] },
  rollupOptions: { external: ['vue'], output: { globals: { vue: 'Vue' } } },
}

```

**多页面应用用 `build.rollupOptions.input` 对象**：比 `glob` 手写更清晰，且 key 决定 chunk 文件名，便于追踪输出结构。

**`server.proxy` 精确匹配前缀避免冲突**：前缀越短越容易误匹配，`/api` 会拦截所有以 `/api` 开头的路径，改用 `/api/v1` 或在 `configure` 钩子中精确过滤。

---

## 常见陷阱

### 陷阱：`import.meta.env` 变量在构建产物中仍然是 `undefined`

**现象：** 本地 `console.log(import.meta.env.VITE_API_URL)` 有值，生产构建后变量变成 `undefined`。  
**原因：** Vite 只替换 `VITE_` 前缀的变量，且替换发生在静态分析阶段。如果变量名通过动态字符串拼接访问，或前缀不是 `VITE_`，Vite 不会替换。  
**解决：** 确认变量以 `VITE_` 开头，且以静态字符串 `import.meta.env.VITE_XXX` 形式访问；不要用 `import.meta.env[key]` 动态访问。

### 陷阱：库模式打包后 CSS 未被摇树优化

**现象：** 以库模式发布组件库，消费方只用了 1 个组件，但引入了整个 CSS 文件，包体膨胀。  
**原因：** Vite 库模式默认把所有 CSS 提取为单个 `style.css`，无法按组件 tree-shake。  
**解决：** 将 CSS 拆分到各组件目录，由消费方按需 `import './ComponentName/style.css'`；或使用 CSS-in-JS / `@layer` 方案；也可配置 `cssCodeSplit: true` 让每个 chunk 生成独立 CSS。

### 陷阱：多页面应用 `base` 路径导致子页面资源 404

**现象：** 部署后首页正常，子页面（如 `/admin/`）的 JS/CSS 加载 404。  
**原因：** 子页面的 HTML 使用相对路径引用构建产物，而构建产物实际输出在根目录下。  
**解决：** 设置 `base: '/'`（绝对路径），确保所有 HTML 引用的资源使用从根出发的绝对路径，不受页面所在目录层级影响。

---

## 参见

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