- 测试
- 开发工具
【免费下载链接】ts-jest
A Jest transformer with source map support that lets you use Jest to test projects written in TypeScript.
Babel 7 于 2018 年 9 月发布,并带来了专为 TypeScript 设计的预设@babel/preset-typescript,让 Babel 用户只需在配置中追加一个预设即可尝试 TypeScript。本文以 ts-jest 官方文档 Babel7 or TypeScript 为核心脉络,系统梳理@babel/preset-typescript与 TypeScript 原生编译器(即 ts-jest 所采用的方案)在能力上的本质差异:类型检查、namespace、const enum、声明合并、legacyimport/export语法以及 JSX 下的尖括号类型断言。读完本文,你将清楚判断自己的测试项目该选哪条路线,并理解 ts-jest 在项目级编译语义上的底层原理。
背景:Babel7 与@babel/preset-typescript的出现
在 Babel 7 发布之前,想要在 Babel 项目中体验 TypeScript,通常需要引入额外的工具链。Babel7 通过官方预设@babel/preset-typescript改变了这一局面——用户无需离开 Babel 生态,只要在 Babel 配置中加入该预设,就能对.ts/.tsx文件进行转译。它的设计目标是"让 Babel 用户以最小的迁移成本尝试 TypeScript"。
但官方文档明确指出:@babel/preset-typescript是一个出色的预设,使用它之前你必须了解它的能力边界(Limitations)。以下内容在 TypeScript 编译器(以及基于 TypeScript 编译器的 ts-jest)中可以正常工作,但在 Babel7 +@babel/preset-typescript下却无法实现。这些差异本质上源于两条技术路线的底层定位不同:
- Babel:把每个文件当作"孤立模块"(isolated module)逐个转译,只做语法层面的转换,不做任何类型层面的检查或语义分析;
- TypeScript(ts-jest 所采用):把文件纳入一个"项目"(project)的编译范围,在项目上下文中做类型检查、符号解析与代码生成。
限制一:不做类型检查(No type-checking)
这是 TypeScript 对比 Babel 的最大PRO(优势):开箱即用(out of the box)的类型检查能力。
在测试场景下,这个差异会直接影响开发体验。使用 ts-jest 时,文件在被编译和运行的同时会执行类型检查,也就是说你获得的是更流畅的TDD 体验——写错的类型会在测试运行时立即暴露,而不是等构建阶段才被发现。
例如下面这行代码,TypeScript 会直接抛出类型错误,而 Babel 会安静地把它转译过去:
const str: string = 42Type 'number' is not assignable to type 'string'.从实现层面看,这一差异对应着 ts-jest 的编译流水线设计。ts-jest 的 Jest 处理流程(见 processing.md)显示:文件经过 transform 时,ts-jest 会调用 TypeScript 编译器 API 对源码进行完整编译,而不是像 Babel 那样只做语法转译。Babel 转译时"没有项目的概念"(no notion of project),而 TypeScript 明确地把文件视为项目的一部分,在项目范围内编译。
适用场景提示:如果你的团队重视类型安全、希望在测试运行时同步获得类型反馈,ts-jest 是更合适的选择;如果你追求极致的转译速度、且类型检查由独立的 CI 步骤承担,那么@babel/preset-typescript的"不做类型检查"特性反而可能成为优势。
限制二:不支持namespace
@babel/preset-typescript无法处理 TypeScript 的namespace语法:
namespace app { export const VERSION = '1.0.0' export class App { /* ... */ } }namespace是 TypeScript 中组织代码的一种方式,它会在编译期生成相应的运行时对象。由于 Babel 按文件孤立转译、缺乏跨文件的全局符号信息,无法正确生成namespace的运行时语义,因此该语法在 Babel 预设下不可用。而 ts-jest 使用 TypeScript 编译器,namespace是完整的受支持特性。
限制三:不支持const enum
const enum(常量枚举)同样无法通过@babel/preset-typescript转译:
const enum Directions { Up, Down, Left, Right, }const enum的特殊之处在于:它在编译期会被内联为字面量值,而不是生成运行时枚举对象,因此它依赖 TypeScript 编译器对成员值的静态求值能力。Babel 的孤立模块转译无法完成这种跨文件的内联优化,自然不支持该语法。
这一点在 ts-jest 仓库中有完整的端到端验证。在 e2e/const-enum 目录下,测试同时覆盖了两种使用形态:
- 编译器模式(compiler):ts-jest 使用完整 TypeScript 编译器,源码 src/bar-constant.ts 与声明文件 src/foo-constant.d.ts 中直接声明并导出
const enum,测试tests/const-enum.spec.ts 正常运行并断言成员值; - 转译器模式(transpiler / isolatedModules):tsconfig-cjs-transpiler.spec.json 中开启
"isolatedModules": true,ts-jest 退化为按文件孤立转译——这正是文档所警告的"会失去const enum等特性"的场景。
四个配置(compiler-cjs、compiler-esm、transpiler-cjs、transpiler-esm)由 e2e/tests/const-enum.test.ts 统一驱动,逐一验证其运行结果。这组测试从侧面印证了官方文档的判断:const enum是"项目级编译"的产物,孤立转译模式下无法保证正确性。
补充说明:如果项目既想享受 Babel 类孤立转译的速度,又需要const enum,TypeScript 编译器提供了preserveConstEnums编译选项(保留const enum的运行时声明而非仅内联)。该选项在 ts-jest 的编译器选项类型 src/raw-compiler-options.ts 中有明确注释:"Disable erasingconst enumdeclarations in generated code",可作参考。
限制四:不支持声明合并(No declaration merging)
Babel 预设下你无法使用 TypeScript 的声明合并(declaration merging)特性。声明合并允许同名声明(如enum、namespace、接口等)在类型系统中合并扩展,例如在enum或namespace上追加成员。这种特性要求编译器在整个项目范围内汇总多个声明文件的信息,而 Babel 的单文件孤立转译模型天然不具备该能力,因此合并相关语法会被直接拒绝。
ts-jest 走的是完整 TypeScript 编译链路,项目级语义分析由 TypeScript 编译器完成,声明合并在 ts-jest 中属于常规受支持能力。
限制五:不支持 legacyimport/export语法
以下 TypeScript 早期的模块导入导出语法,@babel/preset-typescript同样不支持:
import lib = require('lib') // ... export = myVarimport lib = require('lib'):TypeScript 的导入赋值(import assignment)语法,用于以 CommonJS 风格导入模块;export = myVar:导出赋值语法,用于把整个模块导出为单个值(常用于 CommonJS 模块的类型声明)。
这两者都带有明确的 CommonJS 运行时语义,Babel 预设无法还原其行为;而 TypeScript 编译器可以按配置的模块系统(module)正确生成对应代码。ts-jest 的编译器选项类型 src/raw-compiler-options.ts 中列出了module的完整取值集合(CommonJS、AMD、System、UMD、ES6/ES2015、ES2020、ESNext、Node16、NodeNext、Preserve等),说明 ts-jest 会把模块语义交给 TypeScript 编译器按项目配置统一处理,而不是简单剥离语法。
限制六:JSX 开启时无法使用尖括号类型断言(No caret type-casting with JSX enabled)
当 JSX 功能开启时,下面的尖括号(caret)类型断言写法在 Babel 预设下不可用:
const val = <string>input原因在于:<string>这种写法与 JSX 元素的标签语法存在语法歧义——编译器无法区分这是类型断言还是 JSX 元素。TypeScript 编译器在 JSX 模式下会正确处理这一歧义(将<string>input解析为类型断言),而 Babel 预设无法可靠区分,因此直接不支持。
实践建议:在 JSX 项目中,应统一改用as语法进行类型断言:
const val = input as string这既兼容 Babel 转译,也是 TypeScript 官方推荐的更明确的写法。
两条路线的取舍与落地建议
综合官方文档与仓库实现,可以给出如下决策参考:
| 能力维度 | ts-jest(TypeScript 编译器) | Babel7 +@babel/preset-typescript |
|---|---|---|
| 类型检查 | ✅ 编译与运行同步进行,适合 TDD | ❌ 不做任何类型检查 |
namespace | ✅ 支持 | ❌ 不支持 |
const enum | ✅ 支持(孤立转译模式下除外) | ❌ 不支持 |
| 声明合并 | ✅ 支持 | ❌ 不支持 |
legacyimport/export | ✅ 支持 | ❌ 不支持 |
| JSX 下尖括号断言 | ✅ 支持 | ❌ 不支持 |
| 编译模型 | 项目级(project scope)编译 | 文件级孤立转译(isolated module) |
何时选择 ts-jest:
- 需要类型检查与测试运行一体化,追求流畅的 TDD 反馈;
- 项目使用了
namespace、const enum、声明合并、export =等 TypeScript 特有语法; - 希望测试时的编译语义与
tsc构建保持完全一致。
何时考虑 Babel 预设:
- 已有成熟的 Babel 工具链,希望复用现有的 Babel 插件(如代码转换、Polyfill 策略);
- 类型检查已由 CI 或 IDE 独立承担,测试阶段只求最快的转译速度。
如果你倾向于 ts-jest 但希望在某些场景下换取转译速度,可以关注 ts-jest 的isolatedModules相关配置(详见 isolatedModules.md):该模式让 ts-jest 按孤立模块逐个编译文件、跳过类型检查,换取更快的测试运行速度,但同时会失去类型检查和const enum等特性——这正是 Babel 路线能力边界的"镜像"。
结语
@babel/preset-typescript的价值在于让 Babel 用户以极低门槛接触 TypeScript,但它本质上是"语法级"的 TypeScript 支持:类型检查、namespace、const enum、声明合并、legacy 导入导出与 JSX 下的尖括号断言,这六类能力都依赖 TypeScript 的项目级编译语义,因而成为 Babel 路线的天然边界。ts-jest 通过直接复用 TypeScript 编译器,把完整的项目编译语义带入了 Jest 测试流程——这在 e2e/const-enum 的端到端测试和 processing.md 的处理流程文档中都有据可查。理解这两条路线的差异,是选择测试工具链、排查"Babel 能编译但测试报错"类问题的关键起点。
- 测试
- 开发工具
【免费下载链接】ts-jest
A Jest transformer with source map support that lets you use Jest to test projects written in TypeScript.
相关推荐
ts-jest 与 Babel7 之争:`@babel/preset-typescript` 的六项能力边界与 TypeScript 的完整实现
ts jest 与 Babel7 之争: @babel/preset typescript 的六项能力边界与 TypeScript 的完整实现 导读 本文基于
测试开发工具ts-jest 与 Babel 7 对比:@babel/preset-typescript 的六大局限与 TypeScript 项目级编译的优势
ts jest 与 Babel 7 对比:@babel/preset typescript 的六大局限与 TypeScript 项目级编译的优势 本文基于 ts
测试开发工具ts-jest 与 Babel 7 + @babel/preset-typescript 选型指南:六大特性差异与源码级解析
ts jest 与 Babel 7 + @babel/preset typescript 选型指南:六大特性差异与源码级解析 导读 :Babel 7 于 201
测试开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考