Appearance
第13章 Rolldown 构建引擎
如果说开发服务器是 Vite 的左手——灵活、快速、按需响应——那么构建引擎就是它的右手——全面、深入、系统优化。开发时按需编译的策略带来了极致的启动速度和即时反馈,但生产环境需要的是另一种品质:经过 tree-shaking 的精简代码、经过分割的并行加载包、经过压缩的最小体积、经过 hash 的长期缓存。Vite 的构建引擎承担着将开发时的源代码转化为生产级产物的使命。
在 Vite 的演进历程中,构建引擎经历了一次意义深远的技术迁移:底层打包器从 Rollup 切换到了 Rolldown。Rollup 是一个成熟的 JavaScript 打包器,以出色的 tree-shaking 和模块化输出著称;Rolldown 则是其 Rust 实现,保持了与 Rollup 几乎完全兼容的插件 API,同时在打包速度上实现了数量级的提升。这次迁移不仅是性能的飞跃,更标志着 Vite 技术栈向 Rust 生态的深度融合。
本章将深入 build.ts 这个近 1930 行的核心文件,从 build() 函数的入口出发,追踪配置解析、环境创建、Rolldown 选项构建、打包执行到产物输出的完整流程。
本章要点
- 理解
build()函数的完整执行流程与架构设计 - 掌握 Rolldown 与 Rollup 的兼容性设计和关键差异
- 深入
resolveRolldownOptions的配置生成逻辑 - 理解
BuildEnvironment与多环境构建的架构 - 掌握
createBuilder和buildApp的编排机制 - 了解构建 Hook 注入、环境隔离与产物输出策略
13.1 从 Rollup 到 Rolldown
为什么选择 Rolldown
Vite 最初选择 Rollup 作为构建引擎,这是一个经过十年打磨的 JavaScript 打包器。Rollup 的 tree-shaking 算法基于 ES Module 的静态结构分析,能精确地移除未使用的导出,生成高度优化的产物。然而,随着前端项目规模的爆炸式增长——现代应用动辄数千个模块、数十万行代码——Rollup 的 JavaScript 实现在处理大型项目时的性能瓶颈日益明显。模块解析、AST 遍历、代码生成等 CPU 密集型操作在 JavaScript 的单线程环境中难以并行化。
Rolldown 是 Rollup 的 Rust 重写,由 Vite 团队主导开发。Rust 的零开销抽象、精确的内存管理和天然的并行能力使得 Rolldown 在保持 API 兼容性的同时,打包速度提升了 10 倍乃至更多。更重要的是,Rolldown 与 Vite 的其他 Rust 组件(如 oxc 解析器和压缩器)共享底层基础设施,形成了一个高效的工具链闭环。
在 Vite 的源码中,我们可以清晰地看到这一迁移的痕迹:
typescript
// build.ts 中的 import 声明
import type {
RolldownBuild,
RolldownOptions,
RolldownOutput,
RolldownWatcher,
RollupError, // 注意:错误类型仍使用 Rollup 的命名
} from 'rolldown'
import { viteLoadFallbackPlugin as nativeLoadFallbackPlugin } from 'rolldown/experimental'
import { esmExternalRequirePlugin } from 'rolldown/plugins'类型名称从 Rollup* 变为 Rolldown*,但 RollupError 等兼容性类型保持不变,体现了渐进式迁移的策略。
兼容性保障
为了确保现有项目的平滑升级,Vite 在配置层面维护了完整的向后兼容性。rollupOptions 被保留为 rolldownOptions 的别名,使用旧配置名的项目不需要做任何修改就能正常工作:
typescript
export interface BuildEnvironmentOptions {
/**
* Alias to `rolldownOptions`
* @deprecated Use `rolldownOptions` instead.
*/
rollupOptions?: RolldownOptions
/**
* Will be merged with internal rolldown options.
*/
rolldownOptions?: RolldownOptions
}在配置解析阶段,setupRollupOptionCompat 函数负责处理这种别名映射。这种设计让生态系统有充足的时间完成迁移,而不是一刀切地破坏兼容性。
13.2 构建流程全景
build() 入口函数
与人们对构建入口的预期相比,build() 函数出人意料地简洁。它的全部工作就是创建一个构建器实例,然后委托构建器对第一个环境执行构建。这种简洁性不是偷工减料,而是优秀的抽象设计——真正的复杂性被封装在了 createBuilder 和 buildEnvironment 中:
typescript
export async function build(
inlineConfig: InlineConfig = {},
): Promise<RolldownOutput | RolldownOutput[] | RolldownWatcher> {
const builder = await createBuilder(inlineConfig, true)
const environment = Object.values(builder.environments)[0]
if (!environment) throw new Error('No environment found')
return builder.build(environment)
}这个设计的深意在于:单环境构建和多环境构建共享完全相同的底层机制。build() 只是 createBuilder().build() 的快捷方式,当需要构建多个环境时,调用方可以直接使用 createBuilder 获得更精细的控制。
resolveConfigToBuild:配置解析
构建配置的解析通过 resolveConfigToBuild 完成,它是通用的 resolveConfig 函数的构建模式封装。两个可选的回调参数——patchConfig 和 patchPlugins——是多环境构建的关键支撑,它们允许在配置解析完成后对结果进行环境特定的修补:
typescript
function resolveConfigToBuild(
inlineConfig: InlineConfig = {},
patchConfig?: (config: ResolvedConfig) => void,
patchPlugins?: (resolvedPlugins: Plugin[]) => void,
): Promise<ResolvedConfig> {
return resolveConfig(
inlineConfig,
'build', // command: 告诉插件系统当前是构建模式
'production', // mode: 默认的构建模式
'production', // configEnv.mode
false, // isPreview: 不是预览模式
patchConfig, // 配置后处理回调
patchPlugins, // 插件后处理回调
)
}13.3 构建选项解析
resolveBuildEnvironmentOptions:从用户配置到内部表示
这个函数承担着将用户友好的配置选项转换为内部精确表示的重任。它处理了废弃选项的兼容、默认值的合并、特殊值的展开等一系列转换工作。理解这个函数是理解 Vite 构建行为的基础:
typescript
export function resolveBuildEnvironmentOptions(
raw: BuildEnvironmentOptions,
logger: Logger,
consumer: 'client' | 'server' | undefined,
isBundledDev: boolean,
): ResolvedBuildEnvironmentOptions {
// 处理废弃的 polyfillModulePreload 选项
const deprecatedPolyfillModulePreload = raw.polyfillModulePreload
if (deprecatedPolyfillModulePreload !== undefined) {
logger.warn('polyfillModulePreload is deprecated. Use modulePreload.polyfill instead.')
}
// 合并默认值——注意 consumer 参数如何影响默认值
const merged = mergeWithDefaults({
..._buildEnvironmentOptionsDefaults,
cssCodeSplit: !raw.lib,
minify: consumer === 'server' || isBundledDev ? false : 'oxc',
ssr: consumer === 'server',
emitAssets: consumer === 'client',
createEnvironment: (name, config) => new BuildEnvironment(name, config),
}, raw)
// target 的语义化展开
if (merged.target === 'baseline-widely-available') {
merged.target = ESBUILD_BASELINE_WIDELY_AVAILABLE_TARGET
}
if (Array.isArray(merged.target)) {
merged.target = unique(merged.target) // 去重(oxc 不允许重复)
}
// minify 的规范化
if ((merged.minify as string) === 'false') merged.minify = false
else if (merged.minify === true) merged.minify = 'oxc'
return resolved
}consumer 参数是这个函数中最巧妙的设计之一。它根据构建环境的消费者类型(浏览器客户端还是服务端)自动调整默认值:客户端默认启用压缩和资源输出,服务端默认禁用压缩且不输出资源文件。这种基于角色的默认值策略大大减少了用户需要手动配置的选项。
下面的图表展示了各个关键配置选项的默认值,以及它们如何随构建环境类型变化:
构建目标的语义化
baseline-widely-available 是 Vite 引入的一个语义化目标值。它代表 "在 2026-01-01 之前被广泛支持的浏览器基线",会被展开为具体的浏览器版本列表。这种语义化的目标定义比直接指定 ['chrome111', 'firefox114', 'safari16.4'] 更加直观和可维护。去重处理是从 Rollup 到 Rolldown 迁移带来的需求——esbuild 允许目标列表中的重复项,但 oxc 压缩器不允许。
13.4 Rolldown 选项构建
resolveRolldownOptions:配置生成的核心
这是整个构建流程中最关键的配置生成函数。它将 Vite 的高层抽象配置翻译为 Rolldown 可以直接消费的底层选项。每一个配置项的选择都蕴含着对不同构建场景的深入考量:
typescript
export function resolveRolldownOptions(
environment: Environment,
chunkMetadataMap: ChunkMetadataMap,
): RolldownOptions {
const { root, packageCache, build: options } = environment.config
const ssr = environment.config.consumer === 'server'
// 1. 确定入口——不同模式有不同的入口解析逻辑
const resolve = (p: string) => path.resolve(root, p)
const input = libOptions
? options.rollupOptions.input || /* 库模式:从 lib.entry 解析 */
: typeof options.ssr === 'string'
? resolve(options.ssr) // SSR:使用指定的服务端入口
: options.rollupOptions.input || resolve('index.html') // 应用模式:默认 index.html
// 2. 注入环境上下文到插件钩子
const plugins = environment.plugins.map((p) =>
injectEnvironmentToHooks(environment, chunkMetadataMap, p),
)
// 3. 构建完整的 Rolldown 选项
const rollupOptions: RolldownOptions = {
preserveEntrySignatures: ssr ? 'allow-extension' : libOptions ? 'strict' : false,
...options.rollupOptions,
input,
plugins,
onLog(level, log) { onRollupLog(level, log, environment) },
transform: {
target: options.target === false ? undefined : options.target,
define: {
...options.rollupOptions.transform?.define,
// 禁用 Rolldown 内置的 process.env.NODE_ENV 替换
// 因为 Vite 的 define 插件会处理这个
'process.env.NODE_ENV': 'process.env.NODE_ENV',
},
},
// 暂时由 Vite 的 CSS 插件处理 CSS,告诉 Rolldown 将 .css 视为 JS
moduleTypes: { '.css': 'js' },
// 启用 Vite 模式,激活 Rolldown 中的 Vite 特定行为
experimental: { viteMode: true },
}
return rollupOptions
}