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

Сборка приложения ориентирована на конечный исполняемый продукт: веб-страницу, SPA, серверный бандл или desktop-обёртку. В центре внимания — минимальный размер, оптимальная загрузка, код-сплиттинг, кэширование и производительность выполнения.

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

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


Цели сборки в контексте Esbuild

Приложение:

  • Создание готового к запуску бандла
  • Инлайнинг большинства зависимостей
  • Минификация и оптимизация
  • Возможность code splitting
  • Привязка к конкретной среде выполнения (browser/node)

Библиотека:

  • Публикация переиспользуемого API
  • Поддержка нескольких форматов (ESM, CJS, UMD/IIFE)
  • Сохранение совместимости с разными сборщиками (Webpack, Vite, Rollup)
  • Минимизация навязанных решений
  • Чёткое управление внешними зависимостями

Внешние зависимости: ключевое различие

В приложении зависимости обычно включаются в итоговый бандл:

import { format } from "date-fns";

Esbuild объединяет date-fns внутрь итогового файла.

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

Конфигурационно это выражается через external:

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

Почему это критично:

  • предотвращает конфликт версий
  • уменьшает размер итогового приложения у потребителя
  • сохраняет единый экземпляр React / других runtime-библиотек
  • обеспечивает предсказуемость поведения

Форматы вывода: приложение vs библиотека

Приложение

Чаще всего используется один формат:

  • iife для браузера
  • esm для современного фронтенда
  • cjs для Node.js

Обычно достаточно одного целевого бандла:

esbuild.build({
  entryPoints: ["src/main.ts"],
  bundle: true,
  outfile: "dist/app.js",
  platform: "browser"
});

Библиотека

Требует мультиформатной публикации:

  • ESM (esm)
  • CommonJS (cjs)
  • иногда UMD/IIFE для CDN

Пример:

esbuild.build({
  entryPoints: ["src/index.ts"],
  outdir: "dist",
  bundle: true,
  format: "esm",
  splitting: true,
  sourcemap: true
});

Затем отдельные сборки:

format: "cjs"
format: "esm"
format: "iife"

Структура выходных файлов

В приложении

Обычно один файл или несколько чанков:

dist/
  app.js
  chunk-1.js
  chunk-2.js

Чанки управляются исключительно внутренней логикой приложения.


В библиотеке

Структура должна отражать публичный API:

dist/
  index.js
  index.mjs
  index.cjs
  utils/
    format.js
    math.js

или даже:

dist/
  esm/
  cjs/
  types/

Ключевая идея — контролируемая экспортируемая поверхность.


Code splitting: допустимость и ограничения

В приложении code splitting — стандартная практика:

  • динамические import()
  • ленивые загрузки
  • маршрутизация

Esbuild автоматически разбивает бандл:

splitting: true,
format: "esm"

В библиотеке code splitting используется ограниченно.

Причины:

  • потребитель не контролирует загрузку чанков
  • риск несовместимости загрузчиков
  • сложность публикации
  • нарушение предсказуемости API

Поэтому чаще применяется:

  • единый entry point
  • либо явные подмодули (import { x } from "lib/submodule")

Tree-shaking и влияние архитектуры

В приложении

Tree-shaking работает внутри одного проекта:

  • удаляются неиспользуемые части зависимостей
  • оптимизируется финальный bundle

В библиотеке

Библиотека должна помогать tree-shaking потребителю:

  • ESM как основной формат
  • отсутствие побочных эффектов (sideEffects: false)
  • модульная структура

Пример package.json:

{
  "type": "module",
  "sideEffects": false,
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.cjs"
    }
  }
}

Минификация: различие подходов

Приложение

Минификация обязательна:

  • уменьшение размера
  • ускорение загрузки
minify: true

Библиотека

Минификация — спорный выбор:

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

Иногда публикуют два варианта:

  • dist/index.js (readable)
  • dist/index.min.js (production)

Target окружения

Приложение

Зависит от продукта:

  • es2020, es2022
  • browser list
  • node version
target: "es2019"

Библиотека

Должна быть более консервативной:

  • поддержка широкого спектра окружений
  • минимизация использования новых API
  • иногда multiple targets

Работа с React и runtime-библиотеками

В библиотечном режиме критично избегать дублирования runtime:

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

Если этого не сделать:

  • появится два React instance
  • сломаются hooks
  • возникнут трудноотлавливаемые баги

В приложении подобная проблема отсутствует — всё контролируется одним бандлом.


Типы входных точек

Приложение

Обычно один entry point:

src/main.ts

Библиотека

Множественные входные точки:

entryPoints: [
  "src/index.ts",
  "src/utils.ts",
  "src/components/button.ts"
]

Это позволяет:

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

Source maps и отладка

Приложение

Source maps используются для:

  • debugging production issues
  • stack trace mapping
sourcemap: true

Библиотека

Source maps важны ещё сильнее:

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

Иногда публикуют:

  • inline source maps для разработки
  • external .map файлы для production

CSS и ассеты

Приложение

Esbuild может:

  • инлайнить CSS
  • собирать ассеты
  • обрабатывать изображения

Библиотека

Часто избегают:

  • агрессивного бандлинга CSS
  • автоматического изменения ассетов

Предпочтение:

  • явный импорт CSS
  • или отдельная публикация стилей

Различие в стратегии публикации

Приложение

  • не публикуется как пакет
  • деплой как единый артефакт
  • CI/CD pipeline ориентирован на runtime

Библиотека

  • публикация в registry (npm)
  • семантическое версионирование
  • поддержка обратной совместимости
  • changelog как часть контракта

Роль package.json в библиотечной сборке

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

  • main — CommonJS entry
  • module — ESM entry (legacy, но используется)
  • exports — современный контракт
  • types — TypeScript декларации

Esbuild не генерирует .d.ts, поэтому обычно используется отдельный инструмент.


Ошибки при использовании Esbuild в библиотеке

Типичные проблемы:

  • отсутствие external → дублирование зависимостей
  • один формат сборки → плохая совместимость
  • агрессивный bundle → ломается tree-shaking
  • отсутствие exports → некорректные импорты
  • включённый minify без необходимости → ухудшение дебага

Разница в подходе к архитектуре кода

Приложение

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

Библиотека

  • строгие границы модулей
  • изоляция побочных эффектов
  • отсутствие зависимости от среды исполнения
  • чистый API слой

Итоговая логика различий

Сборка приложения в Esbuild — это процесс формирования оптимизированного runtime-артефакта, максимально сжатого и специализированного под конкретную среду.

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