代码转换

Jest在你的项目中以JavaScript的代码形式运行,但是如果你使用一些Node.js不支持的,却可以开箱即用的语法(如JSX,TypeScript中的类型,Vue模板等)那你就需要将代码转换为纯JavaScript,类似于上述语法在构建浏览器时将要做的事情。

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

转换器是为转换源文件提供同步功能的模块。 例如,如果希望能够在Node尚不支持的模块或测试中使用新的语言功能,可以插入许多编译器中的一个,这些编译器将JavaScript的未来版本编译为当前版本。

Jest将会缓存转换后的结果,并且试图让多方因素(比如正在转换的文件源和配置信息被修改等)造成的结果无效

默认值

Jest附带一个现成的转换器-babel Jest。 它将自动加载项目的Babel配置,并转换与以下正则表达式匹配的任何文件:/\.[jt]sx$/表示任何.js.jsx.ts.tsx文件。 此外,babel-jest还将会注入 ES Module mocking中所提到的Babel插件。

如果你覆盖了transform的配置选项,则babel-jest将不会生效,如果你还想要使用Babel的话,那么就得手动添加它。

编写自定义Transformers

你可以编写专属的Transformer, Transformer的API如下所示:

  1. interface SyncTransformer<OptionType = unknown> {
  2. /**
  3. * Indicates if the transformer is capable of instrumenting the code for code coverage.
  4. *
  5. * If V8 coverage is _not_ active, and this is `true`, Jest will assume the code is instrumented.
  6. * If V8 coverage is _not_ active, and this is `false`. Jest will instrument the code returned by this transformer using Babel.
  7. */
  8. canInstrument?: boolean;
  9. createTransformer?: (options?: OptionType) => SyncTransformer<OptionType>;
  10. getCacheKey?: (
  11. sourceText: string,
  12. sourcePath: Config.Path,
  13. options: TransformOptions<OptionType>,
  14. ) => string;
  15. getCacheKeyAsync?: (
  16. sourceText: string,
  17. sourcePath: Config.Path,
  18. options: TransformOptions<OptionType>,
  19. ) => Promise<string>;
  20. process: (
  21. sourceText: string,
  22. sourcePath: Config.Path,
  23. options: TransformOptions<OptionType>,
  24. ) => TransformedSource;
  25. processAsync?: (
  26. sourceText: string,
  27. sourcePath: Config.Path,
  28. options: TransformOptions<OptionType>,
  29. ) => Promise<TransformedSource>;
  30. }
  31. interface AsyncTransformer<OptionType = unknown> {
  32. /**
  33. * Indicates if the transformer is capable of instrumenting the code for code coverage.
  34. *
  35. * If V8 coverage is _not_ active, and this is `true`, Jest will assume the code is instrumented.
  36. * If V8 coverage is _not_ active, and this is `false`. Jest will instrument the code returned by this transformer using Babel.
  37. */
  38. canInstrument?: boolean;
  39. createTransformer?: (options?: OptionType) => AsyncTransformer<OptionType>;
  40. getCacheKey?: (
  41. sourceText: string,
  42. sourcePath: Config.Path,
  43. options: TransformOptions<OptionType>,
  44. ) => string;
  45. getCacheKeyAsync?: (
  46. sourceText: string,
  47. sourcePath: Config.Path,
  48. options: TransformOptions<OptionType>,
  49. ) => Promise<string>;
  50. process?: (
  51. sourceText: string,
  52. sourcePath: Config.Path,
  53. options: TransformOptions<OptionType>,
  54. ) => TransformedSource;
  55. processAsync: (
  56. sourceText: string,
  57. sourcePath: Config.Path,
  58. options: TransformOptions<OptionType>,
  59. ) => Promise<TransformedSource>;
  60. }
  61. type Transformer<OptionType = unknown> =
  62. | SyncTransformer<OptionType>
  63. | AsyncTransformer<OptionType>;
  64. interface TransformOptions<OptionType> {
  65. /**
  66. * If a transformer does module resolution and reads files, it should populate `cacheFS` so that
  67. * Jest avoids reading the same files again, improving performance. `cacheFS` stores entries of
  68. * <file path, file contents>
  69. */
  70. cacheFS: Map<string, string>;
  71. config: Config.ProjectConfig;
  72. /** A stringified version of the configuration - useful in cache busting */
  73. configString: string;
  74. instrument: boolean;
  75. // names are copied from babel: https://babeljs.io/docs/en/options#caller
  76. supportsDynamicImport: boolean;
  77. supportsExportNamespaceFrom: boolean;
  78. supportsStaticESM: boolean;
  79. supportsTopLevelAwait: boolean;
  80. /** the options passed through Jest's config by the user */
  81. transformerConfig: OptionType;
  82. }
  83. type TransformedSource =
  84. | {code: string; map?: RawSourceMap | string | null}
  85. | string;
  86. // Config.ProjectConfig can be seen in code [here](https://github.com/facebook/jest/blob/v26.6.3/packages/jest-types/src/Config.ts#L323)
  87. // RawSourceMap comes from [`source-map`](https://github.com/mozilla/source-map/blob/0.6.1/source-map.d.ts#L6-L12)

可以看出,只有processprocessAsync是必须实现的,尽管我们强烈建议也实现getCacheKey,这样我们就不会浪费资源来传输之前从磁盘中读取过的源文件。 你还可以使用@jest/create-cache-key-function来帮助你实现它

需要注意的是 ECMAScript module的支持是由supports* 选项指明的。 具体来说supportsDynamicImport: true 表示的是这个transformer可以返回ESM和CJS都支持的import()表达式。 而 supportsStaticESM: true 则表示的是支持最高级别的import语句,代码将被解释为ESM而不是CJS。 阅读 Node’s docs了解ESM和CJS之间的具体差异信息

tip" class="reference-link">代码转换 - 图2tip

Make sure TransformedSource contains a source map, so it is possible to report line information accurately in code coverage and test errors. Inline source maps also work but are slower.

例子

检查带有类型的TypeScript

babel-jest默认情况下会传输TypeScript文件,但Babel并不会对类型校验。 如果你需要校验类型你可以使用 ts-jest.

将图片转换为其路径

导入图像是将其包含在浏览器包中的一种方法,但它们不是有效的JavaScript。 在Jest中有一种解决方法是将它们的文件名替换成导入值

fileTransformer.js

  1. const path = require('path');
  2. module.exports = {
  3. process(src, filename, config, options) {
  4. return `module.exports = ${JSON.stringify(path.basename(filename))};`;
  5. },
  6. };

jest.config.js

  1. module.exports = {
  2. transform: {
  3. '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
  4. '<rootDir>/fileTransformer.js',
  5. },
  6. };