Rolldown Dev Engine 设计指南:Full Bundle 模式下保守重建、错误流与恢复触发的四大原则
Rolldown Dev Engine 设计指南Full Bundle 模式下保守重建、错误流与恢复触发的四大原则【免费下载链接】rolldownFast Rust bundler for JavaScript/TypeScript with Rollup-compatible API.项目地址: https://gitcode.com/GitHub_Trending/ro/rolldown本文基于仓库 internal-docs/dev-engine/design.md 编写并辅以 implementation.md 与crates/rolldown_dev源码佐证。本指南系统讲解 rolldown 开发模式构建编排层rolldown_devDev Engine在 Full Bundle Mode 下的设计理念它如何决定“何时、以何种形式HMR 补丁 / 增量重建 / 全量构建重建 bundle”以及构建错误如何流向绑定层消费方典型如 Vite。读完本文你将掌握驱动该引擎行为的四大设计原则、其背后的CoordinatorState状态机与TaskInput任务类型语义并能据此理解 dev server 在页面刷新、构建失败、文件变更等场景下的真实行为契约。一、Dev Engine 在 rolldown 中的定位在 Full Bundle Mode 下rolldown_devcrate 是 rolldown 的开发模式构建编排层位于文件监听器 / dev server与核心Bundler之间。它的职责不是“如何打包”而是“打包什么、何时打包”——在以下三者间做决策HMR patch热更新补丁只计算补丁不做重建Incremental rebuild增量重建不生成 HMR 补丁但重建 bundle 输出Full build全量构建从头完整打包。整个引擎由三部分协作构成见 crates/rolldown_dev/src/DevEnginedev_engine.rs公开异步 API └─ 持有 ArcMutexBundler拥有 coordinator 的 mpsc Sender │ CoordinatorMsg无界 mpsc 通道 ▼ BundleCoordinatorbundle_coordinator.rs单线程消息循环 └─ 拥有 CoordinatorState 状态机 VecDequeTaskInput 工作队列 fs watcher │ spawn ▼ BundlingTaskbundling_task.rs一次构建工作单元 └─ 加锁 Bundler执行 HMR / rebuild通过 CoordinatorMsg 回报 ▼ Bundlercrates/rolldown/src/bundler/ └─ compute_hmr_update_for_file_changes / incremental_generate / incremental_write从线程模型看implementation.md §1BundleCoordinator运行在一个专用 tokio 任务中其run()是单一while let Some(msg) self.rx.recv().await循环——所有协调器状态变更因此天然串行化“消息循环本身就是锁”CoordinatorState无需再加锁每个BundlingTask则在各自独立的任务中运行Bundler通过ArcMutexBundler共享由BundlingTask在 HMR / 重建期间独占加锁。二、四大设计原则design.md 用四个原则定义了rolldown_dev与消费方典型为 Vite之间的契约并约束了实现层implementation.md 的 §7、§13、§16的行为。它们是理解整个引擎行为的钥匙。原则 1保守重建Conservative rebuilds只有当 bundle 处于“stale”陈旧状态时才触发重建——即自上次构建尝试以来输入发生了变化。单独的页面访问、浏览器重连永远不会触发重建。特别地如果上一次构建失败了访问请求不会自动重试——因为输入没有变化同样的错误必然复现。该原则在源码中的落地位置BundleCoordinator::ensure_latest_bundle_output在Failed/FullBuildFailed状态下返回Noneimplementation.md §13b。从 bundle_coordinator.rs 的状态快照方法可以看出协调器通过last_build_errored任一错误状态下为true与has_stale_output两个标志配合让消费方在“stale 且 errored”时不得在访问时触发重建否则会陷入无意义的重载循环。原则 2每次构建都会输出错误Errors are emitted on every buildrolldown_dev通过on_output/on_hmr_updates回调在每一次构建时向绑定层消费方暴露构建错误。它不会静默重试越过错误、不会静默吞掉错误、也不会在请求之间缓存错误——引擎跨 HTTP 请求是无状态的。错误缓存的职责完全在消费方Vite一侧fullBundleEnvironment.ts中用一个lastBuildError: Error | null字段缓存最近一次来自两个通道任一的错误——onOutput全量构建错误与onHmrUpdatesHMR 错误都会设置它任一通道的成功构建都会将其清回null一个能干净计算的 HMR 补丁即可取代之前缓存的错误。随后在vite:client:connect事件上对每一个新连接客户端包括刷新后的重连重放该错误从而让错误遮罩在浏览器刷新后依然可见。两个通道的差异只在实时投递方式上通道错误投递方式onOutput额外通过logger.error打印到终端无浏览器也能看到构建中断并向所有客户端广播hot.sendonHmrUpdates仅逐个发送给每个已连接客户端不打印到终端原则 3文件变更是唯一的恢复触发器File changes are the only recovery trigger构建失败后引擎会等待文件变更才重建。Vite 配置编辑和用户源码编辑都是有效触发器。在rolldown_dev内部其他任何东西都不算恢复手段——页面刷新、时间流逝、手动关闭 UI 遮罩都不行ensure_latest_bundle_output在所有失败状态下都是 no-op§13b访问永远不会自行触发重建。消费方一侧有一个例外HMR 阶段失败后的页面刷新。当最后一次失败源于 HMR 生成last_error_stage Hmr时消费方被允许把页面刷新当作恢复触发器在访问时调用triggerFullBuild§13e强制走一次绕开可能出问题的 HMR 路径的全量重建而不是重放缓存的错误。这一升级严格限定在消费方——rolldown_dev自身行为不变升级决策由消费方基于从BundleState§12读到的last_error_stage做出。Rebuild阶段或全量构建的失败没有这种例外——只有文件变更能恢复它们。仓库内参考消费方的实现位于packages/test-dev-server/src/environments/full-bundle-dev-environment.ts的triggerBundleRegenerationIfStale。该原则的推论失败后的文件变更必须调度能“撤销失败”的工作。实践中这意味着要追踪失败起源于哪个阶段HMR 计算 vs 增量重建以便下一个任务覆盖出错的阶段§7。在 bundle_coordinator.rs 的handle_file_changes中可以看到该逻辑Failed { last_error_stage: Hmr }时排队Hmr或策略为Always时HmrRebuild给watch_change钩子和 HMR 计算第二次机会而Failed { last_error_stage: Rebuild }时无条件排队HmrRebuild——重建阶段的失败说明 bundle 输出相对源码已经陈旧恢复任务必须包含重建。原则 4构建错误可恢复panic 是 bugBuild errors are recoverable; panics are bugs所有经由on_output/on_hmr_updates到达消费方的错误都被视为用户错误——由源码或插件行为引起可通过编辑源码恢复。在该模型下Rolldown 和 Vite 自身被假定无 bug。唯一无法通过文件变更循环恢复的状态是panic它标志着rolldown_dev自身的不变量被违反§16g。这一原则在 error_stage.rs 和 implementation.md §16 中有完整展开错误被分为三种受众——终端用户构建错误、绑定层消费方生命周期错误、引擎自身不变量违反 → panic。判断是否该 panic 的实用测试是这个错误能否由我们 crate 之外的任何东西触发能则走错误通道不能则 panic。三、状态机与任务类型原则如何落到实现CoordinatorState六态调度状态机定义于 crates/rolldown_dev/src/types/coordinator_state.rs是BundleCoordinator上的一个Copy枚举字段仅通过set_initial_build_state变更。它分成由Idle连接的两半初始全量构建半区Initialized→FullBuildInProgress→FullBuildFailed关心第一次构建稳态半区Idle→InProgress→Failed { last_error_stage }关心初始构建成功之后的每一次构建。状态含义Initialized已构造但run()尚未进入瞬态FullBuildInProgress初始TaskInput::FullBuild正在运行FullBuildFailed初始全量构建出错完全没有任何可用输出Idle无构建运行上次构建如有成功InProgress增量任务Hmr/HmrRebuild/Rebuild正在运行Failed { last_error_stage }上次增量任务出错last_error_stage记录是哪个阶段产生的关键状态迁移包括FullBuildFailed → FullBuildInProgress文件变更触发排队FullBuild、Failed → InProgress文件变更触发排队Hmr/HmrRebuild、Idle → FullBuildInProgressTriggerFullBuild清空队列后排队FullBuild。完整迁移图见 implementation.md §3。TaskInput四类排队工作单元定义于 crates/rolldown_dev/src/types/task_input.rs工作队列为queued_tasks: VecDequeTaskInputpub enum TaskInput { FullBuild, // 全量构建初始或恢复 Rebuild { changed_files: … }, // 仅增量重建无 HMR 补丁 Hmr { changed_files: … }, // 仅 HMR 补丁无重建 HmrRebuild { changed_files: … }, // HMR 补丁 增量重建 }三个谓词驱动任务行为requires_full_rebuild()仅FullBuild为真、requires_rebuild()FullBuild/Rebuild/HmrRebuild、require_generate_hmr_update()Hmr/HmrRebuild。协调器弹出一个任务时会贪婪合并队列前部相邻的可合并任务FullBuild吸收一切Rebuild只与Rebuild合并并集changed_filesHmr/HmrRebuild相互合并Hmr HmrRebuild → HmrRebuild。Rebuild与Hmr系列不可互并——增量重建会拉入本不打算参与 HMR 生成的文件。Hmr → HmrRebuild的运行时自动升级RebuildStrategy定义于 crates/rolldown_dev_common/src/types/rebuild_strategy.rs是一个影响引擎两处行为的选项pub enum RebuildStrategy { Always, // HMR 之后总是执行增量重建 Auto, // 默认仅当 HMR 更新包含 full-reload 时才重建 Never, // HMR 之后永不重建 }排队时§9ahandle_file_changes中Always直接排队HmrRebuildAuto/Never排队Hmr运行时§9bbundling_task.rs:104-114HMR 生成之后任务可能改写自己的输入——Auto策略下若生成的 HMR 更新是 full reload 且输入原本是纯Hmr则把self.input升级为HmrRebuild。其依据是一个变更能否热替换、还是必须整页刷新在 HMR diff 计算出来之前是未知的。所以Auto先排队便宜的Hmr任务算出 diff 后再决定是否自我升级。结论是协调器排队的TaskInput变体不一定是实际运行的变体——排队时的Hmr可能在任务中途变成HmrRebuild。四、错误流从阶段标记到消费方回调阶段分类与优先级BundlingTask在run_inner期间跟踪两个独立标志hmr_errored、rebuild_errored并按**Rebuild Hmr** 的优先级推导上报的OptionErrorStagerebuild_erroredhmr_errored上报的error_stagetrue任意Some(Rebuild)falsetrueSome(Hmr)falsefalseNoneRebuild优先的原因正是 §三 的自动升级路径一个Hmr任务可能在任务中途被改写为HmrRebuild然后在重建中失败此时两个标志都为真。上报Rebuild是保守选择——下一次文件变更会强制重建这正是确认修复所需的行为。标志的设置位置plugin_driver.watch_change钩子失败 →hmr_erroredgenerate_hmr_updates失败 →hmr_erroredrebuild()incremental_*失败 →rebuild_errored。run_inner依次执行四步① 对每个变更文件调用plugin_driver.watch_change基于最后一个 bundle 句柄② 若require_generate_hmr_update()则调用generate_hmr_updates③ 执行 §三 的自动升级④ 若requires_rebuild()则置has_rebuild_happen true并调用rebuild()。两条投递通道Throw同步 API单调用者单结果的公开 napi 方法边界用BindingResultT EitherBindingErrors, TJS 包装层用unwrapBindingResult成功返回值或抛出BundleError。用于invalidate、ensureLatestBuildOutput、getBundleState、waitForOngoingBundle。Callback异步生命周期BundlingTask内部异步产生的错误通过构造引擎时注册的on_output/on_hmr_updates回调上报。这是构建错误到达终端用户经由消费方转发进其错误遮罩 / HMR 错误显示的标准通道。选择通道的规则如果消费方无法提前注册回调因为错误源自一次性调用就 throw否则投递给回调。run_inner的三个出错阶段各自拥有错误路由决策不存在顶层错误处理器watch_change失败走on_output且短路返回HMR 生成与重建都无法安全继续generate_hmr_updates失败走on_hmr_updates并可继续rebuild失败走on_output。生命周期错误与 panic 边界DevEngine每个触碰协调器的方法顶部都有create_error_if_closed()入口守卫默认将引擎关闭、协调器退出等生命周期错误暴露给绑定层消费方Vite 需要看到自己“在close之后调用invalidate”的时序错误而非静默吞掉。少数“等待 / 观察类”方法例外地返回Ok——当“你等待的事情不可能再发生”本身就是完整且诚实的答案时如wait_for_ongoing_bundle、BindingDevEngine::ensure_current_build_finish、以及只在close()竞态下返回Ok的ensure_latest_bundle_output。而引擎内部的 panic 站点都是有意保留的不变量断言例如bundling_task.rs:71最终BundleCompleted发送的.expect(...)——协调器在处理Close前会先 await 在途的BundlingTask§4因此按构造该接收端必然存活bundle_coordinator.rs:323, 420的current_bundling_future.clone().unwrap()——仅存在于*InProgress状态状态机保证Some(_)出现None说明漏了一次状态迁移。新增 panic 站点时应在.expect(...)消息中写明所断言的不变量让下一位读者无需重构即可看到契约。五、ensure_latest_bundle_output浏览器访问的“惰性全量输出”管线这是保证浏览器页面加载 / 刷新拿到最新全量 bundle 的路径横跨DevEngine与BundleCoordinator两层§13。DevEngine::ensure_latest_bundle_outputdev_engine.rs是一个带上限的重试循环每次发送EnsureLatestBundleOutput消息并携带 oneshot 回复通道若返回的is_ensure_latest_bundle_output_future为true这个 build 就是专门为刷新输出而调度的等待其完成后跳出循环为false等待的是其他既有任务或正在运行的构建则循环重问返回None输出已新鲜立即跳出。loop_count 100是防止病态不收敛循环的安全阀。协调器侧§13b按状态返回状态动作Initialized警告并返回NoneIdle、队列空、stale排队空文件集Rebuild并调度返回新构建 future标志trueIdle、队列空、fresh返回NoneIdle、队列非空调度队列中的任务返回该构建标志falseFullBuildInProgress/InProgress返回正在运行的 future标志falseFailed/FullBuildFailed返回None完整示例——HMR-only 任务之后的页面加载§13d一个 HMR-only 任务成功完成has_rebuild_happen false→has_generated_bundle_output false→has_stale_bundle_output true状态Idle浏览器加载页面dev server 中间件调用DevEngine::ensure_latest_bundle_output协调器处于Idle且队列空、输出 stale排队TaskInput::Rebuild { changed_files: {} }并调度Idle → InProgress返回带true标志的 futureRebuild任务运行不做 HMR 生成ScanMode::Partial→BundleMode::IncrementalBuild重新生成全量输出BundleCompleted { error: false, has_generated_bundle_output: true }→has_stale_bundle_output false状态回到Idlefuture 解析、标志为true→ 循环跳出中间件把新鲜 bundle 提供给页面。triggerFullBuild手动重试则是独立、fire-and-forget 的操作无条件清空queued_tasks、压入FullBuild并调度调用立即返回不等待。需要等待的调用方可将它与ensure_latest_bundle_output组合——FIFO 通道顺序保证FullBuild在 ensure 消息被处理前就已调度因此访问仍会等待新鲜构建完成。六、has_stale_bundle_output与配套快照标志BundleCoordinator上单个bool字段语义是“磁盘 / 内存中的全量 bundle 输出可能没有反映最新源码”。其演化规则事件has_stale_bundle_output构造true成功的FullBuildfalse失败的FullBuildtrue成功且重建的任务Rebuild/HmrRebuildfalse成功的 HMR-only 任务无重建true失败的增量任务true收到ModuleChangedtrue该标志被ensure_latest_bundle_output§13消费并经由CoordinatorStateSnapshot.has_stale_output暴露为BundleState.has_stale_output。配套标志last_build_errored协调器处于任一错误状态时为true是消费方决定“是否在访问时触发重建”时应与has_stale_output配对的谓词stale 且 errored 的 bundle 绝不能在访问时重建否则“promise 已解析 ⇒ 构建新鲜”的天真解读会导致虚假的重载循环。第三个快照标志last_error_stage: OptionErrorStage仅在Failed { last_error_stage }时为Some经由BundleState.last_error_stage与BindingBundleState.last_error_stageJS 侧为Hmr | Rebuild字符串联合透出支撑 §二 原则 3 的消费方例外。七、已知问题缺失导入的自动恢复design.md 末尾记录了一个尚未解决的开放问题Unresolved Questions对实际使用有直接影响缺失导入missing-import失败的自动恢复。当构建因未解析的导入失败时缺失的文件从未被解析不在watch_paths中。创建它不会触发重建——用户必须 touch 一个被监听的文件或使用triggerFullBuild。仓库内参考消费方的测试watch.test.ts明确承认了这一缺口“the missing files directory is not auto-watched, so we need to touch a watched file”。文中提出的候选修复方向是解析期间遇到文件不存在时记录其路径并把其父目录加入 watcher这样匹配到此前缺失路径的目录级 create 事件就能自动触发重建。八、延伸阅读implementation.md——Dev Engine 的实现地图组件分层、CoordinatorMsg消息协议、CoordinatorState状态机、TaskInput工作类型与各阶段数据流管线bundler-data-lifecycle/implementation.md——BundleMode、Bundle/BundleFactory与增量构建所经过的ScanStageCache生命周期rust-bundler/implementation.md——核心Bundler结构与 Dev Engine 驱动的构建生命周期watch-mode/implementation.md——rolldown_watcher的 actor 监听架构rolldown_dev复用了同一 actor 模式lazy-compilation/implementation.md——经DevEngine::compile_lazy_entry与ModuleChanged消息到达的惰性入口编译dev-server-test-harness/implementation.md——dev server 的浏览器测试脚手架。关键源码入口速查公开 API 与协调器派生见 crates/rolldown_dev/src/dev_engine.rs状态机 / 排队 / 调度见 crates/rolldown_dev/src/bundle_coordinator.rs单次构建工作单元见 crates/rolldown_dev/src/bundling_task.rs三个核心枚举分别见types/coordinator_state.rs、types/task_input.rs、types/coordinator_msg.rsRebuildStrategy见 crates/rolldown_dev_common/src/types/rebuild_strategy.rs。【免费下载链接】rolldownFast Rust bundler for JavaScript/TypeScript with Rollup-compatible API.项目地址: https://gitcode.com/GitHub_Trending/ro/rolldown创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →