Скелет плагина: функция, возвращающая объект

В Rollup плагин представляет собой функцию, которая возвращает объект с набором хуков (hooks). Именно эта форма является базовой и наиболее часто используемой, так как позволяет создавать изолированные, конфигурируемые и переиспользуемые расширения сборщика.

Базовая структура плагина

Минимальный плагин в Rollup выглядит как функция без параметров или с параметрами конфигурации, которая возвращает объект:

export default function myPlugin(options = {}) {
  return {
    name: 'my-plugin'
  };
}

Ключевой момент заключается в том, что Rollup не требует классов или специальных API-конструкторов. Достаточно вернуть объект с определёнными полями.

Роль функции-обёртки

Функция, возвращающая объект плагина, выполняет несколько задач:

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

Пример с параметрами:

export default function myPlugin({ debug = false } = {}) {
  let startTime = Date.now();

  return {
    name: 'my-plugin',

    buildStart() {
      if (debug) {
        console.log('Сборка началась');
      }
      startTime = Date.now();
    },

    buildEnd() {
      if (debug) {
        console.log('Сборка завершилась за', Date.now() - startTime, 'мс');
      }
    }
  };
}

Здесь функция выступает как фабрика плагинов, создающая независимые экземпляры с собственным состоянием.

Структура возвращаемого объекта

Объект, возвращаемый функцией, описывает поведение плагина через следующие элементы:

  • name — обязательное имя плагина (используется Rollup для диагностики и отладки)
  • hooks — методы, которые Rollup вызывает на разных этапах сборки
  • дополнительные свойства для взаимодействия с системой плагинов

Простейший объект:

return {
  name: 'example-plugin'
};

Расширенный объект:

return {
  name: 'example-plugin',

  resolveId(source) {
    return null;
  },

  load(id) {
    return null;
  },

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

Почему именно функция, а не объект

Использование функции как обёртки плагина решает несколько архитектурных задач:

1. Изоляция состояния

Каждый вызов функции создаёт новую область видимости:

export default function plugin() {
  let counter = 0;

  return {
    name: 'counter-plugin',

    buildStart() {
      counter++;
    },

    generateBundle() {
      console.log(counter);
    }
  };
}

Переменная counter не разделяется между разными сборками или экземплярами.

2. Конфигурируемость

Плагин может адаптироваться под входные параметры:

export default function replacePlugin(find, replace) {
  return {
    name: 'replace-plugin',

    transform(code) {
      return code.replaceAll(find, replace);
    }
  };
}

Такой подход делает плагин универсальным и переиспользуемым.

3. Ленивое создание логики

Объект плагина создаётся только при вызове функции, что позволяет отложить вычисления:

export default function expensivePlugin() {
  const heavyData = computeHeavyData();

  return {
    name: 'expensive-plugin',

    transform(code) {
      return code + heavyData;
    }
  };
}

Контракт плагина Rollup

Rollup ожидает, что функция вернёт объект, соответствующий определённому контракту:

  • объект должен быть синхронно возвращён
  • наличие name обязательно для корректной диагностики
  • методы объекта должны соответствовать именам хуков Rollup
  • значения хуков могут быть функциями или асинхронными функциями

Пример корректного контракта:

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

    async load(id) {
      const result = await fetchModule(id);
      return result;
    }
  };
}

Расширенный шаблон скелета плагина

На практике базовый скелет часто включает заготовку всех основных хуков:

export default function plugin(options = {}) {
  let config = options;

  return {
    name: 'full-skeleton-plugin',

    options(inputOptions) {
      return inputOptions;
    },

    buildStart() {
      // инициализация
    },

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

    load(id) {
      return null;
    },

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

    buildEnd() {
      // завершение сборки
    }
  };
}

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

Внутреннее состояние плагина

Функциональный подход позволяет хранить состояние прямо в замыкании:

export default function plugin() {
  const filesProcessed = new Set();

  return {
    name: 'stateful-plugin',

    transform(code, id) {
      filesProcessed.add(id);
      return code;
    },

    generateBundle() {
      console.log('Обработано файлов:', filesProcessed.size);
    }
  };
}

Состояние остаётся доступным на всех этапах сборки.

Особенности архитектуры скелета

Функция, возвращающая объект, даёт несколько важных свойств архитектуры:

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

Типичная форма промышленного плагина

В реальных проектах скелет обычно выглядит как комбинация конфигурации, состояния и хуков:

export default function plugin({ include = [], exclude = [] } = {}) {
  const cache = new Map();

  function shouldProcess(id) {
    if (exclude.some(p => id.includes(p))) return false;
    if (include.length && !include.some(p => id.includes(p))) return false;
    return true;
  }

  return {
    name: 'production-plugin',

    transform(code, id) {
      if (!shouldProcess(id)) return null;

      if (cache.has(id)) return cache.get(id);

      const result = code; // обработка
      cache.set(id, result);

      return result;
    }
  };
}

Такой подход показывает, что функция-обёртка — это не просто формальность, а фундаментальный элемент архитектуры плагинов Rollup.