Как использовать ESM в браузере и в Node.js
ESM (или модули ECMAScript) — это современный формат модулей со многими преимуществами по сравнению с предыдущими форматами, такими как CommonJS. На данный момент он поддерживается в большинстве веб-браузеров, работает очень быстро и, помимо других функций, открывает новые возможности для tree shaking. Однако у этого формата есть серьезные отличия от CommonJS/AMD/UMD, и его может быть сложно использовать, если вы привыкли к одному из старых форматов модулей.
Что такое ESM?
Модули ECMAScript — это формат модуля, созданный как часть стандарта ECMAScript (читай: JavaScript). Он был стандартизирован в версии ES6 ECMAScript, которую вы, возможно, знаете по добавлению многих новых синтаксических функций. ES Modules призван решить важную проблему в JavaScript: отсутствие встроенного способа обмена кодом между сценариями. Возможно, вы знакомы с импортом с помощью require(). Это формат CommonJS, который поддерживается только некоторыми сборщиками и Node.js. Кроме того, с CommonJS есть некоторые проблемы, например, его синхронная сущность. ESM стремится решить все эти проблемы, сделав единый формат модуля более универсальным. Сегодня Chrome, Safari и Firefox полностью поддерживают ESM, поэтому у вас не должно возникнуть проблем с его запуском в современных браузерах. Кроме того, Node v12+ так же поддерживает ESM, хотя вы должны указать, что вы хотите использовать ESM, а не CommonJS. Подробнее о том, как это сделать, мы поговорим позже.
Синтаксис ESM
Синтаксис, используемый для импорта и экспорта модулей, немного сложнее, чем в других модульных системах, таких как CommonJS, но его все же довольно легко понять. Самый простой пример:
// script.js import { example } from "./library.js"; example(); // library.js export const example = function () { console.log("hello world"); };
Здесь мы импортируем функцию с именем example из library.js. Обратите внимание на скобки вокруг примера. Это показывает, что мы импортируем только пример. Итак, если мы хотим импортировать несколько вещей, мы можем сделать это:
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
Убедитесь, что вы используете Node v12 или выше.
Per-file
Самый простой способ использовать ESM в Node.js — просто заменить .js на .mjs в качестве расширения файла скрипта, в котором вы хотите использовать ESM. Новое расширение файла сообщает Node, что он должен рассматривать файл как ESM, а не как CJS. Это работает и в другую сторону. Вы можете использовать .cjs для обозначения файла как CommonJS, если по умолчанию используется ESM (подробнее об этом в следующем разделе).
Package-wide
Часто вы хотите, чтобы весь пакет был ESM по умолчанию. Для этого вы должны добавить «type»: «module» в ваш package.json. Добавление «типа»: «модуль» означает, что файлы будут рассматриваться как файлы 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 Modules из 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 версии 14.8 или более поздней, вы можете использовать top-level await , чтобы сделать import() синхронным. Например,
// CommonJS const module = boolean ? require("module1") : require("module2"); // ES Modules const module = await (boolean ? import("module1") : import("module2"));
С помощью этих советов у вас не должно возникнуть особых проблем с преобразованием кода CommonJS в модули ES.
Использование ESM в браузерах
Native ESM
Самый простой способ использовать ESM в браузере — использовать его встроенную поддержку. Примерно для 95 % пользователей (Caniuse) ESM поддерживается без полифиллов. Чтобы загрузить скрипт с помощью ESM, вам нужно добавить type=»module» в тег скрипта.
<script src="./esmscript.js" type="module">
Затем все модули, которые вы импортируете из этого скрипта, также будут загружаться как модули ES.
Чтобы создать резервный сценарий для браузеров, не поддерживающих модули ES, вы можете использовать атрибут nomodule. nomodule сообщает любому браузеру, который поддерживает модули ES, не загружать скрипт, поэтому его будут загружать только браузеры без поддержки ESM.
Сборщики
В то время как при использование встроенной поддержки ESM в браузере, вы упускаете множество функций и оптимизаций, таких как tree shaking, поддержка зависимостей CommonJS или автоматическое создание резервных копий. К счастью, многие упаковщики поддерживают ESM по умолчанию. Наиболее известными современными сборщиками ESM для Интернета является Vite. Vite — это чрезвычайно быстрый и многофункциональный сборщик, созданный командой Vue. По умолчанию Vite минимизирует и оптимизирует ваш код, и вы можете сделать гораздо больше с плагинами Vite/Rollup. Чтобы создать проект Vite, вам нужно запустить npm create vite@latest. Это поможет вам настроить проект с Vite с помощью модулей ES. Если вам нужна поддержка устаревшего браузера с помощью nomodule, вы можете использовать плагин @vitejs/plugin-legacy. Еще одна функция, которую Vite, такая же как у большинства других сборщиков, — это поддержка зависимостей, использующих CommonJS. Очевидно, что CommonJS требует преобразования и, следовательно, может увеличить вес кода, но это лучше, чем ничего, и вы все равно можете использовать ESM вместе с ним.
Заключение
Теперь вы знакомы с синтаксисом модулей ES и с тем, как реализовать его в Интернете и в Node.js. Обязательно подпишитесь на новостную рассылку или RSS здесь. Я надеюсь, что эта статья была полезной для вас, и спасибо за чтение.
Перевод статьи: How to use ESM on the web and in Node.js
Спасибо за статью!
Очень полезно и интересно.