Современные JavaScript-проекты редко ограничиваются запуском исходного кода без предварительной обработки. Использование TypeScript, JSX, TSX, современных возможностей ECMAScript, модульной системы ESM и различных препроцессоров требует трансформации файлов перед выполнением тестов. В этой области esbuild занимает особое место благодаря исключительно высокой скорости компиляции.
esbuild представляет собой сверхбыстрый сборщик и транспайлер, написанный на Go. Хотя его основное назначение связано со сборкой приложений, он активно применяется и в тестовой инфраструктуре. Наиболее распространённые сценарии:
В экосистеме тестирования взаимодействие esbuild чаще всего происходит через Jest или Vitest.
При выполнении тестов возникает задача подготовки файлов к исполнению. Например, тест может быть написан на TypeScript:
import { sum } from './sum';
test('adds numbers', () => {
expect(sum(2, 3)).toBe(5);
});
Node.js не способен напрямую выполнить подобный код без предварительной трансформации.
Традиционно для этой задачи использовались:
Однако esbuild обеспечивает заметно более высокую производительность благодаря компиляции на уровне машинного кода.
Основные преимущества:
| Возможность | esbuild |
|---|---|
| TypeScript | Да |
| JSX | Да |
| TSX | Да |
| ESM | Да |
| Высокая скорость | Да |
| Bundling | Да |
| Tree Shaking | Да |
Jest использует механизм трансформеров.
Схема работы выглядит следующим образом:
Тестовый файл
↓
Transformer
↓
JavaScript-код
↓
Выполнение Jest
По умолчанию в качестве трансформера может использоваться Babel или ts-jest.
С использованием esbuild цепочка упрощается:
TS/TSX
↓
esbuild
↓
JavaScript
↓
Jest Runtime
Наиболее распространённым решением является пакет:
npm install -D esbuild esbuild-jest
После установки настраивается конфигурация Jest.
Простейшая конфигурация:
module.exports = {
transform: {
'^.+\\.(ts|tsx|js|jsx)$': 'esbuild-jest'
}
};
Теперь любой файл:
.ts
.tsx
.js
.jsx
будет автоматически обрабатываться через esbuild.
Более гибкий вариант:
module.exports = {
transform: {
'^.+\\.tsx?$': [
'esbuild-jest',
{
target: 'es2022',
jsx: 'automatic'
}
]
}
};
Параметры передаются непосредственно в esbuild.
Одно из основных преимуществ esbuild — нативная поддержка TypeScript.
Файл:
interface User {
name: string;
}
const user: User = {
name: 'John'
};
export default user;
будет автоматически преобразован в JavaScript.
Важно понимать, что esbuild:
То есть:
const value: string = 123;
может успешно пройти трансформацию.
Проверка типов обычно запускается отдельно:
tsc --noEmit
Подобная схема позволяет значительно ускорить выполнение тестов.
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 определяет целевую версию JavaScript.
Пример:
{
target: 'es2020'
}
или:
{
target: 'node18'
}
Для тестовой среды Node предпочтительнее указывать конкретную версию:
{
target: 'node20'
}
Это уменьшает объём трансформаций и дополнительно ускоряет запуск.
Многие современные проекты используют ECMAScript Modules.
Пример:
export function sum(a, b) {
return a + b;
}
Jest исторически ориентирован на CommonJS, поэтому совместное использование ESM требует дополнительных настроек.
esbuild способен преобразовывать модули:
{
format: 'cjs'
}
или сохранять формат ESM:
{
format: 'esm'
}
Выбор зависит от конфигурации проекта и версии Jest.
Во многих проектах применяются сокращённые пути импорта.
Пример:
import Button from '@/components/Button';
Конфигурация TypeScript:
{
"compilerOptions": {
"paths": {
"@/*": ["src/*"]
}
}
}
Jest необходимо отдельно сообщить об этих алиасах.
module.exports = {
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1'
}
};
esbuild не решает эту задачу автоматически внутри Jest, поэтому настройки должны совпадать.
Несмотря на скорость, существуют некоторые ограничения.
esbuild выполняет транспиляцию, а не полноценную компиляцию TypeScript.
Поэтому ошибки типов остаются незамеченными:
const age: number = 'twenty';
Для контроля типов необходим запуск:
tsc --noEmit
в CI или в отдельном скрипте.
Если проект использует сложные Babel-трансформации:
babel-plugin-macros
babel-plugin-styled-components
простая замена Babel на esbuild может оказаться невозможной.
В таких случаях часто применяется смешанная схема:
Jest
├─ Babel
└─ esbuild
либо сохранение Babel для специфических файлов.
Vitest создан командой Vite.
Внутри экосистемы Vite уже используется esbuild для множества операций:
Поэтому интеграция получается практически бесшовной.
Схема выглядит следующим образом:
Vitest
↓
Vite
↓
esbuild
↓
Выполнение тестов
Во многих случаях дополнительная настройка вообще не требуется.
Установка минимального набора:
npm install -D vitest
Для React:
npm install -D vitest jsdom
Файл:
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {}
});
После запуска:
npx vitest
TypeScript автоматически обрабатывается через инфраструктуру Vite и esbuild.
Файл:
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);
});
});
Не требуется:
Vitest самостоятельно выполнит необходимые преобразования.
Конфигурация:
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
environment: 'jsdom'
}
});
Компонент:
function Button() {
return <button>Click</button>;
}
JSX и TSX обрабатываются через esbuild автоматически.
Параметры esbuild могут задаваться напрямую.
Пример:
import { defineConfig } from 'vite';
export default defineConfig({
esbuild: {
target: 'es2022'
}
});
Эта настройка влияет и на тестовую среду Vitest.
Для React 17+:
export default defineConfig({
esbuild: {
jsx: 'automatic'
}
});
Для классической схемы:
export default defineConfig({
esbuild: {
jsx: 'transform'
}
});
Конфигурация:
export default defineConfig({
test: {
globals: true
}
});
После этого становятся доступны:
describe()
it()
expect()
beforeEach()
afterEach()
без явного импорта.
При сравнении крупных проектов обычно наблюдаются следующие тенденции:
| Параметр | Jest + Babel | Jest + esbuild | Vitest |
|---|---|---|---|
| Старт тестов | Медленно | Быстро | Очень быстро |
| TypeScript | Через трансформер | Через esbuild | Встроено |
| JSX | Через Babel | Через esbuild | Встроено |
| HMR | Нет | Нет | Да |
| Watch Mode | Хороший | Хороший | Отличный |
Наиболее заметный выигрыш возникает в проектах:
Механизм мокирования работает независимо от esbuild.
Пример:
import { vi } from 'vitest';
vi.mock('./api', () => {
return {
getUsers: () => []
};
});
При этом исходный код модуля по-прежнему проходит через 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 20+
Позволяет снизить количество необходимых трансформаций.
target: 'node20'
Ускоряет компиляцию.
Проверка типов:
npm run typecheck
Тестирование:
npm run test
Подобное разделение обеспечивает максимально быстрый цикл разработки.
Если приложение уже построено на Vite, переход на Vitest позволяет получить практически нативную интеграцию с esbuild без дополнительной настройки и с минимальными затратами на обслуживание тестовой инфраструктуры.
Преимущества:
Недостатки:
Преимущества:
Недостатки:
Преимущества:
Недостатки:
В современной экосистеме JavaScript esbuild становится одним из ключевых инструментов ускорения тестовой инфраструктуры. В связке с Jest он значительно сокращает время транспиляции TypeScript и JSX, а в сочетании с Vitest фактически образует высокопроизводительную среду тестирования, в которой обработка исходного кода выполняется быстро, прозрачно и практически не требует дополнительной настройки.