- 前端构建
- 构建工具
- 开发工具
【免费下载链接】farm
Extremely fast Vite-compatible web build tool written in Rust
Farm 从v0.14.0起支持将编译结果持久化到磁盘,通过compilation.persistentCache开启后,热启动(hot start)与热构建(hot build)可显著提速,官方文档给出的实测数据显示编译时间最高可减少80%。本文将基于 website/docs/advanced/persistent-cache.md 的完整内容,结合 packages/core/src/config/normalize-config/normalize-persistent-cache.ts 与 crates/core/src/config/persistent_cache.rs 等源码,系统讲解持久化缓存的开关方式、失效校验规则、缓存键生成策略、构建依赖追踪以及插件适配注意事项,帮助你在真实项目中安全、高效地开启并使用这一能力。
一、为什么需要持久化缓存:冷启动与热启动的差距
Farm 本身是基于 Rust 编写的高性能构建工具,单次编译已经很快,但面对大型项目(如 Ant Design Pro 级别的应用),每次启动都重新解析、加载、转换全部模块仍然耗时。持久化缓存的核心思路是:把上一次编译得到的模块转换结果、依赖图信息、资源产物等序列化到磁盘,下次启动时直接复用,跳过可跳过的构建阶段。
官方文档使用 examples/arco-pro 项目给出了冷启动(无缓存)与热启动(有缓存)的对比数据:
| 阶段 | 冷启动(无缓存) | 热启动(有缓存) | 提升幅度 |
|---|---|---|---|
| start(启动) | 1519ms | 371ms | 减少 75% |
| build(构建) | 3582ms | 562ms | 减少 84% |
从源码实现看,Farm 的缓存体系由 crates/core/src/cache/mod.rs 中的CacheManager统一管理,内部包含:
- module_cache:模块及模块依赖图的缓存;
- resource_cache:最终资源(资源束)的缓存;
- plugin_cache:各插件自定义数据的缓存;
- custom / lazy_compile_store:自定义缓存与懒编译缓存。
CacheManager在构造时会根据配置创建CacheType::Disk(磁盘缓存)或CacheType::Memory(内存缓存),磁盘缓存通过DiskCacheFactory落地到指定目录(crates/core/src/cache/mod.rs)。
二、开启与关闭缓存:persistentCache 配置
2.1 一键开启
使用compilation.persistentCache即可开启/关闭缓存:
import { defineConfig } from "@farmfe/core"; export default defineConfig({ compilation: { persistentCache: true, }, });注意:persistentCache配置在compilation字段下;而buildDependencies示例中出现了直接写在顶层的情况,实际使用时请统一放在compilation.persistentCache内。
2.2 默认值展开:true 等价于完整对象
官方文档明确:persistentCache: true等价于以下完整配置:
({ persistentCache: { // 缓存存储目录 cacheDir: "node_modules/.farm/cache", // 缓存命名空间 namespace: "farm-cache", buildDependencies: [ "farm.config.ts", "@farmfe/core", "@farmfe/plugin-react", // ... 其他所有依赖 ], moduleCacheKeyStrategy: { timestamp: true, hash: true, }, }, });这一展开逻辑在 Rust 侧有直接印证。在 crates/core/src/config/persistent_cache.rs 中,get_default_config返回的默认对象正是:
namespace: "farm-cache"cache_dir: node_modules/.farm/cache(基于 root 解析为绝对路径)module_cache_key_strategy: { timestamp: true, hash: true }envs、build_dependencies为空(后者由 Node 侧填充)
而PersistentCacheConfig::default()返回Bool(true),即不配置时默认开启。配置为false则完全禁用缓存。此外,在 normalize-persistent-cache.ts 中还有一个细节:当检测到禁用缓存的环境开关(isDisableCache())时,persistentCache会被强制置为false。
2.3 完整配置项速查
依据 compilation-options.md,persistentCache的类型定义如下:
export type PersistentCache = | boolean | { namespace?: string; // 默认 'farm-cache' cacheDir?: string; // 默认 'node_modules/.farm/cache' buildDependencies?: string[];// 默认 farm.config 及其全部深层依赖 moduleCacheKeyStrategy?: { timestamp?: boolean; // 默认 true hash?: boolean; // 默认 true }; envs?: Record<string, string>; // 默认 Farm Env(见环境变量与模式) globalBuiltinCacheKeyStrategy?: { define?: boolean; // 默认 true buildDependencies?: boolean; // 默认 true lockfile?: boolean; // 默认 true packageJson?: boolean; // 默认 true env?: boolean; // 默认 true }; };三、缓存校验(Cache Validation):什么情况会导致缓存失效
缓存并非无条件复用。Farm 在尝试复用缓存前会按以下条件校验,任一条件发生变化,全部缓存立即失效:
- Env 对象:由
persistentCache.envs配置,默认为Farm Env Mode(即process.env.NODE_ENV、process.env.DEV、process.env.PROD),详见 Environment Variables and Modes。 - lockfile:如果
package-lock.json、yarn.lock或pnpm-lock.yaml发生变化,意味着依赖集合有变动,缓存整体失效。 - 构建依赖(Build Dependencies):由
persistentCache.buildDependencies配置,任一构建依赖文件发生变化,全部缓存失效。 - 缓存命名空间(Cache Namespace):不同
namespace下的缓存互不通用。想强制清空全部缓存时,换一个新的namespace即可。 - 内部缓存版本(Internal Cache Version):Farm 内部维护一个缓存版本号,当 Farm 自身版本变化且可能影响输出(例如渲染优化)时,会提升缓存版本号,触发全部缓存失效。该版本号在 crates/core/src/cache/store/constant.rs 中以常量
FARM_CACHE_VERSION(当前为"0.9.1")定义,并作为缓存目录的组成部分。
如果发现缓存不生效,请优先排查以上条件;若缓存已损坏,可直接删除node_modules/.farm/cache目录手动清除。
3.1 缓存目录的磁盘结构
从 crates/core/src/cache/store/disk.rs 可以看到缓存目录的层级组织方式:
{cacheDir}/{FARM_CACHE_VERSION}-{cacheDir 末级目录名}/{namespace}/{development 或 production}/...即:在cacheDir基础上追加FARM_CACHE_VERSION前缀目录 →namespace子目录 → 按开发/生产模式区分的子目录。这从实现层面印证了「内部缓存版本 + 命名空间 + 环境模式」共同参与缓存隔离的设计,也解释了为何手动清缓存时删掉node_modules/.farm/cache即可。
3.2 自定义环境变量参与缓存键(envs)
使用persistentCache.envs指定额外的环境变量键值对参与缓存键计算。当其中任一值变化时,全部缓存失效:
import { defineConfig } from "@farmfe/core"; export default defineConfig({ compilation: { persistentCache: { envs: { MY_API_URL: process.env.MY_API_URL ?? '', }, }, }, });在 persistent_cache.rs 中,所有envs键会先排序,再拼接成key=value&key=value...字符串,经sha256哈希后作为一条「配置哈希」追加到buildDependencies中,最终参与全局缓存键计算——因此任何 env 值的变化都会改变哈希,进而使整份缓存失效。
3.3 全局内置缓存键策略(globalBuiltinCacheKeyStrategy)
persistentCache.globalBuiltinCacheKeyStrategy控制哪些内置键参与缓存失效判断。默认情况下,define、buildDependencies、lockfile、packageJson、env这五类变化都会让整份缓存失效,可针对已知不会影响构建的键选择性关闭:
import { defineConfig } from "@farmfe/core"; export default defineConfig({ compilation: { persistentCache: { globalBuiltinCacheKeyStrategy: { define: true, // define 配置变化时失效缓存 buildDependencies: true, // 构建依赖变化时失效缓存 lockfile: true, // lockfile 变化时失效缓存 packageJson: true, // package.json 变化时失效缓存 env: true, // env 模式变化时失效缓存 }, }, }, });源码中的默认值在 normalize-persistent-cache.ts 中定义,随后与用户配置浅合并,且该策略字段不会透传给 Rust 侧(会在归一化时删除,见第 54 行)。其底层行为是:
env: true时,把已解析的用户 env 全部并入persistentCache.envs(第 56-61 行);define: true时,把config.define的每个键值(非字符串值会先JSON.stringify)并入envs(第 63-81 行)——这也解释了为什么源码注释写明「所有 define 选项都应进入 envs」;packageJson: true时,读取 root 下package.json中type、name、exports、browser、main、module六个关键字段,作为package.json[键名]形式的 env 键值(第 86-107 行);lockfile: true时,自动把package-lock.json、yarn.lock、pnpm-lock.yaml追加进buildDependencies(第 113-124 行)。
四、构建依赖(Build Dependencies)
构建依赖是指可能影响编译过程或编译产物的依赖,例如插件或配置文件。任一构建依赖发生变化,全部缓存即失效。
buildDependencies中的每一项可以是文件路径,也可以是包名,例如:
import { defineConfig } from "@farmfe/core"; import path from "node:path"; export default defineConfig({ compilation: { persistentCache: { buildDependencies: [ // 一个文件路径 path.resolve(process.cwd(), "./plugins/my-plugin.js"), // 一个包名,注意该包必须暴露 package.json "farm-plugin-custom-xxx", ], }, }, });默认情况下,所有配置文件及其依赖都会被纳入;如果希望额外添加一些文件或依赖来触发缓存失效,通过buildDependencies追加即可,一旦这些文件变化,全部缓存失效。
从 normalize-persistent-cache.ts 可以看到默认追踪逻辑的完整实现:
- 若存在
configFileDependencies直接复用,否则调用traceDependencies深度追踪配置文件的依赖; - 绝对路径的依赖直接加入
buildDependencies; - 以字符串/数组形式声明的 Rust 插件(
plugins中类型为string或数组的项)会通过require.resolve(${p}/package.json)解析为包名@版本号形式的依赖键(如@farmfe/plugin-react@1.x.x),版本号变化即触发缓存失效。
在 Rust 侧,persistent_cache.rs 会把全部构建依赖排序拼接后统一做sha256哈希,并将该哈希追加到cacheDir后面,作为最终实际使用的缓存目录——构建依赖变了,哈希就变,等于换了一个全新的缓存目录,这就是「全部缓存失效」在实现层面的具体体现。
五、模块缓存键策略(Module Cache Key Strategy)
Farm 提供两种策略控制模块缓存键的生成方式:
timestamp:检查模块的更新时间戳,若时间戳未变化,则跳过该模块的构建,性能最优;hash:在load与transform之后检查内容哈希,若内容未变化,则跳过该模块后续的构建阶段。
默认两者同时开启(即{ timestamp: true, hash: true })。两种策略的取舍在于:timestamp最快但依赖文件系统时间戳的准确性;hash更严谨但需要先执行load/transform才能拿到内容做比对。更细化的行为可参考 compilation-options.md:
timestamp: true且模块时间戳未变化时,load、transform等构建阶段钩子全部跳过,直接复用缓存的模块;hash: true且内容未变化时,仍会调用load和transform获取转换后的内容,其余钩子跳过并复用缓存模块。
5.1 Node 目标下的自动降级
Farm 会在output.targetEnv为node时自动将timestamp置为false。该逻辑在 normalize-persistent-cache.ts 中实现——因为 Node 环境下模块文件可能由外部进程写入或热更新,时间戳判断不可靠,必须依赖内容哈希保证正确性。
六、插件适配注意事项(Caveats For Plugins)
这是持久化缓存最容易踩坑的地方:当timestamp开启时,load、transform等所有构建阶段钩子都不会被调用。因此,如果插件依赖load/transform收集信息,却未实现plugin_cache_loaded与write_plugin_cache钩子,插件行为可能不符合预期。典型反例:某插件在load与transform中收集信息、在finish钩子中统一输出,就必须实现plugin_cache_loaded(加载缓存)与write_plugin_cache(写入缓存)两个钩子,否则热启动时拿不到数据。
这两个钩子在 Rust 侧是Plugintrait 的一部分(crates/core/src/plugin/mod.rs 与 crates/core/src/plugin/mod.rs):
plugin_cache_loaded(&self, cache: &Vec<u8>, context):命中缓存时把插件缓存字节数据交还给插件;write_plugin_cache(&self, context) -> Option<Vec<u8>>:构建结束时插件返回自己的序列化缓存数据。
调用链位于 plugin_driver.rs 与 plugin_driver.rs:plugin_cache_loaded会在构建开始时遍历所有插件,从PluginCacheManager读取以插件名(非字母数字字符替换为_)为键的缓存并逐个回调;write_plugin_cache则在结束时收集各插件返回的数据写入缓存。底层存储由 crates/core/src/cache/plugin_cache.rs 的PluginCacheManager负责,写盘前还会对缓存内容再做一次sha256摘要参与键管理。
另外,Plugintrait 还提供了handle_persistent_cached_module钩子(crates/core/src/plugin/mod.rs):命中持久化缓存的模块会先经过该钩子,返回false即可主动失效该模块的缓存——这是插件在缓存命中时进行二次校验(如自定义校验逻辑)的扩展点。
七、实战排查与调优建议
- 确认开启:检查配置文件中
compilation.persistentCache是否为true(默认即为 true)或完整对象,并确认没有禁用缓存的环境开关生效。 - 看缓存目录:构建后查看
node_modules/.farm/cache下是否出现以FARM_CACHE_VERSION前缀命名的目录;目录中包含 namespace 子目录与farm-cache.json清单文件(constant.rs)。 - 缓存不生效:按「缓存校验」一节逐条排查——env 是否变化、lockfile 是否变化、buildDependencies 指向的文件是否变化、namespace 是否被改动、Farm 版本是否升级。
- 手动清缓存:删除
node_modules/.farm/cache目录,或直接换一个namespace。 - Node/SSR 场景:
output.targetEnv为node时 timestamp 自动关闭,属预期行为,不必手动干预。 - 开发自定义插件:只要插件依赖
load/transform且需要跨构建保留状态,就必须成对实现plugin_cache_loaded与write_plugin_cache,否则请在缓存策略或插件逻辑上做出取舍。
参考资源
- 官方文档原文:website/docs/advanced/persistent-cache.md
- 配置参考:website/docs/config/compilation-options.md(
persistentCache小节) - 环境变量与模式:website/docs/features/env.md
- 配置归一化实现:packages/core/src/config/normalize-config/normalize-persistent-cache.ts
- Rust 侧配置与默认值:crates/core/src/config/persistent_cache.rs
- 缓存管理器与磁盘存储:crates/core/src/cache/mod.rs、crates/core/src/cache/store/disk.rs
- 插件缓存机制:crates/core/src/cache/plugin_cache.rs、crates/core/src/plugin/plugin_driver.rs
- 前端构建
- 构建工具
- 开发工具
【免费下载链接】farm
Extremely fast Vite-compatible web build tool written in Rust
相关推荐
JAX 持久化编译缓存(Persistent Compilation Cache)配置与原理完全指南
JAX 持久化编译缓存(Persistent Compilation Cache)配置与原理完全指南 JAX 的编译缓存(Persistent Compilat
机器学习深度学习Dragonfly 高级技巧:5个实用方法优化你的附件处理流程
Dragonfly 高级技巧:5个实用方法优化你的附件处理流程 Dragonfly 是一款强大的 Ruby gem,专为动态处理附件而设计,特别适合在 Rail
人工智能机器学习深度学习编译器高性能计算vllm-ascend 持久化增量 csrc 构建缓存(Persistent Incremental Csrc Build Cache)设计全解析
vllm ascend 持久化增量 csrc 构建缓存(Persistent Incremental Csrc Build Cache)设计全解析 导读 本文基
人工智能大模型模型推理服务AscendCANN
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考