Работа с ассетами: эмиссия, референсы, финализация

Ассеты в Rollup представляют собой любые ресурсы, которые не являются JavaScript-кодом в чистом виде, но участвуют в сборке: изображения, шрифты, CSS-файлы, JSON, медиафайлы, а также сгенерированные на лету строки или бинарные данные. Работа с ними требует понимания того, как Rollup представляет такие ресурсы внутри графа модулей и каким образом они превращаются в итоговый бандл.

Внутри Rollup ассеты не являются полноценными модулями JavaScript. Вместо этого они могут быть:

  • встроены в код как строки (inline)
  • вынесены в отдельные файлы (emitted files)
  • преобразованы в URL-референсы
  • заменены на импортируемые идентификаторы

Ключевая особенность заключается в том, что Rollup не навязывает единственный способ работы с ресурсами. Управление полностью делегируется плагинам через Plugin API.

Ассет в Rollup существует в двух состояниях:

  • до эмиссии — абстрактное описание ресурса
  • после эмиссии — физический файл в output bundle

Эмиссия ассетов через emitFile

Основной механизм создания ассетов в Rollup — функция emitFile, доступная в контексте плагинов через this.emitFile.

Она позволяет зарегистрировать новый файл, который будет включён в итоговый бандл независимо от графа JavaScript-модулей.

Основная форма использования

this.emitFile({
  type: 'asset',
  name: 'style.css',
  source: 'body { margin: 0; }'
});

Ключевые параметры

  • type: всегда 'asset' для ресурсов
  • name: человекочитаемое имя файла (опционально)
  • fileName: фиксированное имя выходного файла (если требуется контроль)
  • source: содержимое ассета (строка или Buffer)

Результат эмиссии

emitFile возвращает идентификатор ассета:

const id = this.emitFile({
  type: 'asset',
  source: 'export default "hello";'
});

Этот идентификатор используется для последующих операций, включая получение пути к файлу.

Типы файлов и различие asset/chunk

Rollup различает два вида emitted-объектов:

  • asset — статический ресурс
  • chunk — JavaScript-блок (часть графа модулей)

Эта разница критична:

Тип Содержимое Участвует в графе
asset CSS, изображения нет
chunk JS-код да

Ассеты не проходят через механизмы tree-shaking и не анализируются как код.

Референсы ассетов: fileName и referenceId

После эмиссии ассет не сразу имеет путь в файловой системе. Rollup сначала регистрирует его внутри внутреннего реестра.

Существует два ключевых способа обращения:

referenceId

Возвращается при emitFile и используется для доступа к ассету:

const ref = this.emitFile({
  type: 'asset',
  source: 'content'
});

this.getFileName

Позволяет получить финальное имя файла:

const fileName = this.getFileName(ref);

На момент вызова генерации bundle Rollup ещё может не знать точное имя, если используются хэши или динамическое именование.

Использование референсов в коде

Часто ассеты не просто создаются, но и должны быть связаны с JavaScript-кодом. Типичный сценарий — генерация URL к файлу.

Пример логики плагина:

generateBundle(options, bundle) {
  const ref = this.emitFile({
    type: 'asset',
    name: 'data.txt',
    source: 'hello world'
  });

  const fileName = this.getFileName(ref);

  bundle['entry.js'].code += `\nexport const url = "${fileName}";`;
}

Таким образом создаётся связь между JS и ассетом без прямого импорта.

Инлайн и внешние ассеты

Rollup не ограничивает способ представления ресурса. Плагин может выбрать стратегию:

Inline-ассеты

Содержимое встраивается прямо в код:

export const img = "data:image/png;base64,...";

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

  • отсутствие дополнительных файлов
  • быстрый доступ

Недостатки:

  • увеличение размера JS-бандла
  • отсутствие кэширования ассета отдельно

Внешние ассеты

Файл выносится отдельно:

export const img = "/assets/image.hash.png";

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

  • кэширование браузером
  • оптимизация загрузки

Недостатки:

  • дополнительные HTTP-запросы

Финализация ассетов в generateBundle

Финальный этап обработки ассетов происходит в хукe generateBundle.

На этом этапе:

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

Доступ к ассетам

Ассеты находятся в объекте bundle:

generateBundle(options, bundle) {
  for (const [fileName, file] of Object.entries(bundle)) {
    if (file.type === 'asset') {
      // обработка ассета
    }
  }
}

Структура ассета:

{
  type: 'asset',
  fileName: 'style.hash.css',
  source: 'body { margin: 0 }'
}

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

Rollup предоставляет гибкую систему именования через output.assetFileNames.

Пример:

output: {
  assetFileNames: 'assets/[name]-[hash][extname]'
}

Поддерживаются плейсхолдеры:

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

При этом итоговое имя формируется только на этапе генерации бандла, что влияет на момент разрешения референсов.

Связывание ассетов с импортами

Некоторые плагины реализуют синтаксис импорта ассетов:

import url from './image.png';

Внутри плагина это преобразуется в:

const ref = this.emitFile({
  type: 'asset',
  source: <binary>
});

И затем заменяется на:

const url = getFileName(ref);

Таким образом импорт ассета фактически является синтаксическим сахаром над emitFile.

Механизм хеширования и финализация

Rollup может включать содержимое ассетов в хеш имени файла. Это создаёт важный эффект:

  • изменение содержимого → новое имя файла
  • стабильное кэширование
  • контроль версионирования

Хеш вычисляется на основе source ассета и параметров сборки.

Финальное имя становится известно только после полной фазы генерации.

Особенности жизненного цикла ассетов

Жизненный цикл ассета проходит несколько стадий:

  1. Регистрация через emitFile
  2. Получение referenceId
  3. Разрешение имени файла
  4. Включение в bundle
  5. Запись на диск (writeBundle)

Важно, что до шага 3 ассет не имеет стабильного пути, что требует аккуратной работы с референсами.

Взаимодействие с другими плагинами

Ассеты часто становятся точкой взаимодействия между плагинами:

  • один плагин создаёт ассет
  • другой модифицирует bundle
  • третий добавляет ссылки в код

Rollup гарантирует единый реестр emitted-файлов, что позволяет избегать конфликтов идентификаторов.

Типичные паттерны использования

Генерация CSS из JS

this.emitFile({
  type: 'asset',
  name: 'style.css',
  source: cssString
});

Встраивание JSON как файла

this.emitFile({
  type: 'asset',
  name: 'data.json',
  source: JSON.stringify(data)
});

Генерация служебных файлов

this.emitFile({
  type: 'asset',
  name: 'version.txt',
  source: version
});

Роль ассетов в архитектуре сборки

Ассеты в Rollup выполняют функцию связующего слоя между JavaScript-графом и внешним миром. Они позволяют:

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

Их поведение определяется не ядром Rollup, а системой плагинов, что делает механизм гибким и расширяемым без изменения базовой архитектуры сборщика.