Одновременная сборка нескольких таргетов

Современные JavaScript-проекты часто требуют одновременной сборки под разные среды выполнения. Один и тот же код может поставляться как для браузера, так и для Node.js, либо разделяться на несколько вариантов для различных браузерных наборов возможностей. В таких сценариях используется механизм многотаргетной сборки, позволяющий формировать несколько независимых выходных бандлов из одного исходного графа модулей.

В сборщике Parcel поддержка нескольких таргетов реализована на уровне конфигурации проекта и тесно интегрирована с системой определения окружений, трансформаций и оптимизаций.


Модель таргетов в Parcel

В основе системы лежит концепция targets — именованных конфигураций сборки, каждая из которых описывает:

  • среду выполнения (browser, node, webworker);
  • формат модулей (ESM, CommonJS, глобальные IIFE-сборки);
  • целевые версии движков;
  • входные точки;
  • выходные директории и имена файлов;
  • специфичные флаги оптимизации.

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


Определение нескольких таргетов в package.json

Основной способ конфигурации — поле targets в package.json.

{
  "name": "multi-target-app",
  "source": "src/index.js",
  "targets": {
    "modern": {
      "engines": {
        "browsers": [">0.25%", "not dead"]
      },
      "outputFormat": "esmodule",
      "distDir": "dist/modern"
    },
    "legacy": {
      "engines": {
        "browsers": ["ie 11"]
      },
      "outputFormat": "global",
      "distDir": "dist/legacy"
    },
    "node": {
      "engines": {
        "node": ">=18"
      },
      "outputFormat": "commonjs",
      "distDir": "dist/node"
    }
  }
}

Каждый объект внутри targets формирует отдельный пайплайн сборки.


Параллельная генерация бандлов

При запуске команды сборки Parcel строит единый dependency graph, после чего выполняет:

  1. анализ исходных модулей;
  2. применение трансформаций (Babel, SWC, PostHTML и другие);
  3. разделение результатов по таргетам;
  4. генерацию отдельных бандлов;
  5. запись в соответствующие директории.

Важно, что граф модулей строится один раз. Различия между таргетами применяются на стадии кодогенерации и оптимизации.


Различия между таргетами на уровне трансформаций

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

  • транспиляция синтаксиса;
  • полифиллы;
  • tree-shaking;
  • minification;
  • code splitting.

Например, для legacy-таргета включается более агрессивная транспиляция, включая преобразование async/await в генераторы, а также добавление полифиллов. Для modern-таргета сохраняется нативный ESM и минимальные трансформации.


Browser targets и Browserslist

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

{
  "targets": {
    "default": {
      "engines": {
        "browsers": ["> 1%", "last 2 versions"]
      }
    }
  }
}

Каждый набор браузеров влияет на:

  • выбор синтаксиса JavaScript;
  • необходимость транспиляции CSS features;
  • использование современных API;
  • включение/исключение полифиллов.

Parcel анализирует эти значения и формирует соответствующую стратегию генерации кода.


Node.js таргет и особенности серверной сборки

При сборке под Node.js учитываются следующие аспекты:

  • сохранение CommonJS или ESM формата;
  • исключение браузерных полифиллов;
  • корректная обработка встроенных модулей (fs, path, crypto);
  • оптимизация require/import resolution.

Конфигурация может выглядеть следующим образом:

{
  "targets": {
    "node": {
      "engines": {
        "node": ">=18"
      },
      "outputFormat": "commonjs",
      "isLibrary": true,
      "distDir": "dist/server"
    }
  }
}

Параметр isLibrary часто используется для предотвращения лишней агрессивной оптимизации, характерной для frontend-сборок.


Разделение входных точек между таргетами

Каждый таргет может иметь собственный entry point:

{
  "targets": {
    "browser": {
      "source": "src/browser.js",
      "distDir": "dist/web"
    },
    "server": {
      "source": "src/server.js",
      "distDir": "dist/node"
    }
  }
}

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


Разделение зависимостей по окружениям

При многотаргетной сборке Parcel анализирует контекст импорта. Один и тот же модуль может:

  • попадать только в browser bundle;
  • исключаться из node bundle;
  • заменяться заглушкой через aliasing.

Например:

{
  "targets": {
    "browser": {
      "engines": {
        "browsers": [">0.25%"]
      },
      "alias": {
        "fs": false
      }
    }
  }
}

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


Оптимизация при многотаргетной сборке

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

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

Кэширование трансформаций

Один и тот же модуль не трансформируется повторно для каждого таргета. Вместо этого используется промежуточное представление AST.

Дифференциальная генерация

Различия между таргетами применяются только на этапе генерации кода, а не анализа.

Общие чанки

При совпадении зависимостей Parcel может создавать shared chunks, если таргеты допускают совместимость.


Code splitting в многотаргетной сборке

Каждый таргет может иметь собственную стратегию разделения кода:

  • динамические импорты (import());
  • vendor chunks;
  • runtime chunks;
  • shared chunks между entry points.

Однако важно, что чанки не всегда пересекаются между таргетами. Например, legacy-бандл может содержать собственные полифиллы, которые отсутствуют в modern-бандле.


Формат выходных артефактов

Результатом многотаргетной сборки является структура:

dist/
  modern/
    index.js
    vendor.js
  legacy/
    index.js
    polyfills.js
  node/
    index.js

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


Практика организации монорепозиториев с несколькими таргетами

Многотаргетный подход часто используется в монорепозиториях:

  • UI-библиотека (browser + ESM + CJS);
  • backend API (node);
  • CLI-инструменты (node);
  • web worker модули.

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


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

При создании библиотек важно учитывать:

  • необходимость экспорта в нескольких форматах;
  • сохранение tree-shaking;
  • отсутствие привязки к глобальной среде;
  • корректную работу типов (при использовании TypeScript).

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

{
  "targets": {
    "esm": {
      "outputFormat": "esmodule",
      "distDir": "dist/esm"
    },
    "cjs": {
      "outputFormat": "commonjs",
      "distDir": "dist/cjs"
    },
    "browser": {
      "outputFormat": "global",
      "distDir": "dist/browser"
    }
  }
}

Взаимодействие с tree-shaking и dead code elimination

Многотаргетная сборка усиливает эффективность tree-shaking, так как каждый таргет имеет собственный контекст использования кода.

  • browser-таргет удаляет Node-only модули;
  • node-таргет удаляет DOM API;
  • ESM-таргет сохраняет максимальную гранулярность экспорта.

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


Ограничения многотаргетного подхода

Несмотря на гибкость, существует ряд ограничений:

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

Эти ограничения компенсируются архитектурным разделением логики и корректной настройкой конфигурации.