esbuild и Jest / Vitest

Современные JavaScript-проекты редко ограничиваются запуском исходного кода без предварительной обработки. Использование TypeScript, JSX, TSX, современных возможностей ECMAScript, модульной системы ESM и различных препроцессоров требует трансформации файлов перед выполнением тестов. В этой области esbuild занимает особое место благодаря исключительно высокой скорости компиляции.

esbuild представляет собой сверхбыстрый сборщик и транспайлер, написанный на Go. Хотя его основное назначение связано со сборкой приложений, он активно применяется и в тестовой инфраструктуре. Наиболее распространённые сценарии:

  • компиляция TypeScript перед выполнением тестов;
  • преобразование JSX и TSX;
  • обработка модулей ESM;
  • подготовка тестового окружения;
  • ускорение запуска тестов;
  • создание моков и вспомогательных сборок.

В экосистеме тестирования взаимодействие esbuild чаще всего происходит через Jest или Vitest.


Особенности использования esbuild в тестах

При выполнении тестов возникает задача подготовки файлов к исполнению. Например, тест может быть написан на TypeScript:

import { sum } from './sum';

test('adds numbers', () => {
  expect(sum(2, 3)).toBe(5);
});

Node.js не способен напрямую выполнить подобный код без предварительной трансформации.

Традиционно для этой задачи использовались:

  • Babel;
  • ts-jest;
  • ts-node;
  • SWC.

Однако esbuild обеспечивает заметно более высокую производительность благодаря компиляции на уровне машинного кода.

Основные преимущества:

Возможность esbuild
TypeScript Да
JSX Да
TSX Да
ESM Да
Высокая скорость Да
Bundling Да
Tree Shaking Да

Интеграция esbuild и Jest

Архитектура трансформации в Jest

Jest использует механизм трансформеров.

Схема работы выглядит следующим образом:

Тестовый файл
      ↓
 Transformer
      ↓
JavaScript-код
      ↓
 Выполнение Jest

По умолчанию в качестве трансформера может использоваться Babel или ts-jest.

С использованием esbuild цепочка упрощается:

TS/TSX
  ↓
esbuild
  ↓
JavaScript
  ↓
Jest Runtime

Установка esbuild-jest

Наиболее распространённым решением является пакет:

npm install -D esbuild esbuild-jest

После установки настраивается конфигурация Jest.


Настройка jest.config.js

Простейшая конфигурация:

module.exports = {
  transform: {
    '^.+\\.(ts|tsx|js|jsx)$': 'esbuild-jest'
  }
};

Теперь любой файл:

.ts
.tsx
.js
.jsx

будет автоматически обрабатываться через esbuild.


Настройка через transform

Более гибкий вариант:

module.exports = {
  transform: {
    '^.+\\.tsx?$': [
      'esbuild-jest',
      {
        target: 'es2022',
        jsx: 'automatic'
      }
    ]
  }
};

Параметры передаются непосредственно в esbuild.


Поддержка TypeScript

Одно из основных преимуществ esbuild — нативная поддержка TypeScript.

Файл:

interface User {
  name: string;
}

const user: User = {
  name: 'John'
};

export default user;

будет автоматически преобразован в JavaScript.

Важно понимать, что esbuild:

  • удаляет типы;
  • не выполняет полноценную проверку типов.

То есть:

const value: string = 123;

может успешно пройти трансформацию.

Проверка типов обычно запускается отдельно:

tsc --noEmit

Подобная схема позволяет значительно ускорить выполнение тестов.


Работа с JSX и React

Jest часто используется вместе с React.

Тест:

import { render } from '@testing-library/react';
import App from './App';

test('renders component', () => {
  render(<App />);
});

может быть обработан через esbuild без участия Babel.

Конфигурация:

module.exports = {
  transform: {
    '^.+\\.[tj]sx?$': [
      'esbuild-jest',
      {
        jsx: 'automatic'
      }
    ]
  }
};

Поддерживаются оба режима React:

jsx: 'transform'

и

jsx: 'automatic'

Настройка target

Параметр target определяет целевую версию JavaScript.

Пример:

{
  target: 'es2020'
}

или:

{
  target: 'node18'
}

Для тестовой среды Node предпочтительнее указывать конкретную версию:

{
  target: 'node20'
}

Это уменьшает объём трансформаций и дополнительно ускоряет запуск.


Работа с ESM

Многие современные проекты используют ECMAScript Modules.

Пример:

export function sum(a, b) {
  return a + b;
}

Jest исторически ориентирован на CommonJS, поэтому совместное использование ESM требует дополнительных настроек.

esbuild способен преобразовывать модули:

{
  format: 'cjs'
}

или сохранять формат ESM:

{
  format: 'esm'
}

Выбор зависит от конфигурации проекта и версии Jest.


Использование aliases

Во многих проектах применяются сокращённые пути импорта.

Пример:

import Button from '@/components/Button';

Конфигурация TypeScript:

{
  "compilerOptions": {
    "paths": {
      "@/*": ["src/*"]
    }
  }
}

Jest необходимо отдельно сообщить об этих алиасах.

module.exports = {
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1'
  }
};

esbuild не решает эту задачу автоматически внутри Jest, поэтому настройки должны совпадать.


Ограничения esbuild внутри Jest

Несмотря на скорость, существуют некоторые ограничения.

Отсутствие type-checking

esbuild выполняет транспиляцию, а не полноценную компиляцию TypeScript.

Поэтому ошибки типов остаются незамеченными:

const age: number = 'twenty';

Для контроля типов необходим запуск:

tsc --noEmit

в CI или в отдельном скрипте.


Ограниченная поддержка нестандартных Babel-плагинов

Если проект использует сложные Babel-трансформации:

babel-plugin-macros
babel-plugin-styled-components

простая замена Babel на esbuild может оказаться невозможной.

В таких случаях часто применяется смешанная схема:

Jest
 ├─ Babel
 └─ esbuild

либо сохранение Babel для специфических файлов.


Vitest и esbuild

Почему Vitest особенно хорошо сочетается с esbuild

Vitest создан командой Vite.

Внутри экосистемы Vite уже используется esbuild для множества операций:

  • транспиляции TypeScript;
  • анализа зависимостей;
  • обработки JSX;
  • подготовки модулей.

Поэтому интеграция получается практически бесшовной.

Схема выглядит следующим образом:

Vitest
   ↓
 Vite
   ↓
esbuild
   ↓
 Выполнение тестов

Во многих случаях дополнительная настройка вообще не требуется.


Установка Vitest

Установка минимального набора:

npm install -D vitest

Для React:

npm install -D vitest jsdom

Базовая конфигурация Vitest

Файл:

import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {}
});

После запуска:

npx vitest

TypeScript автоматически обрабатывается через инфраструктуру Vite и esbuild.


Тестирование TypeScript без дополнительной настройки

Файл:

export function multiply(a: number, b: number) {
  return a * b;
}

Тест:

import { describe, expect, it } from 'vitest';
import { multiply } from './multiply';

describe('multiply', () => {
  it('works', () => {
    expect(multiply(2, 4)).toBe(8);
  });
});

Не требуется:

  • ts-jest;
  • babel-jest;
  • ts-node.

Vitest самостоятельно выполнит необходимые преобразования.


Использование React с Vitest

Конфигурация:

import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    environment: 'jsdom'
  }
});

Компонент:

function Button() {
  return <button>Click</button>;
}

JSX и TSX обрабатываются через esbuild автоматически.


Настройка esbuild в Vite

Параметры esbuild могут задаваться напрямую.

Пример:

import { defineConfig } from 'vite';

export default defineConfig({
  esbuild: {
    target: 'es2022'
  }
});

Эта настройка влияет и на тестовую среду Vitest.


Изменение JSX Runtime

Для React 17+:

export default defineConfig({
  esbuild: {
    jsx: 'automatic'
  }
});

Для классической схемы:

export default defineConfig({
  esbuild: {
    jsx: 'transform'
  }
});

Использование глобальных API Vitest

Конфигурация:

export default defineConfig({
  test: {
    globals: true
  }
});

После этого становятся доступны:

describe()
it()
expect()
beforeEach()
afterEach()

без явного импорта.


Производительность Jest и Vitest с esbuild

При сравнении крупных проектов обычно наблюдаются следующие тенденции:

Параметр Jest + Babel Jest + esbuild Vitest
Старт тестов Медленно Быстро Очень быстро
TypeScript Через трансформер Через esbuild Встроено
JSX Через Babel Через esbuild Встроено
HMR Нет Нет Да
Watch Mode Хороший Хороший Отличный

Наиболее заметный выигрыш возникает в проектах:

  • с сотнями тестов;
  • большим количеством TS-файлов;
  • активным режимом разработки.

Использование моков в Vitest

Механизм мокирования работает независимо от esbuild.

Пример:

import { vi } from 'vitest';

vi.mock('./api', () => {
  return {
    getUsers: () => []
  };
});

При этом исходный код модуля по-прежнему проходит через esbuild перед исполнением.


Coverage и esbuild

Покрытие кода тестами требует инструментирования файлов.

Vitest использует:

V8 Coverage

или:

Istanbul

Конфигурация:

export default defineConfig({
  test: {
    coverage: {
      provider: 'v8'
    }
  }
});

Провайдер V8 особенно хорошо сочетается с esbuild благодаря минимальным накладным расходам.


Отладка трансформированного кода

esbuild поддерживает source maps.

Настройка:

{
  sourcemap: true
}

или:

export default defineConfig({
  esbuild: {
    sourcemap: true
  }
});

Это позволяет получать корректные стеки ошибок:

src/services/user.ts:25

вместо ссылок на сгенерированный код.


Оптимизация тестовой среды

Для достижения максимальной скорости обычно применяются следующие практики:

Использование актуальной версии Node.js

Node 20+

Позволяет снизить количество необходимых трансформаций.

Указание target

target: 'node20'

Ускоряет компиляцию.

Разделение type-checking и тестирования

Проверка типов:

npm run typecheck

Тестирование:

npm run test

Подобное разделение обеспечивает максимально быстрый цикл разработки.

Использование Vitest в проектах Vite

Если приложение уже построено на Vite, переход на Vitest позволяет получить практически нативную интеграцию с esbuild без дополнительной настройки и с минимальными затратами на обслуживание тестовой инфраструктуры.


Сравнение подходов

Jest + ts-jest

Преимущества:

  • полная поддержка TypeScript;
  • проверка типов во время компиляции.

Недостатки:

  • заметно медленнее;
  • большие накладные расходы.

Jest + esbuild

Преимущества:

  • высокая скорость;
  • простая настройка;
  • поддержка JSX и TSX.

Недостатки:

  • отсутствие type-checking;
  • возможные ограничения при использовании специфических Babel-плагинов.

Vitest + esbuild

Преимущества:

  • встроенная интеграция;
  • минимальная конфигурация;
  • высокая производительность;
  • удобный watch-режим;
  • тесная интеграция с Vite.

Недостатки:

  • меньшая зрелость экосистемы по сравнению с Jest;
  • некоторые специализированные Jest-плагины могут не иметь прямых аналогов.

В современной экосистеме JavaScript esbuild становится одним из ключевых инструментов ускорения тестовой инфраструктуры. В связке с Jest он значительно сокращает время транспиляции TypeScript и JSX, а в сочетании с Vitest фактически образует высокопроизводительную среду тестирования, в которой обработка исходного кода выполняется быстро, прозрачно и практически не требует дополнительной настройки.