news 2026/9/15 20:34:52

Rolldown 缓存架构深度解析:ScanStageCache 与增量构建的实现原理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Rolldown 缓存架构深度解析:ScanStageCache 与增量构建的实现原理

Rolldown 缓存架构深度解析:ScanStageCache 与增量构建的实现原理

【免费下载链接】rolldownFast Rust bundler for JavaScript/TypeScript with Rollup-compatible API.项目地址: https://gitcode.com/GitHub_Trending/ro/rolldown

本文依据仓库内internal-docs/cache/implementation.md展开,并结合crates/rolldowncrates/rolldown_common等目录下的实际源码进行印证。Rolldown 是一套用 Rust 编写、API 兼容 Rollup 的 JavaScript/TypeScript 打包器,其增量构建与 HMR(热模块替换)能力全部建立在一套多层次的缓存体系之上。读完本文,你将掌握 Rolldown 全部 14 个*Cache型缓存的分工,深入理解架构核心ScanStageCache的数据结构、模块身份模型、部分扫描合并算法(merge),以及完整的读写方清单。

缓存总览:Rolldown 的六类缓存机制

Rolldown 拥有多种截然不同的缓存机制。按字面命名统计,类型名恰好以*Cache结尾的共有14 个。它们按用途可归为六类:增量构建缓存、跨构建失效状态、构建内记忆化、插件临时状态、JS 侧缓存与监听模式的文件系统缓存。其中架构上最核心的是ScanStageCache——它是 bundler 级别的模块解析图快照,正是它让增量构建与 HMR 成为可能;其余缓存则是构建内的记忆化(memoization)、插件临时状态以及一个 JS 侧存储。

1. 增量构建缓存(Incremental-build cache)

类型位置存储内容
ScanStageCachecrates/rolldown/src/types/scan_stage_cache.rs模块图快照 + 模块索引映射表。本文剩余篇幅将围绕它展开。

2. 跨构建失效状态(Cross-build invalidation state)

它们并非结果缓存,而是与ScanStageCache一同持久化,供下一次增量构建判断该失效什么、或回答插件的查询。

数据位置说明
transform_dependenciescrates/rolldown_plugin/src/plugin_driver/addWatchFile()记录的依赖;模块 → 它依赖的文件。详见 internal-docs/bundler-data-lifecycle/implementation.md。
module_infoscrates/rolldown_plugin/src/plugin_driver/插件填充的模块元数据,服务于this.getModuleInfo。详见 internal-docs/bundler-data-lifecycle/implementation.md。

3. 构建内记忆化(Within-build memoization)

类型位置存储内容
SideEffectCache(枚举)crates/rolldown/src/stages/link_stage/tree_shaking/determine_side_effects.rsNone/Visited/Cache(DeterminedSideEffects);link 阶段副作用遍历期间的临时局部记忆。
PackageJsonCachecrates/rolldown_plugin_vite_resolve/src/package_json_cache.rsside_effects_cache: FxDashMap<PathBuf, Arc<PackageJson>>optional_peer_dep_cache: FxDashMap<PathBuf, Arc<…>>
ResolverCachescrates/rolldown_plugin_vite_resolve/src/resolver.rspackage_json: PackageJsonCacheimporter_exists: FxDashSet<String>
TsconfigCachecrates/rolldown_binding/src/transform_cache.rsresolver: Arc<Resolver>cache: FxDashMap<PathBuf, Arc<TsConfig>>。通过#[napi]暴露给 Node 侧。
RawTransformOptionscache字段crates/rolldown_common/src/inner_bundler_options/types/transform_options.rstsconfig → 编译后的 Oxc transform 选项。
oxc_resolver 内部缓存外部 crate,由 bundler 层级的SharedResolver持有文件系统/路径元数据。

4. 插件临时状态(位于PluginContext.meta()

命名为*Cache,但功能上只是每次构建内、在插件 hook 调用之间传递数据的共享 Map。全部位于 crates/rolldown_plugin_utils/src/。

类型位置存储内容
AssetCachecrates/rolldown_plugin_utils/src/file_to_url.rsFxDashMap<String, String>
PublicAssetUrlCachecrates/rolldown_plugin_utils/src/public_file_to_built_url.rsFxDashMap<String, String>
CSSEntriesCachecrates/rolldown_plugin_utils/src/constants.rsFxDashMap<ArcStr, ArcStr>
CSSModuleCachecrates/rolldown_plugin_utils/src/constants.rsFxDashMap<String, FxHashMap<String, String>>
CSSChunkCachecrates/rolldown_plugin_utils/src/constants.rsFxDashMap<ArcStr, String>
RemovedPureCSSFilesCachecrates/rolldown_plugin_utils/src/constants.rsFxDashMap<ArcStr, Arc<OutputChunk>>
CSSUrlCachecrates/rolldown_plugin_utils/src/constants.rsFxDashMap<String, String>

同一文件内还有未以Cache命名的关联结构:ViteMetadataHTMLProxyResultHTMLProxyMapCSSStylesPureCSSChunks

5. JS 侧缓存

类型位置存储内容
PluginContextDatapackages/rolldown/src/plugin/plugin-context-data.tsmoduleOptionMapresolveOptionsMaploadModulePromiseMaprenderedChunkMetanormalizedInputOptionsnormalizedOutputOptions
InvalidateJsSideCachecrates/rolldown_common/src/inner_bundler_options/types/invalidate_js_side_cache.rsArc<InvalidateJsSideCacheFn>——一个由 Rust 持有、回调进 JS 的函数指针。
FilterExprCachecrates/rolldown_binding/src/options/plugin/binding_plugin_options.rs预编译的插件 hook 过滤器表达式(NAPI binding,按插件隔离)。

其中InvalidateJsSideCache在 crates/rolldown_binding/src/utils/normalize_binding_options.rs 中被接线;在 JS 侧(packages/rolldown/src/utils/bindingify-input-options.ts)它被绑定到PluginContextData.clear。调用它即清空 JS 侧的PluginContextData

6. 监听模式的文件系统缓存

notifycrate 的RecommendedCache被持有在 crates/rolldown_fs_watcher/src/ 的去抖器(debouncer)内部,用于跟踪文件系统元数据以进行事件去抖。

ScanStageCache——增量构建的基石

存放位置与两层模型

ScanStageCachebundler 级数据:它跨构建存活。在构建期间,它被临时移入每次构建的Bundle,构建结束后再移回。这段移入/移出由with_cached_bundle/with_cached_bundle_experimental完成(crates/rolldown/src/bundler/impl_bundler_incremental_build.rs)。

从源码看,with_cached_bundle的实现非常讲究失败安全(crates/rolldown/src/bundler/impl_bundler_incremental_build.rs):

let cache = mem::take(&mut self.cache); let mut bundle = self.bundle_factory.create_bundle(bundle_mode, Some(cache))?; // 注意:这里绝不能用 `?` 提前返回 // 一旦 Err 提前退出,bundle 会被 drop,Bundler::cache 停留在 mem::take 安装的 default() // 状态(snapshot = None),下一次 HMR 周期会在 get_snapshot() 中 panic let ret = with_fn(&mut bundle).await; self.cache = bundle.cache; ret

“bundler 级 vs bundle 级”的两层数据模型记录在 internal-docs/bundler-data-lifecycle/implementation.md 中,该文档同时覆盖了构建失败时的缓存完整性保证。

结构体定义

scan_stage_cache.rs 中的完整定义:

#[derive(Default, Debug)] pub struct ScanStageCache { snapshot: Option<NormalizedScanStageOutput>, pub barrel_state: BarrelState, pub module_id_to_idx: FxHashMap<ModuleId, VisitState>, pub importers: IndexVec<ModuleIdx, Vec<ImporterRecord>>, /// 其 importers 记录被部分扫描变更的模块 pub modules_with_changed_importers: FxHashSet<ModuleIdx>, /// 一次中止(并已回滚)的部分扫描留下的文件 pub pending_rescans: Vec<ResolvedId>, pub user_defined_entry: FxHashSet<ModuleId>, pub module_idx_by_abs_path: FxHashMap<ArcStr, ModuleIdx>, pub module_idx_by_stable_id: FxHashMap<StableModuleId, ModuleIdx>, }

各字段职责如下表:

字段用途
snapshot完整模块图。None是合法的临时状态;它是private的,只能通过下述方法访问。
barrel_stateBarrel 再导出解析状态(BarrelState)。
module_id_to_idxModuleIdModuleIdx的注册表/分配器(见“模块身份模型”一节)。
importers反向依赖图:对每个模块,记录谁导入了它。
modules_with_changed_importersimporters记录被部分扫描变更的模块;merge会 drain 它,为这些模块重新推导物化的 importer 集合(见下)。
pending_rescans工作队列:一次中止的部分扫描所涉及的文件(其变更已被ModuleLoader::revert_partial_scan回滚);下一次部分扫描会重试它们,让错误持续浮出水面直到文件被修复。
user_defined_entry配置的根入口ModuleId集合。
module_idx_by_abs_path绝对路径 →ModuleIdx,供 watcher 使用。路径统一使用斜杠规范化。
module_idx_by_stable_idStableModuleIdModuleIdx,供 HMR 使用。

其中module_idx_by_abs_pathmodule_idx_by_stable_id派生数据——每当set_snapshot运行时,build_module_index_maps会清空并从快照重建这两张映射表(仅将Module::Normal的绝对路径以to_slash()形式写入前者)。

快照访问器

scan_stage_cache.rs 提供了一组访问器:

  • set_snapshot(L46)——安装快照并重建索引映射。
  • get_snapshot(L78)——返回&NormalizedScanStageOutputsnapshotNone则 panicunwrap)。
  • get_snapshot_mut(L53)——返回&mutNone时同样 panic
  • take_snapshot(L58)——把快照移出,留下None
  • has_snapshot(L91)——非 panic 变体,仅判断快照是否存在。
  • derive_importers_from_snapshot(L101)——从快照重新推导importers边列表。
  • update_defer_sync_data(L62)——取出快照、运行defer_sync_scan_data在所有结果路径上恢复快照,再传播任何错误。源码注释解释得很清楚:若用?提前退出会 drop 快照,使self.snapshot == None,导致下一个 HMR 周期的get_snapshot()panic;一个“部分同步”的快照是可恢复的,而“缺失”的快照不可恢复。
  • merge(L123)——把一次扫描输出拼接入快照(见下文)。
  • create_output(L324)——为本次构建产出NormalizedScanStageOutput

BundleMode:决定缓存的创建、保留与复用

crates/rolldown_common/src/types/bundle_mode.rs 定义了三种模式:

模式缓存入缓存出适用场景
FullBuildNone丢弃一次性构建,非增量的 watch
IncrementalFullBuild全新保存首次增量构建,或 dev 模式在构建失败后的恢复
IncrementalBuild已有更新后续的增量构建

辅助方法:is_full_build()FullBuildIncrementalFullBuild为真;is_incremental()IncrementalFullBuildIncrementalBuild为真。二者组合可以精确表达“这是一次完整扫描但结果要入缓存”(首次增量)与“这是一次只针对变更文件的部分扫描”(后续增量)的区别。

从 impl_bundler_incremental_build.rs 可以看到模式决策逻辑:若self.cache.has_snapshot()为真则沿用传入的scan_mode,否则强制ScanMode::Full(没有快照就没有可叠加的图,必须完整扫描);随后ScanMode::Full对应IncrementalFullBuildScanMode::Partial(_)对应IncrementalBuild

快照类型:NormalizedScanStageOutput

定义在 crates/rolldown/src/stages/scan_stage.rs。字段包括module_tableindex_ecma_ast(每个模块解析出的 AST)、stmt_infosentry_pointssymbol_ref_dbruntimedynamic_import_exports_usage_mapuser_defined_entry_modulestla_module_counttla_keyword_span_map等。

make_copy(scan_stage.rs)克隆快照,但对symbol_ref_db使用clone_without_scoping克隆(性能优化——符号表作用域在构建之后才恢复,见merge_immutable_fields_for_cache)。

ScanStageOutputvsNormalizedScanStageOutput

ScanStageOutput(scan_stage.rs)是扫描阶段的产出。它的module_tableindex_ecma_aststmt_infosHybridIndexVec,而快照中的同名字段是稠密的基于IndexVec的结构。转换发生在merge(部分扫描)或TryFrom<ScanStageOutput>(完整扫描,scan_stage.rs)中。

模块身份模型:理解merge的前提

ModuleIdModuleIdx的双重身份

一个模块拥有两种身份:

  • ModuleId——解析后的文件路径(+ query)。稳定不变,相当于模块的“名字”。
  • ModuleIdx——小整数(u32的新类型包装)。相当于槽位号/数组下标。在 bundler 会话期间永久有效。

Module::id()(crates/rolldown_common/src/module/mod.rs)返回&ModuleIdModule::idx()(mod.rs)返回模块结构体idx字段中存储的ModuleIdx

module_id_to_idx——注册表兼分配器

module_id_to_idx: FxHashMap<ModuleId, VisitState>是“模块名字 → 槽位”的唯一事实来源。它是单调的:新模块总是被分配idx = module_id_to_idx.len()。槽位按0, 1, 2, …无空洞地发放,且永不复用

VisitState:新鲜度标记

定义于 crates/rolldown/src/module_loader/module_loader.rs:

pub enum VisitState { Seen(ModuleIdx), Invalidate(ModuleIdx) }

两个变体都携带 idx。变体本身是一个新鲜度标记:

  • Seen(i)——模块是最新的;loader 跳过它(不重新扫描)。
  • Invalidate(i)——模块已过期;loader 重新扫描它,复用i作为槽位。

IndexVec/Map/HybridIndexVec

  • IndexVec<ModuleIdx, T>——以ModuleIdx为下标的Vec稠密:对0..len中每个i都存在槽位。
  • FxHashMap<ModuleIdx, T>——稀疏:只持有被插入的键。
  • HybridIndexVec<ModuleIdx, T>(crates/rolldown_common/src/types/hybrid_index_vec.rs)——枚举,要么是IndexVec(..)要么是Map(..)DefaultIndexVec变体。

完整扫描产出所有模块 → 稠密IndexVec部分扫描只产出“变更 + 新发现”的模块 → 稀疏Map

六条不变量

  1. 模块的ModuleIdx在首次解析时被分配恰好一次,之后永不改变、也永不复用。
  2. idx 从 0 起稠密分配;已分配集合恰好是0..module_id_to_idx.len()
  3. 缓存快照是稠密且全量的:module_table与每个并行侧表(index_ecma_aststmt_infossymbol_ref_db的局部 DB)对每个已分配 idx 都有槽位。
  4. 部分扫描输出是稀疏的:恰好包含 {变更} ∪ {新增} 模块,未变更模块缺席。
  5. 对部分扫描输出中的模块,“新增” ⟺ 其 idx ≥ 合并时缓存的当前模块计数。
  6. 模块 loader 为每个模块分配唯一的ModuleIdx,并用同一个值作为扫描输出的Map键、Module.idx字段与module_id_to_idx的值(见try_spawn_new_task)。因此对任意模块这三者相等。

module_id_to_idx的更新生命周期

module_id_to_idx生活在ScanStageCache中。ModuleLoader持有同一缓存的可变借用——cache: &'a mut ScanStageCache(module_loader.rs)——因此 loader 的写入直接变更 bundler 真正的缓存,不存在副本。

module_id_to_idx扫描阶段由 loader 急切地更新merge在扫描之后运行,且只读module_id_to_idx——它从不向其中插入。

写入点(全部在module_loader.rs,扫描期间)

写入点位置效果
运行时模块fetch_modules,L304–L308Entry::Vacant→ 插入Seen(idx)(一次)。
使变更文件失效fetch_modules,L348–L350对每个 watcher 上报的文件:Entry::Occupiedinsert(Invalidate(idx))。idx 不变。
Seen(idx)分支try_spawn_new_task,L230–L244不写;直接返回 idx,模块不被重新扫描。
Invalidate(idx)分支try_spawn_new_task,L246–L251insert(Seen(idx))——模块正在被重新扫描。
None,部分扫描try_spawn_new_task,L252–L259新模块:insert(id, Seen(len)),其中len = module_id_to_idx.len()
None,完整扫描try_spawn_new_task,L260–L264新模块:insert(id, Seen(alloc()))

单条目状态机

(absent) --首次解析--> Seen(idx) --文件变更--> Invalidate(idx) ^ | | loader 重新扫描该模块 | +--------------------------+

idx 在诞生时固定;后续转换只翻转Seen/Invalidate标记。

构建内的时序

在部分扫描中,fetch_modules处理每个 watcher 上报的文件时,先把它翻转为Invalidate,再调用try_spawn_new_task——后者命中Invalidate分支,把它翻回Seen并重新扫描。中间的Invalidate状态正是强制重新扫描的关键:若直接是Seentry_spawn_new_task会立即返回而不重扫。同时它天然去重:一旦翻回Seen,后续解析到同一模块的 importer 只取回 idx。

推论:扫描输出中出现的每个模块,都已在merge运行之前由 loader 登记进module_id_to_idx

ScanStageCache::merge——写入路径

定义于 scan_stage_cache.rs。签名:merge(&mut self, scan_stage_output: ScanStageOutput, plugin_driver: &PluginDriver) -> BuildResult<()>

调用方

  • bundle.rs——在normalize_scan_stage_output_and_update_cache的非完整扫描分支中。
  • hmr_stage.rs——HMR 更新与 lazy-compile 路径(crates/rolldown/src/hmr/)。

完整扫描的构建路径不调用merge,而是走set_snapshot(bundle.rs)。当前所有调用方传入的都是部分扫描输出,其module_tableHybridIndexVec::Map——这正是mergeIndexVec匹配分支为unreachable!()的原因。

从 bundle.rs 可以看到完整流程:

if is_full_scan_mode { // 完整扫描:try_into 归一化 → defer_sync_scan_data →(增量模式)set_snapshot let mut output: NormalizedScanStageOutput = output.try_into()...; defer_sync_scan_data(&self.options, &self.cache.module_id_to_idx, &mut output).await; if is_incremental { self.cache.set_snapshot(output.make_copy()); } return Ok(output); } // 部分扫描:merge 拼接入快照 → 同步 deferred 数据 → 产出本次构建的输出 self.cache.merge(output, &self.plugin_driver)?; self.cache.update_defer_sync_data(&self.options).await?; Ok(self.cache.create_output())

算法分步解析

  1. 首次构建逃生通道(L134–L139)——若snapshotNone,把整个输出通过try_into转换后直接作为快照返回。
  2. 提取modules(L140–L149)——匹配module_tableIndexVec分支为unreachable!()Map分支收集为Vec按 idx 排序。排序把已有模块(idx < 缓存长度)排在新模块(idx ≥ 缓存长度)之前,并将新模块按升序排列,使随后的push恰好落在其分配的槽位上。
  3. 逐模块循环(L152–L216):
    • new_idxMap键(索引扫描输出);idxmodule_id_to_idx[new_module.id()].idx()(索引缓存)。由不变量 6 二者相等。
    • 更新module_idx_by_abs_path(仅普通模块,斜杠规范化)与module_idx_by_stable_id
    • 新模块new_idx ≥ cache.module_table.modules.len()):把模块/AST/stmt infos/局部符号 DB push 到并行的集合上;调整tla_module_counttla_keyword_span_map
    • 已有模块:在idx处覆盖同样的集合;按旧↔新差值调整 TLA 计数;替换或移除 TLA span。
    • 所有载荷都用mem::take/take/mem::replace/mem::swap移动出扫描输出——绝不克隆
  4. 重新推导受影响的缓存模块的 importer 集合(L221–L229)——drainmodules_with_changed_importers(由ModuleLoader::mark_module_importers_changed在每次变更importers边列表时填充),对列出的每个模块,用EcmaView::rebuild_importer_sets从边列表重建其物化的importers/importers_idx/dynamic_importers集合。扫描只为它产出的模块做这件事;一个缓存模块的 importer 若增删或改类了对它的导入,就在这里被刷新。
  5. 合并入口点(L231–L253)——收集部分扫描中出现的所有模块,先从每个缓存的动态入口中移除这些模块的引用,再合并新行。清理是全局的,因为删除或重定向导入不会为旧目标产生行;丢弃失去调用点的陈旧条目;合并可重新添加空related_stmt_infos的无 span 导入。
  6. 修补 barrel 模块(L256–L264)——drainbarrel_state.resolved_barrel_modules,把解析出的导入记录写回缓存模块。
  7. 重算用户定义入口(L266–L291)——从扫描输出的集合出发,加回仍然解析到存活模块的持久化配置根入口(self.user_defined_entry)。这是每次构建重建集合,而非单调扩展。同时把emitFile(type: 'chunk')产出的入口模块也保留在集合中。
  8. 刷新面向插件的ModuleInfo(L295–L303)——对步骤 4 中重新推导的模块,重跑to_module_infoplugin_driver.set_module_info,使this.getModuleInfo(id).importers与合并后的图保持一致(扫描只为它产出的模块刷新)。

merge有两个 panic 面:module_id_to_idx[new_module.id()]下标表达式(键缺失时 panic——只有违反不变量 6 才会触达)以及unreachable!()分支。Module::idx()返回与module_id_to_idx查找相同的值且不可失败。

读写方清单

ScanStageCache的写入方

写入方位置写入内容
ScanStage::scan(scan_mode, &mut self.cache)调用于bundle.rs:104通过 loader 写入非快照字段。
ModuleLoadercache: &'a mut ScanStageCachemodule_loader.rs 及各方法module_id_to_idxbarrel_state(如失效时移除barrel_infos)、importersmodules_with_changed_importersuser_defined_entry(完整增量扫描时)。
ScanStageCache::mergescan_stage_cache.rs;调用于bundle.rshmr_stage.rssnapshotmodule_idx_by_abs_pathmodule_idx_by_stable_idbarrel_state.resolved_barrel_modules(drain)、modules_with_changed_importers(drain)、快照内tla_*字段;为重新推导的模块刷新插件module_infos
ModuleLoader::revert_partial_scanmodule_loader.rs 扫描错误出口module_id_to_idx/importers/barrel_state/ importer 标记恢复到扫描前状态,并填充pending_rescans
ScanStageCache::set_snapshotscan_stage_cache.rs;调用于bundle.rs:263update_defer_sync_data内部snapshot+ 重建module_idx_by_abs_path/module_idx_by_stable_id
ScanStageCache::update_defer_sync_datascan_stage_cache.rs;调用于bundle.rs:270hmr_stage.rs取出并恢复snapshotdefer_sync_scan_data在其中变更每个模块的side_effects
ScanStageCache::create_outputscan_stage_cache.rs;调用于bundle.rs:271变更snapshot.symbol_ref_db(克隆去作用域部分后交换),返回NormalizedScanStageOutput
merge_immutable_fields_for_cachebundle.rs,调用于bundle.rs:295get_snapshot_mut();在 link 阶段后恢复符号表作用域。
with_cached_bundle/with_cached_bundle_experimentalimpl_bundler_incremental_build.rs / L33BundlerBundle之间移动整个ScanStageCache

ScanStageCache的读取方

读取方位置读取内容
HmrStagehmr_stage.rsget_snapshot().module_tableget_snapshot().index_ecma_ast;也使用模块索引映射。HMR 同时是写入方(它调用merge/update_defer_sync_data)。
ModuleLoadermodule_loader.rsget_snapshot()(如module_table.modules.get(..)),以及module_id_to_idxbarrel_stateuser_defined_entry
defer_sync_scan_datacrates/rolldown/src/module_loader/deferred_scan_data.rs读取module_id_to_idx(以&FxHashMap<ModuleId, VisitState>传入);变更快照中每个模块的副作用。
mergescan_stage_cache.rs读取module_id_to_idxuser_defined_entryimporters(重推导 importer 集合)。

值得注意的是,从 bundle.rs 可以看到,一次构建收尾时还会调用invalidate_js_side_cache回调清空 JS 侧缓存,保证下一次构建的插件上下文数据不会过期——这正是第 5 类缓存(JS 侧缓存)与 Rust 侧缓存体系衔接的闭环。

失败安全与缓存完整性

从源码注释可以提炼出两条贯穿始终的设计原则:

  1. 快照只增不删、失败可恢复:部分扫描失败时,ModuleLoader::revert_partial_scan会把module_id_to_idx等字段回滚到扫描前状态,并把需要重试的文件记入pending_rescans;已存在的module_id_to_idx条目具有自愈能力(SeenInvalidateSeen),因此只需移除新键。完整的失败语义记录在 internal-docs/bundler-data-lifecycle/implementation.md 的 “Cache integrity on a failed build” 一节。
  2. 所有路径上归还缓存with_cached_bundle刻意不用?提前返回,保证Bundler::cache在任何结果路径上都被恢复;update_defer_sync_data也在所有结果路径上恢复快照,避免下一次 HMR 周期 panic。

相关文档

  • internal-docs/cache/design.md —— 缓存完整性契约与未决问题。
  • internal-docs/bundler-data-lifecycle/implementation.md —— bundler 级 vs bundle 级数据、BundleMode、构建失败时的缓存完整性。
  • internal-docs/module-id/implementation.md ——ModuleId设计。
  • internal-docs/rust-bundler/implementation.md ——Bundler结构与构建生命周期。
  • internal-docs/watch-mode/implementation.md —— 驱动部分扫描的 watch 模式。

【免费下载链接】rolldownFast Rust bundler for JavaScript/TypeScript with Rollup-compatible API.项目地址: https://gitcode.com/GitHub_Trending/ro/rolldown

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 20:32:28

FrankenPHP 扩展开发完全指南:使用 Go 编写 PHP 扩展模块

FrankenPHP 扩展开发完全指南&#xff1a;使用 Go 编写 PHP 扩展模块 【免费下载链接】frankenphp &#x1f9df; The modern PHP app server 项目地址: https://gitcode.com/GitHub_Trending/fr/frankenphp FrankenPHP 允许开发者使用 Go 语言编写 PHP 扩展模块&#x…

作者头像 李华
网站建设 2026/9/15 20:32:27

OpenCV形状检测实战:从轮廓提取到工业级应用

1. 项目概述&#xff1a;这不是“画个圈圈诅咒你”&#xff0c;而是让计算机真正“看见”物体轮廓的底层能力“OpenCV形状检测”这六个字&#xff0c;乍一听像教科书里的一个课后习题&#xff0c;但在我带过的二十多个工业视觉项目里&#xff0c;它几乎就是产线质检、机器人抓取…

作者头像 李华
网站建设 2026/9/15 20:32:25

深入昇腾ops-nn算子仓库:从Tiling到融合优化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/15 20:31:04

Mysql for Linux安装配置之—— 源码安装

1. 安装--假设已经有mysql-5.5.10.tar.gz及cmake-2.8.4.tar.gz两个源码压缩文件 1&#xff09;先安装cmake--注&#xff1a;1&#xff09;mysql5.5以后通过cmake编译。# tar -zxv -f cmake-2.8.4.tar.gz# cd cmake-2.8.4# ./configure# make# make install2&#xff09;创建mys…

作者头像 李华
网站建设 2026/9/15 20:29:08

SAC算法实战:BipedalWalker Hardcore调参全攻略

都说强化学习入门容易精通难&#xff0c;真正劝退大家的往往不是算法推导&#xff0c;而是调参。尤其是连续控制经典环境BipedalWalker&#xff0c;从普通版到Hardcore版&#xff0c;每一步都在跟超参数较劲。我前后在BipedalWalker和BipedalWalkerHardcore上用SAC&#xff08;…

作者头像 李华