Отличия сборки приложения и библиотеки

Цель и модель результата сборки

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

Приложение рассматривается как конечная точка исполнения. Оно запускается в браузере или Node.js и управляет собственным окружением. В этом случае сборщик стремится:

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

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

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

Эта разница определяет поведение Parcel при формировании итогового результата.


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

В приложении входная точка обычно одна — index.html или index.js. Parcel строит граф зависимостей от этой точки и оптимизирует всё дерево под единый результат.

В библиотеке входных точек может быть несколько:

  • основной модуль API (src/index.js);
  • дополнительные модули (src/utils.js, src/components/*);
  • типовые экспорты для частичного импорта.

Структура библиотеки ориентирована на экспорт модулей, а не на запуск.

Parcel в режиме библиотеки учитывает необходимость сохранить границы модулей, если это требуется форматом вывода (ESM/CJS), либо объединить их только частично.


Форматы вывода: ESM, CommonJS, UMD

Для приложений чаще используется один формат — оптимизированный бандл под конкретную среду.

Для библиотек критически важно поддерживать несколько форматов одновременно:

  • ESM (ECMAScript Modules) — основной современный формат, поддерживающий tree-shaking;
  • CommonJS — совместимость с Node.js и устаревшими инструментами;
  • UMD — универсальный формат для браузеров без сборщика;
  • иногда — отдельные IIFE-бандлы для встраивания через <script>.

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

{
  "targets": {
    "default": {
      "distDir": "dist",
      "source": "src/index.js",
      "context": "browser",
      "outputFormat": "esmodule"
    },
    "cjs": {
      "outputFormat": "commonjs"
    },
    "umd": {
      "outputFormat": "umd",
      "global": "MyLibrary"
    }
  }
}

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


Работа с зависимостями: bundling и external

Одно из ключевых различий — стратегия обработки зависимостей.

В приложении зависимости почти всегда встраиваются в бандл. Это уменьшает количество запросов и упрощает деплой.

В библиотеке часть зависимостей должна оставаться внешней:

  • react, vue, svelte — часто объявляются как peerDependencies;
  • большие утилиты могут быть исключены из сборки;
  • системные зависимости Node.js никогда не бандлятся.

Parcel в библиотечном режиме позволяет управлять этим через externals-подобное поведение (через package.json и конфигурацию targets). Итоговый бандл сохраняет только код самой библиотеки, не дублируя окружение.


Оптимизация и tree-shaking

В приложениях Parcel может агрессивно оптимизировать весь граф:

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

В библиотеках оптимизация более осторожная.

Причина — необходимость сохранить:

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

Особенно важно поведение sideEffects в package.json:

{
  "sideEffects": false
}

или частичное указание:

{
  "sideEffects": ["./src/polyfills.js"]
}

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


Code Splitting и его различие

В приложениях Parcel активно применяет code splitting:

  • динамические import() создают отдельные чанки;
  • страницы и маршруты делятся автоматически;
  • общие зависимости выносятся в shared chunks.

В библиотеках code splitting используется ограниченно. Причина — библиотека должна быть предсказуемой единицей поставки.

Типичные ограничения:

  • минимизация количества выходных файлов;
  • отказ от динамических чанков в основном entry;
  • объединение модулей в единый файл или несколько форматов.

Исключение составляют библиотеки с плагинной архитектурой или крупные UI-kit системы.


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

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

В приложении обычно один target:

  • context: browser
  • engines: { "browsers": [...] }
  • distDir: dist

В библиотеке targets становятся многоуровневыми:

  • отдельный target для ESM;
  • отдельный для CJS;
  • отдельный для UMD;
  • иногда — для server/SSR.

Каждый target может иметь:

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

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


Структура package.json при публикации библиотеки

Для библиотек package.json играет роль контракта с внешним миром.

Ключевые поля:

  • main — CommonJS entry;
  • module — ESM entry;
  • exports — современное описание публичного API;
  • types — TypeScript декларации;
  • files — список публикуемых артефактов.

Пример:

{
  "name": "my-lib",
  "version": "1.0.0",
  "main": "./dist/index.cjs",
  "module": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/index.js",
      "require": "./dist/index.cjs"
    }
  }
}

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


Обработка стилей и ассетов

В приложении Parcel часто инлайнит или глобализирует ассеты:

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

В библиотеке поведение зависит от назначения:

  • UI-библиотеки часто требуют отдельного CSS-бандла;
  • headless-библиотеки могут вообще не включать стили;
  • ассеты обычно экспортируются как часть API или как отдельные файлы.

Ключевое отличие — необходимость контролировать интеграцию с внешним приложением, а не просто доставить готовый UI.


Различия в source maps и отладке

В приложениях source maps часто включаются в полном объёме для упрощения диагностики.

В библиотеках подход более вариативен:

  • inline source maps используются редко;
  • предпочтение отдаётся external maps;
  • иногда публикуются только для development-сборок;
  • production-сборки могут их минимизировать или исключать.

Причина — снижение веса пакета и защита внутренней структуры реализации.


Типичные архитектурные ошибки при сборке библиотек

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

Это приводит к следующим последствиям:

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

Другой распространённый сценарий — избыточное code splitting, при котором библиотека поставляется в виде множества чанков, не ожидаемых сборщиками внешних проектов.


Практики формирования предсказуемого API

Библиотечная сборка требует строгой стабилизации экспортов:

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

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


Различие поведения в development и production

В приложениях различие режимов влияет в первую очередь на производительность:

  • development — быстрые сборки, HMR, подробные ошибки;
  • production — агрессивная оптимизация и минификация.

В библиотеках это различие дополнительно влияет на:

  • форму экспортов;
  • наличие или отсутствие debug-кода;
  • уровень инлайнинга зависимостей;
  • структуру output-файлов.

Production-сборка библиотеки фактически определяет внешний интерфейс, тогда как development используется только для разработки самой библиотеки.