ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ts-jest 处理流程全解析:从 Jest Transformer 到 TypeScript 编译管线的内部工作原理

ts-jest 处理流程全解析:从 Jest Transformer 到 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 仓库内部技术文档 processing.md 的深度解读。全文以文档中的两张流程图Jest 处理流程与 ts-jest 处理流程为骨架结合 src/legacy/ts-jest-transformer.ts、src/legacy/compiler/ts-compiler.ts 等源码实现逐层拆解一个 TypeScript 文件从被require到产出可执行代码所经过的全部环节帮助你理解缓存机制、isolatedModules 分支、AST 变换jest.mock 提升、source map 修复、Babel 接力与 afterProcess 钩子等核心设计从而在排查缓存失效、诊断报错与性能问题时有的放矢。说明本文属于 ts-jest 贡献者向的“内部技术文档”范畴原文档在开头即注明如果你只是使用 ts-jest 而非为其贡献代码这里的内容对你没有直接价值。普通使用者重点阅读官方文档即可本文面向希望深入理解其内部实现的读者。一、总览一次文件转换的两级流水线ts-jest 本质上是一个实现了 JestSyncTransformer接口的 transformer见 src/index.ts 中createTransformer返回的 TsJestTransformer。任何被测试代码require(file)的文件都会先后经过两条流水线Jest 自身的 transformer 调度流程决定是否需要 transform、如何计算缓存 key、缓存命中还是调用processts-jest 内部的处理流程在tsJest.process(source)中完成字符串化、声明文件处理、TS 编译、AST 变换、source map 修复、可选 Babel 接力与 afterProcess 钩子。下面两张图分别对应原文档中的两个 PlantUML 流程此处以文字形式还原其分支结构后续章节将逐一展开每个节点背后的源码证据。1.1 Jest 侧流程require 触发start → require(file) → 存在 transform → 是transformer 实现了 getCacheKey → 是调用 transformer.getCacheKey(...) → 否使用 Jest 内置 cache key 逻辑 → 缓存命中 → 是直接使用缓存内容 → 否调用 transformer.process(...) 并更新缓存 → 执行 require() end1.2 ts-jest 侧流程process 内部start → tsJest.process(source) → should stringify stringifyContentPathRegex 命中 → 是JSON stringify更新 source → 文件名以 .d.ts 结尾 → 是清空 source声明文件无需编译 → 否 → isolated modules compiler 缓存 → 否创建并缓存 TS Language Service → persistent cache 命中 → 是从持久化缓存恢复内存缓存 → 否 → isolated modules → 是用 transpileModule 编译按隔离模块处理 → 否用 Language Service 编译读取文件时使用内存缓存 → 执行自定义 AST transformersjest.mock 提升 用户自定义变换 → 得到编译产物 → 修复 source maps → 更新内存缓存 → 更新持久化缓存 → 更新 source → should use babel → 是调用 babelJest.process(source)babel-jest 处理器接力 → 存在 afterProcess hook → 是调用 hook若返回新值则作为新的 source → 输出 transformed source end二、Jest 侧调度getCacheKey 与缓存策略原文档第一张图描述了 Jest 在加载每个模块前的标准调度。理解这张图需要知道Jest 的 transform 框架先通过getCacheKey计算缓存标识命中缓存则跳过编译未命中才调用process并回写缓存。在 ts-jest 中getCacheKey的实现位于 src/legacy/ts-jest-transformer.ts其缓存 key 由以下要素拼接后经 SHA-1 哈希得到序列化后的 Jest 配置含 ts-jest 的配置后缀见_configsFor中的_transformCfgStrrootDirinstrument标志ts-jest 始终关闭插桩交由 Jest 后续处理supportsStaticESM标志文件内容与文件路径当未启用 isolatedModules且配置了tsCacheDir时还会附加该文件所有已解析依赖模块的路径及其mtimeMs文件修改时间见 ts-jest-transformer.ts。这意味着任何一个被 import 的依赖发生变化缓存 key 都会失效——这正是 watch 模式下 ts-jest 能够感知依赖变更的关键。与此配套的配置缓存机制在 ts-jest-transformer.ts 的_configsFor中ts-jest 会在多次测试运行之间缓存ConfigSet与编译器实例_cachedConfigSets静态数组避免每个文件都重新解析 tsconfig 和重建编译环境。这里还有一个值得注意的细节Jest 先以字符串化配置调用getCacheKey随后才传入真正的配置对象ts-jest 通过序列化字符串匹配来复用已缓存的配置集。三、ts-jest.process 的入口分流process方法src/legacy/ts-jest-transformer.ts是 ts-jest 内部流程的起点其内部核心逻辑在processWithTs同文件 L210-L273中按文件类型与配置做三级分流3.1 字符串化stringifyContentPathRegex当文件路径命中配置项stringifyContentPathRegex在 config-set.ts 中归一化为正则_stringifyContentRegExpts-jest 不会编译该文件而是直接将其内容序列化为module.exportsstringify(sourceText)。这在处理 JSON、SVG、CSS 等非代码资源时非常有用shouldStringifyContent的实现见 config-set.ts。3.2 声明文件.d.ts直接清空若文件以.d.ts结尾DECLARATION_TYPE_EXTts-jest 直接返回空代码不进入编译管线——声明文件只描述类型无需生成运行时代码。若误将.d.ts交给语言服务编译TypeScript 会报UnableToRequireDefinitionFile错误见 ts-compiler.ts因此前置拦截是必要的。3.3 JS/TS 文件的编译分发node_modules 中的 JS 文件直接调用ts.transpileModule快速转换ts-jest-transformer.ts并根据 ESM 模式决定module为ESNext还是CommonJS普通 TS/TSX/JS 文件交给_compiler.getCompiledOutput(...)经由 TsJestCompiler 转发到 TsCompiler其他扩展名不编译原样返回并记录警告若配置了 Babel 则提示应改用 babel-jest 处理该扩展名。四、compilercached语言服务与隔离模块两条路径原文档图中compiler (cached)泳道揭示了 ts-jest 编译器的核心架构非隔离模式使用 TypeScript Language Service隔离模式使用 transpileModule。4.1 非隔离模式Language Service 内存缓存在 TsCompiler 构造函数 中当configSet.isolatedModules为假时ts-jest 会构建内存文件内容缓存_fileContentCache与文件版本缓存_fileVersionCache模块解析缓存createModuleResolutionCache与 memoize 化的readFile/fileExists等宿主接口一个完整的LanguageServicets-compiler.ts其getScriptSnapshot按“内存缓存 → Jest 运行时 cacheFS → 磁盘读取”的优先级取文件内容L564-L591这正是原文档图中“mem cache 用于读取文件”的注释所指。编译时getCompiledOutputL356-L468先_updateMemoryCache更新内存缓存文件内容变化才递增版本号并提升_projectVersion见 L730-L762再通过getEmitOutput产出代码同时收集语义/语法诊断L767-L782。非隔离模式因此具备跨文件的类型检查能力——这是它比 transpileModule 慢但更“完整”的原因。4.2 隔离模式transpileModule 快速编译当isolatedModules为真时不创建语言服务直接走_transpileOutputts-compiler.ts每个文件按隔离模块独立编译不做跨文件类型检查速度显著提升代价是类型错误可能被漏报。原文档图中“files will be compiled as isolated modules”的注释即指此路径。相关配置项说明见官方文档 isolatedModules.md。4.3 编译器选项的运行时修正值得补充的一点是ts-jest 在编译前会对用户 tsconfig 做“运行时修正”fixupCompilerOptionsForModuleKindts-compiler.tsCJS 路径强制module: CommonJSESM 路径保留 ESNext同时根据 TypeScript 版本校验moduleResolution与customConditions的合法组合L261-L354避免产生 TypeScript 不接受的配置组合如CommonJS Bundler在 TS6 下会触发 TS5095。五、自定义 AST transformersjest.mock 提升发生的地方编译完成后无论走哪条编译路径ts-jest 都会应用自定义 AST transformers_makeTransformersts-compiler.ts把before、after、afterDeclarations三类变换器工厂注入 TypeScript 编译流程。原文档图中“here is where hoisting of jest.mock is done, as well as user-defined transformations based on config”正是对这一环节的注脚。默认注入的第一个before变换器是hoist-jest在 config-set.ts 中注册其实现位于 src/transformers/hoist-jest.ts。它负责将jest.mock、jest.unmock、jest.enableAutomock、jest.disableAutomock、jest.deepUnmock等调用含从jest/globals导入的形式见 hoist-jest.ts提升到模块顶部从而保证 mock 在 import 执行前生效——这是 Jest 生态赖以运作的基础语义。此外用户可通过astTransformers配置注入自己的变换器配置解析逻辑见 config-set.ts 及后续代码官方文档见 astTransformers.md。六、修复 source maps 与缓存回写6.1 source map 修复编译产物中的 source map 需要经过updateOutput重写src/legacy/compiler/compiler-utils.ts将 map 的file与sources改写为当前文件名、删除sourceRoot并把 map 内联为data:application/json;charsetutf-8;base64,...形式替换掉编译输出末尾的sourceMappingURL前缀。这样 Jest 在报错时才能准确定位到原始 TS 源码行。6.2 内存缓存与持久化缓存原文档图中“update mem cache / update persistent cache”对应源码中的两级缓存内存缓存_updateMemoryCachets-compiler.ts在每次编译前把最新文件内容与版本写入语言服务的宿主缓存持久化缓存当tsCacheDir配置存在时ts-jest 会复用 Jest 的磁盘缓存体系缓存 key 中已包含依赖模块的 mtime见第二章原文档图中“update mem cache from persistent cache”表示命中磁盘缓存后直接回填内存缓存跳过编译。七、Babel 接力与 afterProcess 钩子processWithTs返回 TS 编译产物后process方法还会继续执行两个可选阶段ts-jest-transformer.tsBabel 接力若配置了babelConfigts-jest 会创建babelJestTransformerconfig-set.ts对 TS 编译产物再执行一次babelJest.process注意instrument: false插桩留给 Jest 自己做。这常用于需要 Babel 插件如 JSX 定制、polyfill 注入的场景配置说明见 babelConfig.md。原文档图中的babelJest.process(source)节点即此环节。afterProcess 钩子通过环境变量TS_JEST_HOOKS指定钩子文件runTsJestHookts-jest-transformer.ts若钩子导出afterProcess函数且返回了新值该返回值将作为最终 source 使用。原文档注释明确提示“如果 hook 返回了内容它会被用作新的 source”。这是一个非公开但被部分用户使用的扩展点。八、ESM 模式下的异步变体当在 ESM 环境supportsStaticESM且配置useESM运行时Jest 会调用processAsyncts-jest-transformer.ts而非同步process。两者的流程一致差异在于异步路径会在编译后检查processWithTsResult.diagnostics存在诊断时直接抛出configs.createTsError(...)构造的类型错误Babel 阶段也改用babelJest.processAsync。此外构造函数中this.process this.process.bind(this)等绑定L64-L70是为了规避 ESM 模式下方法内this丢失的问题。ESM 使用细节见官方指南 esm-support.md。九、小结一张流程图背后的完整调用链将两张流程图与源码对应后可以整理出一条完整调用链require(file) └─ Jest 调度getCacheKey配置 内容 依赖 mtime → SHA-1 ├─ 命中缓存 → 直接 require └─ 未命中 → transformer.process(source) ├─ stringifyContentPathRegex 命中 → module.exports序列化内容 ├─ .d.ts → 返回空代码 ├─ JS/TS → TsJestCompiler.getCompiledOutput │ ├─ isolatedModulesfalse → Language Service内存缓存 类型检查 │ └─ isolatedModulestrue → transpileModule隔离编译 ├─ 自定义 AST transformershoist-jest 默认注入 用户 astTransformers ├─ updateOutput 修复 source map ├─ 更新内存/持久化缓存 ├─ 可选babelJest.process 接力 └─ 可选afterProcess 钩子改写结果 └─ require() 执行最终产物对于想要继续深挖的读者建议从以下源码入口入手变换器入口与缓存 keysrc/legacy/ts-jest-transformer.ts编译核心语言服务 / transpileModule / 选项修正src/legacy/compiler/ts-compiler.ts编译分发器src/legacy/compiler/ts-jest-compiler.ts配置归一化stringify 正则、Babel、诊断、transformers 注册src/legacy/config/config-set.tssource map 修复src/legacy/compiler/compiler-utils.tsjest.mock 提升变换器src/transformers/hoist-jest.ts配套的端到端测试如 e2e/tests/hoist-jest.test.ts、e2e/tests/source-map.test.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点击查看免费下载相关推荐为什么顶尖C开发者都在抛弃DIA SDKRawPDB的7大技术优势解析为什么顶尖C开发者都在抛弃DIA SDKRawPDB的7大技术优势解析 在C开发领域微软的PDBProgram DataBase文件是调试信息的为 jest-expo 生成新的 Native 模块 Jest Mocks从原理到完整工作流为 jest expo 生成新的 Native 模块 Jest Mocks从原理到完整工作流 导读 在 Expo / React Native 项目中 je移动开发前端跨平台原生移动ts-jest 入门指南用 Jest 测试 TypeScript 项目的 Transformer 配置与实践ts jest 入门指南用 Jest 测试 TypeScript 项目的 Transformer 配置与实践 ts jest 是一个带源码映射source测试开发工具上一篇5分钟掌握SPT-AKI Profile Editor高效管理你的离线塔科夫存档下一篇KMS_VL_ALL_AIOWindows和Office智能激活的终极解决方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表