Поле output.entryFileNames, chunkFileNames, assetFileNames

Поля output.entryFileNames, output.chunkFileNames и output.assetFileNames управляют именованием файлов, которые генерирует Rollup во время сборки. Эти параметры особенно важны в production-сборках, где требуется:

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

По умолчанию Rollup создаёт файлы с достаточно простыми именами, однако в реальных проектах почти всегда используется кастомная схема.


Поле output.entryFileNames

Назначение

entryFileNames определяет шаблон имени для entry-файлов — то есть файлов, которые являются точками входа (input).

Пример:

export default {
    input: {
        app: 'src/app.js',
        admin: 'src/admin.js'
    },

    output: {
        dir: 'dist',
        format: 'es',
        entryFileNames: 'js/[name].js'
    }
};

Результат:

dist/
└── js/
    ├── app.js
    └── admin.js

Отличие entry-файлов от chunk-файлов

Entry-файлы:

  • создаются из input;
  • являются основными точками запуска;
  • обычно подключаются напрямую в HTML.

Chunk-файлы:

  • создаются автоматически при code splitting;
  • содержат общие зависимости;
  • подгружаются динамически.

Пример:

input: {
    app: 'src/app.js'
}
// app.js
import('./dashboard.js');

В этом случае:

  • app.js — entry;
  • dashboard-XXXX.js — chunk.

Базовый шаблон

Использование [name]

entryFileNames: '[name].js'

[name] — имя entry-модуля.

Пример:

input: {
    main: 'src/main.js',
    admin: 'src/admin.js'
}

Результат:

main.js
admin.js

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

entryFileNames: 'assets/js/[name].js'

Результат:

dist/
└── assets/
    └── js/
        ├── main.js
        └── admin.js

Использование хэшей

[hash]

entryFileNames: 'js/[name]-[hash].js'

Результат:

js/main-A7FH2K.js
js/admin-L91SDA.js

Хэш зависит от содержимого файла.

Это используется для:

  • долгосрочного кеширования;
  • CDN;
  • cache busting.

Длина хэша

Можно ограничить длину:

entryFileNames: 'js/[name]-[hash:8].js'

Пример:

main-3f8a91bc.js

Использование формата

[format]

entryFileNames: '[name].[format].js'

Результат:

main.es.js
main.cjs.js

Полезно при генерации нескольких форматов:

output: [
    {
        dir: 'dist/es',
        format: 'es',
        entryFileNames: '[name].[format].js'
    },
    {
        dir: 'dist/cjs',
        format: 'cjs',
        entryFileNames: '[name].[format].js'
    }
]

Генерация структуры по типам файлов

Часто используется такая схема:

entryFileNames: 'js/[name]-[hash].js'

Вместе с:

chunkFileNames: 'js/chunks/[name]-[hash].js',
assetFileNames: 'assets/[name]-[hash][extname]'

Результат:

dist/
├── js/
│   ├── app-AB12.js
│   └── chunks/
│       ├── vendor-X91A.js
│       └── dashboard-Z81Q.js
└── assets/
    ├── logo-K2A1.svg
    └── style-P81F.css

Поле output.chunkFileNames

Назначение

chunkFileNames определяет шаблон имён для автоматически создаваемых чанков.

Пример:

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

Когда создаются chunk-файлы

Chunk появляется при:

  • dynamic import;
  • manualChunks;
  • code splitting;
  • общих зависимостях.

Dynamic import

// main.js
button.oncl ick = async () => {
    const module = await import('./dialog.js');
};

Rollup создаст:

main.js
dialog-XXXX.js

Имя dialog-XXXX.js контролируется через chunkFileNames.


Использование [name]

chunkFileNames: 'chunks/[name].js'

Результат:

chunks/dialog.js

Использование [hash]

Production-конфигурация:

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

Результат:

chunks/dialog-91AF7.js

Chunk без имени

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

Тогда появляются имена:

chunk-ABC123.js

или:

index-XYZ.js

Чтобы улучшить именование, используют:

manualChunks

Комбинация с manualChunks

output: {
    chunkFileNames: 'chunks/[name]-[hash].js'
},

manualChunks(id) {
    if (id.includes('node_modules')) {
        return 'vendor';
    }
}

Результат:

chunks/vendor-ABCD.js

Разделение по директориям

chunkFileNames: 'assets/chunks/[name]-[hash].js'

Результат:

assets/
└── chunks/
    ├── vendor-ABCD.js
    └── dashboard-91FA.js

Поле output.assetFileNames

Назначение

assetFileNames отвечает за имена ассетов:

  • CSS;
  • SVG;
  • PNG;
  • шрифтов;
  • WebP;
  • JSON;
  • других файлов.

Что считается asset

Ассетом становится любой файл, который Rollup эмитит как ресурс:

import './style.css';
import logo from './logo.svg';

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

assetFileNames: 'assets/[name]-[hash][extname]'

Результат:

assets/style-A91F.css
assets/logo-K81A.svg

Использование [extname]

[extname] содержит расширение файла.

Пример:

assetFileNames: '[name][extname]'

Результат:

style.css
logo.svg

Использование [ext]

[ext] возвращает расширение без точки.

Пример:

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

Результат:

assets/logo-ABCD.svg
assets/style-X91F.css

Разделение ассетов по типам

Очень распространённый подход:

assetFileNames: assetInfo => {
    const ext = assetInfo.name.split('.').pop();

    if (/png|jpg|svg|webp/.test(ext)) {
        return 'images/[name]-[hash][extname]';
    }

    if (/woff|woff2|ttf/.test(ext)) {
        return 'fonts/[name]-[hash][extname]';
    }

    if (ext === 'css') {
        return 'css/[name]-[hash][extname]';
    }

    return 'assets/[name]-[hash][extname]';
}

Результат:

images/logo-A1B2.svg
fonts/inter-X91F.woff2
css/main-K81A.css

Функция вместо строки

Все три поля:

  • entryFileNames
  • chunkFileNames
  • assetFileNames

могут принимать функцию.


Функция для entryFileNames

entryFileNames(chunkInfo) {
    return `entries/${chunkInfo.name}-[hash].js`;
}

Структура chunkInfo

В функции доступны данные о чанке:

{
    facadeModuleId,
    isDynamicEntry,
    isEntry,
    moduleIds,
    name,
    type
}

Пример разделения entry-файлов

entryFileNames(chunkInfo) {
    if (chunkInfo.name === 'admin') {
        return 'admin/[name]-[hash].js';
    }

    return 'app/[name]-[hash].js';
}

Функция для assetFileNames

assetFileNames(assetInfo) {
    console.log(assetInfo);
    return 'assets/[name]-[hash][extname]';
}

assetInfo содержит:

{
    name,
    names,
    originalFileName,
    originalFileNames,
    source,
    type
}

Практическая production-схема

Одна из наиболее популярных структур:

export default {
    input: {
        app: 'src/app.js',
        admin: 'src/admin.js'
    },

    output: {
        dir: 'dist',
        format: 'es',

        entryFileNames: 'js/[name]-[hash].js',

        chunkFileNames: 'js/chunks/[name]-[hash].js',

        assetFileNames: assetInfo => {
            const ext = assetInfo.name
                .split('.')
                .pop();

            if (/css/.test(ext)) {
                return 'css/[name]-[hash][extname]';
            }

            if (/png|jpg|svg|webp/.test(ext)) {
                return 'images/[name]-[hash][extname]';
            }

            if (/woff|woff2/.test(ext)) {
                return 'fonts/[name]-[hash][extname]';
            }

            return 'assets/[name]-[hash][extname]';
        }
    }
};

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

dist/
├── css/
│   └── main-A91F.css
├── fonts/
│   └── inter-K12A.woff2
├── images/
│   └── logo-9FA1.svg
├── js/
│   ├── app-A81F.js
│   ├── admin-B12A.js
│   └── chunks/
│       ├── vendor-X91A.js
│       └── dialog-P81Q.js
└── assets/
    └── manifest-Q81A.json

Использование [name] при нескольких входах

input: {
    public: 'src/public.js',
    private: 'src/private.js'
}
entryFileNames: '[name].bundle.js'

Результат:

public.bundle.js
private.bundle.js

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

entryFileNames: '[name]/bundle.js'

Результат:

public/bundle.js
private/bundle.js

Генерация версии сборки

Иногда добавляют версию:

const version = '1.4.0';

entryFileNames: `js/[name]-${version}-[hash].js`

Результат:

js/app-1.4.0-ABCD.js

Использование даты сборки

const buildDate = Date.now();

entryFileNames: `js/[name]-${buildDate}-[hash].js`

Влияние хэша на кеширование

Без хэша:

app.js

Браузер может долго хранить файл в кеше.

С хэшем:

app-91AF7.js

После изменения содержимого:

app-7BC91.js

URL меняется, и браузер загружает новую версию.


Проблемы без chunkFileNames

Если не настроить chunk-файлы:

chunk-1.js
chunk-2.js

Такие имена:

  • плохо читаются;
  • неудобны при отладке;
  • затрудняют анализ бандла.

Влияние на sourcemaps

При использовании:

sourcemap: true

Rollup создаёт:

app.js
app.js.map

Имя sourcemap строится автоматически на основе имени файла.


Совместимость с CDN

Для CDN обычно используют:

entryFileNames: 'js/[name]-[hash].js',
chunkFileNames: 'js/chunks/[name]-[hash].js',
assetFileNames: 'assets/[name]-[hash][extname]'

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

  • immutable caching;
  • безопасное обновление;
  • отсутствие конфликтов версий.

Отличие [hash] у entry и chunk

Хэш вычисляется отдельно.

Даже если:

main.js
vendor.js

связаны между собой, их хэши различаются.


Частая ошибка с assetFileNames

Неправильно:

assetFileNames: 'assets/[hash]'

Результат:

assets/A91F2

Файл теряет расширение.

Правильно:

assetFileNames: 'assets/[hash][extname]'

Частая ошибка с вложенными путями

entryFileNames: '/js/[name].js'

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

Правильно:

entryFileNames: 'js/[name].js'

Частая ошибка с одинаковыми именами

entryFileNames: '[name].js'
chunkFileNames: '[name].js'

Иногда это вызывает конфликты.

Особенно при совпадении имён entry и chunk.

Безопаснее:

entryFileNames: 'entries/[name].js',
chunkFileNames: 'chunks/[name].js'

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

При:

preserveModules: true

Rollup сохраняет структуру модулей.

Тогда шаблоны начинают работать иначе.

Пример:

output: {
    dir: 'dist',
    preserveModules: true,
    entryFileNames: '[name].js'
}

Структура будет близка к исходной.


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

Vite использует Rollup внутри production build.

Настройки передаются так:

export default defineConfig({
    build: {
        rollupOptions: {
            output: {
                entryFileNames: 'js/[name]-[hash].js',
                chunkFileNames: 'js/chunks/[name]-[hash].js',
                assetFileNames: 'assets/[name]-[hash][extname]'
            }
        }
    }
});

Наиболее распространённая production-конфигурация

output: {
    dir: 'dist',
    format: 'es',

    entryFileNames: 'assets/[name]-[hash].js',

    chunkFileNames: 'assets/[name]-[hash].js',

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

Такой подход:

  • упрощает деплой;
  • хорошо работает с CDN;
  • обеспечивает cache busting;
  • подходит для большинства SPA и библиотек.