Spec-Zone.ru › Jest

Преобразование кода

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 включает babel-preset-jest. Вы можете отключить это поведение, указав excludeJestPreset: true для babel-jest. Обратите внимание: это также отключит поднятие jest.mock, что может привести к сбоям тестов.

"transform": {
  "\\.[jt]sx?$": ["babel-jest", { "excludeJestPreset": true }],
}
совет

Не забудьте явно указать трансформер 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>;
};
примечание

Приведённые выше определения сокращены для краткости. Полный код можно найти в репозитории Jest на GitHub (не забудьте выбрать соответствующий тег или коммит для вашей версии Jest).

Есть несколько способов импортировать код в Jest — использовать CommonJS (require) или модули ECMAScript (import — существуют статический и динамический варианты). Jest преобразует файлы по мере необходимости (например, при вычислении require или import). Этот процесс, также известный как «транспиляция», может выполняться синхронно (в случае require) или асинхронно (в случае import или import(); последний вариант также работает в модулях CommonJS). Поэтому интерфейс предоставляет обе пары методов для асинхронной и синхронной обработки: process{Async} и getCacheKey{Async}. Последний вызывается, чтобы определить, нужно ли вообще вызывать process{Async}.

Асинхронная транспиляция может переключиться на синхронный вызов process, если processAsync не реализован, но синхронная транспиляция не может использовать асинхронный вызов processAsync. Если ваша кодовая база состоит только из ESM, достаточно реализовать асинхронные варианты. В противном случае, если код загружается через require (в том числе createRequire из ESM), необходимо реализовать синхронный вариант 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} возвращает карту исходного кода вместе с преобразованным кодом: это позволяет точно указывать строки в отчётах о покрытии кода и ошибках тестов. Встроенные карты исходного кода тоже работают, но медленнее.

При разработке трансформера полезно запускать Jest с параметром --no-cache, чтобы регулярно очищать кэш.

Примеры

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))};`,
    };
  },
};
fileTransformer.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',
  },
};
jest.config.js

© 2022 Facebook, Inc.
Licensed under the MIT License.
https://jestjs.io/docs/code-transformation

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API