Импорт изображений, шрифтов и других бинарных файлов

В современных фронтенд-сборках JavaScript-приложений работа с бинарными файлами является частью стандартного пайплайна: изображения, шрифты, иконки, аудио, видео и другие ассеты становятся зависимостями модуля так же, как и обычные JS-файлы. В Esbuild эта модель реализуется через систему загрузчиков (loaders), которые определяют, как конкретный тип файла преобразуется в итоговый JavaScript-бандл.

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

import logo from './logo.png';
import fontUrl from './fonts/Inter.woff2';

Результат такого импорта зависит от выбранного loader-а и конфигурации сборки.


Механизм loaders в Esbuild

Esbuild не имеет отдельной «магии» для изображений или шрифтов. Вместо этого используется универсальная система обработки файлов:

  • file — копирует файл в выходную директорию и возвращает URL
  • dataurl — встраивает файл в виде base64 Data URL
  • copy (через плагины) — более контролируемое копирование без переименования
  • 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'
  }
});

Импорт изображений как файловых URL

Наиболее распространённый сценарий — использование изображений как внешних ресурсов.

При использовании 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/
  • добавляет хеширование имени (если включено)
  • заменяет импорт на строку URL

Пример результата:

// итоговый код
const img = "/assets/photo-8KJ2LQ.png";

Поведение контролируется через assetNames:

build({
  assetNames: 'assets/[name]-[hash]'
});

Это обеспечивает:

  • кеширование в браузере
  • уникальность файлов
  • предотвращение конфликтов имён

Встраивание изображений через Data URL

Для небольших изображений (иконки, UI-элементы) применяется dataurl:

import icon from './icon.svg';

Конфигурация:

loader: {
  '.svg': 'dataurl'
}

Результат:

const icon = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0...";

Особенности подхода:

  • отсутствуют HTTP-запросы
  • увеличивается размер JS-бандла
  • ухудшается кеширование отдельных ассетов
  • целесообразно для файлов до ~5–10 KB

Работа со шрифтами

Шрифты в вебе обрабатываются аналогично изображениям. Наиболее частый вариант — 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:

  • копирует файл в output
  • подменяет путь в CSS
  • сохраняет корректный MIME-тип на уровне сервера

Пример результата в итоговом CSS:

src: url("/assets/Inter-Regular-3F2K9L.woff2") format("woff2");

Импорт бинарных данных как ArrayBuffer

В 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 и стратегия генерации путей

file loader в Esbuild не просто копирует файл. Он формирует новую сущность с учётом нескольких правил:

  • хеширование содержимого (если включено)
  • нормализация имени
  • размещение в outdir
  • генерация относительных или абсолютных URL

Настройка:

build({
  loader: {
    '.png': 'file'
  },
  assetNames: 'static/[name]-[hash]'
});

Также важны параметры:

publicPath: '/cdn/'

Результат:

const img = "/cdn/static/photo-A1B2C3.png";

Использование бинарных ресурсов в CSS

Esbuild обрабатывает CSS как часть графа зависимостей, поэтому url() внутри стилей также проходит через loaders:

.background {
  background-image: url('./bg.png');
}

Если bg.png настроен как file:

  • файл копируется
  • путь переписывается

Если dataurl:

  • изображение инлайнится в CSS

Контроль размера бандла

Импорт бинарных файлов напрямую влияет на размер итоговой сборки. Типичная стратегия:

  • изображения UI > 10 KB → file
  • иконки ≤ 10 KB → dataurl
  • шрифты → file
  • бинарные модели → file или binary (в зависимости от окружения)

Пример гибкой настройки:

loader: {
  '.png': 'file',
  '.jpg': 'file',
  '.svg': 'dataurl',
  '.woff2': 'file'
}

Хеширование и кеширование ассетов

Esbuild поддерживает content hashing через шаблоны имён:

assetNames: 'assets/[name]-[hash]'

Поведение:

  • hash вычисляется от содержимого файла
  • изменение файла → новый URL
  • неизменённые ресурсы кешируются браузером

Пример:

logo.png → logo-A9F3K2.png
logo.png (изменён) → logo-B1C8Q7.png

Интеграция с plugins для расширенного контроля

Стандартные 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 как бинарного и текстового ресурса

SVG может обрабатываться по-разному:

  1. как файл:
loader: { '.svg': 'file' }
  1. как data URL:
loader: { '.svg': 'dataurl' }
  1. как текст (через plugin):
build.onLoad({ filter: /\.svg$/ }, async (args) => {
  const text = await fs.promises.readFile(args.path, 'utf8');
  return {
    contents: text,
    loader: 'text'
  };
});

Это позволяет:

  • инлайнить SVG в React
  • манипулировать XML-структурой
  • оптимизировать и очищать файлы перед сборкой

Особенности работы в разных окружениях

В браузерной сборке:

  • бинарные файлы превращаются в URL или Data URL
  • доступ к raw bytes невозможен без дополнительных плагинов

В Node.js:

  • возможен binary loader
  • можно работать с Buffer и Uint8Array

Типовые ошибки при работе с бинарными ресурсами

  • использование dataurl для больших изображений → резкий рост JS bundle
  • отсутствие assetNames → коллизии имён файлов
  • неправильный publicPath → битые ссылки на CDN
  • попытка импортировать бинарный файл без loader-а → ошибка сборки

Влияние tree-shaking на бинарные ресурсы

Бинарные файлы не участвуют в tree-shaking в классическом смысле:

  • они всегда считаются side-effect ресурсами
  • удаление возможно только при отсутствии импорта в графе зависимостей
  • оптимизация происходит на уровне ссылок, а не содержимого

Сочетание с code splitting

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

const loadImage = async () => {
  const img = await import('./big-image.png');
  return img.default;
};

Esbuild:

  • выделяет отдельный chunk
  • переносит ассет в связанную директорию
  • сохраняет корректные ссылки между чанками

Это особенно важно для:

  • ленивой загрузки медиа
  • модальных окон с графикой
  • страниц с тяжёлыми ресурсами