Управление externals

В процессе сборки JavaScript-приложений bundler анализирует граф зависимостей и объединяет модули в один или несколько выходных файлов. Однако не все зависимости должны попадать в итоговый бандл. Часть модулей может существовать во внешней среде выполнения — предоставляться браузером, CDN, Node.js или отдельной системой загрузки. Такие зависимости называются externals.

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

Ключевые свойства external-зависимостей:

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

Модель работы externals в Parcel

Parcel анализирует зависимости на этапе построения графа модулей. Когда модуль помечен как external, резолвер перестаёт обрабатывать его содержимое как часть графа и заменяет его ссылкой.

При этом поведение отличается от обычного exclude в других сборщиках:

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

Пример логики:

import React from "react";

Если react объявлен external, Parcel не включит код React в сборку, а оставит импорт как есть (или преобразует его в формат, соответствующий целевому окружению).


Конфигурация external в Parcel

В Parcel 2 управление внешними зависимостями осуществляется через поле targets в package.json.

Основная структура:

{
  "name": "app",
  "version": "1.0.0",
  "targets": {
    "default": {
      "distDir": "dist",
      "external": [
        "react",
        "react-dom"
      ]
    }
  }
}

Поведение поля external

  • каждый модуль из списка исключается из бандла;
  • Parcel не пытается разрешить его внутренние зависимости;
  • импорт остаётся в выходном коде в виде import или require, в зависимости от режима сборки.

Использование externals в браузерных приложениях

В браузере external-зависимости обычно связаны с глобальными переменными, предоставленными через CDN или отдельные скрипты.

Пример сценария:

  • React подключается через CDN;
  • приложение использует react как external;
  • глобальная переменная доступна через window.React.

Parcel при этом не подставляет код React в бандл.

Однако важно учитывать, что Parcel сам по себе не «угадывает» глобальные переменные. Связь между модулем и глобальным объектом должна быть обеспечена вручную через HTML или системную интеграцию.


External и Node.js окружение

В Node.js externals используются чаще всего для:

  • встроенных модулей (fs, path, crypto);
  • библиотек, которые устанавливаются отдельно;
  • плагинов и расширений среды выполнения.

Parcel при сборке под Node может оставлять такие зависимости нетронутыми.

Пример:

{
  "targets": {
    "node": {
      "context": "node",
      "external": ["fs", "path"]
    }
  }
}

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

  • уменьшить размер server bundle;
  • избежать дублирования стандартных библиотек;
  • сохранить нативное поведение Node.js.

Отличие externals от alias и resolve

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

external

Полное исключение модуля из сборки.

alias

Перенаправление импорта на другой модуль.

Пример:

{
  "alias": {
    "react": "preact/compat"
  }
}

resolve

Настройка алгоритма поиска модулей.

Ключевое различие:

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

External и peer dependencies

Многие библиотеки публикуются с peerDependencies, предполагая, что зависимости будут предоставлены внешней средой.

Parcel external часто используется для реализации этого поведения.

Пример сценария:

  • UI-библиотека требует react;
  • React не должен дублироваться в сборке библиотеки;
  • react помечается как external.

package.json:

{
  "peerDependencies": {
    "react": "^18.0.0"
  },
  "targets": {
    "default": {
      "external": ["react"]
    }
  }
}

Это предотвращает:

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

External в библиотечных сборках

При создании npm-пакетов external играет критическую роль.

Типичная цель:

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

Пример:

{
  "targets": {
    "library": {
      "context": "browser",
      "outputFormat": "esm",
      "external": ["react", "react-dom"]
    }
  }
}

Результат:

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

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

External-зависимости полностью исключаются из графа, поэтому:

  • tree-shaking для них не применяется;
  • Parcel не анализирует их внутренние экспорты;
  • оптимизация происходит только на уровне остальных модулей.

Это может быть как преимуществом, так и ограничением:

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

SSR и external-зависимости

В серверном рендеринге external используется для разделения окружений:

  • сервер не должен бандлить браузерные библиотеки;
  • клиент не должен дублировать Node.js API.

Пример:

  • window-зависимые модули external на сервере;
  • Node встроенные модули external в браузерной сборке.

Динамические import и external

Dynamic import взаимодействует с external следующим образом:

const mod = await import("analytics-lib");

Если модуль объявлен external:

  • Parcel не включает его в чанки;
  • ожидается, что он будет загружен отдельно (например, через script tag или runtime loader).

Это требует согласованной стратегии загрузки ресурсов.


Ошибки конфигурации external

На практике неправильное использование external приводит к типовым проблемам.

1. Отсутствие глобальной переменной

Ситуация:

  • модуль помечен external;
  • но в runtime он не подключён.

Результат:

  • ReferenceError: X is not defined.

2. Разные версии библиотеки

Если external используется в нескольких пакетах без контроля версии:

  • возможны конфликты API;
  • ломается совместимость контекста выполнения.

3. Случайное исключение внутренних модулей

Иногда external задаётся слишком широко:

"external": ["react*"]

Это приводит к тому, что:

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

External и производительность

Использование external влияет на производительность в двух направлениях.

Плюсы:

  • уменьшение размера бандла;
  • ускорение сборки;
  • возможность кэширования внешних библиотек через CDN.

Минусы:

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

Практические сценарии применения

Подключение через CDN

  • библиотеки загружаются отдельно;
  • Parcel исключает их из бандла;
  • HTML содержит <script> теги.

Микрофронтенды

  • общие зависимости объявляются external;
  • каждая микрофронтенд-секция использует одну версию библиотеки;
  • снижается дублирование кода.

Плагины и расширения

  • ядро приложения предоставляет API;
  • плагины используют external для обращения к ядру;
  • исключается повторная сборка ядра.

Поведение external в различных форматах сборки

Parcel может собирать код в разные форматы:

  • ESM;
  • CommonJS;
  • IIFE.

External ведёт себя по-разному:

  • в ESM сохраняется import;
  • в CJS — require;
  • в IIFE — обращение к глобальной переменной.

Связь external с runtime окружением Parcel

Parcel runtime не загружает external-зависимости автоматически. Это принципиальная модель:

  • bundler формирует код;
  • runtime исполняет результат;
  • внешние зависимости предоставляются инфраструктурой.

Поэтому external всегда требует внешней координации:

  • HTML-скрипты;
  • CDN;
  • системные загрузчики;
  • Node runtime.

Архитектурное значение external

External-зависимости формируют границу между:

  • кодом приложения;
  • окружением выполнения.

Эта граница позволяет:

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

Parcel реализует этот механизм через декларативную конфигурацию, встроенную в targets, сохраняя единый подход к различным платформам и типам сборок.