跳至主内容
版本:29.7

代码转换

非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

Jest 会以 JavaScript 形式运行项目中的代码,但如果你使用了 Node 默认不支持的语法(如 JSX、TypeScript 或 Vue 模板),就需要将这些代码转换为纯 JavaScript,类似于为浏览器构建时需要进行的操作。

Jest 通过 transform 配置选项来支持此功能。

转换器(transformer)是提供源代码文件转换方法的模块。例如,如果你想在模块或测试中使用 Node 尚未支持的新语言特性,可以接入代码预处理器,将未来版本的 JavaScript 转译(transpile)为当前可用的版本。

Jest 会缓存转换结果,并根据多种因素(如被转换文件的来源和配置变更)尝试使缓存失效。

默认配置

Jest 默认内置了一个转换器 – babel-jest。它会加载项目的 Babel 配置,并转换所有匹配 /\.[jt]sx?$/ 正则表达式的文件(即任何 .js.jsx.ts.tsx 文件)。此外,babel-jest 还会注入 ES 模块模拟 中提到的模拟提升(mock hoisting)所需的 Babel 插件。

技巧

如需同时使用其他代码预处理器,请务必显式包含默认的 babel-jest 转换器:

"transform": {
"\\.[jt]sx?$": "babel-jest",
"\\.css$": "some-css-transformer",
}

编写自定义转换器

你可以编写自己的转换器,其 API 定义如下:

interface TransformOptions<TransformerConfig = unknown> {
supportsDynamicImport: boolean;
supportsExportNamespaceFrom: boolean;
/**
* The value is:
* - `false` if Jest runs without Node ESM flag `--experimental-vm-modules`
* - `true` if the file extension is defined in [extensionsToTreatAsEsm](Configuration.md#extensionstotreatasesm-arraystring)
* and Jest runs with Node ESM flag `--experimental-vm-modules`
*
* See more at https://jestjs.io/docs/next/ecmascript-modules
*/
supportsStaticESM: boolean;
supportsTopLevelAwait: boolean;
instrument: boolean;
/** Cached file system which is used by `jest-runtime` to improve performance. */
cacheFS: Map<string, string>;
/** Jest configuration of currently running project. */
config: ProjectConfig;
/** Stringified version of the `config` - useful in cache busting. */
configString: string;
/** Transformer configuration passed through `transform` option by the user. */
transformerConfig: TransformerConfig;
}

type TransformedSource = {
code: string;
map?: RawSourceMap | string | null;
};

interface SyncTransformer<TransformerConfig = unknown> {
canInstrument?: boolean;

getCacheKey?: (
sourceText: string,
sourcePath: string,
options: TransformOptions<TransformerConfig>,
) => string;

getCacheKeyAsync?: (
sourceText: string,
sourcePath: string,
options: TransformOptions<TransformerConfig>,
) => Promise<string>;

process: (
sourceText: string,
sourcePath: string,
options: TransformOptions<TransformerConfig>,
) => TransformedSource;

processAsync?: (
sourceText: string,
sourcePath: string,
options: TransformOptions<TransformerConfig>,
) => Promise<TransformedSource>;
}

interface AsyncTransformer<TransformerConfig = unknown> {
canInstrument?: boolean;

getCacheKey?: (
sourceText: string,
sourcePath: string,
options: TransformOptions<TransformerConfig>,
) => string;

getCacheKeyAsync?: (
sourceText: string,
sourcePath: string,
options: TransformOptions<TransformerConfig>,
) => Promise<string>;

process?: (
sourceText: string,
sourcePath: string,
options: TransformOptions<TransformerConfig>,
) => TransformedSource;

processAsync: (
sourceText: string,
sourcePath: string,
options: TransformOptions<TransformerConfig>,
) => Promise<TransformedSource>;
}

type Transformer<TransformerConfig = unknown> =
| SyncTransformer<TransformerConfig>
| AsyncTransformer<TransformerConfig>;

type TransformerCreator<
X extends Transformer<TransformerConfig>,
TransformerConfig = unknown,
> = (transformerConfig?: TransformerConfig) => X;

type TransformerFactory<X extends Transformer> = {
createTransformer: TransformerCreator<X>;
};
备注

为简洁起见,以上定义经过精简。完整代码可在 GitHub 的 Jest 仓库 查看(请根据你使用的 Jest 版本选择对应标签/提交)。

向 Jest 导入代码主要有两种方式:使用 Common JS (require) 或 ECMAScript 模块 (import,含静态和动态版本)。Jest 会在需要时(例如执行 requireimport 时)对文件进行代码转换。这个过程也称为"转译"(transpilation),可能以_同步_方式(如 require 场景)或_异步_方式(如 importimport() 场景,后者也可在 Common JS 模块中使用)进行。因此,接口提供了两对分别处理异步和同步过程的方法:process{Async}getCacheKey{Async}。后者用于判断是否需要调用 process{Async}

如果未实现 processAsync,异步转译可以回退到同步的 process 调用,但同步转译无法使用异步的 processAsync 方法。如果你的代码库仅使用 ES 模块(ESM),实现异步版本即可。但若有代码通过 require 加载(包括在 ESM 中使用 createRequire),则必须实现同步的 process 方法。

请注意,默认配置下 node_modules 不会被转译,需要修改 transformIgnorePatterns 设置才能实现。

这些支持标志(参见上文的 CallerTransformOptions)虽与此相关,但它们应在转换器内部用于决定返回 ESM 还是 CJS 格式,并不直接影响同步与异步处理的逻辑关系。

虽然非强制要求,但我们强烈建议同时实现 getCacheKey 方法。这样当可从磁盘读取先前缓存结果时,就能避免重复执行资源消耗型的转译操作。您可使用 @jest/create-cache-key-function 工具包协助实现此功能。

您无需让自定义转换器直接实现 Transformer 接口,可选择导出 createTransformer 工厂函数来动态创建转换器。这种设计允许在 Jest 配置中灵活设置转换器参数。

备注

ECMAScript 模块支持状态通过传入的 supports* 选项标识。特别当 supportsDynamicImport: true 时,表示转换器可返回 import() 动态导入表达式(该语法在 ESM 和 CJS 中均受支持)。若 supportsStaticESM: true 则表明支持顶层 import 静态导入语句,此时代码将被解释为 ESM 而非 CJS。具体差异请参阅 Node 官方文档

技巧

请确保 process{Async} 方法在返回转换后代码时同步返回源码映射(source map),这对代码覆盖率统计和测试错误报告中的行号精准定位至关重要。虽然内联源码映射也可生效,但其处理效率较低。

开发转换器过程中,建议使用 --no-cache 参数运行 Jest 以频繁清除缓存

示例

支持类型检查的 TypeScript

虽然 babel-jest 默认会转译 TypeScript 文件,但 Babel 不会执行类型校验。若需此功能,可使用 ts-jest 转换器。

将图片转换为路径引用

在浏览器打包场景中导入图片是常见需求,但这些资源并非合法 JavaScript 代码。Jest 的一种处理方案是将导入值替换为对应文件名。

fileTransformer.js
const path = require('path');

module.exports = {
process(sourceText, sourcePath, options) {
return {
code: `module.exports = ${JSON.stringify(path.basename(sourcePath))};`,
};
},
};
jest.config.js
module.exports = {
transform: {
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
'<rootDir>/fileTransformer.js',
},
};