esbuild и TypeScript: рекомендуемый рабочий процесс

Что такое Esbuild

Esbuild — это современный инструмент для сборки (bundling) и трансформации JavaScript и TypeScript-кода, созданный Эваном Уоллесом. Основная особенность Esbuild заключается в исключительной производительности: большинство операций выполняется в десятки и даже сотни раз быстрее по сравнению с традиционными инструментами сборки.

Esbuild написан на языке Go и компилируется в нативный исполняемый файл, благодаря чему достигается высокая скорость обработки проектов любого размера.

Основные возможности Esbuild:

  • Сборка JavaScript-модулей в единый файл.
  • Поддержка TypeScript без дополнительной настройки.
  • Трансформация JSX.
  • Поддержка React.
  • Минификация кода.
  • Tree Shaking.
  • Code Splitting.
  • Генерация Source Maps.
  • Работа с CSS.
  • Поддержка современных стандартов ECMAScript.
  • Режим отслеживания изменений (Watch Mode).
  • Встроенный локальный сервер разработки.

Почему появился Esbuild

До появления Esbuild основными инструментами сборки были:

  • Webpack
  • Rollup
  • Parcel
  • Browserify

Несмотря на широкое распространение, все они имели общий недостаток — сравнительно низкую скорость работы на крупных проектах.

Например, сборка проекта через Webpack может занимать десятки секунд или даже минуты. Esbuild выполняет аналогичные задачи за доли секунды благодаря:

  1. Реализации на Go.
  2. Активному использованию многопоточности.
  3. Минимизации количества промежуточных операций.
  4. Оптимизированному парсеру JavaScript.

Архитектура Esbuild

Внутри Esbuild можно условно выделить несколько компонентов:

Парсер

Считывает исходный код:

import { sum } from './math.js';

console.log(sum(10, 20));

и преобразует его в AST (Abstract Syntax Tree).


Анализ зависимостей

После построения AST Esbuild находит все импортируемые модули:

import React from 'react';
import App from './App';

Формируется граф зависимостей проекта.


Трансформация

На этом этапе выполняется:

  • Компиляция TypeScript.
  • Преобразование JSX.
  • Замена современных конструкций на совместимые версии JavaScript.
  • Удаление неиспользуемого кода.

Генерация результата

Финальная стадия включает:

  • Объединение модулей.
  • Минификацию.
  • Создание source maps.
  • Запись файлов.

Установка Esbuild

Через npm

npm install esbuild --save-dev

Через yarn

yarn add esbuild --dev

Через pnpm

pnpm add esbuild -D

Проверка установки

npx esbuild --version

Пример вывода:

0.25.0

Первый проект на Esbuild

Структура проекта:

project/
├── src/
│   └── index.js
└── package.json

Файл:

// src/index.js

console.log('Hello Esbuild');

Сборка:

npx esbuild src/index.js --bundle --outfile=dist/bundle.js

После выполнения команды:

dist/
└── bundle.js

Основные параметры CLI

–bundle

Включает режим объединения модулей.

npx esbuild src/index.js --bundle --outfile=dist/app.js

–outfile

Указывает итоговый файл.

--outfile=dist/main.js

–outdir

Используется для вывода нескольких файлов.

--outdir=dist

–minify

Минификация кода.

npx esbuild src/index.js --bundle --minify --outfile=dist/app.js

Исходный код:

function sayHello(name) {
    console.log('Hello', name);
}

После минификации:

function o(l){console.log("Hello",l)}

–sourcemap

Создание source maps.

npx esbuild src/index.js \
  --bundle \
  --sourcemap \
  --outfile=dist/app.js

–watch

Автоматическая пересборка.

npx esbuild src/index.js \
  --bundle \
  --watch \
  --outfile=dist/app.js

–target

Определяет целевую платформу.

--target=es2020

Другие варианты:

es2015
es2016
es2017
es2018
es2019
es2020
es2021
es2022
esnext

Использование через JavaScript API

CLI подходит для простых задач, но реальные проекты обычно используют программный API.

Базовая сборка

const esbuild = require('esbuild');

esbuild.build({
    entryPoints: ['src/index.js'],
    bundle: true,
    outfile: 'dist/bundle.js'
});

Асинхронная версия

const esbuild = require('esbuild');

async function build() {
    await esbuild.build({
        entryPoints: ['src/index.js'],
        bundle: true,
        outfile: 'dist/bundle.js'
    });
}

build();

Обработка ошибок

const esbuild = require('esbuild');

esbuild.build({
    entryPoints: ['src/index.js'],
    bundle: true,
    outfile: 'dist/bundle.js'
}).catch(() => process.exit(1));

Работа с TypeScript

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

Установка TypeScript необязательна для простой компиляции.

Файл:

interface User {
    id: number;
    name: string;
}

const user: User = {
    id: 1,
    name: 'Alex'
};

console.log(user);

Сборка:

npx esbuild src/index.ts \
  --bundle \
  --outfile=dist/app.js

Ограничения TypeScript

Esbuild выполняет:

  • удаление типов;
  • трансформацию синтаксиса.

Но не выполняет полноценную проверку типов.

Например:

let age: number = "25";

Esbuild успешно соберёт файл.

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

tsc --noEmit

Типичная схема:

{
  "scripts": {
    "typecheck": "tsc --noEmit",
    "build": "esbuild src/index.ts --bundle --outfile=dist/app.js"
  }
}

Работа с JSX

Esbuild умеет компилировать JSX без дополнительных загрузчиков.

Пример:

function App() {
    return <h1>Hello React</h1>;
}

Сборка:

npx esbuild src/index.jsx \
  --bundle \
  --outfile=dist/app.js

React и Esbuild

Установка:

npm install react react-dom

Точка входа

import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(
    document.getElementById('root')
);

root.render(<App />);

Сборка

npx esbuild src/index.jsx \
  --bundle \
  --outfile=dist/app.js

Новый JSX Runtime

npx esbuild src/index.jsx \
  --bundle \
  --jsx=automatic \
  --outfile=dist/app.js

Tree Shaking

Tree Shaking — удаление неиспользуемого кода.

Файл:

export function used() {
    return 'used';
}

export function unused() {
    return 'unused';
}

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

import { used } from './utils';

console.log(used());

После сборки функция unused() будет удалена.

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

  • меньший размер бандла;
  • более быстрая загрузка;
  • снижение потребления памяти.

Code Splitting

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

Пример:

const module = await import('./heavy-module');

Сборка:

npx esbuild src/index.js \
  --bundle \
  --splitting \
  --format=esm \
  --outdir=dist

Результат:

dist/
├── index.js
├── chunk-ABC123.js
└── chunk-XYZ456.js

Минификация

Esbuild поддерживает три вида оптимизации:

Удаление пробелов

const a = 1;
const b = 2;

const a=1,b=2;

Удаление комментариев

// Comment
const value = 10;

const value=10;

Сокращение идентификаторов

function calculateTotalPrice() {}

function a(){}

Source Maps

Source Maps связывают итоговый бандл с исходным кодом.

Команда:

npx esbuild src/index.js \
  --bundle \
  --sourcemap \
  --outfile=dist/app.js

Результат:

dist/
├── app.js
└── app.js.map

В браузере разработчик будет видеть оригинальные файлы вместо минифицированного кода.


Работа с CSS

Esbuild умеет обрабатывать CSS.

Файл:

body {
    margin: 0;
}

Импорт:

import './styles.css';

Сборка:

npx esbuild src/index.js \
  --bundle \
  --outfile=dist/app.js

Результат:

dist/
├── app.js
└── app.css

Встроенный сервер разработки

Esbuild содержит простой dev server.

Пример:

const esbuild = require('esbuild');

esbuild.serve({
    servedir: 'public'
}, {
    entryPoints: ['src/index.js'],
    bundle: true,
    outfile: 'public/app.js'
});

Сервер автоматически запускается локально.


Режим Watch

Позволяет отслеживать изменения файлов.

const esbuild = require('esbuild');

const ctx = await esbuild.context({
    entryPoints: ['src/index.js'],
    bundle: true,
    outfile: 'dist/app.js'
});

await ctx.watch();

После сохранения любого файла выполняется пересборка.


Платформы выполнения

Esbuild поддерживает несколько платформ.

Browser

{
    platform: 'browser'
}

Оптимизация под браузеры.


Node.js

{
    platform: 'node'
}

Корректно обрабатываются встроенные модули Node.js:

import fs from 'fs';

Neutral

{
    platform: 'neutral'
}

Используется для библиотек и универсального кода.


Форматы модулей

IIFE

{
    format: 'iife'
}

Подходит для подключения через <script>.


CommonJS

{
    format: 'cjs'
}

Используется в Node.js.


ES Modules

{
    format: 'esm'
}

Современный стандарт JavaScript-модулей.


Плагины Esbuild

Плагин представляет собой объект с методом setup.

Пример:

const examplePlugin = {
    name: 'example',
    setup(build) {
        build.onResolve({ filter: /.*/ }, args => {
            console.log(args.path);

            return null;
        });
    }
};

Подключение:

await esbuild.build({
    entryPoints: ['src/index.js'],
    bundle: true,
    plugins: [examplePlugin],
    outfile: 'dist/app.js'
});

Жизненный цикл плагина

Основные хуки:

build.onStart(...)
build.onResolve(...)
build.onLoad(...)
build.onEnd(...)
build.onDispose(...)

Схема работы:

onStart
    ↓
onResolve
    ↓
onLoad
    ↓
Сборка
    ↓
onEnd

Создание собственного загрузчика

Пример виртуального файла:

const virtualPlugin = {
    name: 'virtual',
    setup(build) {

        build.onResolve(
            { filter: /^virtual$/ },
            () => ({
                path: 'virtual',
                namespace: 'virtual'
            })
        );

        build.onLoad(
            {
                filter: /.*/,
                namespace: 'virtual'
            },
            () => ({
                contents: `
                    export const version = "1.0.0";
                `,
                loader: 'js'
            })
        );
    }
};

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

import { version } from 'virtual';

Производительность Esbuild

Типичные показатели:

Инструмент Время сборки
Webpack 20–60 сек
Rollup 5–20 сек
Parcel 5–15 сек
Esbuild менее 1 сек

Конкретные результаты зависят от:

  • размера проекта;
  • числа зависимостей;
  • мощности процессора;
  • использования кэша.

Ограничения Esbuild

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

Нет полноценной проверки типов TypeScript

Необходимо запускать:

tsc --noEmit

отдельно.


Ограниченная экосистема плагинов

По сравнению с Webpack количество плагинов существенно меньше.


Меньше возможностей кастомизации

Некоторые сложные сценарии легче реализовать через Webpack или Vite.


Esbuild и Vite

Многие разработчики считают Vite конкурентом Esbuild, однако это разные инструменты.

Vite использует Esbuild для:

  • быстрого старта проекта;
  • предварительной обработки зависимостей;
  • трансформации TypeScript.

При этом финальная production-сборка обычно выполняется через Rollup.

Схема:

Vite
 ├─ Dev Server
 ├─ HMR
 ├─ Конфигурация
 └─ Esbuild
       ↓
 Быстрые преобразования

Практические рекомендации

Для современных проектов рекомендуется следующая схема:

{
  "scripts": {
    "dev": "node build-dev.js",
    "build": "node build-prod.js",
    "typecheck": "tsc --noEmit"
  }
}

Где:

  • Esbuild отвечает за сборку.
  • TypeScript отвечает за проверку типов.
  • ESLint отвечает за качество кода.
  • Prettier отвечает за форматирование.

Когда использовать Esbuild

Esbuild особенно эффективен в следующих случаях:

  • SPA-приложения на React.
  • TypeScript-проекты.
  • Node.js-сервисы.
  • Библиотеки JavaScript.
  • Инструменты командной строки.
  • Микрофронтенды.
  • CI/CD-конвейеры, где важна скорость сборки.

Когда Esbuild может не подойти

Стоит рассмотреть альтернативы, если необходимы:

  • очень сложные пайплайны обработки ресурсов;
  • большое количество специализированных плагинов;
  • максимальная совместимость со старой инфраструктурой;
  • глубокая настройка процесса сборки.

Итоги

Esbuild стал одним из самых значимых инструментов современной JavaScript-экосистемы благодаря сочетанию высокой скорости и простоты использования. Он предоставляет полный набор возможностей для сборки JavaScript, TypeScript, JSX и CSS, поддерживает минификацию, tree shaking, code splitting и создание source maps, при этом оставаясь значительно быстрее большинства конкурентов.

Для большинства современных проектов Esbuild способен полностью заменить классические сборщики или стать основой более сложных инструментов разработки, таких как Vite. Именно поэтому понимание принципов работы Esbuild, его API, системы плагинов и механизмов оптимизации является важным навыком для любого современного JavaScript-разработчика.