在 Vite 8 中,vite build 命令已经从传统的 Rollup 打包,彻底转向了由 Rust 驱动的全新工具链。
Vite 8 最大的改变,是其构建流程的底层核心被完全重写,统一使用 Rust 生态的工具。
- 单一打包器 Rolldown:此前,Vite 在开发环境使用
esbuild追求速度,在生产环境使用Rollup追求能力,这导致了行为不一致。Vite 8 使用一个名为 Rolldown 的 Rust 打包器,统一了开发和生产环境的构建链路。它完全兼容 Rollup 的插件 API,使得绝大多数现有 Vite 插件无需修改即可在 Vite 8 中运行。 - 高性能引擎 Oxc:Rolldown 本身构建于 Oxc(另一个用 Rust 编写的工具集)之上。Oxc 为 Rolldown 提供了极快的解析、转换能力,使其在处理 TypeScript 和 JSX 文件时性能大幅领先。
vite build 有哪些命令行参数?
// build
cli
.command('build [root]', 'build for production')
.option(
'--target <target>',
`[string] transpile target (default: 'baseline-widely-available')`,
)
.option('--outDir <dir>', `[string] output directory (default: dist)`)
.option(
'--assetsDir <dir>',
`[string] directory under outDir to place assets in (default: assets)`,
)
.option(
'--assetsInlineLimit <number>',
`[number] static asset base64 inline threshold in bytes (default: 4096)`,
)
.option(
'--ssr [entry]',
`[string] build specified entry for server-side rendering`,
)
.option(
'--sourcemap [output]',
`[boolean | "inline" | "hidden"] output source maps for build (default: false)`,
)
.option(
'--minify [minifier]',
`[boolean | "terser" | "esbuild"] enable/disable minification, ` +
`or specify minifier to use (default: esbuild)`,
)
.option('--manifest [name]', `[boolean | string] emit build manifest json`)
.option('--ssrManifest [name]', `[boolean | string] emit ssr manifest json`)
.option(
'--emptyOutDir',
`[boolean] force empty outDir when it's outside of root`,
)
.option('-w, --watch', `[boolean] rebuilds when modules have changed on disk`)
.option('--app', `[boolean] same as \`builder: {}\``)
vite build 接收的 options 有哪些?
源码
createBuilder
/**
* Creates a ViteBuilder to orchestrate building multiple environments.
* 创建和配置 vite构建器
* @experimental
* params inlineConfig 行内配置
* params useLegacyBuilder 是否使用旧版构建器
*/
export async function createBuilder(
inlineConfig: InlineConfig = {},
useLegacyBuilder: null | boolean = false,
): Promise<ViteBuilder> {
// 处理旧版兼容
const patchConfig = (resolved: ResolvedConfig) => {
if (!(useLegacyBuilder ?? !resolved.builder)) return
// Until the ecosystem updates to use `environment.config.build` instead of `config.build`,
// we need to make override `config.build` for the current environment.
// We can deprecate `config.build` in ResolvedConfig and push everyone to upgrade, and later
// remove the default values that shouldn't be used at all once the config is resolved
const environmentName = resolved.build.ssr ? 'ssr' : 'client'
;(resolved.build as ResolvedBuildOptions) = {
...resolved.environments[environmentName].build,
}
}
// 配置解析
const config = await resolveConfigToBuild(inlineConfig, patchConfig)
// 是否使用旧版构建器
useLegacyBuilder ??= !config.builder
// 构建器配置
const configBuilder = config.builder ?? resolveBuilderOptions({})!
const environments: Record<string, BuildEnvironment> = {}
// 创建 ViteBuilder 对象
const builder: ViteBuilder = {
environments,
config,
/**
* 构建整个应用
*/
async buildApp() {
// 创建插件上下文
const pluginContext = new BasicMinimalPluginContext(
{ ...basePluginContextMeta, watchMode: false },
config.logger,
)
// order 'pre' and 'normal' hooks are run first, then config.builder.buildApp, then 'post' hooks
// 是否已调用配置构建器的 buildApp 方法
let configBuilderBuildAppCalled = false
// 执行插件的 buildApp 钩子
for (const p of config.getSortedPlugins('buildApp')) {
const hook = p.buildApp
if (
!configBuilderBuildAppCalled &&
typeof hook === 'object' &&
hook.order === 'post' // 只在 post 阶段调用
) {
configBuilderBuildAppCalled = true
await configBuilder.buildApp(builder)
}
const handler = getHookHandler(hook)
await handler.call(pluginContext, builder)
}
// 如果未调用配置构建器的 buildApp 方法,调用默认 buildApp 方法
if (!configBuilderBuildAppCalled) {
await configBuilder.buildApp(builder)
}
// fallback to building all environments if no environments have been built
// 检查是否有环境被构建
if (
Object.values(builder.environments).every(
(environment) => !environment.isBuilt,
)
) {
for (const environment of Object.values(builder.environments)) {
// 构建所有环境
await builder.build(environment)
}
}
},
/**
* 构建环境
* @param environment
* @returns
*/
async build(
environment: BuildEnvironment,
): Promise<RolldownOutput | RolldownOutput[] | RolldownWatcher> {
const output = await buildEnvironment(environment)
environment.isBuilt = true
return output
},
async runDevTools() {
const devtoolsConfig = config.devtools
if (devtoolsConfig.enabled) {
try {
const { start } = await import(`@vitejs/devtools/cli-commands`)
await start(devtoolsConfig.config)
} catch (e) {
config.logger.error(
colors.red(`Failed to run Vite DevTools: ${e.message || e.stack}`),
{ error: e },
)
}
}
},
}
/**
* 环境设置函数
*/
async function setupEnvironment(name: string, config: ResolvedConfig) {
const environment = await config.build.createEnvironment(name, config)
await environment.init()
environments[name] = environment
}
// 环境初始化
// 使用旧版构建器
if (useLegacyBuilder) {
await setupEnvironment(config.build.ssr ? 'ssr' : 'client', config)
} else {
// 新版构建器
const environmentConfigs: [string, ResolvedConfig][] = []
for (const environmentName of Object.keys(config.environments)) {
// We need to resolve the config again so we can properly merge options
// and get a new set of plugins for each build environment. The ecosystem
// expects plugins to be run for the same environment once they are created
// and to process a single bundle at a time (contrary to dev mode where
// plugins are built to handle multiple environments concurrently).
let environmentConfig = config
if (!configBuilder.sharedConfigBuild) {
const patchConfig = (resolved: ResolvedConfig) => {
// Until the ecosystem updates to use `environment.config.build` instead of `config.build`,
// we need to make override `config.build` for the current environment.
// We can deprecate `config.build` in ResolvedConfig and push everyone to upgrade, and later
// remove the default values that shouldn't be used at all once the config is resolved
;(resolved.build as ResolvedBuildOptions) = {
...resolved.environments[environmentName].build,
}
}
const patchPlugins = (resolvedPlugins: Plugin[]) => {
// Force opt-in shared plugins
let j = 0
for (let i = 0; i < resolvedPlugins.length; i++) {
const environmentPlugin = resolvedPlugins[i]
if (
configBuilder.sharedPlugins ||
environmentPlugin.sharedDuringBuild
) {
for (let k = j; k < config.plugins.length; k++) {
if (environmentPlugin.name === config.plugins[k].name) {
resolvedPlugins[i] = config.plugins[k]
j = k + 1
break
}
}
}
}
}
// 为每个环境名称创建环境配置
environmentConfig = await resolveConfigToBuild(
inlineConfig,
patchConfig,
patchPlugins,
)
}
environmentConfigs.push([environmentName, environmentConfig])
}
// 并行初始化所有环境
await Promise.all(
environmentConfigs.map(
async ([environmentName, environmentConfig]) =>
await setupEnvironment(environmentName, environmentConfig),
),
)
}
return builder
}
buildEnvironment
buildEnvironment 函数是 Vite 8 中为单个环境(如 client 或 ssr)执行生产构建的核心函数:
- 首先解析 Rolldown 打包配置。
- 然后根据是否开启监听模式(
options.watch)分别创建 Rolldown 的 watcher 以持续构建并监听文件变化,或一次性调用 Rolldown 完成打包。 - 构建过程中会收集每个输出 chunk 的元数据,支持多输出配置(如同时输出 ESM 和 CJS),并最终将产物写入磁盘或返回结果对象。
- 同时提供详细的日志输出和错误增强处理,在结束前确保关闭 Rolldown 实例以释放资源。
/**
* Build an App environment, or a App library (if libraryOptions is provided)
* Vite 8 中负责生产构建单个环境(如 client、ssr)的核心函数。
* 基于 Rolldown(Rust 打包器)执行打包,支持普通构建和监听模式(watch)
**/
async function buildEnvironment(
environment: BuildEnvironment,
): Promise<RolldownOutput | RolldownOutput[] | RolldownWatcher> {
const { logger, config } = environment
const { root, build: options } = config
// 记录开始构建的日志
logger.info(
colors.cyan(
`vite v${VERSION} ${colors.green(
`building ${environment.name} environment for ${environment.config.mode}...`,
)}`,
),
)
let bundle: RolldownBuild | undefined
let startTime: number | undefined
try {
// 收集每个输出 chunk 的元数据(如模块 ID、文件大小等)
const chunkMetadataMap = new ChunkMetadataMap()
// 解析 Rolldown 选项
const rollupOptions = resolveRolldownOptions(environment, chunkMetadataMap)
// watch file changes with rollup
// 监视文件变化
if (options.watch) {
logger.info(colors.cyan(`\nwatching for file changes...`))
const resolvedOutDirs = getResolvedOutDirs(
root,
options.outDir,
options.rollupOptions.output,
)
const emptyOutDir = resolveEmptyOutDir(
options.emptyOutDir,
root,
resolvedOutDirs,
logger,
)
const resolvedChokidarOptions = resolveChokidarOptions(
{
// @ts-expect-error chokidar option does not exist in rolldown but used for backward compat
...(rollupOptions.watch || {}).chokidar,
// @ts-expect-error chokidar option does not exist in rolldown but used for backward compat
...options.watch.chokidar,
},
resolvedOutDirs,
emptyOutDir,
environment.config.cacheDir,
)
const { watch } = await import('rolldown')
// 调用 rolldown.watch 创建监听器
const watcher = watch({
...rollupOptions,
watch: {
...rollupOptions.watch,
...options.watch,
notify: convertToNotifyOptions(resolvedChokidarOptions),
},
})
watcher.on('event', (event) => {
if (event.code === 'BUNDLE_START') {
logger.info(colors.cyan(`\nbuild started...`))
chunkMetadataMap.clearResetChunks()
} else if (event.code === 'BUNDLE_END') {
event.result.close()
logger.info(colors.cyan(`built in ${event.duration}ms.`))
} else if (event.code === 'ERROR') {
const e = event.error
enhanceRollupError(e)
clearLine()
logger.error(e.message, { error: e })
}
})
return watcher
}
// 普通构建
// write or generate files with rolldown
const { rolldown } = await import('rolldown')
startTime = Date.now()
// 创建 Rolldown 构建实例
bundle = await rolldown(rollupOptions)
// 多个输出配置
const res: RolldownOutput[] = []
for (const output of arraify(rollupOptions.output!)) {
// bundle.write(outputOptions) 将产物写入磁盘
// bundle.generate(outputOptions) 仅返回产物对象
res.push(await bundle[options.write ? 'write' : 'generate'](output))
}
for (const output of res) {
for (const chunk of output.output) {
// 注入 chunk 元数据
injectChunkMetadata(chunkMetadataMap, chunk)
}
}
logger.info(
`${colors.green(`✓ built in ${displayTime(Date.now() - startTime)}`)}`,
)
// 返回构建结果
return Array.isArray(rollupOptions.output) ? res : res[0]
} catch (e) {
enhanceRollupError(e)
clearLine()
if (startTime) {
logger.error(
`${colors.red('✗')} Build failed in ${displayTime(Date.now() - startTime)}`,
)
startTime = undefined
}
throw e
} finally {
// 关闭 Rolldown 构建实例
if (bundle) await bundle.close()
}
}
Vite 8 的生产构建底层完全基于 Rolldown(Rust 打包器),支持两种构建模式:一次性打包(默认 vite build)和 监听打包(vite build --watch)。
命令分析
"build": "run-p type-check \"build-only {@}\" --"
"build-only": "vite build",
run-p:来自 npm-run-all,表示并行执行后面的脚本
type-check:第一个要运行的脚本(通常用于 TypeScript 类型检查)。"build-only {@}":第二个要运行的脚本。build-only是另一个 npm 脚本(自定义,例如vite build)。{@}是npm-run-all的特殊占位符,代表传递给当前build命令的所有原始参数。
测试
{
build: {
emptyOutDir:true, // 清空目录
copyPublicDir: true,
reportCompressedSize: true,//启用/禁用 gzip 压缩大小报告
chunkSizeWarningLimit:500,// 规定触发警告的 chunk 大小。(以 kB 为单位)。
assetsInlineLimit:4096,// 4kb 小于此阈值的导入或引用资源将内联为 base64 编码,以避免额外的 http 请求
// baseline-widely-available 具体来说,它是 `['chrome111', 'edge111', 'firefox114', 'safari16.4']`
// esnext —— 即假设有原生动态导入支持,并只执行最低限度的转译。
target: 'baseline-widely-available',
// 如果禁用,整个项目中的所有 CSS 将被提取到一个 CSS 文件中。
cssCodeSplit: true,// 启用,在异步 chunk 中导入的 CSS 将内联到异步 chunk 本身,并在其被加载时一并获取。
cssMinify: 'lightningcss',// Vite 默认使用 [Lightning CSS](https://lightningcss.dev/minification.html) 来压缩 CSS
// true,将会创建一个独立的 source map 文件
// inline,source map 将作为一个 data URI 附加在输出文件中
sourcemap:false,
license:true, // true,构建过程将生成一个 .vite/license.md文件,
}
}
示例 build.outDir 、build.assetsDir
build.outDir默认值 dist,build.assetsDir默认值 assets。
build: {
outDir: 'dist-cube',
assetsDir: 'public',
},
示例 build.minify
1、默认情况。
minify 默认压缩,客户端构建默认为'oxc'。
2、配置不压缩。
build: {
outDir: 'dist-cube',
assetsDir: 'public',
minify: false, // 不压缩
},
3、配置 esbuild 。
build: {
outDir: 'dist-cube',
assetsDir: 'public',
minify: 'esbuild',
},
提示 在 vite8 中 esbuild 已废弃。建议使用 oxc 。
4、 配置 terser。
build: {
outDir: 'dist-cube',
assetsDir: 'public',
minify: 'terser',
},
当设置为 'esbuild' 或 'terser' 时,必须分别安装 esbuild 或 Terser。
npm add -D esbuild
npm add -D terser
示例 build.manifest / ssrManifest
manifest 设置为 true 时,路径将是 .vite/manifest.json。
ssrManifest 设置为 true 时,路径将是 .vite/ssr-manifest.json。
vue3-vite-cube/dist-cube/index.html
<!doctype html>
<html lang="">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App菜单</title>
<script type="module" crossorigin src="/public/index-B9iM-AOo.js"></script>
<link rel="modulepreload" crossorigin href="/public/runtime-core.esm-bundler-HXD8ebTp.js">
<link rel="stylesheet" crossorigin href="/public/index-DuS5nk76.css">
</head>
<body>
<div id="app"></div>
</body>
</html>