Система загрузчиков в esbuild строится вокруг принципа преобразования импортируемого ресурса в строковое представление, понятное JavaScript-коду или сборщику. Каждый импорт анализируется, и для него выбирается loader, определяющий стратегию обработки:
Для ассетов (изображений, шрифтов, бинарных файлов) ключевым становится поведение, при котором исходный файл не встраивается в бандл напрямую, а переносится в выходную директорию с возможной заменой импорта на ссылку.
Именно это поведение в практической разработке часто называют
copy loader, хотя формально в esbuild он реализуется
через loader: "file".
Под термином copy loader обычно понимается стратегия:
Это поведение отличается от:
text — вставляет содержимое файла как строку;base64 — кодирует файл в base64 и инлайнит;binary — загружает как Uint8Array в bundle.Copy-стратегия ближе всего к file loader, который:
Конфигурация 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.pnglogo содержит строку путиПри использовании file loader esbuild:
Типичный результат:
// исходник
import img from "./image.png";
// результат
var img = "image-3k9d8f.png";
Copy-стратегия в esbuild не является полноценным asset pipeline как в Webpack, но решает те же задачи:
| Поведение | esbuild loader | Результат |
|---|---|---|
| Копирование файла | file |
внешний файл + URL |
| Встраивание текста | text |
строка в bundle |
| Встраивание бинарных данных | binary |
Uint8Array |
| Inline base64 | base64 |
data URL |
Ключевая характеристика copy-подхода:
сохранение оригинального файла без изменения содержимого и без включения его в JavaScript-бандл
import "./styles.css";
import icon from "./icon.svg";
Конфигурация:
loader: {
".css": "css",
".svg": "file"
}
Поведение:
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
В режиме разработки copy-loader сохраняет те же принципы:
Это важно для:
Если один файл импортируется несколько раз:
import a from "./img.png";
import b from "./img.png";
esbuild:
Без хеширования возможны конфликты:
assetNames: "assets/[name]"
Два файла logo.png из разных папок могут перезаписать
друг друга.
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"
};
});
}
};
Такой подход позволяет:
Copy-поведение используется в случаях:
Copy-стратегия напрямую влияет на:
При правильной конфигурации:
Copy-loader в esbuild вписывается в более широкую модель:
Эта граница принципиальна для: