В архитектуре Rollup значительная часть работы с результатами сборки
строится вокруг концепции модулей и артефактов, которые появляются в
процессе бандлинга. Помимо JavaScript-кода, сборка часто включает
дополнительные ресурсы: изображения, шрифты, JSON-файлы, текстовые
шаблоны, CSS-фрагменты и любые другие ассеты. Управление такими
ресурсами реализуется через механизм генерации файлов на этапе
generateBundle и renderChunk, где ключевую
роль играет API this.emitFile.
Метод this.emitFile предоставляет плагинам Rollup
возможность программно создавать новые сущности в графе сборки. Эти
сущности могут представлять:
Ассеты в контексте Rollup — это файлы, не являющиеся JavaScript-модулями, но включаемые в итоговую сборку.
Основное назначение emitFile заключается в динамическом
добавлении файлов в процесс генерации бандла без необходимости
физического присутствия этих файлов в исходном проекте.
this.emitFile({
type: 'asset',
fileName: 'style.css',
source: 'body { margin: 0; }'
});
или
this.emitFile({
type: 'chunk',
id: './virtual-module.js'
});
Rollup различает два основных типа:
Ассет создаётся через указание 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.
Одной из ключевых возможностей является создание файлов на основе состояния сборки.
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() {
this.emitFile({
type: 'asset',
name: 'manifest.json',
source: JSON.stringify({ build: Date.now() })
});
}
Используется для модификации конкретного чанка и добавления сопутствующих ресурсов.
renderChunk() {
this.emitFile({
type: 'asset',
name: 'chunk-info.txt',
source: 'chunk processed'
});
}
Хотя технически возможно вызывать emitFile в этих хуках,
это приводит к побочным эффектам в графе модулей и требует осторожности.
Ассеты, созданные здесь, могут зависеть от порядка загрузки модулей.
Rollup предоставляет несколько уровней контроля над именованием ассетов:
Жёстко задаёт имя файла:
this.emitFile({
type: 'asset',
fileName: 'static/config.json',
source: '{}'
});
Используется как подсказка для assetFileNames:
this.emitFile({
type: 'asset',
name: 'config.json',
source: '{}'
});
output: {
assetFileNames: 'assets/[name]-[hash][extname]'
}
В этом случае Rollup может игнорировать fileName и
формировать путь по шаблону.
При включённом хешировании содержимого ([hash]) Rollup
рассчитывает уникальный идентификатор на основе source.
assetFileNames: 'assets/[name]-[hash].css'
Изменение содержимого ассета приводит к изменению имени файла, что используется для кеширования на уровне браузера.
Хотя ассеты не являются модулями, они входят в итоговую структуру бандла и могут:
this.getFileNameОднако они не участвуют в tree-shaking и не обрабатываются как JavaScript-узлы графа.
Rollup старается стабилизировать генерацию ассетов:
source → одинаковый hashfileNameemitFile не должно влиять на
результат при корректной архитектуре плагинаthis.emitFile({
type: 'asset',
fileName: 'manifest.json',
source: JSON.stringify(bundle, null, 2)
});
this.emitFile({
type: 'asset',
name: 'template.html',
source: renderTemplate(data)
});
const id = this.emitFile({
type: 'asset',
name: 'runtime-data.json',
source: JSON.stringify(runtimeData)
});
return `export const url = "/${this.getFileName(id)}";`;
emitFile не гарантирует немедленного появления файла —
он регистрируется в системе Rollupthis.getFileName возможен только после
регистрации файлаАссеты тесно связаны с настройками:
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 формирует важный слой абстракции между исходным
кодом и выходными файлами. Плагины могут:
Эта модель делает Rollup гибким инструментом для построения сложных сборочных пайплайнов, где итоговый бандл представляет не только код, но и связанную экосистему ресурсов.