Skip to content

第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 与多环境构建的架构
  • 掌握 createBuilderbuildApp 的编排机制
  • 了解构建 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() 函数出人意料地简洁。它的全部工作就是创建一个构建器实例,然后委托构建器对第一个环境执行构建。这种简洁性不是偷工减料,而是优秀的抽象设计——真正的复杂性被封装在了 createBuilderbuildEnvironment 中:

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 函数的构建模式封装。两个可选的回调参数——patchConfigpatchPlugins——是多环境构建的关键支撑,它们允许在配置解析完成后对结果进行环境特定的修补:

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
}

基于 VitePress 构建