ECMAScript-модули (кратко их называют ES-модулями) — это модули, формат которых описан в стандарте ECMAScript, при работе с которыми используются инструкции
В Node.js, начиная с версии 13.2.0, имеется стабильная поддержка ES-модулей.

Этот материал посвящён особенностям работы с ES-модулями в Node.js.
На платформе Node.js по умолчанию используются модули формата CommonJS. Для того чтобы платформа смогла бы использовать ES-модули, нужно кое-что сделать.
А именно, Node.js сможет пользоваться ES-модулями в следующих случаях:
Рассмотрим первые два способа работы с модулями (применение .mjs-файлов и использование
Легче всего сообщить Node.js о том, что некий файл надо воспринимать как ES-модуль, можно, дав этому файлу расширение
Код ES-модуля, приведённый ниже, хранится в файле
Другой ES-модуль,
Это — всё что нужно для того чтобы пользоваться ES-модулями в Node.js!
Попробуем запустить
В ответ будет выведено название месяца —
Поэкспериментировать с этим скриптом можно здесь.
По умолчанию Node.js воспринимает файлы с расширением
Теперь все .js-файлы в папке, содержащей такой
Переработаем наш пример. А именно — переименуем
Проверить это можно, выполнив в командной строке следующее:
Система выдаст
Вот ссылка на страницу с интерактивным примером.
Спецификатор — это строковой литерал, представляющий путь к тому месту, откуда нужно импортировать модуль.
В следующем примере кода спецификатором является строка
В Node.js существует три вида спецификаторов: относительные, простые и абсолютные
Импорт модуля с использованием относительного спецификатора приведёт к разрешению пути к импортируемому модулю относительно расположения текущего (импортирующего) модуля. Относительные спецификаторы обычно начинаются с символов
При использовании относительных спецификаторов нужно обязательно указывать расширение файла (
Простой спецификатор начинается с имени модуля (то есть — у него в начале нет символов
Например, если в
Простые спецификаторы применяются и при импорте встроенных модулей Node.js:
Абсолютные спецификаторы используются для импорта модулей с указанием абсолютного пути к ним:
Обратите внимание на то, что в абсолютном спецификаторе присутствует префикс
Стандартный механизм импорта ES-модулей всегда выполняет код модуля, упомянутого в команде вида
Иногда бывает так, что нужно импортировать модуль динамически. В таких случаях можно воспользоваться асинхронной функцией вида
Команда
Например, давайте сделаем так, чтобы модуль
Команда
Запустим в командной строке следующее:
Скрипт выведет
Поэкспериментировать с кодом можно здесь.
Разработчик может оказаться в ситуации, когда ему нужно импортировать CommonJS-модуль в ES-модуль, или выполнить обратную процедуру.
К счастью, Node.js позволяет, используя механизмы импорта по умолчанию, включать в состав ES-модулей CommonJS-модули:
При импорте CommonJS-модуля в ES-модуле то, что экспортировано в CommonJS-модуле с использованием команды
Однако, функция
Я рекомендую настолько, насколько это возможно, воздерживаться от смешивания модулей разных форматов.
В области видимости ES-модуля недоступны сущности, специфичные для CommonJS-модулей. Среди них можно отметить следующие:
Но для определения абсолютного пути к текущему модулю можно пользоваться свойством
Node.js позволяет работать с ES-модулями при условии, что расширением файла модуля является
Хотя делать этого и не рекомендуется, но, если нужно, CommonJS-модули можно импортировать в ES-модули, пользуясь выражением вида
Планируете ли вы полностью перейти на ES-модули в своих Node.js-проектах?


import и export:// ECMAScript-модуль
// инструкция import
import myFunc from './my-func';
//инструкция export
export myOtherFunc(param) {
const result = myFunc(param);
// ....
return otherResult;
}
В Node.js, начиная с версии 13.2.0, имеется стабильная поддержка ES-модулей.

Этот материал посвящён особенностям работы с ES-модулями в Node.js.
1. Условия, необходимые для работы с ES-модулями в Node.js
На платформе Node.js по умолчанию используются модули формата CommonJS. Для того чтобы платформа смогла бы использовать ES-модули, нужно кое-что сделать.
А именно, Node.js сможет пользоваться ES-модулями в следующих случаях:
- Если файл модуля имеет расширение
.mjs. - Или если в
package.jsonближайшей родительской папки модуля имеется конструкция{ «type»: «module» }. - Или если при запуске Node.js используется флаг
--input-type=module, и при этом код модуля передаётся платформе в виде строки с использованием аргумента--eval="<module-code>", или поступает изSTDIN.
Рассмотрим первые два способа работы с модулями (применение .mjs-файлов и использование
{ «type»: «module» } в package.json).?1.1. Расширение файлов .mjs
Легче всего сообщить Node.js о том, что некий файл надо воспринимать как ES-модуль, можно, дав этому файлу расширение
.mjs.Код ES-модуля, приведённый ниже, хранится в файле
month-from-date.mjs (обратите внимание на расширение .mjs). Модуль экспортирует функцию monthFromDate(), которая определяет название месяца по произвольной дате, переданной ей.// month-from-date.mjs (ES-модуль)
const MONTHS = ['January', 'February', 'March','April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'];
export function monthFromDate(date) {
if (!(date instanceof Date)) {
date = new Date(date);
}
return MONTHS[date.getMonth()];
}
Другой ES-модуль,
month.mjs, похожим образом использует инструкцию import для импорта функции monthFromDate() из модуля month-from-date.mjs. Этот модуль, кроме того, поддерживает приём аргументов из командной строки, выводя название месяца после обработки переданной ему даты:// month.mjs (ES-модуль)
import { monthFromDate } from './month-from-date.mjs';
const dateString = process.argv[2] ?? null;
console.log(monthFromDate(dateString));
Это — всё что нужно для того чтобы пользоваться ES-модулями в Node.js!
Попробуем запустить
month.mjs в командной строке:node ./month.mjs "2022-02-01"
В ответ будет выведено название месяца —
February.Поэкспериментировать с этим скриптом можно здесь.
?1.2. Использование { «type»: «module» } в package.json
По умолчанию Node.js воспринимает файлы с расширением
.js как CommonJS-модули. Для того чтобы такие файлы выглядели бы для Node.js как ES-модули, нужно просто записать в поле type файла package.json значение module:{
"name": "my-app",
"version": "1.0.0",
"type": "module",
// ...
}
Теперь все .js-файлы в папке, содержащей такой
package.json, будут восприниматься как ES-модули.Переработаем наш пример. А именно — переименуем
month-from-date.mjs в month-from-date.js, а month.mjs в month.js (не трогая инструкции import и export). Затем, в файл package.json, который находится в той же папке, что и эти файлы, внесём запись «type»: «module». После этого Node.js будет воспринимать наши .js-файлы как ES-модули.Проверить это можно, выполнив в командной строке следующее:
node ./month.js "2022-03-01"
Система выдаст
March.Вот ссылка на страницу с интерактивным примером.
2. Импорт ES-модулей
Спецификатор — это строковой литерал, представляющий путь к тому месту, откуда нужно импортировать модуль.
В следующем примере кода спецификатором является строка
path:// 'path' - это спецификатор
import module from 'path';
В Node.js существует три вида спецификаторов: относительные, простые и абсолютные
?2.1. Относительные спецификаторы
Импорт модуля с использованием относительного спецификатора приведёт к разрешению пути к импортируемому модулю относительно расположения текущего (импортирующего) модуля. Относительные спецификаторы обычно начинаются с символов
'.', '..' или './':// Относительные спецификаторы:
import module1 from './module1.js';
import module2 from '../folder/module2.mjs';
При использовании относительных спецификаторов нужно обязательно указывать расширение файла (
.js, .mjs и так далее).?2.2. Простые спецификаторы
Простой спецификатор начинается с имени модуля (то есть — у него в начале нет символов
'.', './', '..', '/') и используется для импорта встроенных модулей Node.js или модулей из папки node_modules.Например, если в
node_modules установлен пакет lodash-es, то импортировать его можно, воспользовавшись простым спецификатором:// Простые спецификаторы:
import lodash from 'lodash-es';
import intersection from 'lodash-es/intersection';
Простые спецификаторы применяются и при импорте встроенных модулей Node.js:
import fs from 'fs';
?2.3. Абсолютные спецификаторы
Абсолютные спецификаторы используются для импорта модулей с указанием абсолютного пути к ним:
// Абсолютный спецификатор:
import module from 'file:///usr/opt/module.js';
Обратите внимание на то, что в абсолютном спецификаторе присутствует префикс
file://.3. Динамический импорт модулей
Стандартный механизм импорта ES-модулей всегда выполняет код модуля, упомянутого в команде вида
import module from 'path', и импортирует этот модуль. Делается это вне зависимости от того, используется ли в коде этот модуль или нет.Иногда бывает так, что нужно импортировать модуль динамически. В таких случаях можно воспользоваться асинхронной функцией вида
import('./path-to-module'):async function loadModule() {
const {
default: defaultComponent,
component1
} = await import('./path-to-module');
// ...
}
loadModule();
Команда
import('./path-to-module') асинхронно загружает модуль и возвращает промис, результатом успешного разрешения которого являются компоненты импортированного модуля. Свойство default представляет собой результаты импорта, выполняемого по умолчанию, а именованные импорты оказываются в свойствах с соответствующими именами.Например, давайте сделаем так, чтобы модуль
month-from-date.js загружался бы в скрипте month.js только в том случае, если пользователь, при запуске скрипта, передал ему дату:// month.js (ES-модуль)
const dateString = process.argv[2] ?? null;
if (dateString === null) {
console.log('Please indicate date argument');
} else {
(async function() {
const { monthFromDate } = await import('./month-from-date.js');
console.log(monthFromDate(dateString));
})();
}
Команда
const { monthFromDate } = await import('./month-from-date.mjs') выполняет динамическую загрузку модуля и присваивает результат именованного экспорта константе с тем же именем, которое имеет экспортированная функция.Запустим в командной строке следующее:
node ./month.js "2022-04-01"
Скрипт выведет
April.Поэкспериментировать с кодом можно здесь.
4. Совместное использование модулей разных форматов
Разработчик может оказаться в ситуации, когда ему нужно импортировать CommonJS-модуль в ES-модуль, или выполнить обратную процедуру.
К счастью, Node.js позволяет, используя механизмы импорта по умолчанию, включать в состав ES-модулей CommonJS-модули:
// ES-модуль
import defaultComponent from './module.commonjs.js';
// используется `defaultComponent`...
При импорте CommonJS-модуля в ES-модуле то, что экспортировано в CommonJS-модуле с использованием команды
module.exports, превращается в импорт по умолчанию. Правда, надо отметить, что именованные импорты из CommonJS-модулей не поддерживаются.Однако, функция
require(), используемая для импорта CommonJS-модулей, не умеет импортировать ES-модули. Вместо неё в CommonJS-модулях можно, для импорта ES-модулей, использовать асинхронную функцию import():// CommonJS-модуль
async function loadESModule() {
const {
default: defaultComponent,
component1
} = await import('./module.es.mjs');
// ...
}
loadESModule();
Я рекомендую настолько, насколько это возможно, воздерживаться от смешивания модулей разных форматов.
5. ES-модули и окружение Node.js
В области видимости ES-модуля недоступны сущности, специфичные для CommonJS-модулей. Среди них можно отметить следующие:
- require()
- exports
- module.exports
- __dirname
- __filename
Но для определения абсолютного пути к текущему модулю можно пользоваться свойством
import.meta.url:// ES-модуль, путь к которому выглядит как "/usr/opt/module.mjs"
console.log(import.meta.url); // "file:///usr/opt/module.mjs"
6. Итоги
Node.js позволяет работать с ES-модулями при условии, что расширением файла модуля является
.mjs, или в том случае, если в ближайшей родительской папке модуля имеется файл package.json, содержащий конструкцию { «type»: «module» }. Если эти условия соблюдены — это значит, что у нас имеются следующие возможности по импорту модулей:- Можно воспользоваться относительным путём к модулю:
import module from './module.js'. - Можно применить абсолютный путь к модулю:
import module from 'file:///abs/path/module.js'. - Можно импортировать модули, которые имеются в папке
node_modules:import lodash from 'lodash-es'. - Можно импортировать встроенные модули Node.js:
import fs from 'fs'. - Модули можно импортировать и динамически, пользуясь конструкцией вида
import('./path-to-module').
Хотя делать этого и не рекомендуется, но, если нужно, CommonJS-модули можно импортировать в ES-модули, пользуясь выражением вида
import defaultImport from './common.js'. При этом то, что было экспортировано из CommonJS-модуля с использованием команды module.exports, превращается в ES-модуле в импорт по умолчанию.Планируете ли вы полностью перейти на ES-модули в своих Node.js-проектах?


muturgan
Весьма интересно, спасибо.
Это конечно перевод, но всё равно было бы интересно понять, какие преимущества даёт использование es модулей в node.js проекте
jhonyxakep
Имхо:
Лучше структуризация подключений библиотек
Модули сейчас поддерживаются и нодой, и браузерами, и разными утилитами упаковки, так что в целом более кроссплатформенно
Не помню как в node, но в chrome точно поддерживается top-level await, так что меньше мусора при написании асинхронного кода.
Меня правда расстраивает, что для таких библиотек был введен отдельный синтаксис, хотя тот же commonJS не оказывает влияния на него. Я обеспокоен, что из за особых синтаксисов, интерпретаторы js будут разрастатся по размеру и сложности, и это плохо повлияет на проникновение языка глубже в другие сферы
iliazeus
Мне кажется, что это, наоборот, очень хорошо, потому что такой особый синтаксис накладывает много ограничений на импорт модулей, позволяя иметь меньше магии и лучше оптимизироваться.