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 的场景:
- 动态
import()引入的 CommonJS 包(Vite 静态扫描时发现不了) - monorepo 中的本地包(链接包不走预构建,但其依赖可能是 CJS)
- 某些通过插件间接引入的依赖
需要加入 exclude 的场景:
- 已是纯 ESM 的包且体积小,无需合并
- 包含 native 模块,esbuild 无法处理
- 包含副作用,不适合被 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 默认为 production,mode 默认为 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 配置:将 plugins、build、server 分别提取为独立函数或文件,避免单文件过长,方便 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:打包为库时,把 vue、react 等宿主提供的依赖加入 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 引用的资源使用从根出发的绝对路径,不受页面所在目录层级影响。