Опция packages: external для всех node_modules

Опция packages: "external" управляет тем, как сборщик обрабатывает зависимости из node_modules. При её активации все пакеты, установленные через npm/yarn/pnpm, перестают встраиваться в итоговый бандл и вместо этого остаются внешними импортами, которые будут разрешаться во время выполнения.

Ключевая идея: сборщик перестаёт «впитывать» зависимости и превращает их в ссылки на рантайм-окружение Node.js или другого загрузчика модулей.


Поведение по умолчанию и отличие от external

В стандартной конфигурации esbuild пытается собрать зависимости в один или несколько файлов. Это означает:

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

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

packages: "external"

происходит иная модель:

  • каждый импорт из node_modules остаётся импортом;
  • код пакетов не инлайнится в сборку;
  • итоговый бандл содержит только ссылки вида require("lodash") или import "lodash".

Принцип работы на уровне модулей

Если в коде есть:

import express from "express";
import lodash from "lodash";

После сборки с packages: "external" результат будет концептуально таким:

import express from "express";
import lodash from "lodash";

Но уже в контексте скомпилированного файла (например, CommonJS или ESM), без внедрения исходников этих библиотек.

Важно: esbuild не удаляет импорты, а меняет стратегию их обработки.


Отличие от ручного external

В esbuild существует также поле:

external: []

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

Пример:

external: ["react", "react-dom"]

Это означает:

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

packages: "external" — глобальная стратегия:

  • автоматически делает внешними все пакеты из node_modules.

Сценарии применения

Серверные приложения (Node.js)

Наиболее частый кейс:

  • API на Express / Fastify / NestJS;
  • запуск в среде, где node_modules доступны;
  • нет необходимости уменьшать размер бандла.

В этом случае сборка превращается в транспиляцию:

// исходник
import db from "database-lib";
// результат
import db from "database-lib";

Lambda-функции и serverless

Иногда используется обратная стратегия — полное бандлирование. Однако packages: "external" полезна, если:

  • окружение уже содержит зависимости;
  • используется shared layer (AWS Lambda Layers);
  • требуется минимальный bundle size deployment artifact.

Монорепозитории

В монорепо с workspace-зависимостями:

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

Влияние на размер и производительность

Использование packages: "external" приводит к:

Уменьшению времени сборки

  • меньше анализа зависимостей;
  • меньше трансформаций кода;
  • отсутствует глубокий tree-shaking внутри node_modules.

Минимальному размеру output-файла

Но важно понимать:

  • размер уменьшается только на этапе сборки;
  • фактический размер приложения остаётся в node_modules.

Особенности работы с ESM и CommonJS

ESM режим

import "chalk";

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

CommonJS режим

const chalk = require("chalk");

Также сохраняется как внешний require.


CLI использование

esbuild src/index.js --bundle --packages=external --outfile=dist/bundle.js

Эта команда:

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

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

Опция packages: "external" часто используется вместе с:

platform: "node"

Совместное поведение:

  • node — включает Node.js-специфику;
  • packages: "external" — сохраняет внешние зависимости.

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

Tree-shaking внутри сторонних библиотек фактически отключается:

  • esbuild не анализирует внутренности node_modules;
  • не удаляет неиспользуемые экспорты библиотек;
  • оптимизация применяется только к пользовательскому коду.

Ограничения и подводные камни

1. Риск отсутствующих зависимостей

Если пакет не установлен в runtime:

Error: Cannot find module 'some-package'

2. Разные окружения

  • локальная сборка может работать;
  • production окружение может отличаться по node_modules.

3. Несовместимость с browser-bundle

Для браузера эта стратегия обычно неприемлема:

  • node_modules недоступны;
  • требуется полное бандлирование.

Комбинация с external паттернами

Иногда используется гибрид:

external: ["react", "react-dom"],
packages: "external"

Логика:

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

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

При packages: "external" esbuild превращается из полноценного бандлера в:

  • транспилятор TypeScript/JS;
  • минимизатор пользовательского кода;
  • инструмент подготовки runtime-сборки без упаковки зависимостей.

Это особенно заметно в архитектурах, где:

  • деплой происходит через Docker;
  • node_modules устанавливаются отдельно;
  • сборка отделена от окружения выполнения.

Поведение с динамическими импортами

import("express").then(mod => {
  mod.default();
});

При external-режиме:

  • динамический импорт остаётся динамическим;
  • пакет не попадает в bundle;
  • загрузка происходит в runtime.

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

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

  • .ts файлы компилируются как обычно;
  • типы удаляются;
  • импорт пакетов остаётся внешним.
import type { Request } from "express";

Типовой импорт исчезает, но runtime-пакет остаётся внешним.


Типичная конфигурация

import esbuild from "esbuild";

esbuild.build({
  entryPoints: ["src/index.ts"],
  bundle: true,
  platform: "node",
  packages: "external",
  outfile: "dist/index.js"
});

Такой подход фиксирует:

  • отсутствие встраивания node_modules;
  • сохранение Node.js-экосистемы;
  • быстрый build pipeline.

Влияние на архитектуру проекта

Использование packages: "external" фактически делит систему на две части:

  • код приложения, управляемый esbuild;
  • зависимости, управляемые пакетным менеджером и runtime.

Это меняет характер сборки:

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