Loader copy

Модель обработки ресурсов в esbuild

Система загрузчиков в esbuild строится вокруг принципа преобразования импортируемого ресурса в строковое представление, понятное JavaScript-коду или сборщику. Каждый импорт анализируется, и для него выбирается loader, определяющий стратегию обработки:

  • преобразование в JavaScript-модуль;
  • инлайнинг содержимого;
  • сохранение в виде внешнего файла;
  • преобразование в base64 или текст.

Для ассетов (изображений, шрифтов, бинарных файлов) ключевым становится поведение, при котором исходный файл не встраивается в бандл напрямую, а переносится в выходную директорию с возможной заменой импорта на ссылку.

Именно это поведение в практической разработке часто называют copy loader, хотя формально в esbuild он реализуется через loader: "file".


Логика «copy loader» и его отличие от встроенных стратегий

Под термином copy loader обычно понимается стратегия:

  • файл копируется в output директорию;
  • оригинальный контент не модифицируется;
  • импорт заменяется строкой с URL или путём к файлу;
  • содержимое не инлайнится в bundle.

Это поведение отличается от:

  • text — вставляет содержимое файла как строку;
  • base64 — кодирует файл в base64 и инлайнит;
  • binary — загружает как Uint8Array в bundle.

Copy-стратегия ближе всего к file loader, который:

  • сохраняет файл как отдельный ассет;
  • генерирует уникальное имя (часто с хешем);
  • возвращает путь к файлу как строку.

Loader: “file” как основа copy-поведения

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

import esbuild from "esbuild";

esbuild.build({
  entryPoints: ["src/index.js"],
  bundle: true,
  outdir: "dist",
  loader: {
    ".png": "file",
    ".jpg": "file",
    ".woff2": "file",
    ".svg": "file"
  }
});

Поведение:

import logo from "./logo.png";

console.log(logo);

После сборки:

  • файл logo.png копируется в dist/
  • имя может быть преобразовано в logo-8a7f3c.png
  • переменная logo содержит строку пути

Механика генерации пути

При использовании file loader esbuild:

  1. вычисляет хэш содержимого (если включено хеширование);
  2. формирует новое имя файла;
  3. записывает файл в output директорию;
  4. заменяет импорт на строковый путь.

Типичный результат:

// исходник
import img from "./image.png";

// результат
var img = "image-3k9d8f.png";

Отличие copy-подхода от asset pipeline

Copy-стратегия в esbuild не является полноценным asset pipeline как в Webpack, но решает те же задачи:

Поведение esbuild loader Результат
Копирование файла file внешний файл + URL
Встраивание текста text строка в bundle
Встраивание бинарных данных binary Uint8Array
Inline base64 base64 data URL

Ключевая характеристика copy-подхода:

сохранение оригинального файла без изменения содержимого и без включения его в JavaScript-бандл


Использование с CSS и импортами ассетов

import "./styles.css";
import icon from "./icon.svg";

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

loader: {
  ".css": "css",
  ".svg": "file"
}

Поведение:

  • CSS обрабатывается и включается в bundle
  • SVG копируется как отдельный файл
  • переменная icon содержит путь к файлу

Управление именами выходных файлов

esbuild позволяет управлять шаблоном именования через assetNames:

esbuild.build({
  entryPoints: ["src/app.js"],
  bundle: true,
  outdir: "dist",
  loader: {
    ".png": "file"
  },
  assetNames: "assets/[name]-[hash]"
});

Параметры:

  • [name] — исходное имя файла
  • [hash] — хэш содержимого
  • [ext] — расширение

Результат:

dist/assets/logo-a1b2c3.png

Публичный путь и корректная генерация ссылок

Важным аспектом copy-поведения является корректная работа с базовым URL:

esbuild.build({
  outdir: "dist",
  publicPath: "/static",
  loader: {
    ".png": "file"
  }
});

Результат импорта:

import img from "./image.png";
console.log(img);

Будет:

/static/image-8d9f2c.png

Поведение при dev-сборке

В режиме разработки copy-loader сохраняет те же принципы:

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

Это важно для:

  • корректного кеширования браузером;
  • интеграции с CDN;
  • предсказуемого поведения URL.

Пограничные случаи и особенности

1. Повторный импорт одного файла

Если один файл импортируется несколько раз:

import a from "./img.png";
import b from "./img.png";

esbuild:

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

2. Конфликты имён

Без хеширования возможны конфликты:

assetNames: "assets/[name]"

Два файла logo.png из разных папок могут перезаписать друг друга.


3. Отсутствие runtime обработки

copy-подход не требует runtime-логики:

  • нет загрузчиков в браузере;
  • нет динамической обработки;
  • результат — статическая строка.

Использование через плагины

Хотя file loader покрывает базовые сценарии, copy-поведение можно расширять через плагины:

import fs from "fs";

const copyPlugin = {
  name: "copy-plugin",
  setup(build) {
    build.onResolve({ filter: /\.(data)$/ }, args => {
      return { path: args.path, namespace: "copy" };
    });

    build.onLoad({ filter: /.*/, namespace: "copy" }, args => {
      const contents = fs.readFileSync(args.path);
      const outfile = "dist/" + args.path.split("/").pop();

      fs.writeFileSync(outfile, contents);

      return {
        contents: `module.exports = "/${outfile}"`,
        loader: "js"
      };
    });
  }
};

Такой подход позволяет:

  • копировать произвольные типы файлов;
  • реализовывать кастомные правила именования;
  • интегрировать CDN-логики.

Типовые сценарии применения

Copy-поведение используется в случаях:

  • изображений в UI (png, jpg, svg);
  • шрифтов (woff, woff2, ttf);
  • медиафайлов;
  • статических JSON или данных (через кастомные правила);
  • любых ресурсов, которые должны оставаться внешними.

Влияние на кеширование и доставку

Copy-стратегия напрямую влияет на:

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

При правильной конфигурации:

  • JS остаётся минимальным;
  • ассеты кешируются независимо;
  • обновление ресурсов не инвалидирует весь bundle.

Связь с архитектурой сборки

Copy-loader в esbuild вписывается в более широкую модель:

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

Эта граница принципиальна для:

  • SPA приложений;
  • библиотек UI компонентов;
  • систем с CDN-раздачей статических файлов;
  • микрофронтенд-архитектур.