import
и export
:
// ECMAScript-модуль// инструкция importimport myFunc from './my-func';//инструкция exportexport 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-проектах?
