跳到主要内容

生产构建

构建命令

使用 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 年前发布的浏览器):

浏览器最低版本
Chrome111+
Edge111+
Firefox114+
Safari16.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)要求在分发软件时保留原始许可证声明。使用这个功能可以:

  1. 合规性:确保你的应用遵守所有依赖的许可证要求
  2. 透明度:让用户了解你的应用使用了哪些开源项目
  3. 便捷性:自动收集所有依赖的许可证,无需手动整理

生成的文件可以托管在网站上,供用户查看和确认。


### 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, // 递归包含依赖
},
},
},
},
})

配置项详解

选项类型说明
groupsarray分组配置数组
groups[].namestring分组名称,用于生成 chunk 文件名
groups[].testRegExp | function匹配模块的条件,可以是正则或函数
groups[].prioritynumber优先级,数值越大优先级越高
groups[].minSizenumber该分组的最小 chunk 大小
groups[].minShareCountnumber模块被共享的最少次数才进入该分组
minSizenumber全局最小 chunk 大小
maxSizenumber全局最大 chunk 大小,超过会拆分
minShareCountnumber全局共享次数阈值

实际应用示例

场景一:分离核心框架和业务库

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 分析产物大小,找出优化空间。