ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

ts-jest 自定义 AST Transformer 完全指南:astTransformers 配置、编写与实战

2026/10/8 8:06:42 拓冰建站 浏览量
ts-jest 自定义 AST Transformer 完全指南:astTransformers 配置、编写与实战 测试开发工具【免费下载链接】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点击查看免费下载astTransformers是ts-jest提供的核心扩展点允许开发者在 Jest 编译 TypeScript 的过程中注入自定义的 TypeScript AST transformer实现源码级的代码转换与钩子注入。本文以ts-jest仓库中的官方文档为骨架结合 types 定义、配置解析实现 与端到端测试用例系统讲解before/after/afterDeclarations三种阶段的区别、两种配置写法、自定义 transformer 的模块契约以及真实项目中的集成案例——读完你可以独立写出并接入自己的 AST transformer。一、astTransformers是什么ts-jest默认会通过一个 TypeScript AST transformer 对少数jest方法如jest.mock、jest.unmock等执行 hoisting提升操作保证它们在使用前就绪。这是ts-jest内建行为不需要用户干预。同时ts-jest把这条通道开放给了用户你可以编写自定义的 TypeScript AST transformer并通过astTransformers选项把它纳入编译流程。该选项的类型定义如下见 src/types.tsexport interface AstTransformerT Recordstring, unknown { path: string options?: T } export interface ConfigCustomTransformer { before?: Arraystring | AstTransformer after?: Arraystring | AstTransformer afterDeclarations?: Arraystring | AstTransformer }可以看到每个阶段都接受两种形态的元素纯字符串路径或带options的对象{ path, options }。三个执行阶段astTransformers允许你指定三种类型的 TypeScript AST transformer它们分别对应 TypeScript 编译器中CustomTransformers的三个阶段阶段含义拿到的语法形态before在 TypeScript 自身的 transformers之前运行原始 TS 语法例如import语句尚未转成require或defineafter在 TypeScript 自身的 transformers之后运行转译后的语法例如require/define等 CommonJS 或模块包装形态afterDeclarations在d.ts声明文件生成阶段运行输出的类型声明.d.ts可在此改写类型输出简而言之before处理人写的源码after处理机器转出来的代码afterDeclarations处理对外发布的类型声明。三种阶段既可以单独使用也可以同时配置。二、基础配置使用 transformer 字符串路径最简单的用法是在before中直接给出 transformer 模块的路径import type { Config } from jest const jestConfig: Config { // [...] transform: { // ^.\\.[tj]sx?$ 处理 ts,js,tsx,jsx // ^.\\.m?[tj]sx?$ 处理 ts,js,tsx,jsx,mts,mjs,mtsx,mjsx ^.\\.tsx?$: [ ts-jest, { astTransformers: { before: [my-custom-transformer], }, }, ], }, } export default jestConfigts-jest在解析阶段会把字符串路径当作模块路径解析使用 Node 风格解析即nodeResolve: true见 config-set.ts 中的resolveTransformers实现const resolveTransformers (transformers: Arraystring | AstTransformer): AstTransformerDesc[] transformers.map((transformer) { if (typeof transformer string) { return resolveTransformerFunc(this.resolvePath(transformer, { nodeResolve: true })) } else { return { ...resolveTransformerFunc(this.resolvePath(transformer.path, { nodeResolve: true })), options: transformer.options, } } })因此路径可以指向一个 npm 包名如my-custom-transformer也可以指向仓库内或 node_modules 中的相对/绝对路径。三、带 options 的配置为 transformer 传递参数很多 transformer 需要接收配置参数。此时把元素从字符串升级为对象import type { Config } from jest const jestConfig: Config { // [...] transform: { // ^.\\.[tj]sx?$ 处理 ts,js,tsx,jsx // ^.\\.m?[tj]sx?$ 处理 ts,js,tsx,jsx,mts,mjs,mtsx,mjsx ^.\\.tsx?$: [ ts-jest, { astTransformers: { before: [ { path: my-custom-transformer-that-needs-extra-opts, options: {}, // 在此传入 transformer 需要的额外配置 }, ], }, }, ], }, } export default jestConfig这些options最终会作为factory(compilerInstance, opts)的第二个参数传给 transformer 工厂函数对应 AstTransformerDesc 中factory(tsCompiler, opts?)的签名。重要限制options配置值会被jest-worker序列化因此只允许传可序列化SERIALIZABLE的值——即 JSON 能表示的数据类型。函数、类实例、RegExp、Map等不可序列化对象不应出现在options中否则在 worker 进程间传递时会出问题。仓库端到端测试 e2e/transformer-options/jest-compiler-cjs.config.ts 给出了带 options 的真实用法——接入formatjs/ts-transformer并关闭默认消息提取astTransformers: { before: [ { path: rootDir/node_modules/formatjs/ts-transformer/ts-jest-integration, options: { removeDefaultMessage: true, }, }, ], },对应测试 transformer-options.spec.tsx 验证了defaultMessage确实被移除import App from ../src/App describe(transformer-options, () { it(should pass, () { expect(App.prototype.render().props.defaultMessage).toBeUndefined() }) })四、编写自定义 TypeScript AST transformer自定义 transformer 本质上是一个普通 JS/TS 模块需要导出一个固定形态的模块version、name与factory。ts-jest自己使用的 transformer见 src/transformers/hoist-jest.ts就是最好的范本/** * 每次 transformer 内容变化时记得提升 version。 * 这是为了告知 Jest 不要复用包含旧 transformer 内容的缓存 */ export const version 4 // 用于构造缓存键 export const name hoist-jest export function factory({ configSet }: TsCompilerInstance) { // ... }模块契约详解version: number缓存版本号。修改 transformer 实现后必须递增否则 Jest 可能复用旧缓存导致变更不生效name: stringtransformer 名称参与缓存键构造factory(compilerInstance, options?)返回一个(ctx) (sf) ...形式的 TypeScript transformer 工厂。src/transformers/README.md见 README.md给出了一个可直接套用的 boilerplateimport { SourceFile, TransformationContext, Transformer, Visitor } from typescript import type { TsCompilerInstance } from ts-jest/dist/types /** * Remember to increase the version whenever transformers content is changed. This is to inform Jest to not reuse * the previous cache which contains old transformers content */ export const version 1 // Used for constructing cache key export const name hoist-jest export function factory(compilerInstance: TsCompilerInstance) { const ts compilerInstance.configSet.compilerModule function createVisitor(ctx: TransformationContext, sf: SourceFile) { const visitor: Visitor (node) { // 在此检查每个节点必要时返回新节点 // 若保持节点不变则继续遍历子节点 return ts.visitEachChild(node, visitor, ctx) } return visitor } // 返回 CustomTransformers 所期望的工厂 return (ctx: TransformationContext): TransformerSourceFile { return (sf: SourceFile) ts.visitNode(sf, createVisitor(ctx, sf)) } }注意factory的第一个参数是TsCompilerInstance它携带configSet、compilerModule等信息你可以在 transformer 内访问用户配置与当前typescript模块实例。仓库中还有一个最简实现 src/mocks/funny-transformer.ts它只是原样返回源码文件可作为最小的空 transformer模板export const version 1 export const name funny-transformer export function factory(): TransformerFactorySourceFile { return () { return (sf: SourceFile) sf } }内建 hoist-jest transformer 的工作机制ts-jest默认在before阶段注入的 hoist-jest transformersrc/transformers/hoist-jest.ts负责对mock、unmock、enableAutomock、disableAutomock、deepUnmock这五个 jest 方法进行源码提升。它支持多种引用形式见 hoist-jest.ts命名导入import { jest } from jest/globals别名导入import { jest as aliasedJest } from jest/globals命名空间导入import * as JestGlobals from jest/globals它通过visitEachChild自底向上遍历 AST对可提升的语句块排序hoist-jest.ts。理解这个内建 transformer 的实现是编写高质量自定义 transformer 的最佳起点。五、底层原理ts-jest 如何解析与装配 transformersastTransformers的解析发生在ConfigSet初始化阶段见 config-set.ts核心流程如下默认注入内建 transformerresolvedTransformers.before首先被置为[hoistJestTransformer]factory/name/version三元组。解析每个自定义 transformer对.ts后缀的 transformer 源文件使用esBuild先转译为 CJS 再加载config-set.ts其他路径直接require若模块缺少version或name会打印对应警告MissingTransformerVersion/MissingTransformerName。按阶段装配before追加到内建 hoist transformer 之后resolvedTransformers.before?.push(...)after直接覆盖resolvedTransformers.afterafterDeclarations直接覆盖resolvedTransformers.afterDeclarations。装配完成后这些 transformers 会被传入 TypeScript 编译器。在编译路径上ts-compiler.ts 通过transpileModule或tsTranspileModule把resolvedTransformers传入在需要完整程序program的场景下则通过getCustomTransformers提供给语言服务ts-compiler.ts。最终解析结果对应 src/types.ts 中定义的TsJestAstTransformerexport interface TsJestAstTransformer { before: AstTransformerDesc[] after: AstTransformerDesc[] afterDeclarations: AstTransformerDesc[] }编写 transformer 的两种语言形态从解析流程可知transformer 模块有两种书写方式JS 模块直接导出version/name/factory如 src/mocks/funny-transformer.ts 经构建后的产物TS 源文件以.ts结尾的 transformer 文件也会被支持——ts-jest会用 esBuild 现场转译后加载。端到端测试 e2e/transformer-in-ts/jest-compiler-cjs.config.ts 就是直接把src/transformers/hoist-jest.ts当作beforetransformer 使用astTransformers: { before: [ { path: rootDir/../../src/transformers/hoist-jest.ts, }, ], },六、实战建议与注意事项1. 阶段选择决定代码形态需要操作源码级语义如重写import、处理装饰器、插入jest.mock提升逻辑→ 用before需要操作转译后代码如改写require调用、处理模块包装→ 用after需要改写对外类型声明→ 用afterDeclarations。2. 保持缓存版本纪律version字段直接关系到 Jest 的转换缓存。每次修改 transformer 逻辑都要递增version否则可能出现代码改了但测试跑的仍是旧转换结果的诡异问题。3. 遵守序列化约束options会被jest-worker序列化传递到 worker 进程只能传 JSON 可序列化的数据。复杂对象函数、类实例、Map/Set请改用其他方式传递例如通过模块闭包或环境变量。4. 路径解析ts-jest会以nodeResolve方式解析 transformer 路径因此既可以是 npm 包名也可以是绝对/相对路径对.ts源文件会自动转译加载。在 Jest 配置中推荐使用rootDir前缀如rootDir/node_modules/formatjs/ts-transformer/ts-jest-integration以保证路径与项目根目录对齐。5. 与 typescript 模块保持同一实例在factory内部优先通过compilerInstance.configSet.compilerModule获取ts模块如 boilerplate 所示而不是自行import * as ts from typescript。这样能确保 AST 节点判断ts.isXxx与工厂函数与ts-jest实际使用的编译器实例一致避免跨版本类型判断失效。七、总结astTransformers是ts-jest面向需要自定义编译行为场景的一等扩展点通过before/after/afterDeclarations三阶段配置你可以精确选择在原始源码、转译后代码或类型声明三个层面介入编译过程。配合字符串路径或{ path, options }对象两种配置形态、version/name/factory的模块契约以及仓库中 hoist-jest.ts、funny-transformer.ts 等现成范本你可以快速落地 i18n 消息提取、代码混淆、装饰器增强、类型改写等自定义编译能力同时保持与 Jest 缓存、worker 序列化体系的兼容。进一步参考配置解析实现见 config-set.ts类型定义见 types.ts编写指南见 src/transformers/README.md端到端验证用例见 transformer-options 与 transformer-in-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 的 astTransformers 配置指南编写并接入自定义 TypeScript AST Transformerts jest 的 astTransformers 配置指南编写并接入自定义 TypeScript AST Transformer ts jest 在把 Ty测试开发工具ts-jest 自定义 TypeScript AST 变换器astTransformers配置与编写完整指南ts jest 自定义 TypeScript AST 变换器astTransformers配置与编写完整指南 ts jest 在将 TypeScript 代测试开发工具ts-jest 的 astTransformers 选项在 Jest 编译管线中注入自定义 TypeScript AST Transformer 的完整指南ts jest 的 astTransformers 选项在 Jest 编译管线中注入自定义 TypeScript AST Transformer 的完整指南测试开发工具上一篇Wand-Enhancer终极指南3步解锁WeMod完整功能告别Pro限制下一篇如何将Windows电脑变成AirPlay接收器5步快速部署指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考