В современных фронтенд-сборках JavaScript-приложений работа с бинарными файлами является частью стандартного пайплайна: изображения, шрифты, иконки, аудио, видео и другие ассеты становятся зависимостями модуля так же, как и обычные JS-файлы. В Esbuild эта модель реализуется через систему загрузчиков (loaders), которые определяют, как конкретный тип файла преобразуется в итоговый JavaScript-бандл.
Ключевая идея заключается в том, что любой файл может быть импортирован напрямую:
import logo from './logo.png';
import fontUrl from './fonts/Inter.woff2';
Результат такого импорта зависит от выбранного loader-а и конфигурации сборки.
Esbuild не имеет отдельной «магии» для изображений или шрифтов. Вместо этого используется универсальная система обработки файлов:
file — копирует файл в выходную директорию и возвращает
URLdataurl — встраивает файл в виде base64 Data URLcopy (через плагины) — более контролируемое копирование
без переименованияbinary (через плагины) — возвращает
ArrayBuffer/Uint8Array (в Node-окружениях)Настройка производится через loader:
import { build } from 'esbuild';
build({
entryPoints: ['src/index.js'],
bundle: true,
outdir: 'dist',
loader: {
'.png': 'file',
'.jpg': 'file',
'.svg': 'file',
'.woff2': 'file',
'.ttf': 'file'
}
});
Наиболее распространённый сценарий — использование изображений как внешних ресурсов.
При использовании file loader-а:
import img from './assets/photo.png';
const image = document.createElement('img');
image.src = img;
document.body.appendChild(image);
На этапе сборки Esbuild:
photo.png в dist/assets/Пример результата:
// итоговый код
const img = "/assets/photo-8KJ2LQ.png";
Поведение контролируется через assetNames:
build({
assetNames: 'assets/[name]-[hash]'
});
Это обеспечивает:
Для небольших изображений (иконки, UI-элементы) применяется
dataurl:
import icon from './icon.svg';
Конфигурация:
loader: {
'.svg': 'dataurl'
}
Результат:
const icon = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0...";
Особенности подхода:
Шрифты в вебе обрабатываются аналогично изображениям. Наиболее частый
вариант — woff2 как файл:
import fontUrl from './fonts/Inter-Regular.woff2';
CSS-интеграция:
@font-face {
font-family: 'Inter';
src: url('./fonts/Inter-Regular.woff2') format('woff2');
}
При использовании file loader Esbuild:
Пример результата в итоговом CSS:
src: url("/assets/Inter-Regular-3F2K9L.woff2") format("woff2");
В Node.js-окружениях и низкоуровневых сценариях требуется доступ к
«сырым» данным файла. Для этого используется
loader: 'binary'.
Через плагин:
import fs from 'fs';
const binaryLoaderPlugin = {
name: 'binary-loader',
setup(build) {
build.onLoad({ filter: /\.(bin|dat)$/ }, async (args) => {
const contents = await fs.promises.readFile(args.path);
return {
contents,
loader: 'binary'
};
});
}
};
Использование:
import data from './model.bin';
console.log(data.byteLength);
Это возвращает Uint8Array.
file loader в Esbuild не просто копирует файл. Он
формирует новую сущность с учётом нескольких правил:
outdirНастройка:
build({
loader: {
'.png': 'file'
},
assetNames: 'static/[name]-[hash]'
});
Также важны параметры:
publicPath: '/cdn/'
Результат:
const img = "/cdn/static/photo-A1B2C3.png";
Esbuild обрабатывает CSS как часть графа зависимостей, поэтому
url() внутри стилей также проходит через loaders:
.background {
background-image: url('./bg.png');
}
Если bg.png настроен как file:
Если dataurl:
Импорт бинарных файлов напрямую влияет на размер итоговой сборки. Типичная стратегия:
filedataurlfilefile или binary (в
зависимости от окружения)Пример гибкой настройки:
loader: {
'.png': 'file',
'.jpg': 'file',
'.svg': 'dataurl',
'.woff2': 'file'
}
Esbuild поддерживает content hashing через шаблоны имён:
assetNames: 'assets/[name]-[hash]'
Поведение:
Пример:
logo.png → logo-A9F3K2.png
logo.png (изменён) → logo-B1C8Q7.png
Стандартные loaders покрывают базовые сценарии, однако расширенная работа с бинарными ресурсами часто требует плагинов.
Пример кастомного loader-а с оптимизацией изображений:
import imagemin from 'imagemin';
const imagePlugin = {
name: 'image-optimizer',
setup(build) {
build.onLoad({ filter: /\.(png|jpg)$/ }, async (args) => {
const buffer = await fs.promises.readFile(args.path);
const optimized = await imagemin.buffer(buffer);
return {
contents: optimized,
loader: 'file'
};
});
}
};
SVG может обрабатываться по-разному:
loader: { '.svg': 'file' }
loader: { '.svg': 'dataurl' }
build.onLoad({ filter: /\.svg$/ }, async (args) => {
const text = await fs.promises.readFile(args.path, 'utf8');
return {
contents: text,
loader: 'text'
};
});
Это позволяет:
В браузерной сборке:
В Node.js:
binary loaderBuffer и Uint8Arraydataurl для больших изображений → резкий
рост JS bundleassetNames → коллизии имён файловpublicPath → битые ссылки на CDNБинарные файлы не участвуют в tree-shaking в классическом смысле:
При динамическом импорте:
const loadImage = async () => {
const img = await import('./big-image.png');
return img.default;
};
Esbuild:
Это особенно важно для: