代码转换
Jest 将项目中的代码运行为 JavaScript,但如果使用了一些 Node 默认不支持的语法(例如 JSX、TypeScript、Vue 模板),则需要将这些代码转换为纯 JavaScript,类似于为浏览器构建时所做的那样。
Jest 通过 transform
配置选项支持此功能。
转换器是一个模块,它提供了一种转换源文件的方法。例如,如果希望在模块或测试中使用 Node 尚未支持的新语言特性,可以插入一个代码预处理器,将未来版本的 JavaScript 转换为当前版本。
Jest 会缓存转换结果,并尝试根据一些因素(例如被转换文件的来源和配置更改)使缓存结果失效。
默认值
Jest 默认提供了一个转换器 - babel-jest
。它将加载项目的 Babel 配置,并转换任何与 /\.[jt]sx?$/
正则表达式匹配的文件(换句话说,任何 .js
、.jsx
、.ts
或 .tsx
文件)。此外,babel-jest
将注入 Babel 插件,该插件对于 ES 模块模拟 中提到的模拟提升是必需的。
如果希望与其他代码预处理器一起使用,请记住显式包含默认的 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://jest.node.org.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>;
};
为了简洁,上面的定义被简化了。完整的代码可以在 Jest GitHub 仓库 中找到(请记住选择与 Jest 版本匹配的标签/提交)。
有两种方法可以将代码导入 Jest - 使用 Common JS (require
) 或 ECMAScript 模块 (import
- 存在于静态和动态版本中)。Jest 按需通过代码转换处理文件(例如,当 require
或 import
被评估时)。这个过程也称为“转译”,可能同步发生(在 require
的情况下),也可能异步发生(在 import
或 import()
的情况下,后者也可以从 Common JS 模块中工作)。因此,接口同时公开异步和同步过程的两种方法对:process{Async}
和 getCacheKey{Async}
。后者用于确定是否需要调用 process{Async}
。
如果 processAsync
未实现,异步转译可以回退到同步 process
调用,但同步转译不能使用异步 processAsync
调用。如果代码库仅限于 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}
方法返回转换后的代码以及源映射,以便在代码覆盖率和测试错误中准确地报告行信息。内联源映射也适用,但速度较慢。
在开发转换器期间,使用 --no-cache
运行 Jest 可以帮助频繁地 删除缓存。
示例
带有类型检查的 TypeScript
虽然 babel-jest
默认会转译 TypeScript 文件,但 Babel 不会验证类型。如果需要验证类型,可以使用 ts-jest
。
将图像转换为其路径
导入图像是在浏览器包中包含图像的一种方式,但它们不是有效的 JavaScript。在 Jest 中处理此问题的一种方法是将导入的值替换为其文件名。
const path = require('path');
module.exports = {
process(sourceText, sourcePath, options) {
return {
code: `module.exports = ${JSON.stringify(path.basename(sourcePath))};`,
};
},
};
module.exports = {
transform: {
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
'<rootDir>/fileTransformer.js',
},
};