esbuild как bundler внутри Vite

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

Внутри Vite библиотека esbuild выполняет несколько важных задач:

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

При этом Vite не использует esbuild как единственный bundler. Архитектура построена таким образом, что разные этапы работы выполняются разными инструментами.

Условно процесс выглядит следующим образом:

Исходный код
      │
      ▼
  esbuild
(быстрая трансформация)
      │
      ▼
 Vite Dev Server
      │
      ▼
 Rollup
(финальная production-сборка)
      │
      ▼
 Готовые файлы

Такой подход позволяет объединить преимущества двух технологий:

  • скорость esbuild;
  • гибкость и богатую экосистему Rollup.

Почему Vite использует esbuild

До появления esbuild большинство сборщиков использовали JavaScript-парсеры и транспиляторы, работающие внутри среды Node.js.

Например:

  • Babel;
  • Terser;
  • TypeScript Compiler;
  • различные AST-инструменты на JavaScript.

Хотя подобные решения обладают широкими возможностями, их производительность ограничивается скоростью выполнения JavaScript.

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

Сравнение типичных задач:

Операция Babel TypeScript esbuild
Транспиляция TS Средняя Средняя Очень высокая
Обработка JSX Средняя Средняя Очень высокая
Минификация Средняя Не поддерживается Высокая
Bundling Средняя Нет Высокая

Именно поэтому команда Vite выбрала esbuild для наиболее ресурсоёмких операций, которые должны выполняться практически мгновенно.


esbuild во время разработки

Наиболее заметна работа esbuild в режиме разработки.

При запуске команды:

npm run dev

Vite создаёт локальный сервер и начинает обслуживать модули по требованию.

Когда браузер запрашивает файл:

import { createApp } from "vue";

или

import App from "./App.tsx";

Vite не запускает полную сборку проекта.

Вместо этого происходит быстрая трансформация конкретного модуля через esbuild.

Например, файл:

const user: string = "Alex";

export default user;

через esbuild превращается в:

const user = "Alex";

export default user;

Трансформация происходит практически мгновенно даже для крупных проектов.


Предварительная оптимизация зависимостей

Одной из важнейших задач esbuild внутри Vite является Dependency Pre-Bundling.

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

import React from "react";
import ReactDOM from "react-dom";
import lodash from "lodash";
import axios from "axios";

Каждая библиотека может содержать:

  • сотни файлов;
  • различные форматы модулей;
  • вложенные зависимости.

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

Для решения проблемы Vite выполняет предварительную оптимизацию зависимостей.


Как работает оптимизация

Предположим, проект содержит:

import React from "react";
import lodash from "lodash";

При первом запуске Vite анализирует дерево импортов:

react
 ├─ scheduler
 ├─ react-jsx-runtime
 └─ ...
 
lodash
 ├─ internal modules
 └─ ...

После анализа esbuild объединяет зависимости в оптимизированные модули.

Условно:

node_modules/react/*
node_modules/lodash/*

превращаются в:

node_modules/.vite/deps/

В результате браузер загружает меньше файлов и быстрее запускает приложение.


Каталог .vite/deps

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

node_modules/.vite

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

Пример структуры:

node_modules
└── .vite
    └── deps
        ├── react.js
        ├── react-dom.js
        ├── lodash.js
        └── package.json

Эти файлы были созданы esbuild.

Каждый файл представляет собой оптимизированную версию зависимости для быстрого использования браузером.


Анализ импортов

Перед обработкой модулей Vite выполняет анализ импортов.

Например:

import { format } from "date-fns";
import axios from "axios";

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

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

Пример динамического импорта:

const page = await import("./pages/about.js");

Подобные конструкции также анализируются во время работы сервера разработки.


Обработка TypeScript

Одной из наиболее востребованных возможностей является поддержка TypeScript.

Файл:

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

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

export default user;

Во время разработки проходит через esbuild.

Результат:

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

export default user;

Типы полностью удаляются.

Важно понимать различие между TypeScript Compiler и esbuild.

TypeScript Compiler:

tsc

выполняет:

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

esbuild выполняет только транспиляцию:

TypeScript → JavaScript

без проверки типов.

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

tsc --noEmit

для статического анализа.


Обработка JSX и TSX

Vite активно использует esbuild для работы с React-проектами.

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

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

трансформируется в:

function App() {
    return React.createElement(
        "h1",
        null,
        "Hello"
    );
}

или в код нового JSX Runtime:

import { jsx } from "react/jsx-runtime";

function App() {
    return jsx("h1", {
        children: "Hello"
    });
}

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


Обработка современных возможностей JavaScript

esbuild способен преобразовывать современные конструкции ECMAScript в код, совместимый с указанной платформой.

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

const name = user?.profile?.name;

может быть преобразован в:

var _a;

const name =
    (_a = user == null
        ? void 0
        : user.profile) == null
            ? void 0
            : _a.name;

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

  • Optional Chaining;
  • Nullish Coalescing;
  • Arrow Functions;
  • Async/Await;
  • Class Fields;
  • Dynamic Imports;
  • Private Fields;
  • Top Level Await.

Настройка esbuild внутри Vite

Vite позволяет передавать параметры напрямую в esbuild.

Пример:

import { defineConfig } from "vite";

export default defineConfig({
    esbuild: {
        target: "es2020"
    }
});

Здесь задаётся целевая версия JavaScript.


Изменение JSX Factory

Для нестандартных библиотек можно изменить генерацию JSX.

export default defineConfig({
    esbuild: {
        jsxFactory: "h",
        jsxFragment: "Fragment"
    }
});

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


Отключение трансформации

При необходимости обработку esbuild можно отключить:

export default defineConfig({
    esbuild: false
});

После этого преобразование кода может быть передано другим инструментам.


Использование esbuild для минификации

Во время production-сборки Vite способен использовать esbuild как минификатор.

Настройка:

export default defineConfig({
    build: {
        minify: "esbuild"
    }
});

В этом режиме удаляются:

  • пробелы;
  • комментарии;
  • переносы строк;
  • неиспользуемые конструкции.

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

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

может быть преобразован в:

function sum(n,u){return n+u}

esbuild и Terser

Vite поддерживает два основных варианта минификации:

build: {
    minify: "esbuild"
}

или

build: {
    minify: "terser"
}

Сравнение:

Характеристика esbuild Terser
Скорость Очень высокая Ниже
Размер итогового бандла Хороший Иногда лучше
Потребление памяти Низкое Выше
Настраиваемость Ограниченная Очень высокая

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


Взаимодействие esbuild и Rollup

Важно понимать распределение обязанностей между инструментами.

Во время разработки:

Vite Dev Server
        │
        ▼
     esbuild

Во время production-сборки:

Исходный код
      │
      ▼
    Rollup
      │
      ▼
  Code Splitting
      │
      ▼
 Tree Shaking
      │
      ▼
 Финальные чанки

При этом esbuild продолжает использоваться для отдельных операций:

  • транспиляции;
  • минификации;
  • обработки зависимостей.

Rollup отвечает за:

  • объединение модулей;
  • генерацию чанков;
  • плагины сборки;
  • оптимизацию дерева зависимостей.

Таким образом, Vite фактически объединяет сильные стороны двух инструментов:

esbuild → максимальная скорость

Rollup → максимальная гибкость

Оптимизация больших проектов

В крупных приложениях преимущества esbuild становятся особенно заметными.

Типичный проект может содержать:

src
├── components
├── pages
├── stores
├── composables
├── hooks
├── services
└── utils

Количество модулей нередко достигает нескольких тысяч.

При каждом изменении файла Vite выполняет:

  1. поиск изменённого модуля;
  2. трансформацию через esbuild;
  3. обновление графа зависимостей;
  4. отправку HMR-сообщения браузеру.

Благодаря высокой скорости esbuild обновление интерфейса часто происходит за десятки миллисекунд.


Влияние на Hot Module Replacement

Механизм HMR напрямую выигрывает от использования esbuild.

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

Изменение файла
        │
        ▼
   esbuild
(трансформация)
        │
        ▼
 Обновление графа
 зависимостей
        │
        ▼
 HMR сообщение
        │
        ▼
 Перерисовка интерфейса

Если бы на каждом изменении запускалась полноценная сборка, скорость разработки существенно снизилась бы.

Использование esbuild позволяет свести задержки к минимуму.


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

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

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

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

Поэтому в экосистеме Vite используются дополнительные инструменты:

esbuild
    +
TypeScript
    +
Rollup
    +
PostCSS
    +
Framework Plugins

Каждый компонент отвечает за собственную область задач.


Жизненный цикл обработки файла в Vite

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

Исходный файл
      │
      ▼
 Анализ импортов
      │
      ▼
    esbuild
(трансформация)
      │
      ▼
 Плагины Vite
      │
      ▼
 Отправка браузеру

Для production-сборки процесс расширяется:

Исходный файл
      │
      ▼
    esbuild
      │
      ▼
     Rollup
      │
      ▼
 Tree Shaking
      │
      ▼
 Code Splitting
      │
      ▼
 Минификация
      │
      ▼
 Готовый бандл

Именно такая архитектура позволила Vite стать одним из самых быстрых инструментов фронтенд-разработки: esbuild обеспечивает молниеносную обработку модулей и зависимостей, а Rollup выполняет сложные задачи финальной оптимизации и формирования production-бандлов.