Модули: ECMAScript модули
Введение
ECMAScript модули — это официальный стандартный формат для упаковки кода JavaScript для повторного использования. Модули определяются с помощью различных import и export операторов.
Следующий пример ES модуля экспортирует функцию:
// addTwo.mjs
function addTwo(num) {
return num + 2;
}
export { addTwo }; copy Следующий пример ES модуля импортирует функцию из addTwo.mjs:
// app.mjs
import { addTwo } from './addTwo.mjs';
// Prints: 6
console.log(addTwo(4)); copy Node.js полностью поддерживает ECMAScript модули в соответствии с текущим спецификацией и обеспечивает меж совместимость между ними и исходным форматом модулей CommonJS.
Включение
Node.js имеет две системы модулей: модули CommonJS и ECMAScript модули.
Авторы могут указать Node.js интерпретировать JavaScript как ES модуль через .mjs расширение файла, package.json "type" поле со значением "module", --input-type флаг со значением "module", или --experimental-default-type флаг со значением "module". Это явные маркеры кода, предназначенного для выполнения в качестве ES модуля.
Обратно, авторы могут указать Node.js интерпретировать JavaScript как CommonJS через .cjs расширение файла, package.json "type" поле со значением "commonjs", --input-type флаг со значением "commonjs", или --experimental-default-type флаг со значением "commonjs".
Когда код не содержит явных маркеров для какой-либо системы модулей, Node.js проверит исходный код модуля на наличие синтаксиса ES модулей. Если такой синтаксис найден, Node.js выполнит код как ES модуль; в противном случае модуль будет выполнен как CommonJS. Подробнее см. Определение системы модулей.
Пакеты
Этот раздел перенесен в Модули: Пакеты.
import Спецификаторы
Терминология
Спецификатор оператора import — это строка после ключевого слова from, например, 'node:path' в import { sep } from 'node:path'. Спецификаторы также используются в операторах export from и в качестве аргумента выражения import().
Существует три типа спецификаторов:
-
Относительные спецификаторы, такие как
'./startup.js'или'../config.mjs'. Они ссылаются на путь относительно расположения файла-импортера. Расширение файла всегда необходимо для этих спецификаторов. -
Обычные спецификаторы, такие как
'some-package'или'some-package/shuffle'. Они могут ссылаться на основную точку входа пакета по имени пакета или на определенный модуль функций в рамках пакета, префикс которого задается именем пакета, как в примерах соответственно. Включение расширения файла необходимо только для пакетов без поля"exports". -
Абсолютные спецификаторы, такие как
'file:///opt/nodejs/config.js'. Они напрямую и явно ссылаются на полный путь.
Разрешения обычных спецификаторов обрабатываются алгоритмом разрешения и загрузки модулей Node.js. Все остальные разрешения спецификаторов всегда разрешаются только с помощью стандартной семантики разрешения относительных URL.
Как и в CommonJS, файлы модулей в рамках пакетов могут быть доступны путем добавления пути к имени пакета, если только в поле пакета package.json не содержится поле "exports", в этом случае доступ к файлам внутри пакетов возможен только через пути, определенные в "exports".
Подробности о правилах разрешения пакетов, которые применяются к обычным спецификаторам в разрешении модулей Node.js, см. в документации по пакетам.
Обязательные расширения файлов
Расширение файла должно быть предоставлено при использовании ключевого слова import для разрешения относительных или абсолютных спецификаторов. Индекс каталога (например, './startup/index.js') также должен быть полностью указан.
Это поведение соответствует тому, как работает import в браузерных средах, предполагая типично настроенный сервер.
URL-адреса
ES модули разрешаются и кэшируются как URL-адреса. Это означает, что специальные символы должны быть кодированы по процентам, например, # с %23 и ? с %3F.
file:, node:, и data: схемы URL поддерживаются. Спецификатор, такой как 'https://example.com/app.js', не поддерживается напрямую в Node.js, если не используется настраиваемый HTTPS-загрузчик.
file: URL-адреса
Модули загружаются несколько раз, если используемый для их разрешения спецификатор import имеет разные запросы или фрагменты.
import './foo.mjs?query=1'; // loads ./foo.mjs with query of "?query=1" import './foo.mjs?query=2'; // loads ./foo.mjs with query of "?query=2" copy
Корень тома можно указать через /, //, или file:///. Учитывая различия между разрешением URL и путей (таких как особенности кодирования процентов), рекомендуется использовать url.pathToFileURL при импорте пути.
data: импорты
data: URL-адреса поддерживаются для импорта со следующими типами MIME:
-
text/javascriptдля ES модулей -
application/jsonдля JSON -
application/wasmдля Wasm
import 'data:text/javascript,console.log("hello!");';
import _ from 'data:application/json,"world!"' with { type: 'json' }; copy data: URL-адреса разрешают только обычные спецификаторы для встроенных модулей и абсолютные спецификаторы. Разрешение относительных спецификаторов не работает, потому что data: не является специальной схемой. Например, попытка загрузить ./foo из data:text/javascript,import "./foo"; не приводит к разрешению, потому что нет понятия относительного разрешения для data: URL-адресов.
node: импорты
node: URL-адреса поддерживаются в качестве альтернативного способа загрузки встроенных модулей Node.js. Эта схема URL позволяет ссылаться на встроенные модули с помощью допустимых абсолютных строк URL-адресов.
import fs from 'node:fs/promises'; copy
Атрибуты импорта
Эта функция ранее называлась «утверждения импорта», и использовалось ключевое слово
assertвместоwith. Все используемые в коде ключевые словаassertдолжны быть обновлены наwith.
Предложение Атрибуты импорта добавляет встроенный синтаксис для операторов импорта модулей для передачи дополнительной информации вместе со спецификатором модуля.
import fooData from './foo.json' with { type: 'json' };
const { default: barData } =
await import('./bar.json', { with: { type: 'json' } }); copy Node.js поддерживает следующие type значения, для которых атрибут является обязательным:
Атрибут type
|
Необходим для |
|---|---|
'json' |
JSON модули |
Встроенные модули
Ядерные модули предоставляют именованные экспорты своего публичного API. Также предоставляется экспорт по умолчанию, который соответствует значению экспорта CommonJS. Экспорт по умолчанию может быть использован, среди прочего, для изменения именованных экспортов. Именованные экспорты встроенных модулей обновляются только путем вызова module.syncBuiltinESMExports().
import EventEmitter from 'node:events'; const e = new EventEmitter(); copy
import { readFile } from 'node:fs';
readFile('./foo.txt', (err, source) => {
if (err) {
console.error(err);
} else {
console.log(source);
}
}); copy import fs, { readFileSync } from 'node:fs';
import { syncBuiltinESMExports } from 'node:module';
import { Buffer } from 'node:buffer';
fs.readFileSync = () => Buffer.from('Hello, ESM');
syncBuiltinESMExports();
fs.readFileSync === readFileSync; copy
import() выражения
Динамические import() поддерживаются как в CommonJS, так и в ES модулях. В CommonJS модулях он может использоваться для загрузки ES модулей.
import.meta
Свойство import.meta meta — это Object, содержащее следующие свойства.
import.meta.dirname
-
<строка> Имя каталога текущего модуля. Оно совпадает с
path.dirname()пути кimport.meta.filename.
Примечание: присутствует только в модулях
file:.
import.meta.filename
- <строка> Полный абсолютный путь и имя файла текущего модуля, с
- разрешенными символическими ссылками.
- Это соответствует
url.fileURLToPath()объекта -
import.meta.url.
Примечание: доступно только для локальных модулей. Модули, не использующие протокол
file:, не предоставляют это свойство.
import.meta.url
-
<строка> Абсолютный
file:URL модуля.
Определяется точно так же, как в браузерах, предоставляя URL текущего файла модуля.
Это позволяет использовать полезные шаблоны, такие как загрузка файлов относительно текущего:
import { readFileSync } from 'node:fs';
const buffer = readFileSync(new URL('./data.proto', import.meta.url)); copy
import.meta.resolve(specifier)
-
specifier<строка> Спецификатор модуля для разрешения относительно текущего модуля. - Возвращает: <строка> Абсолютный URL-строку, к которой бы сопоставлялся указанный спецификатор.
import.meta.resolve — функция разрешения относительно модуля, привязанная к каждому модулю, возвращающая строку URL.
const dependencyAsset = import.meta.resolve('component-lib/asset.css');
// file:///app/node_modules/component-lib/asset.css
import.meta.resolve('./dep.js');
// file:///app/dep.js copy Поддерживаются все функции разрешения модулей Node.js. Разрешение зависимостей подчиняется разрешенным экспортам в пакете.
Примечания:
- Это может привести к синхронным операциям с файловой системой, что может повлиять на производительность, как и
require.resolve. - Эта функция недоступна внутри пользовательских загрузчиков (это создаст тупик).
Нестандартный API:
При использовании флага --experimental-import-meta-resolve, функция принимает второй аргумент:
Взаимодействие с CommonJS
import операторы
Оператор import может ссылаться на ES-модуль или CommonJS-модуль. import операторы разрешены только в ES-модулях, но динамические import() выражения поддерживаются в CommonJS для загрузки ES-модулей.
При импорте CommonJS-модулей объект module.exports предоставляется в качестве значения по умолчанию. Имена экспорта могут быть доступны, предоставленные статическим анализом для лучшей совместимости экосистемы.
require
CommonJS-модуль require всегда рассматривает файлы, на которые он ссылается, как CommonJS.
Использование require для загрузки ES-модуля не поддерживается, поскольку ES-модули имеют асинхронное выполнение. Вместо этого используйте import() для загрузки ES-модуля из CommonJS-модуля.
Пространства имен CommonJS
CommonJS-модули состоят из объекта module.exports, который может быть любого типа.
При импорте CommonJS-модуля его можно надёжно импортировать, используя импорт ES-модуля по умолчанию или соответствующий синтаксический сахар:
import { default as cjs } from 'cjs';
// The following import statement is "syntax sugar" (equivalent but sweeter)
// for `{ default as cjsSugar }` in the above import statement:
import cjsSugar from 'cjs';
console.log(cjs);
console.log(cjs === cjsSugar);
// Prints:
// <module.exports>
// true copy Представление пространства имён ECMAScript Module для CommonJS-модуля всегда является пространством имён с ключом экспорта default, указывающим на значение CommonJS module.exports.
Этот экзотический объект пространства имён модуля можно непосредственно наблюдать при использовании import * as m from 'cjs' или динамического импорта:
import * as m from 'cjs';
console.log(m);
console.log(m === await import('cjs'));
// Prints:
// [Module] { default: <module.exports> }
// true copy Для лучшей совместимости с существующим использованием в экосистеме JavaScript, Node.js также пытается определить именованные экспорты CommonJS каждого импортированного CommonJS-модуля, чтобы предоставить их как отдельные экспорты ES-модулей с помощью процесса статического анализа.
Например, рассмотрим CommonJS-модуль, написанный:
// cjs.cjs exports.name = 'exported'; copy
Предыдущий модуль поддерживает именованные импорты в ES-модулях:
import { name } from './cjs.cjs';
console.log(name);
// Prints: 'exported'
import cjs from './cjs.cjs';
console.log(cjs);
// Prints: { name: 'exported' }
import * as m from './cjs.cjs';
console.log(m);
// Prints: [Module] { default: { name: 'exported' }, name: 'exported' } copy Как видно из последнего примера вывода экзотического объекта пространства имён модуля, экспорт name копируется из объекта module.exports и устанавливается непосредственно в пространстве имён ES-модуля при импорте модуля.
Обновления живой привязки или новые экспорты, добавленные к module.exports, не обнаруживаются для этих именованных экспортов.
Обнаружение именованных экспортов основано на общих синтаксических шаблонах, но не всегда правильно обнаруживает именованные экспорты. В таких случаях использование формы импорта по умолчанию, описанной выше, может быть лучшим вариантом.
Обнаружение именованных экспортов охватывает многие распространенные шаблоны экспорта, шаблоны переэкспорта и результаты работы инструментов сборки и транспайлеров. См. cjs-module-lexer для точной семантики реализованной функциональности.
Отличия между ES-модулями и CommonJS
Отсутствуют require, exports, или module.exports
В большинстве случаев ES-модуль import может использоваться для загрузки CommonJS-модулей.
Если необходимо, функцию require можно создать внутри ES-модуля, используя module.createRequire().
Отсутствуют __filename или __dirname
Эти переменные CommonJS недоступны в ES-модулях.
Случаи использования __filename и __dirname можно воспроизвести с помощью import.meta.filename и import.meta.dirname.
Отсутствует загрузка плагинов
Плагины в настоящее время не поддерживаются с импортами ES-модулей.
Их можно загрузить с помощью module.createRequire() или process.dlopen.
Отсутствует require.resolve
Относительное разрешение можно обработать через new URL('./local', import.meta.url).
Для полного require.resolve замещения существует API import.meta.resolve.
В качестве альтернативы можно использовать module.createRequire().
Отсутствует NODE_PATH
NODE_PATH не участвует в разрешении import спецификаторов. Пожалуйста, используйте символические ссылки, если это поведение нужно.
Отсутствуют require.extensions
require.extensions не используется import. Модульные пользовательские крючки могут предоставить замену.
Отсутствует require.cache
require.cache не используется import, так как у загрузчика ES-модулей есть свой отдельный кэш.
JSON-модули
Файлы JSON могут быть проинициализированы оператором import:
import packageConfig from './package.json' with { type: 'json' }; copy Синтаксис with { type: 'json' } обязателен; см. Атрибуты импорта.
Импортированный JSON экспонирует только экспорт default. Поддержка именованных экспортов отсутствует. В кэше CommonJS создаётся запись, чтобы избежать дублирования. Тот же объект возвращается в CommonJS, если JSON-модуль уже был импортирован из того же пути.
Модули Wasm
Импорт модулей WebAssembly поддерживается под флагом --experimental-wasm-modules, что позволяет импортировать любые файлы .wasm как обычные модули, а также поддерживает их импорты модулей.
Эта интеграция соответствует Предложению по интеграции ES-модулей для WebAssembly.
Например, файл index.mjs содержащий:
import * as M from './module.wasm'; console.log(M); copy
выполненный под:
node --experimental-wasm-modules index.mjs copy
предоставит интерфейс экспорта для инициализации module.wasm.
Ключевое слово `await` на верхнем уровне модуля ECMAScript
Ключевое слово `await` может использоваться в теле модуля ECMAScript на верхнем уровне.
Предположим, у нас есть a.mjs,
export const five = await Promise.resolve(5); copy
И b.mjs, с
import { five } from './a.mjs';
console.log(five); // Logs `5` copy node b.mjs # works copy
Если выражение `await` на верхнем уровне модуля никогда не разрешается, процесс Node.js завершится со статусом ошибки.
import { spawn } from 'node:child_process';
import { execPath } from 'node:process';
spawn(execPath, [
'--input-type=module',
'--eval',
// Never-resolving Promise:
'await new Promise(() => {})',
]).once('exit', (code) => {
console.log(code); // Logs `13`
}); copy Импорты HTTPS и HTTP
Поддержка импорта модулей, основанных на сети, с использованием `import` и `require`, доступна при включении флага --experimental-network-imports. Это позволяет использовать импорты, похожие на веб-браузерные, в Node.js, но с некоторыми отличиями из-за соображений стабильности и безопасности, которые отличаются при работе в привилегированной среде, в отличие от браузерной среды.
Импорты ограничены HTTP/1
Автоматическое согласование протокола для HTTP/2 и HTTP/3 пока не поддерживается.
HTTP ограничен адресами обратной петли
http: уязвим к атакам «человек посередине» и не разрешен для использования с адресами за пределами IPv4-адресного 127.0.0.0/8 (127.0.0.1 до 127.255.255.255) и IPv6-адреса ::1. Поддержка импорта по HTTP предназначена для локального разработки.
Аутентификация никогда не отправляется на сервер назначения.
Заголовки Authorization, Cookie, и Proxy-Authorization не отправляются на сервер. Избегайте включения информации об пользователе в URL импорта. Разрабатывается модель безопасности для безопасного использования этих импортов на сервере.
CORS никогда не проверяется на целевом сервере
CORS предназначен для ограничения доступа к API определенным набором хостов. Эта функция не поддерживается, так как не имеет смысла для серверного имплементации.
Невозможно загрузить зависимости, не основанные на сети
Эти модули не могут получить доступ к другим модулям, которые не находятся по http: или https:. Чтобы сохранить доступ к локальным модулям, избегая проблемы безопасности, передавайте ссылки на локальные зависимости:
// file.mjs
import worker_threads from 'node:worker_threads';
import { configure, resize } from 'https://example.com/imagelib.mjs';
configure({ worker_threads }); copy // https://example.com/imagelib.mjs
let worker_threads;
export function configure(opts) {
worker_threads = opts.worker_threads;
}
export function resize(img, size) {
// Perform resizing in worker_thread to avoid main thread blocking
} copy Загрузка по сети не включена по умолчанию
Пока для загрузки ресурсов по http: или https: требуется флаг --experimental-network-imports. В будущем будет использован другой механизм для принудительного управления доступом. Требуется явное включение, чтобы предотвратить непреднамеренное использование потенциально изменчивого состояния зависимостями, что может повлиять на надёжность приложений Node.js.
Загрузчики
Документация по Загрузчикам теперь находится по адресу Модули: Настраиваемые крючки.
Резолюция и алгоритм загрузки
Функции
По умолчанию резольвер имеет следующие свойства:
- Резолюция на основе FileURL, как используется в ES модулях
- Резолюция относительных и абсолютных URL
- Отсутствуют расширения по умолчанию
- Отсутствуют главные папки
- Поиск резолюции пакетов с пустыми спецификаторами через node_modules
- Не генерирует ошибки при неизвестных расширениях или протоколах
- Может предоставить подсказку о формате загрузке
По умолчанию загрузчик имеет следующие свойства
- Поддержка загрузки встроенных модулей через
node:URL - Поддержка загрузки модулей "inline" через
data:URL - Поддержка загрузки модулей
file: - Возвращает ошибку при любом другом протоколе URL
- Возвращает ошибку при неизвестных расширениях для
file:загрузки (поддерживаются только.cjs,.js, и.mjs)
Алгоритм резолюции
Алгоритм загрузки спецификатора ES модуля задаётся методом ESM_RESOLVE ниже. Он возвращает резолированный URL для спецификатора модуля относительно parentURL.
Алгоритм резолюции определяет полный резолированный URL для загрузки модуля, а также его предполагаемый формат модуля. Алгоритм резолюции не определяет, может ли быть загружен протокол резолированного URL или разрешены ли расширения файлов, вместо этого эти валидации применяются Node.js во время фазы загрузки (например, если ему было предложено загрузить URL с протоколом, который не является file:, data:, node:, или если --experimental-network-imports включен, https:).
Алгоритм также пытается определить формат файла на основе расширения (см. алгоритм ESM_FILE_FORMAT ниже). Если он не распознаёт расширение файла (например, если это не .mjs, .cjs, или .json), то возвращается формат undefined, который вызовет ошибку во время фазы загрузки.
Алгоритм определения формата модуля резолированного URL предоставляется методом ESM_FILE_FORMAT, который возвращает уникальный формат модуля для любого файла. Формат "module" возвращается для ECMAScript модуля, в то время как формат "commonjs" используется для указания загрузки через устаревший загрузчик CommonJS. Дополнительные форматы, такие как "addon", могут быть расширены в будущих обновлениях.
Во всех следующих алгоритмах все ошибки подпрограмм распространяются как ошибки этих основных процедур, если не указано иное.
defaultConditions — массив имён условных сред, ["node", "import"].
Резольвер может выдать следующие ошибки:
- Неверный спецификатор модуля: спецификатор модуля является недопустимым URL, именем пакета или подпутем пакета.
- Недопустивая конфигурация пакета: конфигурация package.json недействительна или содержит недействительную конфигурацию.
- Неверная цель пакета: экспорт или импорт пакета определяет целевой модуль для пакета, который является недопустимым типом или строковой целью.
- Путь пакета не экспортирован: экспорт пакета не определяет или не разрешает целевой подпуть в пакете для данного модуля.
- Импорт пакета не определён: импорты пакета не определяют спецификатор.
- Модуль не найден: запрашиваемый пакет или модуль не существует.
- Неподдерживаемый импорт каталога: резолированный путь соответствует каталогу, который не является поддерживаемой целью для импорта модулей.
Спецификация алгоритма резолюции
ESM_RESOLVE(specifier, parentURL)
- Пусть resolved будет undefined.
- Если specifier является корректным URL, тогда
- Установить resolved в результат парсинга и ресериализации specifier как URL.
- В противном случае, если specifier начинается с "/", "./" или "../", тогда
- Установить resolved в результат резолюции URL specifier относительно parentURL.
- В противном случае, если specifier начинается с "#", тогда
- Установить resolved в результат PACKAGE_IMPORTS_RESOLVE(specifier, parentURL, defaultConditions).
- В противном случае,
- Примечание: specifier теперь является спецификатором без имени пакета.
- Установить resolved в результат PACKAGE_RESOLVE(specifier, parentURL).
- Пусть format будет undefined.
- Если resolved — это URL "file:", тогда
- Если resolved содержит какие-либо кодировки процентов "/" или "\" (соответственно "%2F" и "%5C"), то
- Выдать ошибку Неверный спецификатор модуля.
- Если файл по адресу resolved является каталогом, тогда
- Выдать ошибку Неподдерживаемый импорт каталога.
- Если файла по адресу resolved не существует, то
- Выдать ошибку Модуль не найден.
- Установить resolved в реальный путь resolved, сохраняя те же параметры запроса URL и фрагменты.
- Установить format в результат ESM_FILE_FORMAT(resolved).
- В противном случае,
- Установить format в формат модуля, связанный с типом содержимого URL resolved.
- Вернуть format и resolved фазе загрузки
PACKAGE_RESOLVE(packageSpecifier, parentURL)
```
- Assert: specifier начинается с "#".
- Если specifier точно равен "#" или начинается с "#/", то
- Выбросить ошибку Invalid Module Specifier.
- Пусть packageURL будет результатом LOOKUP_PACKAGE_SCOPE(parentURL).
- Если packageURL не null, то
- Пусть pjson будет результатом READ_PACKAGE_JSON(packageURL).
- Если pjson.imports — непустой объект, то
- Пусть resolved будет результатом PACKAGE_IMPORTS_EXPORTS_RESOLVE( specifier, pjson.imports, packageURL, true, conditions).
- Если resolved не null и не undefined, вернуть resolved.
- Выбросить ошибку Package Import Not Defined.
PACKAGE_IMPORTS_EXPORTS_RESOLVE(matchKey, matchObj, packageURL, isImports, conditions)
- Если matchKey — ключ в matchObj и не содержит "*", то
- Пусть target — значение matchObj[matchKey].
- Вернуть результат PACKAGE_TARGET_RESOLVE(packageURL, target, null, isImports, conditions).
- Пусть expansionKeys — список ключей в matchObj, содержащих только один "*", отсортированный по функции сортировки PATTERN_KEY_COMPARE, которая упорядочивает по убыванию специфичности.
- Для каждого ключа expansionKey в expansionKeys выполнить
- Пусть patternBase — подстрока expansionKey до, но не включая первый символ "*".
- Если matchKey начинается с patternBase, но не равно ему, то
- Пусть patternTrailer — подстрока expansionKey с индекса, следующего за первым символом "*".
- Если patternTrailer имеет нулевую длину или matchKey оканчивается на patternTrailer и длина matchKey не меньше длины expansionKey, то
- Пусть target — значение matchObj[expansionKey].
- Пусть patternMatch — подстрока matchKey, начиная с индекса длины patternBase до длины matchKey минус длины patternTrailer.
- Вернуть результат PACKAGE_TARGET_RESOLVE(packageURL, target, patternMatch, isImports, conditions).
- Вернуть null.
PATTERN_KEY_COMPARE(keyA, keyB)
- Assert: keyA оканчивается на "/" или содержит только один "*".
- Assert: keyB оканчивается на "/" или содержит только один "*".
- Пусть baseLengthA — индекс "*" в keyA плюс один, если keyA содержит "*", или длина keyA иначе.
- Пусть baseLengthB — индекс "*" в keyB плюс один, если keyB содержит "*", или длина keyB иначе.
- Если baseLengthA больше baseLengthB, вернуть -1.
- Если baseLengthB больше baseLengthA, вернуть 1.
- Если keyA не содержит "*", вернуть 1.
- Если keyB не содержит "*", вернуть -1.
- Если длина keyA больше длины keyB, вернуть -1.
- Если длина keyB больше длины keyA, вернуть 1.
- Вернуть 0.
PACKAGE_TARGET_RESOLVE(packageURL, target, patternMatch, isImports, conditions)
ESM_FILE_FORMAT(url)
LOOKUP_PACKAGE_SCOPE(url)
READ_PACKAGE_JSON(packageURL)
DETECT_MODULE_SYNTAX(source)
Настройка алгоритма разрешения спецификаторов ESM
Модульные хуки настройки предоставляют механизм для настройки алгоритма разрешения спецификаторов ESM. Пример, обеспечивающий разрешение в стиле CommonJS для спецификаторов ESM, представлен в commonjs-extension-resolution-loader.
© Joyent, Inc. and other Node contributors
Licensed under the MIT License.
Node.js is a trademark of Joyent, Inc. and is used with its permission.
We are not endorsed by or affiliated with Joyent.
https://nodejs.org/dist/latest-v20.x/docs/api/esm.html