Vite 中级指南

本文覆盖 Vite 中级配置与使用模式,包括完整的配置项参考、多页面应用、库模式、环境变量进阶等内容。适合已掌握 Vite 基础的开发者。 需要加入 include 的场景: 1. 动态 import() 引入的 CommonJS 包(Vite 静态扫描时发现不了) 2. monorepo 中的本地包(链接包不走预构建,但其依赖可能是 CJS) 3. 某些通过插件间接引入的依赖 需要加入 exclude 的场景: 1. 已是纯 ESM 的包且体积小,无需合并 2. 包含 native 模块,esbuild 无法处理 3. 包含副作用,不适合被 esbui

分享

官方文档: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 兼容标志
// 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 常用
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 时的配置项
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 完整用法

/* 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';
}
// 在 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 大小自动选择
export default defineConfig({
  json: {
    namedExports: true,
    stringify: 'auto',
  },
})
// 按名导入(需要 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 控制)
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> 标签
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 是否显示错误覆盖层
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
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 处理
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        # 登录页脚本

配置示例

// 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 入口文件

<!-- 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 包。

完整配置示例

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

库的入口文件

// 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 配置

{
  "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

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

7. Glob 导入

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

基本用法

// 懒加载(默认):返回动态导入函数的 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 字符串)
// 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',
])

实战示例:自动注册路由

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

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

// 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(推荐)

// 主线程
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)
}
// workers/heavy-task.js
self.onmessage = (event) => {
  const result = event.data.data.reduce((sum, n) => sum + n, 0)
  self.postMessage(result)
}

方式二:?worker 查询参数

// 导入 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

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。

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

const worker = new InlineWorker()

在 Worker 中使用 ES Module

// 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            <- 自定义模式

自定义模式

# 使用自定义模式运行
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

// 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 类型扩展

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

在代码中使用环境变量

// 只有 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 默认为 productionmode 默认为 production,但可以单独修改 mode 而不影响 NODE_ENV:

# mode 为 staging,但 NODE_ENV 仍为 production(触发生产构建优化)
vite build --mode staging

10. CSS 进阶

PostCSS 配置

// 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'),
  ],
}
/* 使用 postcss-nested 后可以这样写 CSS */
.button {
  padding: 8px 16px;

  &:hover {
    background: blue;
  }

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

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

Sass/SCSS 全局变量注入

// vite.config.js
export default defineConfig({
  css: {
    preprocessorOptions: {
      scss: {
        // 每个 scss 文件顶部自动追加,无需手动 import
        additionalData: `
          @use "@/styles/variables" as *;
          @use "@/styles/mixins" as *;
          @use "@/styles/functions" as *;
        `,
      },
    },
  },
})
// src/styles/variables.scss
$primary-color: #1890ff;
$secondary-color: #52c41a;
$font-size-base: 14px;
$border-radius: 4px;
// 组件中无需 import 直接使用变量
.my-component {
  color: $primary-color;
  font-size: $font-size-base;
  border-radius: $border-radius;
}

CSS 代码分割策略

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 必须使用绝对路径

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

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

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 合并为一个文件

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

库模式下 CSS 不会自动注入

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

// 库作者:在 package.json 中声明 CSS 导出路径
{
  "exports": {
    ".": {
      "import": "./dist/my-lib.es.js",
      "require": "./dist/my-lib.cjs.js"
    },
    "./style": "./dist/style.css"
  }
}
// 用户使用库时必须手动引入 CSS
import { MyButton } from 'my-component-lib'
import 'my-component-lib/style'  // 必须,否则无样式

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

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

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

define 配置的值需要 JSON.stringify

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

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)在某些场景下失效

// 手动接受模块热更新(用于非框架代码)
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 配置:将 pluginsbuildserver 分别提取为独立函数或文件,避免单文件过长,方便 CI 中按环境合并配置。

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

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

库模式下声明 external:打包为库时,把 vuereact 等宿主提供的依赖加入 build.rollupOptions.external,避免将框架代码打入产物。

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初级指南
Vite高级指南
TypeScript完全指南

阅读更多

Web 安全基础

1. HTML 转义(服务端渲染必须): 2. CSP(Content Security Policy): 3. HttpOnly Cookie:防止 JS 读取会话 Cookie: 4. 前端框架防护: 攻击者在第三方网站构造一个表单,诱导已登录用户提交,浏览器会自动携带目标站的 Cookie。 触发条件: 1. 用户已登录目标网站(Cookie 有效) 2. 目标 API 仅凭 Cookie 识别用户身份 3. 请求来源未验证 1. CSRF Token(推荐): 2. SameSite Cookie: 3. 验证 Origin/Referer 头:

By yellowdog

HTTP 协议深度指南

HTTP(HyperText Transfer Protocol)是 Web 的基础传输协议,基于 TCP/IP,采用请求/响应模型。 相关文档:Web安全基础(/web-an-quan-ji-chu/) FastAPI完全指南(/fastapi-wan-quan-zhi-nan/) Nginx完全指南(/nginx-wan-quan-zhi-nan/) 幂等性:多次执行相同请求,服务器状态结果相同。PUT /users/1 多次执行结果一致;POST /users 每次创建新资源,非幂等。 浏览器直接从本地缓存读取,不向服务器发送请求。 缓存命中时,状

By yellowdog

系统设计基础

SLA 对照表: 选择建议:无状态服务(Web 层、API 层)优先水平扩展;数据库初期垂直扩展,达到瓶颈后考虑分库分表或读写分离。 缓存穿透(查询不存在的 key,每次都打到 DB): 缓存击穿(热点 key 过期,瞬间大量请求打到 DB): 缓存雪崩(大量 key 同时过期,或缓存服务宕机): 令牌桶 Python 实现: Redis 实现分布式限流(滑动窗口): URL 命名规则: Cursor 分页响应格式: 雪花算法结构(64 bit): 定义:分布式系统不能同时满足以下三个特性: 在分布式环境中 P 是必须保证的,所以实际是 CP vs AP

By yellowdog

算法思路与模板

二分查找要求序列有序,每次将搜索范围缩减一半,时间复杂度 O(log n)。 两个指针从两端向中间收缩,常用于有序数组。 滑动窗口维护一个满足条件的区间 left, right,right 不断向右扩张,条件不满足时收缩 left。 滑动窗口通用框架: 1. 确定"子问题":原问题可以分解为哪些规模更小的同类问题 2. 定义 dpi 或 dpij 的含义,要足够清晰 3. 推导状态转移方程 4. 确定初始状态(边界条件) 5. 确定计算顺序(确保依赖的子问题先计算) 每件物品最多选一次。dpj = 容量为 j 时的最大价值,逆序遍历容量防止重复选取。 每

By yellowdog