this.emitFile и работа с ассетами

В архитектуре Rollup значительная часть работы с результатами сборки строится вокруг концепции модулей и артефактов, которые появляются в процессе бандлинга. Помимо JavaScript-кода, сборка часто включает дополнительные ресурсы: изображения, шрифты, JSON-файлы, текстовые шаблоны, CSS-фрагменты и любые другие ассеты. Управление такими ресурсами реализуется через механизм генерации файлов на этапе generateBundle и renderChunk, где ключевую роль играет API this.emitFile.

Общая идея emitFile

Метод this.emitFile предоставляет плагинам Rollup возможность программно создавать новые сущности в графе сборки. Эти сущности могут представлять:

  • отдельные чанки (chunks)
  • ассеты (assets)

Ассеты в контексте Rollup — это файлы, не являющиеся JavaScript-модулями, но включаемые в итоговую сборку.

Основное назначение emitFile заключается в динамическом добавлении файлов в процесс генерации бандла без необходимости физического присутствия этих файлов в исходном проекте.


Сигнатура и типы файлов

Базовая форма вызова

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

или

this.emitFile({
  type: 'chunk',
  id: './virtual-module.js'
});

Типы объектов

Rollup различает два основных типа:

  • asset — произвольный файл, создаваемый плагином
  • chunk — отдельный модуль, который становится частью графа зависимостей

Ассеты: создание и управление

Создание ассета

Ассет создаётся через указание type: 'asset'. Он может содержать текстовый или бинарный источник.

const fileId = this.emitFile({
  type: 'asset',
  name: 'runtime-config.json',
  source: JSON.stringify({ mode: 'production' })
});

Здесь возвращается идентификатор файла, который используется для дальнейшего получения пути.


Доступ к имени файла

После регистрации ассета Rollup сам определяет финальное имя файла, особенно если включено хеширование.

Для получения итогового имени используется:

this.getFileName(fileId);

Пример:

const assetId = this.emitFile({
  type: 'asset',
  name: 'logo.svg',
  source: '<svg>...</svg>'
});

const fileName = this.getFileName(assetId);

fileName может измениться в зависимости от конфигурации output.assetFileNames.


Динамическая генерация ассетов

Одной из ключевых возможностей является создание файлов на основе состояния сборки.

Пример генерации CSS-фрагмента

export default function cssPlugin() {
  return {
    name: 'css-generator',
    generateBundle() {
      this.emitFile({
        type: 'asset',
        fileName: 'bundle.css',
        source: '.app { display: flex; }'
      });
    }
  };
}

Здесь ассет создаётся на этапе генерации бандла и попадает в итоговую директорию dist.


Виртуальные модули и ассеты

Rollup позволяет связать виртуальные модули с ассетами через emitFile. Это особенно важно при генерации ресурсов на основе JS-кода.

export default function plugin() {
  return {
    name: 'virtual-css',
    resolveId(id) {
      if (id === 'virtual:style') return id;
    },
    load(id) {
      if (id === 'virtual:style') {
        const fileId = this.emitFile({
          type: 'asset',
          name: 'style.css',
          source: 'body { background: black; }'
        });

        const fileName = this.getFileName(fileId);

        return `export default "/${fileName}";`;
      }
    }
  };
}

Здесь ассет создаётся внутри load, а его путь экспортируется как строка.


Поведение в разных хуках

generateBundle

Наиболее распространённый хук для создания ассетов. На этом этапе уже известна структура бандла.

generateBundle() {
  this.emitFile({
    type: 'asset',
    name: 'manifest.json',
    source: JSON.stringify({ build: Date.now() })
  });
}

renderChunk

Используется для модификации конкретного чанка и добавления сопутствующих ресурсов.

renderChunk() {
  this.emitFile({
    type: 'asset',
    name: 'chunk-info.txt',
    source: 'chunk processed'
  });
}

load и transform

Хотя технически возможно вызывать emitFile в этих хуках, это приводит к побочным эффектам в графе модулей и требует осторожности. Ассеты, созданные здесь, могут зависеть от порядка загрузки модулей.


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

Rollup предоставляет несколько уровней контроля над именованием ассетов:

fileName

Жёстко задаёт имя файла:

this.emitFile({
  type: 'asset',
  fileName: 'static/config.json',
  source: '{}'
});

name

Используется как подсказка для assetFileNames:

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

assetFileNames в конфигурации

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

В этом случае Rollup может игнорировать fileName и формировать путь по шаблону.


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

При включённом хешировании содержимого ([hash]) Rollup рассчитывает уникальный идентификатор на основе source.

assetFileNames: 'assets/[name]-[hash].css'

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


Ассеты и граф сборки

Хотя ассеты не являются модулями, они входят в итоговую структуру бандла и могут:

  • зависеть от чанков логически (через plugin-логику)
  • быть доступны через this.getFileName
  • экспортироваться из модулей как строки путей

Однако они не участвуют в tree-shaking и не обрабатываются как JavaScript-узлы графа.


Кеширование и стабильность

Rollup старается стабилизировать генерацию ассетов:

  • одинаковый source → одинаковый hash
  • одинаковая конфигурация → предсказуемый fileName
  • изменение порядка emitFile не должно влиять на результат при корректной архитектуре плагина

Частые сценарии использования

Генерация манифеста сборки

this.emitFile({
  type: 'asset',
  fileName: 'manifest.json',
  source: JSON.stringify(bundle, null, 2)
});

Инлайн-вынос больших строк

this.emitFile({
  type: 'asset',
  name: 'template.html',
  source: renderTemplate(data)
});

Экспорт ресурсов для runtime

const id = this.emitFile({
  type: 'asset',
  name: 'runtime-data.json',
  source: JSON.stringify(runtimeData)
});

return `export const url = "/${this.getFileName(id)}";`;

Ограничения и особенности

  • emitFile не гарантирует немедленного появления файла — он регистрируется в системе Rollup
  • итоговое имя файла может отличаться от заданного
  • ассеты не обрабатываются как модули
  • доступ к this.getFileName возможен только после регистрации файла
  • вызовы из разных хуков могут давать разный контекст генерации

Взаимодействие с output options

Ассеты тесно связаны с настройками:

  • output.assetFileNames — управление путями
  • output.dir — директория вывода
  • output.sourcemap — не влияет на ассеты, но влияет на общий процесс генерации

Расширенные паттерны использования

Генерация нескольких ассетов из одного плагина

generateBundle() {
  ['a', 'b', 'c'].forEach(name => {
    this.emitFile({
      type: 'asset',
      name: `${name}.txt`,
      source: `file ${name}`
    });
  });
}

Условная генерация

generateBundle(options, bundle) {
  if (Object.keys(bundle).length > 10) {
    this.emitFile({
      type: 'asset',
      name: 'warning.txt',
      source: 'Large bundle detected'
    });
  }
}

Ассеты как часть плагинной архитектуры

emitFile формирует важный слой абстракции между исходным кодом и выходными файлами. Плагины могут:

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

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