esbuild-loader — загрузчик для Webpack, использующий
компилятор esbuild вместо Babel для транспиляции JavaScript и
TypeScript-кода. Основная причина его популярности — чрезвычайно высокая
скорость сборки.
esbuild написан на языке Go и выполняет компиляцию
значительно быстрее традиционной связки babel-loader +
@babel/core. На крупных проектах разница во времени сборки
может измеряться не процентами, а десятками секунд.
Типичная схема работы:
Исходный код
↓
esbuild-loader
↓
Webpack
↓
Готовый bundle
Вместо:
Исходный код
↓
babel-loader
↓
Webpack
↓
Готовый bundle
Минимальный набор зависимостей:
npm install -D esbuild esbuild-loader
или:
yarn add -D esbuild esbuild-loader
Простейшая конфигурация Webpack:
const path = require('path');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.js$/,
loader: 'esbuild-loader',
options: {
target: 'es2017'
}
}
]
}
};
targetОпция target определяет, до какого стандарта ECMAScript
необходимо преобразовывать код.
Примеры:
options: {
target: 'es2015'
}
options: {
target: 'es2020'
}
options: {
target: 'chrome80'
}
Поддерживаются:
Примеры:
target: 'node18'
target: 'firefox100'
esbuild-loader умеет компилировать TypeScript без
использования ts-loader.
Конфигурация:
module.exports = {
module: {
rules: [
{
test: /\.ts$/,
loader: 'esbuild-loader',
options: {
loader: 'ts',
target: 'es2018'
}
}
]
},
resolve: {
extensions: ['.ts', '.js']
}
};
Для React-проектов:
{
test: /\.tsx?$/,
loader: 'esbuild-loader',
options: {
loader: 'tsx',
target: 'es2017'
}
}
esbuild-loader поддерживает JSX без Babel.
Пример:
{
test: /\.jsx$/,
loader: 'esbuild-loader',
options: {
loader: 'jsx',
target: 'es2017'
}
}
Для нового JSX Runtime:
options: {
loader: 'tsx',
target: 'es2017',
jsx: 'automatic'
}
Для старого режима:
options: {
jsx: 'transform'
}
Типичный pipeline Babel:
babel-loader
↓
@babel/core
↓
plugins
↓
presets
↓
AST-трансформации
Pipeline esbuild-loader:
esbuild-loader
↓
esbuild
Архитектура esbuild значительно проще и агрессивно
оптимизирована по производительности.
На практике:
| Инструмент | Средняя скорость |
|---|---|
| babel-loader | Медленно |
| ts-loader | Средне |
| esbuild-loader | Очень быстро |
Особенно заметна разница:
Типичная Babel-конфигурация:
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env'
]
}
}
}
Эквивалент через esbuild-loader:
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'esbuild-loader',
options: {
target: 'es2017'
}
}
Конфигурация становится значительно компактнее.
Полный отказ от Babel требуется не всегда. Часто используется комбинированный подход:
esbuild-loader
↓
babel-loader
Например:
{
test: /\.js$/,
use: [
{
loader: 'babel-loader',
options: {
plugins: [
'@babel/plugin-proposal-decorators'
]
}
},
{
loader: 'esbuild-loader',
options: {
target: 'es2017'
}
}
]
}
В таком случае:
esbuild-loader выполняет быструю базовую
компиляцию;Несмотря на скорость, esbuild-loader не является полной
заменой Babel во всех сценариях.
У Babel огромная экосистема:
esbuild поддерживает только ограниченный набор
возможностей.
Некоторые предложения ECMAScript поддерживаются в Babel раньше.
Например:
В таких случаях Babel остаётся необходимым.
Babel умеет автоматически подключать polyfills:
useBuiltIns: 'usage'
esbuild этого не делает.
Поэтому для старых браузеров обычно дополнительно используются:
esbuild-loader компилирует TypeScript, но не выполняет
проверку типов.
То есть:
const value: string = 100;
Будет успешно скомпилирован.
Проверка типов выполняется отдельно:
tsc --noEmit
Либо через:
Пример настройки:
npm install -D fork-ts-checker-webpack-plugin
Конфигурация:
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
module.exports = {
module: {
rules: [
{
test: /\.tsx?$/,
loader: 'esbuild-loader',
options: {
loader: 'tsx',
target: 'es2018'
}
}
]
},
plugins: [
new ForkTsCheckerWebpackPlugin()
]
};
EsbuildPluginПомимо loader существует plugin-версия:
const { EsbuildPlugin } = require('esbuild-loader');
Она используется для:
Стандартный production-webpack:
optimization: {
minimize: true
}
Обычно используется:
TerserPlugin
Но esbuild умеет минифицировать код гораздо быстрее.
const { EsbuildPlugin } = require('esbuild-loader');
module.exports = {
optimization: {
minimizer: [
new EsbuildPlugin({
target: 'es2017'
})
]
}
};
EsbuildPlugin поддерживает и CSS:
new EsbuildPlugin({
css: true
})
Опция define позволяет заменять константы на этапе
сборки.
Пример:
new EsbuildPlugin({
define: {
__DEV__: 'true'
}
})
Код:
if (__DEV__) {
console.log('development');
}
После сборки:
if (true) {
console.log('development');
}
esbuild-loader поддерживает source maps.
Конфигурация:
module.exports = {
devtool: 'source-map',
module: {
rules: [
{
test: /\.js$/,
loader: 'esbuild-loader'
}
]
}
};
Часто используется:
exclude: /node_modules/
Полный пример:
{
test: /\.[jt]sx?$/,
exclude: /node_modules/,
loader: 'esbuild-loader',
options: {
target: 'es2018'
}
}
Распространённая конфигурация:
{
test: /\.[jt]sx?$/,
loader: 'esbuild-loader',
options: {
loader: 'tsx',
target: 'es2018'
}
}
Поддерживаются:
.js.jsx.ts.tsxProduction-конфигурация:
const { EsbuildPlugin } = require('esbuild-loader');
module.exports = {
mode: 'production',
module: {
rules: [
{
test: /\.[jt]sx?$/,
loader: 'esbuild-loader',
options: {
target: 'es2018'
}
}
]
},
optimization: {
minimizer: [
new EsbuildPlugin({
target: 'es2018'
})
]
}
};
| Возможность | babel-loader | esbuild-loader |
|---|---|---|
| Скорость | Низкая | Очень высокая |
| Plugins ecosystem | Огромная | Ограниченная |
| TypeScript | Да | Да |
| Type checking | Нет | Нет |
| JSX | Да | Да |
| Experimental proposals | Отлично | Ограниченно |
| Polyfills | Отлично | Ограниченно |
| Минификация | Через Terser | Встроенная |
| Конфигурация | Более сложная | Простая |
Наиболее эффективен:
Babel часто необходим при:
Дополнительное ускорение достигается через filesystem cache:
module.exports = {
cache: {
type: 'filesystem'
},
module: {
rules: [
{
test: /\.js$/,
loader: 'esbuild-loader'
}
]
}
};
Обычно esbuild-loader настолько быстрый, что
thread-loader уже не нужен.
Но иногда используется:
{
test: /\.js$/,
use: [
'thread-loader',
'esbuild-loader'
]
}
На практике выигрыш часто минимален.
Пример development-конфигурации:
{
test: /\.[jt]sx?$/,
loader: 'esbuild-loader',
options: {
loader: 'tsx',
target: 'es2018'
}
}
Дополнительно:
new EsbuildPlugin({
define: {
'process.env.NODE_ENV': '"production"'
}
})
Это позволяет:
esbuild-loader совместим с механизмами tree shaking
Webpack.
Пример:
// utils.js
export function used() {}
export function unused() {}
import { used } from './utils';
Неиспользуемый код может быть удалён при production-сборке.
Если поддерживаются только современные браузеры:
target: 'es2022'
или:
target: 'chrome110'
Тогда количество трансформаций уменьшается, а производительность возрастает ещё сильнее.
esbuild-loader корректно работает с:
Пример:
const module = await import('./feature.js');
Webpack продолжит выполнять split chunks и lazy loading.
esbuild-loader не заменяет стандартные механизмы
Webpack:
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
}
Он отвечает исключительно за компиляцию файлов.
const path = require('path');
const { EsbuildPlugin } = require('esbuild-loader');
module.exports = {
mode: 'production',
entry: './src/index.tsx',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
clean: true
},
resolve: {
extensions: ['.tsx', '.ts', '.js']
},
module: {
rules: [
{
test: /\.[jt]sx?$/,
exclude: /node_modules/,
loader: 'esbuild-loader',
options: {
loader: 'tsx',
target: 'es2018'
}
}
]
},
optimization: {
minimizer: [
new EsbuildPlugin({
target: 'es2018'
})
]
}
};