CommonJS vs ESM

CommonJS vs ESM

Никита Балихин

За время своего существования Javascript сменил несколько разных модульных систем. На текущий момент актуальными среди них остаются CommonJS и ESM (EcmaScript Modules). Фактически же стандартом являются только ESM: по ним есть спецификация, они нативно поддерживаются в современных браузерах и в Node.js (аж с версии 12.17.0).

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

CommonJS

CommonJS — модульный формат, который появился в Node.js и никогда не поддерживался в браузерах. Он добавляет в язык глобальную переменную module и функцию require.

Синтаксис CommonJS

CommonJS модули:

  • Синхронныеrequire всегда выполняется синхронно, приостанавливая выполнение программы на время подключения модуля;
  • Динамические — вызов require и изменение module.exports могут находиться в любом блоке кода в том числе в условии и цикле.
Динамический require

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

Динамическая природа CommonJS не позволяет сборщикам (например, Webpack) однозначно построить дерево зависимостей в приложении и удалить неиспользуемый код из итогового бандла (Tree Shaking), что негативно скажется на его размере.

ESM

EcmaScript Modules — модульный формат, который в первую очередь появился в виде спецификации, затем был реализован в браузерах и впоследствии также появился в Node.js. ESM привносит в Javascript ключевые слова import и export.

Синтаксис ESM

EcmaScript модули:

  • Асинхронныеimport не приостанавливает выполнение программы (но в Node.js они по прежнему синхронные);
  • Статические — операторы import и export могут находиться только на верхнем уровне модуля, что исключает возможность делать условные связи между модулями.
Статический import

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

Динамический import

Асинхронность позволяет браузеру загружать ES модули параллельно, что актуально со времён появления протокола HTTP/2, поддерживающего мультиплексирование запросов (если, конечно, приложение поставляется пользователю в виде нативных ES модулей, а не бандлом).

Статическая природа ESM позволяет сборщику однозначно построить дерево зависимостей приложения и избавиться от лишнего кода. Динамические же импорты будут вынесены сборщиком в отдельные чанки вне основного бандла, поэтому они не сказываются на работе механизма Tree Shaking.

Поддержка ESM

EcmaScript модули на текущий момент имеют достаточно широкую поддержку. Опенсорс разработчик Sindre Sorhus (в вашем package-lock.json наверняка найдутся find-up или chalk — это всё его разработки) с определённого момента решил толкать экосистему Javascript в сторону ESM достаточно радикальным образом: он начал публиковать свои пакеты исключительно в ESM формате и написал инструкцию по переходу на ESM.

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

Использование ESM в Node.js

Для использования ESM пакетов в Node.js ваше приложение должно тоже быть в формате ESM, так как подключить ES модуль в CommonJS нельзя, а вот наоборот — вполне возможно.
Добиться этого можно двумя способами:

  • Расширение .mjs (для Javascript) или .mts (для Typescript) у файлов
  • "type": "module" в package.json

В импортах в настоящих ES модулях при этом нужно явно указывать расширение файлов.

Заключение

ESM — будущее (и отчасти настоящее) экосистемы Javascript. По возможности разработку стоит вести именно в этом модульном формате, так как он имеет ряд объективных преимуществ и имеет "future proof" в виде спецификации, но на текущий момент особенно в энтерпрайз разработке не стоит забывать и о CommonJS, поставляя пользователям библиотеки код сразу в обоих модульных форматах для максимальной совместимости.

Report Page