Форматы вывода библиотеки: es, cjs, umd, iife

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

  • совместимость с различными средами выполнения;
  • способ подключения библиотеки;
  • поддержка tree-shaking;
  • возможность работы в браузере без сборщика;
  • размер итогового бандла;
  • совместимость с Node.js и старыми инструментами.

Vite использует Rollup в качестве системы сборки, поэтому поддерживает несколько стандартных форматов:

  • es
  • cjs
  • umd
  • iife

Настройка выполняется через build.lib.formats.

import { defineConfig } from 'vite'

export default defineConfig({
  build: {
    lib: {
      entry: 'src/index.js',
      name: 'MyLibrary',
      formats: ['es', 'cjs', 'umd']
    }
  }
})

В результате Vite создаёт несколько файлов библиотеки в разных форматах одновременно.


Формат ES (es)

Назначение

Формат es основан на стандарте ECMAScript Modules (ESM). Это современный стандарт модульной системы JavaScript.

Файлы используют:

export
import

Этот формат считается основным для современных библиотек.


Особенности ES-модулей

Статическая структура импортов

Импорты анализируются ещё до выполнения кода.

import { sum } from './math.js'

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

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

Пример сборки

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

import { defineConfig } from 'vite'

export default defineConfig({
  build: {
    lib: {
      entry: 'src/index.js',
      formats: ['es'],
      fileName: 'my-lib'
    }
  }
})

Результат

dist/
  my-lib.js

Содержимое будет выглядеть примерно так:

function sum(a, b) {
  return a + b
}

export { sum }

Подключение ES-модуля

В браузере

<script type="module">
  import { sum } from './my-lib.js'

  console.log(sum(2, 3))
</script>

В другом проекте

import { sum } from 'my-lib'

Преимущества формата es

Tree-shaking

Сборщики могут удалить неиспользуемый код.

export function a() {}
export function b() {}
export function c() {}

Если импортируется только a, остальные функции не попадут в итоговый бандл.


Современный стандарт

Поддерживается:

  • Vite
  • Rollup
  • Webpack
  • Parcel
  • Node.js
  • браузерами

Лучшая оптимизация

ESM позволяет:

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

Недостатки es

Ограниченная совместимость со старыми системами

Старые версии:

  • Node.js;
  • Webpack;
  • CommonJS-сред;

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


Невозможность прямого использования в старых браузерах

Internet Explorer не поддерживает ES-модули.


Формат CommonJS (cjs)

Назначение

cjs — модульная система Node.js, существовавшая до появления ESM.

Использует:

require()
module.exports

Пример CommonJS

function sum(a, b) {
  return a + b
}

module.exports = {
  sum
}

Импорт:

const { sum } = require('./math')

Сборка в формате cjs

import { defineConfig } from 'vite'

export default defineConfig({
  build: {
    lib: {
      entry: 'src/index.js',
      formats: ['cjs']
    }
  }
})

Результат

dist/
  my-lib.cjs

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

Синхронная загрузка

Модули загружаются во время выполнения.

const lib = require('./lib')

Это ухудшает возможности оптимизации по сравнению с ESM.


Отличная поддержка Node.js

Большая часть старой экосистемы npm использует CommonJS.


Совместимость со старыми инструментами

Формат нужен для:

  • старых версий Node.js;
  • legacy-систем;
  • старых серверных приложений.

Недостатки cjs

Ограниченный tree-shaking

Из-за динамической природы require() сборщики хуже анализируют зависимости.


Устаревающая архитектура

Современная экосистема постепенно переходит на ESM.


Меньшая эффективность в браузере

CommonJS изначально создавался для Node.js.


Когда использовать cjs

Формат нужен, если библиотека:

  • ориентирована на Node.js;
  • должна поддерживать старые проекты;
  • используется в legacy-инфраструктуре.

Формат UMD (umd)

Назначение

umd означает Universal Module Definition.

Формат предназначен для работы одновременно:

  • в браузере;
  • в AMD;
  • в CommonJS;
  • в глобальной области видимости.

Универсальность UMD

UMD автоматически определяет среду выполнения.

Пример структуры:

(function (global, factory) {
  if (typeof exports === 'object') {
    module.exports = factory()
  } else if (typeof define === 'function' && define.amd) {
    define(factory)
  } else {
    global.MyLibrary = factory()
  }
})(this, function () {
  return {}
})

Сборка UMD

import { defineConfig } from 'vite'

export default defineConfig({
  build: {
    lib: {
      entry: 'src/index.js',
      name: 'MyLibrary',
      formats: ['umd']
    }
  }
})

Обязательный параметр name

Для UMD необходимо указать:

name: 'MyLibrary'

Это имя глобальной переменной:

window.MyLibrary

Подключение UMD в браузере

<script src="./my-lib.umd.js"></script>

<script>
  console.log(MyLibrary)
</script>

Преимущества UMD

Широкая совместимость

UMD работает практически везде.


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

Подходит для CDN и <script>.


Поддержка старых проектов

Многие библиотеки раньше распространялись именно в UMD:

  • jQuery
  • Lodash
  • Moment.js

Недостатки UMD

Большой размер

UMD содержит дополнительную обвязку.


Отсутствие полноценного tree-shaking

Оптимизация хуже, чем у ESM.


Устаревающий формат

Современные библиотеки всё реже используют UMD как основной формат.


Формат IIFE (iife)

Назначение

iife — Immediately Invoked Function Expression.

Код оборачивается в самовызывающуюся функцию.


Пример IIFE

(function () {
  console.log('library loaded')
})()

Сборка IIFE

import { defineConfig } from 'vite'

export default defineConfig({
  build: {
    lib: {
      entry: 'src/index.js',
      name: 'MyLibrary',
      formats: ['iife']
    }
  }
})

Результат

var MyLibrary = (function () {
  function sum(a, b) {
    return a + b
  }

  return {
    sum
  }
})()

Использование IIFE

<script src="./my-lib.iife.js"></script>

<script>
  console.log(MyLibrary.sum(1, 2))
</script>

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

Максимальная совместимость с браузерами

Не требует модульной системы.


Автономность

Код выполняется сразу после загрузки.


Простота подключения

Достаточно одного тега <script>.


Недостатки IIFE

Нет импортов и экспортов

Формат не поддерживает модульную архитектуру.


Нет tree-shaking

Весь код всегда включается в файл.


Глобальные переменные

Библиотека попадает в глобальный объект.


Плохая масштабируемость

IIFE подходит только для простых сценариев.


Сравнение форматов

Формат Модули Tree-shaking Node.js Browser Script Современность
es Да Отличный Да Да (type="module") Высокая
cjs Да Ограниченный Отличный Нет Средняя
umd Частично Слабый Да Да Низкая
iife Нет Нет Нет Да Низкая

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

На практике библиотеки часто публикуют несколько сборок.

Пример:

formats: ['es', 'cjs']

или:

formats: ['es', 'umd']

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

dist/
  my-lib.js
  my-lib.cjs
  my-lib.umd.js

Настройка имён файлов

Функция fileName

fileName: (format) => `my-library.${format}.js`

Результат

dist/
  my-library.es.js
  my-library.cjs.js
  my-library.umd.js

Полная конфигурация библиотеки

import { defineConfig } from 'vite'

export default defineConfig({
  build: {
    lib: {
      entry: 'src/index.js',
      name: 'MyLibrary',
      formats: ['es', 'cjs', 'umd'],
      fileName: (format) => `my-library.${format}.js`
    },
    rollupOptions: {
      external: ['vue'],
      output: {
        globals: {
          vue: 'Vue'
        }
      }
    }
  }
})

Параметр external

Исключение зависимостей из бандла

external: ['vue']

Vue не попадёт внутрь библиотеки.


globals для UMD/IIFE

При использовании UMD или IIFE внешние зависимости должны быть доступны глобально.

globals: {
  vue: 'Vue'
}

Тогда библиотека ожидает:

<script src="vue.global.js"></script>

Совместимость с package.json

Поле main

Обычно указывает на CommonJS-сборку.

{
  "main": "./dist/my-lib.cjs.js"
}

Поле module

Указывает на ESM-сборку.

{
  "module": "./dist/my-lib.es.js"
}

Поле exports

Современный способ описания экспортов.

{
  "exports": {
    ".": {
      "import": "./dist/my-lib.es.js",
      "require": "./dist/my-lib.cjs.js"
    }
  }
}

Современная стратегия публикации

Наиболее распространённая схема:

ESM  -> основной формат
CJS  -> совместимость с Node.js
UMD  -> CDN и браузеры

IIFE используется значительно реже.


Когда выбирать es

Подходит для:

  • современных frontend-библиотек;
  • Vite-проектов;
  • React;
  • Vue;
  • Svelte;
  • tree-shaking;
  • npm-пакетов нового поколения.

Когда выбирать cjs

Подходит для:

  • Node.js;
  • старых серверных приложений;
  • legacy npm-пакетов;
  • совместимости со старой экосистемой.

Когда выбирать umd

Подходит для:

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

Когда выбирать iife

Подходит для:

  • виджетов;
  • standalone-скриптов;
  • небольших библиотек;
  • встраиваемых решений.

Форматы и tree-shaking

Максимальная эффективность достигается только в ESM.

Причина — статический анализ импортов.

import { button } from 'ui-lib'

Сборщик может удалить всё остальное.


Почему UMD и IIFE хуже оптимизируются

UMD и IIFE:

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

Особенности работы Vite

При библиотечной сборке:

build.lib

Vite автоматически:

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

Генерация sourcemap

Для всех форматов можно включить sourcemap:

build: {
  sourcemap: true
}

Минификация

Все форматы поддерживают минификацию:

build: {
  minify: 'esbuild'
}

или:

build: {
  minify: 'terser'
}

Ограничения UMD и IIFE

Code splitting работает ограниченно или недоступен.

Причина — браузерная природа форматов и необходимость единого файла.


Влияние формата на размер сборки

Обычно:

ESM   -> минимальный размер
CJS   -> немного больше
UMD   -> ещё больше
IIFE  -> максимальный размер

Причина — дополнительные runtime-обёртки.


Практическая схема для современных библиотек

Минимальный набор

formats: ['es', 'cjs']

Для браузерной совместимости

formats: ['es', 'umd']

Для максимальной универсальности

formats: ['es', 'cjs', 'umd']

Почему многие библиотеки отказываются от UMD

Причины:

  • рост поддержки ESM;
  • современные CDN умеют работать с ES-модулями;
  • уменьшение роли старых сборщиков;
  • стремление уменьшить размер пакета.

Почему ESM стал стандартом

ESM обеспечивает:

  • лучшую производительность;
  • более эффективную сборку;
  • современную архитектуру модулей;
  • нативную поддержку браузерами;
  • улучшенный tree-shaking;
  • оптимальное разделение кода.

Именно поэтому большинство современных библиотек Vite ориентируются прежде всего на формат es.