Изменения в API плагинов между версиями

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

Изменения происходили постепенно, однако наиболее заметные модификации коснулись следующих областей:

  • контекста плагина (PluginContext);
  • хуков разрешения модулей;
  • работы с ассетами и чанками;
  • генерации исходных карт;
  • обработки предупреждений;
  • механизмов наблюдения за файлами;
  • асинхронного взаимодействия с графом модулей;
  • совместимости с ESM-конфигурациями.

Общая стратегия развития API

На ранних этапах развития Rollup API был относительно компактным. Многие операции выполнялись через небольшое количество хуков:

export default function myPlugin() {
    return {
        name: 'my-plugin',

        resolveId(id) {
            return null;
        },

        load(id) {
            return null;
        },

        transform(code, id) {
            return code;
        }
    };
}

Со временем появились новые требования:

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

В результате API стало значительно богаче.


Изменения контекста плагина

Ранние версии

В старых версиях контекст плагина содержал относительно ограниченный набор методов:

transform(code, id) {
    this.warn('Debug message');
    return code;
}

Наиболее часто использовались:

this.warn(...)
this.error(...)
this.parse(...)

Количество служебных методов было невелико.


Расширение PluginContext

Позже контекст получил множество новых возможностей:

this.resolve(...)
this.emitFile(...)
this.getFileName(...)
this.addWatchFile(...)
this.getModuleInfo(...)
this.getModuleIds()

Пример:

async transform(code, id) {
    const resolved = await this.resolve('./helper.js', id);

    return code;
}

Ранее подобная операция часто требовала самостоятельной логики поиска файлов.

Появление this.resolve() позволило использовать существующую цепочку плагинов для разрешения зависимостей.


Изменения в хуке resolveId

Старый формат

Изначально хук выглядел достаточно просто:

resolveId(importee, importer) {
    return null;
}

Параметры:

  • importee — импортируемый путь;
  • importer — модуль-источник.

Новый формат

Позже сигнатура была расширена:

resolveId(source, importer, options) {
    return null;
}

Появился объект options.

Пример:

resolveId(source, importer, options) {
    console.log(options.isEntry);

    return null;
}

Новые свойства позволяют определить:

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

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


Изменения в load

Простая загрузка модулей

Классический вариант:

load(id) {
    if (id === 'virtual') {
        return 'export default 123';
    }

    return null;
}

Возврат расширенной информации

Современные версии поддерживают более сложные объекты:

load(id) {
    return {
        code: 'export const value = 123;',
        map: null
    };
}

Плагин может сразу предоставить:

  • исходный код;
  • source map;
  • дополнительную информацию для последующих этапов.

Изменения в transform

Возврат строки

Долгое время было достаточно вернуть строку:

transform(code) {
    return code.replace('__DEV__', 'true');
}

Возврат объекта

Позже основным вариантом стал объект:

transform(code) {
    return {
        code: code.replace('__DEV__', 'true'),
        map: null
    };
}

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


Метаданные

В более новых версиях появились пользовательские метаданные:

transform(code) {
    return {
        code,
        meta: {
            transformed: true
        }
    };
}

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


Появление emitFile

Одним из наиболее важных изменений стало внедрение механизма генерации файлов.

До появления emitFile

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

Разработчики прибегали к обходным решениям:

import fs from 'fs';

generateBundle() {
    fs.writeFileSync(
        'dist/data.json',
        '{}'
    );
}

Такой подход нарушал внутреннюю модель Rollup.


Современный подход

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

Rollup самостоятельно управляет ресурсом.

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

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

Изменения в работе с ассетами

После появления emitFile() API продолжило расширяться.

Старые варианты

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

Получение итогового имени

Позже появился метод:

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

const fileName =
    this.getFileName(referenceId);

Особенно полезно при использовании хешей:

assets/style-[hash].css

Плагину больше не требуется угадывать итоговое имя файла.


Изменения в генерации чанков

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

Пример:

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

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

Это особенно важно для:

  • рантаймов;
  • автоматических импортов;
  • инфраструктурных модулей.

Изменения в getModuleInfo

Отсутствие информации о графе

В старых версиях получение сведений о модуле было ограниченным.

Плагин часто вынужден был самостоятельно вести внутренние структуры данных.


Новый API

const info =
    this.getModuleInfo(id);

Пример:

const info = this.getModuleInfo(id);

console.log(info.importedIds);
console.log(info.isEntry);

Доступны сведения:

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

Появление getModuleIds

Для обхода графа появился специальный итератор:

for (const id of this.getModuleIds()) {
    console.log(id);
}

Ранее подобные задачи были намного сложнее.

Это открыло возможности для:

  • анализа зависимостей;
  • генерации отчётов;
  • проверки архитектурных правил.

Изменения в системе предупреждений

Старый формат

this.warn('Something happened');

Новый объект предупреждения

this.warn({
    code: 'CUSTOM_WARNING',
    message: 'Something happened'
});

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

  • структурированная диагностика;
  • фильтрация предупреждений;
  • улучшенная интеграция с CLI.

Улучшенные ошибки

Раньше:

this.error('Invalid syntax');

Теперь:

this.error({
    message: 'Invalid syntax',
    id,
    pos: 120
});

Rollup способен показать:

  • файл;
  • строку;
  • колонку;
  • фрагмент исходного кода.

Изменения в source map API

Примитивные карты

В ранних версиях многие плагины вообще возвращали:

{
    code,
    map: null
}

Полноценная поддержка карт

Позже система source maps стала обязательной частью экосистемы.

Распространённый вариант:

return {
    code: result.code,
    map: result.map
};

Многие официальные плагины были обновлены именно ради корректной цепочки карт преобразований.


Изменения в watch API

Ручное отслеживание файлов

Ранее плагины нередко использовали собственные механизмы слежения.


addWatchFile

Появился официальный способ регистрации зависимостей:

this.addWatchFile(
    'config/settings.json'
);

Теперь изменение файла автоматически вызывает пересборку.

Это особенно полезно для:

  • шаблонов;
  • JSON-конфигураций;
  • YAML-файлов;
  • внешних ресурсов.

Изменения в resolve() внутри плагинов

Одним из крупнейших улучшений API стало появление метода:

await this.resolve(...)

Пример:

const resolved =
    await this.resolve(
        './utils.js',
        importer
    );

Результат:

{
    id: '/src/utils.js'
}

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


Изменения в хуке moduleParsed

Со временем появился дополнительный этап обработки уже разобранного модуля.

moduleParsed(moduleInfo) {
    console.log(moduleInfo.id);
}

Плагин получает доступ к AST после завершения анализа.

Это удобно для:

  • статического анализа;
  • проверки импортов;
  • генерации отчётов.

Изменения в generateBundle

Ранние возможности

generateBundle(options, bundle) {
}

Обычно использовалось только чтение содержимого сборки.


Современные сценарии

generateBundle(options, bundle) {
    for (const file of Object.values(bundle)) {
        console.log(file.fileName);
    }
}

Плагин способен:

  • изменять чанки;
  • изменять ассеты;
  • удалять элементы;
  • создавать новые ресурсы.

Изменения в типах плагинов TypeScript

По мере развития Rollup улучшалась типизация.

Старый код часто использовал:

export default function plugin(): any {
}

Современный вариант:

import type { Plugin } from 'rollup';

export default function plugin(): Plugin {
    return {
        name: 'example'
    };
}

Появились отдельные типы:

Plugin
OutputChunk
OutputAsset
ModuleInfo
PluginContext
TransformResult
ResolvedId

Это значительно повысило надёжность разработки.


Изменения совместимости с ESM

Старые плагины часто публиковались как CommonJS-модули:

module.exports = function () {
    return {};
};

Современная экосистема Rollup всё чаще использует ESM:

export default function () {
    return {};
}

Соответственно изменились:

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

Миграция старых плагинов

Наиболее распространённые изменения при обновлении старого плагина:

  1. Замена строковых предупреждений на структурированные объекты.
  2. Использование this.resolve() вместо ручного поиска модулей.
  3. Переход на emitFile() для создания ресурсов.
  4. Поддержка map в результатах load() и transform().
  5. Использование getModuleInfo() вместо собственных реестров зависимостей.
  6. Добавление addWatchFile() для внешних файлов.
  7. Обновление типов TypeScript до актуальных интерфейсов Rollup.
  8. Адаптация к ESM-формату публикации.

Большинство изменений в API плагинов направлено на унификацию работы с графом модулей, улучшение диагностики, поддержку сложных сценариев сборки и снижение количества неофициальных обходных решений. В результате современные плагины получают значительно больше возможностей для интеграции во внутренние механизмы Rollup, чем плагины первых поколений.