Настройка library для публикации как npm-пакета

Архитектура сборки библиотеки

Сборка библиотеки в Webpack отличается от сборки приложения тем, что результат должен быть универсальным артефактом, пригодным для использования в разных окружениях: Node.js (CommonJS), ESM-сборках, браузере через глобальную переменную, а также в системах модульной загрузки AMD. Это требует явной настройки экспорта, формата модуля и управления внешними зависимостями.

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

  • корректно экспортирует публичное API
  • не дублирует внешние зависимости
  • поддерживает разные системы модулей
  • имеет предсказуемую структуру файлов

Базовая конфигурация output для библиотеки

Ключевой блок настройки — output, определяющий формат и способ публикации результата сборки.

output: {
  path: path.resolve(__dirname, 'dist'),
  filename: 'index.js',
  clean: true,
}

Однако для библиотек этого недостаточно. Необходимо явно указать поведение экспорта.


Параметр library и его эволюция в Webpack 5

В Webpack 4 использовались libraryTarget, в Webpack 5 он заменён на output.library.type.

Современная конфигурация:

output: {
  path: path.resolve(__dirname, 'dist'),
  filename: 'index.js',
  library: {
    name: 'MyLibrary',
    type: 'umd',
  },
  globalObject: 'globalThis',
  clean: true,
}
Значения library.type
  • umd — универсальный формат (CommonJS + AMD + global)
  • commonjs2 — экспорт через module.exports
  • module — ESM-вывод (подходит для "type": "module")
  • var — глобальная переменная в браузере
  • assign — присваивание в глобальный объект
  • this / window / global — привязка к конкретной среде

Универсальный формат UMD

UMD остаётся стандартом для библиотек, которые должны работать в любом окружении.

output: {
  filename: 'my-lib.js',
  library: {
    name: 'MyLib',
    type: 'umd',
    export: 'default',
  },
  globalObject: 'globalThis',
}

Особенности:

  • поддерживает require
  • поддерживает AMD (define)
  • доступен как глобальная переменная в браузере
  • требует явного globalObject, чтобы избежать ошибок в Node.js

Управление экспортом через export

Параметр library.export управляет тем, какая часть модуля становится публичным API.

library: {
  name: 'MyLib',
  type: 'umd',
  export: 'default',
}

Возможные варианты:

  • default — экспортируется export default
  • named — экспортируются именованные экспорты
  • ['default', 'utils'] — выборочный экспорт

Поддержка ESM-библиотек

Современные пакеты всё чаще публикуются как ES Modules.

output: {
  filename: 'index.mjs',
  library: {
    type: 'module',
  },
  module: true,
  environment: {
    module: true,
  },
}

Обязательные условия:

  • "type": "module" в package.json или использование .mjs
  • включение experiments.outputModule (в некоторых версиях Webpack)
experiments: {
  outputModule: true,
}

Конфигурация package.json для npm-пакета

Webpack-сборка библиотеки тесно связана с корректным описанием пакета.

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

Параметр exports обеспечивает:

  • поддержку ESM и CommonJS одновременно
  • контроль точек входа
  • предотвращение доступа к внутренним файлам

Разделение сборок: ESM и CJS

Часто применяется стратегия двойной сборки:

module.exports = [
  {
    mode: 'production',
    entry: './src/index.js',
    output: {
      filename: 'index.js',
      path: path.resolve(__dirname, 'dist'),
      library: {
        type: 'commonjs2',
      },
      clean: true,
    },
  },
  {
    mode: 'production',
    entry: './src/index.js',
    output: {
      filename: 'index.mjs',
      path: path.resolve(__dirname, 'dist'),
      library: {
        type: 'module',
      },
      module: true,
      clean: false,
    },
    experiments: {
      outputModule: true,
    },
  },
];

Работа с внешними зависимостями (externals)

Для библиотек критично не включать зависимости вроде React, Lodash и аналогов в бандл.

externals: {
  react: 'react',
  'react-dom': 'react-dom',
}

Или универсальный вариант:

externals: {
  lodash: {
    commonjs: 'lodash',
    commonjs2: 'lodash',
    amd: 'lodash',
    root: '_',
  },
}

Результат:

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

Автоматическое определение externals

Используется для крупных библиотек:

externals: /^(react|react-dom|lodash)$/i

Или через функцию:

externals: ({ request }, callback) => {
  if (/^@?lodash/.test(request)) {
    return callback(null, 'commonjs ' + request);
  }
  callback();
}

Оптимизация выходного артефакта

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

optimization: {
  minimize: true,
  usedExports: true,
  sideEffects: false,
}

Особое значение имеет sideEffects в package.json:

{
  "sideEffects": false
}

Это позволяет tree-shaking на стороне потребителя.


Управление глобальной переменной

При публикации UMD-библиотеки в браузерном окружении важно контролировать имя глобального объекта:

output: {
  globalObject: 'globalThis',
}

Причины:

  • window не работает в Node.js
  • self не универсален
  • globalThis поддерживает все окружения

Разделение development и production сборки

Разные режимы влияют на итоговый пакет.

Production:

mode: 'production',
devtool: 'source-map',

Development:

mode: 'development',
devtool: 'eval-source-map',

Для библиотек development-сборка часто используется только локально, а в npm публикуется production-версия.


Генерация source maps

Source maps критичны для отладки библиотек:

devtool: 'source-map'

Дополнительно возможно разделение:

  • .map файлы публикуются вместе с пакетом
  • либо исключаются через .npmignore

Поддержка TypeScript (типизация библиотеки)

При использовании TypeScript обычно добавляется отдельный процесс генерации типов:

{
  "types": "dist/index.d.ts"
}

Webpack сам по себе не генерирует .d.ts, поэтому используется tsc:

{
  "compilerOptions": {
    "declaration": true,
    "emitDeclarationOnly": true,
    "outDir": "dist"
  }
}

Контроль структуры выходной директории

Типичная структура:

dist/
  index.js
  index.mjs
  index.js.map
  index.mjs.map
  index.d.ts

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

  • совместимости версий
  • корректного exports mapping
  • предсказуемого импорта

Использование banner для метаданных

Добавление метаинформации в начало файла:

const webpack = require('webpack');

plugins: [
  new webpack.BannerPlugin({
    banner: 'MyLib v1.0.0',
  }),
]

Минимизация конфликтов при публикации

Чтобы избежать конфликтов в глобальной области:

library: {
  name: ['MyScope', 'MyLib'],
  type: 'umd',
}

В этом случае библиотека публикуется как:

window.MyScope.MyLib

Итоговая структура конфигурации

const path = require('path');

module.exports = {
  mode: 'production',
  entry: './src/index.js',

  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'index.js',
    library: {
      name: 'MyLib',
      type: 'umd',
      export: 'default',
    },
    globalObject: 'globalThis',
    clean: true,
  },

  externals: {
    react: 'react',
  },

  optimization: {
    minimize: true,
    sideEffects: false,
  },
};