生产构建
构建命令
使用 vite build 命令进行生产构建:
# 基本构建
npm run build
# 指定模式
vite build --mode production
# 指定输出目录
vite build --outDir dist
# 生成 source map
vite build --sourcemap
# 监听文件变化(用于开发)
vite build --watch
浏览器兼容性
默认目标
Vite 默认针对 Baseline Widely Available 浏览器(至少 2.5 年前发布的浏览器):
| 浏览器 | 最低版本 |
|---|---|
| Chrome | 111+ |
| Edge | 111+ |
| Firefox | 114+ |
| Safari | 16.4+ |
这些浏览器版本对应 Baseline Widely Available 特性集。
自定义目标
通过 build.target 配置指定目标浏览器:
// vite.config.js
export default defineConfig({
build: {
target: 'es2015', // 最低支持 es2015
// 其他选项: 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'esnext'
},
})
最低浏览器支持
即使设置更低的目标,Vite 仍需要以下最低浏览器支持(原生 ESM 和 import.meta):
- Chrome >= 64
- Firefox >= 67
- Safari >= 11.1
- Edge >= 79
重要提示:Vite 默认只处理语法转换,不包含 polyfill。如果需要 polyfill,可以参考 cdnjs.cloudflare.com/polyfill,它会根据用户的浏览器 UserAgent 自动生成 polyfill 包。
旧浏览器支持
使用 @vitejs/plugin-legacy 插件支持旧浏览器:
npm install -D @vitejs/plugin-legacy
// vite.config.js
import { defineConfig } from 'vite'
import legacy from '@vitejs/plugin-legacy'
export default defineConfig({
plugins: [
legacy({
targets: ['defaults', 'not IE 11'],
additionalLegacyPolyfills: ['regenerator-runtime/runtime'],
}),
],
})
公共基础路径
配置 base
如果项目部署在嵌套路径,需要配置 base:
// vite.config.js
export default defineConfig({
base: '/my-app/', // 项目部署在 /my-app/ 路径下
})
相对基础路径
如果不知道确切的基础路径,可以使用相对路径:
export default defineConfig({
base: './', // 相对路径
// 或
base: '', // 空字符串也表示相对路径
})
这会使所有生成的 URL 相对于各自文件。
重要提示:相对基础路径需要 import.meta 支持。如果需要支持不支持 import.meta 的浏览器,可以使用 @vitejs/plugin-legacy 插件。
运行时获取
如果不知道确切的基础路径,可以使用相对路径:
export default defineConfig({
base: './', // 相对路径
})
运行时获取
使用 import.meta.env.BASE_URL 在代码中获取基础路径:
// 构建时会静态替换
const baseUrl = import.meta.env.BASE_URL // "/my-app/"
// 动态拼接 URL
const imageUrl = `${import.meta.env.BASE_URL}images/logo.png`
构建配置
输出配置
export default defineConfig({
build: {
// 输出目录(默认:dist)
outDir: 'dist',
// 静态资源目录(相对于 outDir)
assetsDir: 'assets',
// 构建前是否清空输出目录
emptyOutDir: true,
// 是否生成 source map
sourcemap: false,
// 目标浏览器版本
target: 'es2015',
// 代码压缩工具
minify: 'esbuild', // 'esbuild' | 'terser' | false
// CSS 代码分割
cssCodeSplit: true,
// 小于此值的资源内联为 base64(默认:4096 bytes)
assetsInlineLimit: 4096,
// chunk 大小警告阈值(KB)
chunkSizeWarningLimit: 500,
// 是否写入 manifest.json
manifest: false,
// 是否生成 SSR manifest
ssrManifest: false,
// 报告压缩后的大小
reportCompressedSize: true,
// 生成依赖许可证文件(Vite 8 新增)
license: false, // true | { fileName: 'license.md' }
},
})
依赖许可证生成(Vite 8 新增)
Vite 8 新增了 build.license 选项,可以自动生成构建中使用的所有依赖的许可证文件:
export default defineConfig({
build: {
// 启用许可证生成,默认输出到 .vite/license.md
license: true,
// 或自定义输出文件名
license: { fileName: 'license.md' },
},
})
启用后,Vite 会在输出目录生成许可证文件,内容格式如下:
# Licenses
The app bundles dependencies which contain the following licenses:
## dep-1 - 1.2.3 (MIT)
MIT License
...
## dep-2 - 4.5.6 (Apache-2.0)
Apache License 2.0
...
为什么需要许可证文件?
许多开源许可证(如 MIT、Apache 2.0、GPL)要求在分发软件时保留原始许可证声明。使用这个功能可以:
- 合规性:确保你的应用遵守所有依赖的许可证要求
- 透明度:让用户了解你的应用使用了哪些开源项目
- 便捷性:自动收集所有依赖的许可证,无需手动整理
生成的文件可以托管在网站上,供用户查看和确认。
### Rollup 选项
Vite 使用 Rolldown 进行生产构建,可以通过 `build.rollupOptions` 配置:
```javascript
export default defineConfig({
build: {
rollupOptions: {
// 入口点(多页面应用)
input: {
main: resolve(__dirname, 'index.html'),
admin: resolve(__dirname, 'admin/index.html'),
},
// 输出配置
output: {
// 入口文件命名
entryFileNames: 'js/[name]-[hash].js',
// chunk 文件命名
chunkFileNames: 'js/[name]-[hash].js',
// 资源文件命名
assetFileNames: (assetInfo) => {
const info = assetInfo.name.split('.')
const ext = info[info.length - 1]
if (/\.(png|jpe?g|gif|svg|webp|ico)$/i.test(assetInfo.name)) {
return 'img/[name]-[hash][extname]'
}
if (/\.(woff2?|eot|ttf|otf)$/i.test(assetInfo.name)) {
return 'fonts/[name]-[hash][extname]'
}
if (ext === 'css') {
return 'css/[name]-[hash][extname]'
}
return 'assets/[name]-[hash][extname]'
},
// 手动代码分割
manualChunks: {
// 将 vue 相关库打包在一起
'vue-vendor': ['vue', 'vue-router', 'pinia'],
// 将 UI 库打包在一起
'ui-vendor': ['element-plus'],
},
},
// 外部依赖(不打包进 bundle)
external: ['jquery'],
// 全局变量映射(用于 UMD/IIFE)
globals: {
jquery: '$',
},
},
},
})
代码分割
代码分割(Code Splitting)是现代前端优化的核心技术之一。它的核心思想是将代码拆分成多个小块,按需加载,而不是一次性加载所有代码。这带来了几个关键好处:
首屏加载更快:用户只需要下载当前页面需要的代码,而不是整个应用的所有代码。一个大型 SPA 应用可能有数 MB 的 JavaScript,但如果首屏只需要 200KB,用户就能更快看到内容。
更好的缓存利用率:将第三方库(如 Vue、React)和业务代码分开打包。当业务代码更新时,用户不需要重新下载没有变化的第三方库。
按需加载减少带宽:某些功能(如管理后台、报表页面)可能只有少数用户使用,将它们拆分成独立 chunk,只有访问时才加载。
动态导入
Vite 自动处理动态导入的代码分割:
// 路由懒加载
const routes = [
{
path: '/user',
component: () => import('./views/User.vue'),
},
{
path: '/admin',
component: () => import('./views/Admin.vue'),
},
]
// 组件懒加载
const AsyncModal = defineAsyncComponent(() =>
import('./components/Modal.vue')
)
// 条件加载
if (condition) {
const module = await import('./heavy-module.js')
module.doSomething()
}
手动代码分割
通过 manualChunks 控制代码分割:
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: (id) => {
// 将 node_modules 中的依赖单独打包
if (id.includes('node_modules')) {
// 按包名分组
const match = id.match(/node_modules\/(?:@([^/]+)\/([^/]+)|([^/]+))/)
const scope = match?.[1]
const name = match?.[2] || match?.[3]
return `vendor/${scope ? `${scope}-` : ''}${name}`
}
// 将特定目录的代码打包在一起
if (id.includes('/src/views/')) {
return 'views'
}
},
},
},
},
})
多页面应用(MPA)
Vite 支持多页面应用配置:
// vite.config.js
import { resolve } from 'path'
export default defineConfig({
build: {
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html'),
about: resolve(__dirname, 'about/index.html'),
contact: resolve(__dirname, 'contact/index.html'),
},
},
},
})
项目结构:
project/
├── index.html # 主页面
├── about/
│ └── index.html # 关于页面
├── contact/
│ └── index.html # 联系页面
└── src/
└── ...
库模式简介
Vite 支持库模式,用于构建发布到 npm 的 JavaScript 库。库模式与应用模式的主要区别在于输出格式和依赖处理方式。
基本配置示例:
// vite.config.js
import { resolve } from 'path'
export default defineConfig({
build: {
lib: {
entry: resolve(__dirname, 'lib/main.js'),
name: 'MyLib',
fileName: 'my-lib',
},
rollupOptions: {
external: ['vue'],
output: {
globals: {
vue: 'Vue',
},
},
},
},
})
构建后会生成两种格式:
dist/my-lib.js(ES Module 格式)dist/my-lib.umd.cjs(UMD 格式)
库模式的详细内容,包括多入口配置、package.json 配置、CSS 处理、TypeScript 类型定义、发布流程等,请参阅 库模式开发 章节。
构建优化
CSS 优化
export default defineConfig({
build: {
// CSS 代码分割(默认:true)
cssCodeSplit: true,
// CSS 目标浏览器(默认与 build.target 相同)
cssTarget: 'chrome61',
},
css: {
// 使用 Lightning CSS(实验性)
transformer: 'lightningcss',
lightningcss: {
targets: {
chrome: 80,
},
},
},
})
资源优化
export default defineConfig({
build: {
// 小于 4KB 的资源内联为 base64
assetsInlineLimit: 4096,
// 触发警告的 chunk 大小
chunkSizeWarningLimit: 500,
// 启用/禁用压缩大小报告
reportCompressedSize: false,
},
})
Tree Shaking
Vite 自动进行 tree shaking,但可以通过注释控制:
// 保留副作用(不 tree-shaking)
/* @__PURE__ */ console.log('side effect')
// 标记为无副作用(可以 tree-shaking)
/*#__PURE__*/
export function helper() {
return 'helper'
}
加载错误处理
Vite 在动态导入失败时会触发 vite:preloadError 事件:
window.addEventListener('vite:preloadError', (event) => {
// 新部署后,旧资源可能被删除
// 可以在这里处理,例如刷新页面
window.location.reload()
})
建议为 HTML 文件设置 Cache-Control: no-cache,确保用户获取最新的资源引用。
构建分析
使用 rollup-plugin-visualizer
npm install -D rollup-plugin-visualizer
// vite.config.js
import { visualizer } from 'rollup-plugin-visualizer'
export default defineConfig({
plugins: [
visualizer({
open: true, // 构建后自动打开报告
gzipSize: true,
brotliSize: true,
}),
],
})
预览构建
使用 vite preview 在本地预览生产构建:
# 构建后预览
npm run build
npm run preview
# 指定端口预览
vite preview --port 4173
# 指定主机
vite preview --host
构建性能优化
提高构建速度
export default defineConfig({
build: {
// 禁用 source map 加速构建
sourcemap: false,
// 禁用压缩大小报告
reportCompressedSize: false,
// 使用 esbuild 压缩(比 terser 快)
minify: 'esbuild',
},
})
减少构建输出
export default defineConfig({
build: {
// 提高内联限制,减少 HTTP 请求
assetsInlineLimit: 8192,
rollupOptions: {
output: {
// 控制代码分割粒度
manualChunks: (id) => {
// 将大型依赖单独打包
if (id.includes('node_modules/large-lib')) {
return 'large-lib'
}
},
},
},
},
})
Rolldown 高级配置
Vite 8 使用 Rolldown 作为打包器,提供了一些 Rollup 不具备的高级配置选项。
advancedChunks(替代 manualChunks)
虽然 Rolldown 仍然支持 manualChunks 选项以保持兼容性,但它已被标记为废弃。Rolldown 提供了更细粒度的 advancedChunks 选项,类似于 Webpack 的 splitChunks:
// 旧配置(Rollup)
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks(id) {
if (/\/react(?:-dom)?/.test(id)) {
return 'vendor'
}
},
},
},
},
})
// 新配置(Rolldown)
export default defineConfig({
build: {
rollupOptions: {
output: {
advancedChunks: {
groups: [
{ name: 'vendor', test: /\/react(?:-dom)?/ }
],
},
},
},
},
})
advancedChunks 配置选项
advancedChunks 提供了更灵活的控制,支持以下配置:
export default defineConfig({
build: {
rollupOptions: {
output: {
advancedChunks: {
// 分组配置
groups: [
{
name: 'react-vendor',
test: /\/react(?:-dom)?/,
priority: 10, // 更高优先级,优先匹配
minSize: 10000, // 该组最小大小
minShareCount: 2, // 最少被共享次数
},
{
name: 'ui-vendor',
test: /\/node_modules\/(antd|@ant-design)\//,
priority: 5,
},
{
name: 'utils-vendor',
test: /\/node_modules\/(lodash|dayjs)\//,
},
],
// 全局配置
minSize: 10000, // 全局最小 chunk 大小(字节)
maxSize: 244000, // 全局最大 chunk 大小(字节)
// 共享模块配置
minShareCount: 1, // 模块被共享的最少次数
// 输出控制
includeDependenciesRecursively: true, // 递归包含依赖
},
},
},
},
})
配置项详解
| 选项 | 类型 | 说明 |
|---|---|---|
groups | array | 分组配置数组 |
groups[].name | string | 分组名称,用于生成 chunk 文件名 |
groups[].test | RegExp | function | 匹配模块的条件,可以是正则或函数 |
groups[].priority | number | 优先级,数值越大优先级越高 |
groups[].minSize | number | 该分组的最小 chunk 大小 |
groups[].minShareCount | number | 模块被共享的最少次数才进入该分组 |
minSize | number | 全局最小 chunk 大小 |
maxSize | number | 全局最大 chunk 大小,超过会拆分 |
minShareCount | number | 全局共享次数阈值 |
实际应用示例
场景一:分离核心框架和业务库
advancedChunks: {
groups: [
// 核心 React 库(最高优先级)
{
name: 'react-core',
test: /\/react(-dom|-router)?\//,
priority: 100,
},
// 状态管理库
{
name: 'state-management',
test: /\/(redux|mobx|zustand|jotai)\//,
priority: 50,
},
// UI 组件库
{
name: 'ui-lib',
test: /\/(antd|mui|chakra)\//,
priority: 30,
},
// 工具库
{
name: 'utils',
test: /\/(lodash|dayjs|axios)\//,
priority: 20,
},
],
minSize: 20000, // 20KB 以下不单独分割
}
场景二:按页面分割代码
advancedChunks: {
groups: [
{
name: 'home',
test: /\/src\/pages\/Home\//,
priority: 10,
},
{
name: 'dashboard',
test: /\/src\/pages\/Dashboard\//,
priority: 10,
},
{
name: 'admin',
test: /\/src\/pages\/Admin\//,
priority: 10,
},
],
}
使用函数进行复杂匹配
advancedChunks: {
groups: [
{
name: 'vendor',
test: (module) => {
// 自定义匹配逻辑
const id = module.id || module.moduleIdentifier
return id.includes('node_modules') && !id.includes('node_modules/my-local-lib')
},
priority: 1,
},
],
}
模块级持久缓存
Rolldown 支持模块级的持久缓存,可以显著提升增量构建速度:
export default defineConfig({
build: {
rollupOptions: {
cache: true, // 启用缓存
},
},
})
Module Federation 支持
Rolldown 原生支持 Module Federation,无需额外插件:
export default defineConfig({
build: {
rollupOptions: {
output: {
format: 'es',
},
},
},
plugins: [
{
name: 'module-federation',
// 配置 Module Federation
},
],
})
原生插件
Rolldown 和 Oxc 提供了 Rust 实现的原生插件,性能更好。Vite 8 默认启用了原生插件(experimental.enableNativePlugin: 'v1')。
如果遇到兼容性问题,可以调整此选项:
export default defineConfig({
experimental: {
enableNativePlugin: 'resolver', // 只启用解析器原生插件
// 或
enableNativePlugin: false, // 禁用原生插件
},
})
withFilter 包装器
对于插件开发者,可以使用 withFilter 包装器减少 Rust 和 JavaScript 运行时之间的通信开销:
import { withFilter, defineConfig } from 'vite'
import svgr from 'vite-plugin-svgr'
export default defineConfig({
plugins: [
// 只对 .svg?react 文件加载 svgr 插件
withFilter(
svgr({ /* 配置 */ }),
{ load: { id: /\.svg\?react$/ } },
),
],
})
写在最后
生产构建是应用上线的最后一步。本章介绍了构建命令、浏览器兼容性、代码分割、多页面应用和库模式等内容。
Vite 8 使用 Rolldown 作为打包器,提供了更好的性能和更灵活的代码分割选项(如 advancedChunks)。构建完成后,建议使用 rollup-plugin-visualizer 分析产物大小,找出优化空间。