ts-jest 处理流程全解析:从 Jest 的 require 到 TypeScript 编译、缓存与后处理管线
测试开发工具【免费下载链接】ts-jestA Jest transformer with source map support that lets you use Jest to test projects written in TypeScript.项目地址https://gitcode.com/gh_mirrors/ts/ts-jest点击查看免费下载本文基于 ts-jest 29.2 版本文档website/versioned_docs/version-29.2/processing.md中官方绘制的两幅处理流程图展开逐节点拆解 Jest 调用 transformer 的标准协议以及 ts-jest 从源码输入到最终输出的完整内部管线——包括 stringify 分支、.d.ts快速路径、语言服务Language Service与隔离模块isolatedModules两种编译模式、内存/持久双重缓存、AST 自定义 transformer含jest.mock提升、source map 修复、可选的 babel-jest 阶段与afterProcess钩子。读完你将能准确理解 ts-jest 每个配置项如isolatedModules、stringifyContentPathRegex、astTransformers、babelConfig究竟在管线中的哪个环节生效以及为什么getCacheKey与配置序列化对缓存命中如此关键。文档定位一份写给贡献者的内部技术说明在进入正文之前需要说明文档的定位。website/versioned_docs/version-29.2/processing.md开篇即声明These are internal technical documents. If youre not a contributor tots-jest, but simply trying to use the library youll find nothing of value here这是内部技术文档如果你不是 ts-jest 的贡献者、只是打算使用该库这里找不到对你有用的东西。也就是说这份文档描述的是 ts-jest内部如何处理文件的机制而不是面向终端用户的 API 手册。本文在保留其全部流程节点的基础上结合仓库源码src/legacy/ts-jest-transformer.ts、src/legacy/compiler/ts-compiler.ts、src/legacy/config/config-set.ts 等逐层印证每个节点背后的真实实现帮助读者把流程图上的每一个菱形分支、矩形动作对应到实际代码。文档用两幅 PlantUML 流程图描述两级处理Jest processJest 运行时的通用文件处理流程require(file)之后发生了什么ts-jestprocessts-jest transformer 内部从process(source)入口到产出transformed source的完整管线。下面分别展开。Jest 侧的处理流程transformer 协议与缓存命中文档中第一幅图描述了 Jest 自身的处理流程这是 ts-jest 作为 transformer 被调用的外部框架背景require(file) └─ has a transform? ── yes ├─ transformer has getCacheKey? ── yes → transformer.getCacheKey(...) │ ── no → use jest built-in ├─ in cache? ── yes → use cache content │ ── no → transformer.process(...) → update cache └─ require()要点拆解当代码中执行require(file)时Jest 运行时jest-runtime会先判断该文件是否配置了 transform即是否命中transform配置项的正则命中 transform 后若该 transformer 实现了getCacheKey方法Jest 会先调用它计算缓存键若未实现则使用 Jest 内置的缓存键算法接着根据缓存键查询缓存目录命中则直接使用缓存内容跳过编译未命中才调用transformer.process(...)执行真实转换并把产物写入缓存。源码印证getCacheKey 由哪些元素构成ts-jest 的TsJestTransformer实现了同步与异步两套getCacheKeysrc/legacy/ts-jest-transformer.ts。缓存键由以下要素拼接后经sha1哈希得到序列化后的 Jest 配置字符串this._transformCfgStr含 ts-jest 自身全部影响输出的配置见下文 ConfigSet 的cacheSuffixrootDir、instrumentts-jest 强制为 off、supportsStaticESM文件原始内容fileContent与文件路径filePath当未开启 isolatedModules且配置了tsCacheDir时还会把该文件解析出的全部依赖模块路径及其mtimeMs修改时间追加进缓存键。源码中有一段注释解释了为什么要缓存 ConfigSetSince configuration are used to create a good cache key, everything depending on it is here. Fast jest relies on correct cache keys depending on all settings that could affect the generated output.src/legacy/config/config-set.ts。同时_configsFor中展示了 Jest 调用次序的一个细节Jest 会先用字符串化版本的配置调用getCacheKey随后再用完整对象版本调用process因此 ts-jest 维护了一个_cachedConfigSets数组通过JsonableValue的序列化比较来复用同一个ConfigSet与编译器实例。ConfigSet的_resolveTsCacheDir方法src/legacy/config/config-set.ts则计算出cacheSuffix——它是编译器版本、ts-jest digest、babel 配置、tsconfig 的 options 与 raw、isolatedModules、diagnostics 配置、以及全部 transformer 的name-version组合后的哈希当jestConfig.cache开启时缓存目录落在${cacheDirectory}/ts-jest/${suffix前两位}/${suffix其余}。这解释了为什么修改任意影响输出的配置都会导致缓存失效重新编译。ts-jest 内部管线从 process 入口到 transformed source文档中第二幅图是核心描述了 ts-jest transformer 内部的完整处理流程。我们将它拆成六个阶段并对应源码逐一展开。阶段一入口与前置判断tsJest.process(source)入口是TsJestTransformer.process以及 ESM 场景下的processAsync。在 src/legacy/ts-jest-transformer.ts 中process依次完成通过_configsFor(transformOptions)获取或创建缓存的ConfigSet调用shouldStringifyContent(sourcePath)判断是否走 stringify 快速路径调用processWithTs得到 TypeScript 编译产物若配置了 babel 且不处于 stringify 分支再调用 babel-jest 处理器最后调用runTsJestHook执行afterProcess钩子。构造函数里有个值得注意的细节process、getCacheKey等在构造时被bind(this)源码注释说明这是因为Jest in ESM mode 下this可能是 undefinedsrc/legacy/ts-jest-transformer.ts。阶段二stringify 分支shouldStringifyContentif (should stringify?) ── yes → json stringify → update source当文件路径命中stringifyContentPathRegex时ts-jest 不会尝试编译而是直接把文件内容序列化为 JS 字符串字面量。对应的ConfigSet.shouldStringifyContentsrc/legacy/config/config-set.ts用正则测试文件路径processWithTs中该分支的产物为module.exports${stringify(sourceText)}src/legacy/ts-jest-transformer.ts。典型的适用场景是把.json、快照文本等非代码内容注入到模块中避免 TypeScript 编译报错或体积膨胀。例如仓库 e2e 用例 e2e/esm-features/src/foo.json 配合对应测试配置即可验证该路径。阶段三.d.ts快速路径if (filename ends with .d.ts) ── yes → wipe source无需编译声明文件当文件以.d.ts结尾时ts-jest 直接清空输出——processWithTs中isDefinitionFile分支返回空字符串代码{ code: }src/legacy/ts-jest-transformer.ts。原因正如流程图旁注no need to compile definition files无需编译声明文件。声明文件只有类型信息、没有运行时实现编译它既无必要也会引入风险源码中getCompiledOutput也专门对.d.ts的 require 抛错见 src/legacy/compiler/ts-compiler.ts。阶段四编译核心——isolatedModules 分支与双重缓存这是整个管线最复杂的部分compiler (cached) └─ isolated modules? ── no → create and cache ts language service └─ in persistent cache? ── yes → update mem cache from persistent cache ── no ├─ isolated modules? ── yes → compile with transpileModule文件按隔离模块编译 │ ── no → compile with service复用上面创建的 language service读文件走 mem cache └─ custom AST transformers先看编译器实例的创建与缓存。TsJestCompiler是薄封装src/legacy/compiler/ts-jest-compiler.ts真正干活的是TsCompiler。在TsCompiler构造函数中src/legacy/compiler/ts-compiler.ts若未开启isolatedModules会创建_fileContentCache内存文件内容缓存、_fileVersionCache、带memoize的readFile/fileExists宿主对象并立即创建 TypeScript Language Service若开启isolatedModules则跳过 Language Service编译时直接走ts.transpileModule。再看持久缓存与内存缓存的关系。流程图显示命中持久缓存时先update mem cache from persistent cache未命中时编译完成后依次update mem cache与update persistent cache。这正是ConfigSet.tsCacheDir与getCacheKey中依赖 mtime 追踪所服务的机制持久缓存磁盘用于跨进程/跨运行复用内存缓存用于同一进程内 Language Service 读取文件。getCompiledOutputsrc/legacy/compiler/ts-compiler.ts是分支落地的位置Language Service 路径先把最新文件内容写入内存缓存_updateMemoryCache再调用_languageService.getEmitOutput(fileName)取编译产物同时getDiagnostics收集类型诊断watch 模式下还会利用依赖图depGraphs对受影响文件重新做类型检查isolatedModules 路径调用_transpileOutput内部最终落到ts.transpileModule现代 Node 模块类型下则走仓库自带的tsTranspileModule封装见 src/transpilers/typescript/transpile-module.ts仅做单文件转译、不做类型检查。两种模式的差异可以用一句话概括Language Service 模式有完整程序上下文、能做类型诊断与增量复用代价是更重isolatedModules 模式快但放弃类型检查这也是 ts-jest 文档中isolatedModules选项的核心语义。阶段五自定义 AST transformers含 jest.mock 提升custom AST transformers └─ 这里完成 jest.mock 的提升以及用户基于配置定义的自定义转换编译输出之后ts-jest 会应用一组 AST 级 transformer。ConfigSet._setupConfigSet中src/legacy/config/config-set.ts默认总是注入hoist-jest到before阶段——它就是jest.mock/jest.unmock/jest.enableAutomock/jest.disableAutomock/jest.deepUnmock提升hoisting的实现定义在 src/transformers/hoist-jest.ts通过 AST 遍历把上述调用语句重排到文件顶部模块导入之后保证 mock 在测试代码执行前生效若配置了astTransformers则按before/after/afterDeclarations三组解析用户自定义 transformer.ts后缀的 transformer 会被 esbuild 编译成 CJS 后加载其余按 Node 模块解析每个 transformer 需提供factory、name、version缺version或name会给出警告因为它们参与缓存键计算见 src/legacy/config/config-set.ts。阶段六source map 修复与缓存更新→ compiled source :fix source maps; :update mem cache; :update persistent cache;fix source maps 对应compiler-utils.ts中的updateOutputsrc/legacy/compiler/compiler-utils.ts它解析 TypeScript 生成的 source map把file与sources改写为规范化后的源文件名、删除sourceRoot再将 map 以 base64 data URL 的形式替换输出文本末尾的sourceMappingURL前缀。这样调试与覆盖率映射能正确指回原始 TS 源码仓库 e2e 用例 e2e/source-map 专门验证这一点。随后按前文所述更新内存缓存与持久缓存供下一次运行复用。后处理阶段babel-jest 与 afterProcess 钩子编译完成后ts-jest 还有两个可选的后处理环节babel-jest 阶段if (should use babel?) ── yes → babelJest.process(source)当用户配置了babelConfig时ConfigSet会创建 babel-jest transformersrc/legacy/config/config-set.ts支持字符串路径或内联对象配置process中随后把 TS 编译产物交给它做二次转换src/legacy/ts-jest-transformer.ts。注意两处细节调用 babel-jest 时显式传instrument: false因为插桩coverage instrumentation会由 Jest 随后统一完成避免重复插桩stringify 分支下不会调用 babelshouldStringifyContent为真时babelJest被置为undefined。典型场景是TypeScript 负责类型与语法编译、Babel 负责 JSX 之外的其他语法转换如装饰器等即仓库示例 examples/js-with-babel 所演示的组合。afterProcess 钩子if (has afterProcess hook?) ── yes → call afterProcess hook若返回值非空则作为新源码runTsJestHooksrc/legacy/ts-jest-transformer.ts通过环境变量TS_JEST_HOOKS指向的模块文件加载钩子若其中导出afterProcess(args, result)且返回值非空则用返回值替换最终产物。注释明确说明This is not supposed to be a public API but we keep it as some people use it这并非公开 API但保留给部分使用者。最终管线输出transformed source交由 Jest 运行时继续执行。关键配置在管线中的位置速查结合流程图与源码把常用配置项与管线环节对应起来配置项生效环节说明stringifyContentPathRegex阶段二stringify 分支命中正则的文件直接序列化不编译isolatedModules阶段四编译模式决定走transpileModule还是 Language Service同时影响getCacheKey是否纳入依赖 mtimetsconfig阶段四编译参数决定编译选项、是否开启类型诊断diagnostics阶段四类型检查控制诊断是否抛出、忽略码、排除文件astTransformers阶段五AST transformers注入自定义转换hoist-jest默认始终在before阶段babelConfig后处理babel-jest 阶段开启后 TS 产物再经 babel 二次转换useESM全程编译模式与缓存键影响supportsStaticESM与模块格式判定小结processing.md用两幅流程图精炼地概括了 ts-jest 的完整处理链外部看是 Jest 的取缓存键 → 查缓存 → 命中跳过 / 未命中编译并回填协议内部看则是stringify 快速路径 →.d.ts清空 → 隔离模块或 Language Service 编译 → 自定义 AST transformers含jest.mock提升→ source map 修复 → 双重缓存更新 → 可选 babel 二次转换 →afterProcess钩子的多级管线。理解这条管线对排查两类问题特别有帮助一是缓存相关问题改了配置或依赖却不重新编译多半要检查getCacheKey元素与cacheSuffix是否覆盖到该配置二是转换顺序问题自定义 AST transformer 何时生效、babel 与 TS 编译谁先谁后。本文所引用的全部实现均可在仓库源码中直接查阅transformer 入口在 src/legacy/ts-jest-transformer.ts编译器在 src/legacy/compiler/ts-compiler.ts 与 src/legacy/compiler/ts-jest-compiler.ts配置解析在 src/legacy/config/config-set.tshoisting 实现在 src/transformers/hoist-jest.tssource map 修复在 src/legacy/compiler/compiler-utils.ts。赞分享测试开发工具【免费下载链接】ts-jestA Jest transformer with source map support that lets you use Jest to test projects written in TypeScript.项目地址https://gitcode.com/gh_mirrors/ts/ts-jest点击查看免费下载相关推荐ts-jest 处理流程全解析从 Jest Transformer 到 TypeScript 编译管线的内部工作原理ts jest 处理流程全解析从 Jest Transformer 到 TypeScript 编译管线的内部工作原理 本文是 ts jest 仓库内部技术文档测试开发工具ts-jest 处理流程深度解析从 Jest Transformer 到 TypeScript 编译管线的完整链路ts jest 处理流程深度解析从 Jest Transformer 到 TypeScript 编译管线的完整链路 ts jest 的核心价值在于它作为 Je测试开发工具ts-jest 处理流程Processing Flow源码级解析从 Jest 调用链到 TypeScript 编译、Babel 与多层缓存ts jest 处理流程Processing Flow源码级解析从 Jest 调用链到 TypeScript 编译、Babel 与多层缓存 本文是 ts测试开发工具上一篇数据平台怎么扛住高并发Metabase 性能调优完整指南下一篇League-Toolkit英雄联盟客户端5大核心功能与3步配置指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考