Поле external: функция и массив

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

Основная задача external — исключить указанные модули из процесса бандлинга. Rollup, обнаружив импорт, помеченный как внешний, не включает его код в выходной файл, а оставляет ссылку на него в виде import (ESM) или require (CJS-плагины/вывод).

Ключевая идея:

  • внутренние модули проекта объединяются в бандл;
  • внешние зависимости остаются ссылками.

Это особенно важно при разработке библиотек, где:

  • не требуется дублировать код зависимостей;
  • пользователь сам устанавливает node_modules;
  • важно сохранить совместимость с tree-shaking у потребителя.

Формат массива external

Наиболее распространённый вариант — использование массива строк:

export default {
  input: 'src/index.js',
  external: ['react', 'lodash', 'axios']
};

Каждая строка в массиве — это имя модуля, которое Rollup будет считать внешним.

Поведение при использовании массива

Если в коде встречается импорт:

import axios from 'axios';

Rollup:

  • не добавляет axios в бандл;
  • оставляет импорт как есть (или трансформирует в зависимости от формата вывода);
  • помечает модуль как внешний.

Поддерживаемые типы значений массива

Массив external может содержать не только строки, но и более гибкие формы сопоставления:

1. Строковые идентификаторы

external: ['react']

Совпадает только с точным именем импорта.


2. Регулярные выражения

external: [/^@org\//]

Любой импорт, начинающийся с @org/, будет считаться внешним:

import utils from '@org/utils';
import core from '@org/core';

Оба будут исключены из бандла.


3. Функции

Наиболее гибкий вариант — функция, принимающая идентификатор модуля:

external: (id) => {
  return id === 'react';
}

Rollup вызывает эту функцию для каждого import или require. Если возвращается true, модуль считается внешним.


Механика работы external

Rollup обрабатывает граф зависимостей следующим образом:

  1. Строит дерево импортов начиная с input.

  2. Для каждого узла проверяет external.

  3. Если модуль совпадает с правилом:

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

Важно

external влияет не только на финальный код, но и на процесс анализа:

  • исключённые модули не участвуют в tree-shaking;
  • их содержимое полностью игнорируется;
  • оптимизации кода внутри них не выполняются.

Поведение в разных форматах вывода

ES Modules (output.format = “esm”)

import React from 'react';

Остаётся без изменений.


CommonJS

const React = require('react');

Rollup оставляет require как ссылку на внешнюю зависимость.


IIFE / UMD

Во встроенных форматах ситуация сложнее:

  • внешние зависимости не включаются;
  • могут появляться ссылки на глобальные переменные;
  • требуется настройка globals.

Пример:

external: ['react'],
output: {
  format: 'umd',
  globals: {
    react: 'React'
  }
}

Результат:

(function (React) { ... }(window.React));

Функция external и контроль зависимостей

Функциональный вариант позволяет реализовать сложную логику исключения:

Исключение всех node_modules

external: (id) => id.includes('node_modules')

На практике чаще используется более точное правило:

external: (id) => !id.startsWith('.') && !id.startsWith('/')

Здесь:

  • относительные пути (./, ../) остаются внутри бандла;
  • абсолютные и пакетные зависимости считаются внешними.

Исключение по паттернам

external: (id) => {
  return /^@company\//.test(id);
}

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


Взаимодействие external с resolve-плагинами

Плагины типа @rollup/plugin-node-resolve сначала пытаются разрешить модуль, после чего Rollup проверяет external.

Важно:

  • external применяется к уже разрешённому id;
  • это означает, что можно исключать как исходные импорты, так и пути после резолва.

Пример:

external: ['react']

Импорт:

import React from 'react';

После резолва Rollup всё равно сопоставляет строку react.


Влияние на tree-shaking

external полностью отключает tree-shaking для этих модулей, так как:

  • код не анализируется;
  • Rollup не знает внутренней структуры зависимостей;
  • вся ответственность за оптимизацию лежит на внешнем пакете.

Это делает external важным инструментом баланса между размером бандла и границами ответственности.


Ошибки при использовании external

1. Слишком широкие правила

external: (id) => true

Результат — пустой бандл с одними импортами.


2. Несовпадение форматов импорта

external: ['react']

Но код:

import React from 'react/index.js';

Такой импорт не будет распознан как внешний.


3. Конфликт с алиасами

Если используется alias-плагин:

resolve({
  alias: {
    '@': './src'
  }
})

и одновременно:

external: ['@/utils']

Важно учитывать, что external проверяется после разрешения путей.


Практическая модель применения

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

external: [
  'react',
  'react-dom',
  /^lodash/
]

или функция:

external: (id) => {
  if (id === 'react' || id === 'react-dom') return true;
  if (id.startsWith('lodash')) return true;
  return false;
}

Такой подход позволяет:

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

Роль external в библиотечной разработке

При создании библиотек external фактически определяет публичный контракт:

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

От корректности настройки зависит:

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