Опция chunkNames и assetNames

При сборке проектов Esbuild генерирует различные типы файлов:

  • JavaScript-чанки (chunks);
  • CSS-файлы;
  • изображения;
  • шрифты;
  • SVG-файлы;
  • прочие статические ресурсы.

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

Для этих задач используются параметры:

  • chunkNames — настройка имен файлов для автоматически создаваемых чанков;
  • assetNames — настройка имен статических ресурсов.

Обе опции работают через шаблоны именования и позволяют организовать предсказуемую структуру сборки.


Что такое чанки в Esbuild

Чанк представляет собой отдельный модульный файл, который создается при разделении кода (Code Splitting).

Например:

// main.js
import('./admin.js');

При включенном разделении кода:

await esbuild.build({
    entryPoints: ['main.js'],
    bundle: true,
    splitting: true,
    format: 'esm',
    outdir: 'dist'
});

Esbuild может сформировать:

dist/
├── main.js
├── admin-ABCD1234.js
└── chunk-EFGH5678.js

Файл chunk-EFGH5678.js является автоматически созданным чанком, содержащим общий код между несколькими модулями.

Параметр chunkNames позволяет управлять именами подобных файлов.


Опция chunkNames

Базовый синтаксис

await esbuild.build({
    entryPoints: ['src/main.js'],
    bundle: true,
    splitting: true,
    format: 'esm',
    outdir: 'dist',
    chunkNames: 'chunks/[name]-[hash]'
});

Результат:

dist/
├── main.js
└── chunks/chunk-X8D4KJ2L.js

Шаблон:

chunks/[name]-[hash]

определяет:

  • папку хранения;
  • имя файла;
  • наличие хеша.

Поддерживаемые плейсхолдеры в chunkNames

[name]

Имя чанка.

Пример:

chunkNames: 'chunks/[name]'

Результат:

chunks/chunk.js

Обычно для автоматически создаваемых чанков Esbuild использует имя chunk.


[hash]

Уникальный хеш содержимого файла.

Пример:

chunkNames: 'chunks/[name]-[hash]'

Результат:

chunks/chunk-QWERT123.js

Хеш меняется только тогда, когда изменяется содержимое файла.

Это важный механизм для долгосрочного кэширования.


Расширение файла

Расширение добавляется автоматически.

Например:

chunkNames: 'chunks/[name]-[hash]'

создает:

chunks/chunk-ABC123.js

Не требуется писать:

chunkNames: 'chunks/[name]-[hash].js'

Это приведет к некорректному результату:

chunk-ABC123.js.js

Организация каталогов для чанков

Часто все динамические части приложения выносят в отдельный каталог.

Пример:

chunkNames: 'js/chunks/[name]-[hash]'

Результат:

dist/
├── main.js
└── js/
    └── chunks/
        ├── chunk-A1B2.js
        ├── chunk-C3D4.js
        └── chunk-E5F6.js

Такая структура делает выходную директорию более понятной.


Использование хеша для кэширования

Современные браузеры активно используют кэш.

Если файл называется:

chunk.js

то после обновления приложения браузер может продолжать использовать старую версию.

Использование:

chunkNames: 'chunks/[name]-[hash]'

дает:

chunk-ABCD1234.js

После изменения кода:

chunk-EFGH5678.js

URL становится новым, поэтому браузер загружает свежий файл.

Такой подход считается стандартом для production-сборок.


Что такое assets в Esbuild

Assets — это любые статические ресурсы, которые попадают в сборку.

Наиболее распространенные типы:

png
jpg
jpeg
gif
svg
webp
woff
woff2
ttf
eot
mp4
webm

Например:

import logo from './logo.png';

или

background-image: url('./bg.jpg');

После обработки Esbuild копирует такие файлы в выходной каталог.

Именно для них предназначена настройка assetNames.


Опция assetNames

Базовый пример

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

Результат:

dist/
└── assets/
    └── logo-X7A3F9.png

Поддерживаемые плейсхолдеры в assetNames

[name]

Имя исходного файла.

Файл:

logo.png

Шаблон:

assetNames: 'assets/[name]'

Результат:

assets/logo.png

[hash]

Хеш содержимого.

Пример:

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

Результат:

assets/logo-A7B9C2D1.png

[ext]

Расширение файла.

Пример:

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

Результат:

assets/png/logo-A7B9C2D1.png

Для шрифтов:

assets/woff2/font-E2F3A4.woff2

Для SVG:

assets/svg/icon-D8K2P1.svg

Группировка ресурсов по расширениям

Одна из наиболее популярных схем.

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

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

Структура:

dist/
└── assets/
    ├── png/
    │   ├── logo-A1B2.png
    │   └── banner-C3D4.png
    │
    ├── svg/
    │   ├── icon-E5F6.svg
    │   └── menu-G7H8.svg
    │
    └── woff2/
        └── font-I9J0.woff2

Преимущества:

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

Организация ресурсов по типам

Иногда расширения недостаточно.

Можно использовать разные сборочные конфигурации.

Например:

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

Результат:

images/logo-A1B2.png
images/banner-C3D4.jpg
images/icon-E5F6.svg

Для шрифтов:

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

Результат:

fonts/inter-G7H8.woff2
fonts/roboto-K1L2.ttf

Такой подход часто применяется в корпоративных проектах.


Совместное использование entryNames, chunkNames и assetNames

Полная схема именования обычно выглядит так:

await esbuild.build({
    entryPoints: ['src/main.js'],
    bundle: true,
    splitting: true,
    format: 'esm',
    outdir: 'dist',

    entryNames: 'js/[name]-[hash]',
    chunkNames: 'js/chunks/[name]-[hash]',
    assetNames: 'assets/[ext]/[name]-[hash]'
});

Результат:

dist/
├── js/
│   ├── main-D4K2A1.js
│   └── chunks/
│       ├── chunk-B8F6C2.js
│       └── chunk-P3Q7R9.js
│
└── assets/
    ├── png/
    │   └── logo-A1B2.png
    │
    ├── svg/
    │   └── icon-C3D4.svg
    │
    └── woff2/
        └── font-E5F6.woff2

Подобная структура хорошо масштабируется даже для очень больших приложений.


Особенности хеширования

Значение [hash] вычисляется на основе содержимого файла.

Если файл не изменился:

logo-A1B2C3.png

останется тем же.

Если содержимое изменилось:

logo-X9Y8Z7.png

будет создано новое имя.

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

  • эффективное кэширование;
  • снижение нагрузки на сервер;
  • корректное обновление ресурсов у пользователей.

Использование в production-сборках

Для production наиболее распространена следующая конфигурация:

await esbuild.build({
    entryPoints: ['src/main.js'],
    bundle: true,
    minify: true,
    splitting: true,
    format: 'esm',
    outdir: 'dist',

    entryNames: '[name]-[hash]',
    chunkNames: 'chunks/[name]-[hash]',
    assetNames: 'assets/[ext]/[name]-[hash]'
});

Она обеспечивает:

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

Типичные ошибки при использовании chunkNames

Отсутствие splitting

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

chunkNames: 'chunks/[name]-[hash]'

не будет иметь эффекта без:

splitting: true

Поскольку чанки просто не создаются.


Неверный формат модуля

Code Splitting работает только для:

format: 'esm'

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

format: 'cjs'

не позволит использовать разделение кода.


Ручное указание расширения

Ошибка:

chunkNames: 'chunks/[name]-[hash].js'

Результат:

chunk-A1B2.js.js

Расширение должно добавляться самим Esbuild.


Типичные ошибки при использовании assetNames

Отсутствие file-loader

Если ресурс не обрабатывается как файл:

loader: {
    '.png': 'file'
}

то настройка assetNames может не применяться.


Отсутствие хеша

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

assetNames: 'assets/[name]'

создает:

assets/logo.png

После обновления содержимого имя останется прежним.

Это ухудшает работу браузерного кэша.

Для production предпочтительно:

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

или

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

Рекомендуемые шаблоны

Минимальный вариант

chunkNames: '[name]-[hash]'
assetNames: '[name]-[hash]'

Стандартная структура

chunkNames: 'chunks/[name]-[hash]'
assetNames: 'assets/[ext]/[name]-[hash]'

Для крупных проектов

entryNames: 'js/[name]-[hash]'
chunkNames: 'js/chunks/[name]-[hash]'
assetNames: 'assets/[ext]/[name]-[hash]'

Структура получается логичной, масштабируемой и удобной для сопровождения, а использование хешей обеспечивает корректную работу механизмов кэширования в production-окружении.