ESM (или модули ECMAScript) — это современный формат модулей со многими преимуществами по сравнению с предыдущими форматами, такими как CommonJS. Он изначально поддерживается в большинстве веб-браузеров, работает очень быстро и, помимо других функций, открывает новые возможности для встряхивания дерева. Однако это серьезное изменение по сравнению с CommonJS/AMD/UMD, и его может быть сложно использовать, если вы привыкли к одному из этих форматов модулей.


Что такое ЭСМ?

Модули ECMAScript — это формат модуля, созданный как часть стандарта ECMAScript (читай: JavaScript). Он был стандартизирован в версии ES6 ECMAScript, которую вы, возможно, знаете по добавлению многих синтаксических функций. ES Modules призван решить важную проблему в JavaScript: нет встроенного способа обмена кодом между сценариями. Возможно, вы знакомы с импортом вещей с помощью require(). Это использование CommonJS, который поддерживается только некоторыми сборщиками и Node.js. Кроме того, с CommonJS есть некоторые проблемы, например, его синхронный характер. ESM стремится решить все эти проблемы, сделав единый формат модуля универсальным. Сегодня Chrome, Safari и Firefox полностью поддерживают ESM, поэтому у вас не должно возникнуть проблем с его запуском в современных браузерах. Кроме того, Node v12+ поддерживает ESM, хотя вы должны указать, что используете ESM, а не CommonJS. Подробнее о том, как это сделать, мы поговорим позже.


Синтаксис ЕСМ

Синтаксис, используемый для импорта и экспорта модулей, немного сложнее, чем в других модульных системах, таких как CommonJS, но его все же довольно легко понять. Самый простой пример:

// script.js
import { example } from "./library.js";
example();

// library.js
export const example = function () {
    console.log("hello world");
};
Войти в полноэкранный режим

Выйти из полноэкранного режима

Здесь мы импортируем функцию с именем example из library.js. Обратите внимание на скобки вокруг example. Это показывает, что мы только импортируем example. Итак, если мы хотим импортировать несколько вещей, мы можем сделать это:

import { example, example2 } from "./library.js";
Войти в полноэкранный режим

Выйти из полноэкранного режима

Как видите, мы поставили оба example а также example2 в скобках, чтобы импортировать обе экспортированные переменные. А что, если экспортируется только одна вещь? С использованием export defaultмы можем удалить скобки.

// script.js
import defaultExample from "./library.js";
defaultExample();

// library.js
export default function () {
    console.log("hello world");
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

Экспорт по умолчанию также позволяет нам называть импортируемое значение как угодно. Мы также можем сделать это с именованным экспортом, но это немного сложнее:

import { example as newExampleName } from "./library.js";
newExampleName();
Войти в полноэкранный режим

Выйти из полноэкранного режима

Еще одна вещь, которую вы, возможно, захотите сделать, это динамически импортировать что-то. Динамический импорт работает так же, как require() в CommonJS, за исключением того, что они работают асинхронно:

import("./library.js").then((library) => {
    library.example();
});
Войти в полноэкранный режим

Выйти из полноэкранного режима

Наконец, вы можете захотеть импортировать все функции в модуль, что вы можете сделать с помощью import * as:

// script.js
import * as library from "./library.js";
library.example();
library.example2();

// library.js
export const example = function () {
    console.log("hello world");
};
export const example2 = function () {
    console.log("hello world again");
};
Войти в полноэкранный режим

Выйти из полноэкранного режима


Использование ESM с узлом

Убедитесь, что вы используете Node v12 или выше.


По файлу

Самый простой способ использовать ESM в Node.js — просто заменить .js с .mjs как расширение файла для скрипта, в котором вы хотите использовать ESM. Новое расширение файла сообщает Node, что он должен рассматривать файл как ESM, а не как CJS. Это работает и в другую сторону. Вы можете использовать .cjs чтобы обозначить файл как CommonJS, если по умолчанию используется ESM (подробнее об этом в следующем разделе).


Полный пакет

Часто вы хотите, чтобы весь пакет был ESM по умолчанию. Для этого необходимо добавить "type": "module" на ваш package.json. Добавление "type": "module" означает, что файлы будут рассматриваться как файлы ESM, если они не имеют .cjs расширение. Однако зависимости могут по-прежнему использовать CommonJS.


Импорт модулей CJS из ESM

Чтобы облегчить переход на ESM, Node предлагает возможность загружать модули CommonJS из ESM. Вы можете получить module.exports объект, импортировав его как экспорт по умолчанию. Например:

// script.mjs
import example from "example-package";
example.helloworld();

// example-package index.cjs
module.exports = {
    helloworld: function () {
        console.log("hello world");
    },
};
Войти в полноэкранный режим

Выйти из полноэкранного режима

Эта функция очень полезна, поскольку позволяет использовать пакеты NPM, созданные с помощью CommonJS.


Импорт модулей ES из CJS

К сожалению, Node не поддерживает импорт модулей ES из модулей CommonJS. К счастью, многие компиляторы позволяют конвертировать ESM в CommonJS, что позволяет вам написать библиотеку, которая предлагает отличный опыт для обеих групп. TypeScript является наиболее заметным в этой категории. Вы можете писать код с помощью модулей ES и ориентироваться как на ESM, так и на CJS.


Миграция с CJS на ESM

Существуют инструменты, облегчающие перенос кода с CommonJS на модули ES. Одним из самых популярных инструментов является cjstoesm, инструмент командной строки, который автоматически преобразует код CommonJS в Node в его эквивалент ESM. Почти весь код CommonJS трансформируется. Однако есть вещи, которые не трансформируются. Наиболее примечательным является __dirname. __dirname не является частью Node.js, но это то, что Node не поддерживает в «режиме ESM». К счастью, есть заменители. Простой способ полифилла __dirname это сделать это:

const __dirname = new URL(".", import.meta.url).pathname;
Войти в полноэкранный режим

Выйти из полноэкранного режима

Этот фрагмент использует import.meta.urlкоторый доступен во всех контекстах ESM.

Еще одна вещь, которую сложнее мигрировать, — это условный импорт. Вы можете использовать динамический импорт для замены запущенных require() динамически, но с этим возникают проблемы из-за того, как import() является асинхронным и require() не является. К счастью, есть простое решение. Если вы используете Node.js v14.8 или более позднюю версию, вы можете использовать ожидание верхнего уровня делать import() синхронный. Например,

// CommonJS
const module = boolean ? require("module1") : require("module2");

// ES Modules
const module = await (boolean ? import("module1") : import("module2"));
Войти в полноэкранный режим

Выйти из полноэкранного режима

С помощью этих советов у вас не должно возникнуть особых проблем с преобразованием кода CommonJS в модули ES.


Использование ESM в Интернете


Родной ЕСМ

Самый простой способ использовать ESM в Интернете — использовать его встроенную поддержку. Примерно для более чем 95% пользователей (Каниус), ESM поддерживается без полифиллов. Чтобы загрузить скрипт с ESM, вам нужно добавить type="module" в теге script.

<script src="./esmscript.js" type="module">
Войти в полноэкранный режим

Выйти из полноэкранного режима

Затем все модули, которые вы импортируете из этого скрипта, также будут загружаться как модули ES.

Чтобы создать сценарий резервного копирования для браузеров, не поддерживающих модули ES, вы можете использовать nomodule атрибут. nomodule сообщает любому браузеру, который поддерживает модули ES, не загружать скрипт, поэтому его будут загружать только браузеры без поддержки ESM.


Бандлеры

Хотя просто использовать встроенную поддержку ESM в браузере, вы упустите множество функций и оптимизаций, таких как встряхивание дерева, поддержка зависимостей CommonJS или автоматическое создание резервных копий. К счастью, многие упаковщики поддерживают ESM по умолчанию. Наиболее известными современными сборщиками ESM для Интернета являются Быстрый. Vite — это чрезвычайно быстрый и многофункциональный сборщик, созданный командой Vue. По умолчанию Vite минимизирует и оптимизирует ваш код, и вы можете сделать гораздо больше с плагинами Vite/Rollup. Чтобы создать проект Vite, вам нужно запустить npm create vite@latest. Это поможет вам настроить проект с Vite с помощью модулей ES. Если вы хотите, чтобы поддержка устаревшего браузера использовалась nomoduleвы можете использовать плагин @vitejs/плагин-наследие. Еще одна функция, которую Vite, как и большинство других сборщиков, имеет, — это поддержка зависимостей, использующих CommonJS. Очевидно, что CommonJS требует преобразования и, следовательно, может увеличить вес кода, но это лучше, чем ничего, и вы все равно можете использовать ESM вместе с ним.


Вывод

Теперь вы знакомы с синтаксисом модулей ES и с тем, как реализовать его в Интернете и в Node.js. Обязательно подпишитесь на рассылку новостей или RSS здесь. Я надеюсь, что эта статья была полезной для вас, и спасибо за чтение.