构建生产版本
本章涵盖 vite build 的完整配置、代码分割策略、库模式、多页应用和浏览器兼容性。
构建基础
vite build # 默认生产构建
vite build --mode staging # 指定模式
vite build --watch # 监听模式(适合库开发)
vite build --sourcemap # 生成 source map构建流程:
源码(src) → TypeScript 编译(esbuild)→ Rollup 打包 → Terser 压缩 → 输出 dist/🔬 深入原理:生产构建时,Vite 不再使用原生 ESM 策略,而是用 Rollup 打包所有模块并进行 Tree Shaking、代码分割和压缩,以优化最终的加载性能。
build 配置项
// vite.config.js
export default defineConfig({
build: {
// === 输出 ===
outDir: 'dist', // 输出目录
assetsDir: 'assets', // 静态资源子目录
assetsInlineLimit: 4096, // 内联 base64 阈值(字节)
emptyOutDir: true, // 构建前清空输出目录
// === 代码分割 ===
rollupOptions: {
output: {
manualChunks: { /* ... */ },
},
},
// === CSS ===
cssCodeSplit: true, // CSS 代码分割
cssMinify: 'lightningcss', // CSS 压缩:'esbuild' / 'lightningcss'
cssTarget: 'chrome61', // CSS 目标浏览器(配合 lightningcss)
// === 输出兼容性 ===
target: 'modules', // JS 目标:'modules'(ES2020)/ 'es2015' 等
modulePreload: { polyfill: true },
minify: 'esbuild', // 压缩器:'esbuild' / 'terser' / false
// === Source Map ===
sourcemap: false, // 是否生成 sourcemap
// sourcemap: 'hidden', // 生成但不引用
// === 文件命名 ===
rollupOptions: {
output: {
entryFileNames: 'assets/[name].[hash].js',
chunkFileNames: 'assets/[name].[hash].js',
assetFileNames: 'assets/[name].[hash].[ext]',
},
},
// === 其他 ===
copyPublicDir: true, // 是否复制 public 目录
reportCompressedSize: true, // 报告 gzip 后大小
chunkSizeWarningLimit: 500, // chunk 大小警告阈值(KB)
watch: null, // 构建监听
},
})代码分割(Code Splitting)
自动分割
Vite 自动对动态 import() 进行代码分割:
// 自动分割:每个动态 import 会生成独立的 chunk
const LazyComponent = lazy(() => import('./HeavyComponent.jsx'))
// 构建产物:
// dist/assets/HeavyComponent.hash.js ← 独立 chunk
// dist/assets/index.hash.js ← 主入口
手动分割(manualChunks)
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
// 将 React 相关库拆分到独立 chunk
'react-vendor': ['react', 'react-dom', 'react-router-dom'],
// 将 UI 库拆分
'ui-vendor': ['antd', '@ant-design/icons'],
// 将工具库拆分
'utils': ['lodash', 'dayjs', 'axios'],
},
},
},
},
})函数式拆分
manualChunks(id) {
// node_modules 中的库分包策略
if (id.includes('node_modules')) {
// React 生态单独打一个包
if (id.includes('react') || id.includes('react-dom') || id.includes('react-router')) {
return 'react-vendor'
}
// UI 组件库单独分包
if (id.includes('antd') || id.includes('@ant-design')) {
return 'ui-vendor'
}
// 其余第三方库
return 'vendor'
}
}💡 最佳实践:不要过度拆分——浏览器并发连接有限(HTTP/1.1 约 6 个,HTTP/2 约 100 个)。React/Vue 这类稳定的大型库适合抽离为 vendor chunk,利用浏览器长期缓存。
库模式
开发供他人使用的 JS 库时,使用库模式:
// vite.config.js
export default defineConfig({
build: {
lib: {
entry: './src/index.js', // 库入口
name: 'MyLib', // UMD 全局变量名
formats: ['es', 'umd', 'cjs'], // 输出格式:es / umd / cjs / iife
fileName: (format) => `my-lib.${format}.js`,
},
rollupOptions: {
// 外部化那些不打包进库的依赖
external: ['react', 'react-dom'],
output: {
// 在 UMD/CJS 模式下,这些外部依赖需要有全局变量
globals: {
react: 'React',
'react-dom': 'ReactDOM',
},
},
},
},
})| 输出格式 | 适用场景 | 说明 |
|---|---|---|
es |
ESM 环境(现代打包工具、浏览器) | ES Module,支持 Tree Shaking |
cjs |
Node.js CommonJS 环境 | require() 导入 |
umd |
浏览器 script 标签、各种环境 | 同时支持 AMD/CJS/全局变量 |
iife |
浏览器 script 标签 | 立即执行函数,污染全局 |
package.json 字段配置
{
"name": "my-lib",
"type": "module",
"main": "./dist/my-lib.cjs.js", // CJS 入口
"module": "./dist/my-lib.es.js", // ESM 入口
"types": "./dist/index.d.ts", // 类型声明
"exports": {
".": {
"import": "./dist/my-lib.es.js",
"require": "./dist/my-lib.cjs.js",
"types": "./dist/index.d.ts"
}
},
"files": ["dist"]
}💡 最佳实践:同时提供
es(for 现代构建工具 + Tree Shaking)和cjs(for 旧 Node.js)格式。ES 格式的type: "module"和 CJS 的main字段必须正确设置。
多页应用
当项目有多个 HTML 入口时(非 SPA):
// vite.config.js
import { resolve } from 'path'
export default defineConfig({
build: {
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html'),
admin: resolve(__dirname, 'admin/index.html'),
nested: resolve(__dirname, 'nested/index.html'),
},
},
},
})目录结构对应:
├── index.html
├── admin/
│ └── index.html
├── nested/
│ └── index.html
└── src/每个 HTML 文件需要包含自己的 <script type="module"> 入口。
浏览器兼容性
export default defineConfig({
build: {
// JS 目标浏览器(传递给 esbuild)
target: 'es2015', // 默认 'modules'(≈ es2020)
// CSS 目标(使用 lightningcss 时)
cssTarget: 'chrome61',
// 动态导入 polyfill
modulePreload: {
polyfill: true, // 为旧浏览器注入 modulepreload polyfill
},
},
})常见 target 值
| target 值 | 含义 | 说明 |
|---|---|---|
'modules' |
支持 ES Modules 的浏览器 | 默认值 |
'es2020' |
ES2020 支持的浏览器 | BigInt、globalThis、import.meta |
'es2019' |
ES2019 | flat/flatMap、Object.fromEntries |
'es2018' |
ES2018 | async/await、spread |
'es2015' |
ES2015/ES6 支持的浏览器 | 兼容性最好但产物最大 |
'chrome87' |
Chrome 87+ | 精确到浏览器版本 |
@vitejs/plugin-legacy
如果需要支持更老的浏览器(如 IE11),使用 legacy 插件:
npm i -D @vitejs/plugin-legacy terserimport legacy from '@vitejs/plugin-legacy'
export default defineConfig({
plugins: [
legacy({
targets: ['defaults', 'not IE 11'],
}),
],
})压缩器选择
export default defineConfig({
build: {
// esbuild(默认):极快压缩,体积略大于 Terser
minify: 'esbuild',
// terser:较慢,压缩率更高,支持高级压缩选项
// minify: 'terser',
// terserOptions: {
// compress: { drop_console: true },
// },
// false:不压缩(不推荐生产环境)
// minify: false,
},
})| 压缩器 | 速度 | 压缩率 | 场景 |
|---|---|---|---|
esbuild |
⚡ 极快 | 良好 | 默认,大多数场景首选 |
terser |
🐢 较慢 | 最优 | 对体积有极致要求的生产环境 |
false |
— | — | 调试构建产物 |
构建分析
# 生成构建体积分析(Rollup 的 stats 输出)
vite build --debug
# 可视化分析
npm i -D rollup-plugin-visualizer// vite.config.js
import { visualizer } from 'rollup-plugin-visualizer'
export default defineConfig({
plugins: [
visualizer({
open: true, // 构建后自动打开浏览器
gzipSize: true, // 显示 gzip 后大小
brotliSize: true, // 显示 brotli 大小
}),
],
})⚡ 性能提示:
reportCompressedSize: true(默认已开启)会在构建日志中打印 gzip 后的大小,方便快速评估实际传输体积。