Как стать автором
Обновить
2660.07
RUVDS.com
VDS/VPS-хостинг. Скидка 15% по коду HABR15

Использование ECMAScript-модулей в Node.js

Время на прочтение 6 мин
Количество просмотров 20K
Автор оригинала: Dmitri Pavlutin
ECMAScript-модули (кратко их называют ES-модулями) — это модули, формат которых описан в стандарте ECMAScript, при работе с которыми используются инструкции 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-проектах?


Теги:
Хабы:
+33
Комментарии 4
Комментарии Комментарии 4

Публикации

Информация

Сайт
ruvds.com
Дата регистрации
Дата основания
Численность
11–30 человек
Местоположение
Россия
Представитель
ruvds