Skip to main content
Version: 29.7

代码转换

Jest 将项目中的代码作为 JavaScript 运行,但如果你使用 Node 不支持的某些语法(例如 JSX、TypeScript、Vue 模板),那么你需要将该代码转换为纯 JavaScript,类似于 你在为浏览器构建时会这样做。

¥Jest runs the code in your project as JavaScript, but if you use some syntax not supported by Node out of the box (such as JSX, TypeScript, Vue templates) then you'll need to transform that code into plain JavaScript, similar to what you would do when building for browsers.

Jest 通过 transform 配置选项支持这一点。

¥Jest supports this via the transform configuration option.

转换器是一个提供转换源文件方法的模块。例如,如果你希望能够在 Node 尚不支持的模块或测试中使用新的语言功能,你可以插入一个代码预处理器,将未来版本的 JavaScript 转换为当前版本。

¥A transformer is a module that provides a method for transforming source files. For example, if you wanted to be able to use a new language feature in your modules or tests that aren't yet supported by Node, you might plug in a code preprocessor that would transpile a future version of JavaScript to a current one.

Jest 将缓存转换的结果,并尝试根据多种因素使该结果无效,例如正在转换的文件的源和更改的配置。

¥Jest will cache the result of a transformation and attempt to invalidate that result based on a number of factors, such as the source of the file being transformed and changing configuration.

默认值

¥Defaults

Jest 附带一台开箱即用的转换器 – babel-jest。它将加载项目的 Babel 配置并转换与 /\.[jt]sx?$/ RegExp 匹配的任何文件(换句话说,任何 .js.jsx.ts.tsx 文件)。另外,babel-jest 将注入 ES 模块模拟 中谈到的模拟提升所需的 Babel 插件。

¥Jest ships with one transformer out of the box – babel-jest. It will load your project's Babel configuration and transform any file matching the /\.[jt]sx?$/ RegExp (in other words, any .js, .jsx, .ts or .tsx file). In addition, babel-jest will inject the Babel plugin necessary for mock hoisting talked about in ES Module mocking.

提示

如果你希望将默认的 babel-jest 转换器与其他代码预处理器一起使用,请记住显式包含它:

¥Remember to include the default babel-jest transformer explicitly, if you wish to use it alongside with additional code preprocessors:

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

编写自定义转换器

¥Writing custom transformers

你可以编写自己的转换器。Transformer 的 API 如下:

¥You can write your own transformer. The API of a transformer is as follows:

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://jest.nodejs.cn/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 版本选择正确的标签/提交)。

¥The definitions above were trimmed down for brevity. Full code can be found in Jest repo on GitHub (remember to choose the right tag/commit for your version of Jest).

你可以通过多种方式将代码导入 Jest - 使用通用 JS (require) 或 ECMAScript 模块 (import - 存在静态和动态版本)。Jest 根据需要通过代码转换传递文件(例如,当评估 requireimport 时)。此过程也称为 "transpilation",可能同步发生(对于 require),也可能异步发生(对于 importimport(),后者也适用于 Common JS 模块)。因此,该接口公开了异步和同步进程的两对方法:process{Async}getCacheKey{Async}。调用后者是为了确定我们是否需要调用 process{Async}

¥There are a couple of ways you can import code into Jest - using Common JS (require) or ECMAScript Modules (import - which exists in static and dynamic versions). Jest passes files through code transformation on demand (for instance when a require or import is evaluated). This process, also known as "transpilation", might happen synchronously (in the case of require), or asynchronously (in the case of import or import(), the latter of which also works from Common JS modules). For this reason, the interface exposes both pairs of methods for asynchronous and synchronous processes: process{Async} and getCacheKey{Async}. The latter is called to figure out if we need to call process{Async} at all.

如果 processAsync 未实现,异步转译可以回退到同步 process 调用,但同步转译不能使用异步 processAsync 调用。如果你的代码库仅是 ESM,则实现异步变体就足够了。否则,如果通过 require 加载任何代码(包括 ESM 中的 createRequire),则你需要实现同步 process 变体。

¥Asynchronous transpilation can fall back to the synchronous process call if processAsync is unimplemented, but synchronous transpilation cannot use the asynchronous processAsync call. If your codebase is ESM only, implementing the async variants are sufficient. Otherwise, if any code is loaded through require (including createRequire from within ESM), then you need to implement the synchronous process variant.

请注意,node_modules 不会使用默认配置进行转译,必须修改 transformIgnorePatterns 设置才能这样做。

¥Be aware that node_modules is not transpiled with default config, the transformIgnorePatterns setting must be modified in order to do so.

与此半相关的是我们传递的支持标志(参见上面的 CallerTransformOptions),但这些应该在转换中使用来确定它是否应该返回 ESM 或 CJS,并且对同步与异步没有直接影响

¥Semi-related to this are the supports flags we pass (see CallerTransformOptions above), but those should be used within the transform to figure out if it should return ESM or CJS, and has no direct bearing on sync vs async

虽然不是必需的,但我们强烈建议也实现 getCacheKey,这样当我们可以从磁盘读取先前的结果时,我们就不会浪费资源进行转译。你可以使用 @jest/create-cache-key-function 来帮助实现它。

¥Though not required, we highly recommend implementing getCacheKey as well, so we do not waste resources transpiling when we could have read its previous result from disk. You can use @jest/create-cache-key-function to help implement it.

你可以选择导出 createTransformer(一个用于动态创建转换器的工厂函数),而不是让自定义转换器直接实现 Transformer 接口。这是为了允许在你的 Jest 配置中有一个转换器配置。

¥Instead of having your custom transformer implement the Transformer interface directly, you can choose to export createTransformer, a factory function to dynamically create transformers. This is to allow having a transformer config in your jest config.

注意

ECMAScript 模块 支持由传入的 supports* 选项指示。具体来说,supportsDynamicImport: true 表示转换器可以返回 import() 表达式,ESM 和 CJS 都支持。如果 supportsStaticESM: true 则意味着支持顶层 import 语句,并且代码将被解释为 ESM 而不是 CJS。有关差异的详细信息,请参阅 Node 的文档

¥ECMAScript module support is indicated by the passed in supports* options. Specifically supportsDynamicImport: true means the transformer can return import() expressions, which is supported by both ESM and CJS. If supportsStaticESM: true it means top level import statements are supported and the code will be interpreted as ESM and not CJS. See Node's docs for details on the differences.

提示

确保 process{Async} 方法返回源映射以及转换后的代码,这样就可以在代码覆盖率和测试错误中准确报告行信息。内联源映射也可以工作,但速度较慢。

¥Make sure process{Async} method returns source map alongside with transformed code, so it is possible to report line information accurately in code coverage and test errors. Inline source maps also work but are slower.

在转换器的开发过程中,使用 --no-cache 到频繁的 删除缓存 运行 Jest 会很有用。

¥During the development of a transformer it can be useful to run Jest with --no-cache to frequently delete cache.

示例

¥Examples

具有类型检查功能的 TypeScript

¥TypeScript with type checking

虽然 babel-jest 默认情况下会转译 TypeScript 文件,但 Babel 不会验证类型。如果你愿意,可以使用 ts-jest

¥While babel-jest by default will transpile TypeScript files, Babel will not verify the types. If you want that you can use ts-jest.

将图片转换为路径

¥Transforming images to their path

导入图片是将图片包含在浏览器打包包中的一种方法,但它们不是有效的 JavaScript。在 Jest 中处理它的一种方法是将导入的值替换为其文件名。

¥Importing images is a way to include them in your browser bundle, but they are not valid JavaScript. One way of handling it in Jest is to replace the imported value with its filename.

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',
},
};