
Jest 自定义 Transformer 缓存键生成指南jest/create-cache-key-function 深入解析【免费下载链接】jestDelightful JavaScript Testing.项目地址: https://gitcode.com/gh_mirrors/je/jest导读Jest 会将每个测试文件的转换结果transpilation 产物缓存到磁盘从而在二次运行时跳过昂贵的编译过程而决定「这份缓存是否还能复用」的正是 transformer 暴露的getCacheKey函数。jest/create-cache-key-function是 Jest 官方提供的工具模块用于快速生成符合规范的缓存键生成函数。本文将基于当前仓库的源码实现完整讲解该模块的安装、API、底层哈希原理、新旧版本兼容机制并结合 ScriptTransformer 与单元测试帮助你写出正确、高效的getCacheKey实现。一、为什么需要缓存键Jest 转换缓存的运作方式Jest 在执行测试时会按需对源码进行代码转换transpilation例如把 TypeScript、JSX 或新语法转成当前 Node 环境可运行的代码。这个过程开销不小因此 Jest 会把转换结果写入磁盘缓存下次遇到相同输入时直接读取不再重复转换。决定缓存是否命中的是「缓存键cache key」只有缓存键一致Jest 才会认为上一次的转换结果依然有效。在 docs/CodeTransformation.md 中官方明确说明了getCacheKey的职责——它被调用以判断「是否还需要调用process」并强烈建议实现它Though not required, wehighly recommendimplementinggetCacheKeyas well, so we do not waste resources transpiling when we could have read its previous result from disk.在 packages/jest-transform/src/ScriptTransformer.ts 中可以看到这一流程的实现L152-L184_getCacheKey先调用 transformer 的getCacheKey得到transformerCacheKey再由_buildCacheKeyFromFileInfo把该键与callerSupport调用方能力标志、CACHE_VERSION一起组合出最终缓存键随后_createCachedFilename用缓存键的前两个字符建立子目录、把键拼进缓存文件名L225-L238。也就是说transformer 的getCacheKey返回的字符串只是缓存键的一部分Jest 还会叠加自己的版本与运行环境信息。但这一部分是决定缓存命中率的核心如果getCacheKey没有覆盖到影响转换结果的输入就会产生「错误的缓存命中」导致陈旧结果被复用。二、安装jest/create-cache-key-function是一个独立的 npm 包作为开发依赖安装即可$ npm install --save-dev jest/create-cache-key-function该包同时提供 CommonJSrequire与 ESMimport两种入口见 package.json并带有完整的 TypeScript 类型声明。当前仓库版本要求 Node.js^18.14.0 || ^20.0.0 || ^22.0.0 || 24.0.0。三、APIcreateCacheKey(files?, values?, length?)模块默认导出一个createCacheKey工厂函数它返回一个可直接作为 transformergetCacheKey使用的函数createCacheKey( files?: Arraystring, values?: Arraystring, length?: number, ): GetCacheKeyFunction三个参数均为可选参数类型默认值说明filesArraystring[]绝对路径数组指向代码中未直接体现、但会影响转换结果的文件例如外部配置文件valuesArraystring[]字符串数组表示需要纳入缓存键的其他值例如环境变量lengthnumber32Windows 上为16最终缓存键的长度3.1 默认长度为何因平台而异从 src/index.ts 的实现可以看到默认长度由运行平台决定export default function createCacheKey( files: Arraystring [], values: Arraystring [], length process.platform win32 ? 16 : 32, ): GetCacheKeyFunction { return getCacheKeyFunction(getGlobalCacheKey(files, values, length), length); }16是 Windows 文件系统较老的 FAT 等在文件名长度上的保守值避免因路径过长导致缓存文件创建失败Unix 系默认使用 32 位十六进制字符串。3.2 关键注意点测试源码已自动计入文档特别提醒你测试文件自身的源码已经被 Jest 计入缓存键Jest 在_buildCacheKeyFromFileInfo中会把fileData作为哈希输入见 ScriptTransformer.ts L132-L145。因此files数组应当只用来补充与你的转换逻辑相关、但不在待转换文件里的文件典型的例子是自定义 transformer 自身__filenametransformer 依赖的某个包的package.json你的转换配置读取的独立配置文件如.babelrc、tsconfig.json等。如果把待转换文件本身再放进files不会产生正确性问题但会带来无意义的重复读取与哈希计算。四、工作原理缓存键到底由什么构成要正确使用createCacheKey需要理解它生成的函数内部做了哪些哈希。核心逻辑分为两层见 src/index.ts。4.1 第一层全局缓存键getGlobalCacheKey在调用createCacheKey时模块会立即计算一个「全局缓存键」其输入包括环境变量NODE_ENV模块加载时快照环境变量BABEL_ENV模块加载时快照你传入的values数组按顺序你传入的files数组中每个文件的实时内容通过readFileSync读取。return [ NODE_ENV, BABEL_ENV, ...values, ...files.map((file: string) readFileSync(file)), ] .reduce( (hash, chunk) hash.update(\0, utf8).update(chunk || ), createHash(sha1), ) .digest(hex) .slice(0, length);也就是说files中文件内容一旦发生变化即使传入的路径字符串没变全局缓存键也会随之改变——这正是把「外部配置文件纳入缓存失效判断」的原理。注意NODE_ENV/BABEL_ENV是在模块被require时读取的这是有意为之的约定切换环境后进程重启缓存键自然失效。4.2 第二层每次调用时的最终缓存键createCacheKey返回的函数在每次被 Jest 调用时对每个待转换文件会在全局缓存键基础上继续叠加globalCacheKey上面计算的结果sourceText——待转换文件的源码内容虽然是 Jest 再叠加但这里也计入一次保证与 Jest 自身行为一致待转换文件相对于config.rootDir的相对路径config.rootDir存在时若文件位于根目录外则跳过此项instrument标志——本次转换是否需要做覆盖率插桩为true时哈希中写入instrumentconfigString——项目配置的序列化字符串callerSupport——调用方能力标志的 JSON 序列化。return createHash(sha1) .update(globalCacheKey) .update(\0, utf8) .update(sourceText) .update(\0, utf8) .update(config.rootDir ? relative(config.rootDir, sourcePath) : ) .update(\0, utf8) .update(instrument ? instrument : ) .update(\0, utf8) .update(inferredConfigString) .update(\0, utf8) .update(callerSupport(inferredOptions)) .digest(hex) .slice(0, length);各哈希片段之间用\0空字节分隔避免「abc」与「abc」这类拼接歧义导致哈希碰撞。callerSupport会序列化 4 个布尔标志supportsDynamicImport、supportsExportNamespaceFrom、supportsStaticESM、supportsTopLevelAwaitL61-L74。这与 Jest 内部 ScriptTransformer.ts 中的callerSupportL1013-L1022 的设计完全一致——同一份源码在 ESM 与 CJS 两种调用模式下转换结果可能不同因此二者绝不可共享缓存条目。4.3 覆盖率插桩与缓存隔离由于instrument标志被计入缓存键打开/关闭覆盖率收集--coverage时转换缓存会自动隔离不会互相污染。这与 Jest 的collectCoverage机制配合良好也是_buildCacheKeyFromFileInfo中「instrument 时不使用常规缓存路径」策略之外的又一层保障。五、完整使用示例在自定义 transformer 中接入把以上 API 组合进一个自定义 transformer是 README.md 的核心场景。完整代码如下const createCacheKeyFunction require(jest/create-cache-key-function).default; const filesToAccountFor [ __filename, // 转换器自身代码变化应使缓存失效 require.resolve(some-package-name/package.json), // 依赖包版本变化应使缓存失效 ]; const valuesToAccountFor [process.env.SOME_LOCAL_ENV, Some_Other_Value]; module.exports { process(src, filename, config, options) {}, getCacheKey: createCacheKeyFunction(filesToAccountFor, valuesToAccountFor), };要点拆解__filename让转换器自身的修改触发缓存失效——否则你改完 transformer 逻辑Jest 可能继续复用旧的转换缓存require.resolve(some-package-name/package.json)把依赖包的版本信息纳入考量——升级依赖后缓存自动失效valuesToAccountFor可放入任意影响输出的字符串比如只有本地开发才设置的SOME_LOCAL_ENV返回的getCacheKey函数签名与 Jest 的Transformer[getCacheKey]兼容其类型定义见 packages/jest-transform/src/types.ts 中SyncTransformer/AsyncTransformer接口。如果你希望配置读取行为更精细例如从 transformer 配置项动态读取文件列表也可以把createCacheKeyFunction(...)放进工厂函数中按配置生成。六、新旧版本兼容Jest 27 前后的调用签名差异这是该模块最值得注意的设计细节。Jest 27 之前getCacheKey的签名为(fileData, filePath, configStr, options)configStr是独立参数Jest 27 及以后改为(sourceText, sourcePath, options)配置字符串被合并进options.configString中见 src/index.ts L87-L111。createCacheKey返回的函数对两种调用方式做了透明兼容当第四个参数options存在时从其中解构出config、instrument并通过configStringOf取出configString当只有旧式configStr参数时直接把它当作配置字符串缺失的 caller 能力标志按false处理与 Jest 27 之前「未传入即视为全 false」的行为一致。这意味着你写出的 transformer 可以同时服务 jest27 与 jest27无需为版本维护两套getCacheKey。七、缓存失效行为验证从单元测试看设计约束packages/jest-create-cache-key-function/src/tests/index.test.ts 用一组针对性用例固化了模块的契约可以作为你使用时的行为参考源码内容参与哈希同样的参数下test与test code;两个输入产生不同缓存键instrument参与哈希instrument: true与instrument: false得到不同键L28-L49caller 能力标志参与哈希supportsStaticESM: true与supportsStaticESM: false得到不同键而缺失标志与显式全 false 等价L51-L90配置字符串参与哈希configString不同如 target 从es5改为es2020则缓存键不同新旧两种调用签名下均成立L92-L127平台默认长度模拟win32平台时生成的缓存键长度为16Linux 下为32L129-L142。这些用例直接印证了「哪些输入变化会导致缓存失效」的完整清单源码、覆盖率插桩开关、调用方能力、项目配置、环境变量通过values、外部文件内容通过files。如果这些因素中任一影响你的转换结果却未被覆盖就会产生错误缓存命中的风险。八、使用注意事项小结只放「必要」的外部文件到files测试源码已被 Jest 与模块自身双重计入无需重复files中的文件每次都会同步readFileSync读取数量过多会拖慢启动。values适合放置低开销的配置值与files不同values直接使用字符串本身而不读文件适合环境变量、版本号等轻量信息。默认长度按需覆盖32 位十六进制SHA-1 截断碰撞概率已经极低如果你有特殊约束如文件名长度限制可通过第三个参数调整。环境变量在模块加载时快照NODE_ENV/BABEL_ENV是require时刻的值这是有意设计——同一进程内动态修改这些环境变量不会即时反映到缓存键请勿依赖这一点。getCacheKey要尽量快它对每个待转换文件都会被调用内部应避免重活模块级的「全局缓存键」预先计算机制createCacheKey调用时一次性算好正是为此设计你只需保证files列表在模块加载时确定即可。九、延伸阅读自定义 transformer 的完整接口与process/getCacheKey协作方式docs/CodeTransformation.md缓存键在 Jest 内部的组合与缓存文件落盘逻辑packages/jest-transform/src/ScriptTransformer.tstransformer 接口类型定义SyncTransformer/AsyncTransformerpackages/jest-transform/src/types.ts本模块源码与行为测试src/index.ts、src/tests/index.test.ts【免费下载链接】jestDelightful JavaScript Testing.项目地址: https://gitcode.com/gh_mirrors/je/jest创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考