Spec-Zone.ru › Node.js 24 LTS

Модули: модули ECMAScript

История
Версия Изменения
v22.0.0

Прекращена поддержка утверждений импорта.

v23.1.0, v22.12.0, v20.18.3, v18.20.5

Атрибуты импорта больше не являются экспериментальными.

v21.0.0, v20.10.0, v18.20.0

Добавлена экспериментальная поддержка атрибутов импорта.

v20.0.0, v18.19.0

Хуки настройки модулей выполняются вне основного потока.

v18.6.0, v16.17.0

Добавлена поддержка цепочек хуков настройки модулей.

v17.1.0, v16.14.0

Добавлена экспериментальная поддержка утверждений импорта.

v17.0.0, v16.12.0

Хуки настройки объединены, удалены хуки getFormat, getSource, transformSource и getGlobalPreloadCode, добавлены хуки load и globalPreload, разрешено возвращать format из хуков resolve или load.

v14.8.0

Снято требование флага для await верхнего уровня.

v15.3.0, v14.17.0, v12.22.0

Реализация модулей переведена в стабильный статус.

v14.13.0, v12.20.0

Добавлена поддержка обнаружения именованных экспортов CommonJS.

v14.0.0, v13.14.0, v12.20.0

Удалено предупреждение об экспериментальных модулях.

v13.2.0, v12.17.0

Для загрузки модулей ECMAScript больше не требуется флаг командной строки.

v12.0.0

Добавлена поддержка модулей ES с расширением файла .js через поле package.json "type".

v8.5.0

Добавлено в: v8.5.0

Стабильность: 2 — Стабильный

Введение

Модули 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". Это явные маркеры того, что код предназначен для выполнения в качестве модуля ES.

И наоборот, авторы могут явно указать Node.js интерпретировать JavaScript как CommonJS с помощью расширения файла .cjs, поля package.json "type" со значением "commonjs" или флага --input-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.

Поддерживаются схемы URL file:, node: и data:. Спецификатор вида 'https://example.com/app.js' изначально не поддерживается в Node.js, если не используется пользовательский загрузчик HTTPS.

URL file:

Модули загружаются несколько раз, если в спецификаторе 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:
Добавлено в: v12.10.0

URL data: поддерживаются для импорта со следующими типами 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

URL data: разрешают только непривязанные к пути спецификаторы для встроенных модулей и абсолютные спецификаторы. Разрешение относительных спецификаторов не работает, поскольку data: не является специальной схемой. Например, попытка загрузить ./foo из data:text/javascript,import "./foo"; завершится ошибкой разрешения, поскольку для URL data: не существует понятия относительного разрешения.

Импорт node:
История
Версия Изменения
v16.0.0, v14.18.0

В require(...) добавлена поддержка импорта node:.

v14.13.1, v12.20.0

Добавлено в: v14.13.1, v12.20.0

URL node: поддерживаются как альтернативный способ загрузки встроенных модулей Node.js. Эта схема URL позволяет ссылаться на встроенные модули с помощью корректных абсолютных строк URL.

import fs from 'node:fs/promises'; copy

Атрибуты импорта

История
Версия Изменения
v21.0.0, v20.10.0, v18.20.0

Утверждения импорта заменены атрибутами импорта.

v17.1.0, v16.14.0

Добавлено в: v17.1.0, v16.14.0

Атрибуты импорта — это встроенный синтаксис инструкций импорта модулей, позволяющий передавать дополнительную информацию вместе со спецификатором модуля.

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

Атрибут 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

При импорте встроенных модулей все именованные экспорты (то есть свойства объекта экспортов модуля) заполняются, даже если к ним не обращаются по отдельности. Поэтому первоначальный импорт встроенных модулей может быть немного медленнее, чем их загрузка с помощью require() или process.getBuiltinModule(), где объект экспортов модуля вычисляется сразу, но некоторые его свойства могут инициализироваться только при первом обращении к ним по отдельности.

Выражения import()

Динамический import() предоставляет асинхронный способ импорта модулей. Он поддерживается как в CommonJS, так и в модулях ES и может использоваться для загрузки модулей обоих типов.

import.meta

  • Тип: <Object>

Мета-свойство import.meta — это Object, содержащее следующие свойства. Оно поддерживается только в модулях ES.

import.meta.dirname

История
Версия Изменения
v24.0.0

Это свойство больше не является экспериментальным.

v21.2.0, v20.11.0

Добавлено в: v21.2.0, v20.11.0

  • Тип: <string> Имя каталога текущего модуля.

Это то же самое, что и path.dirname() для import.meta.filename.

Ограничение: присутствует только в модулях file:.

import.meta.filename

История
Версия Изменения
v24.0.0

Это свойство больше не является экспериментальным.

v21.2.0, v20.11.0

Добавлено в: v21.2.0, v20.11.0

  • Тип: <string> Полный абсолютный путь и имя файла текущего модуля с разрешенными символическими ссылками.

Это то же самое, что и url.fileURLToPath() для import.meta.url.

Ограничение: это свойство поддерживается только локальными модулями. Модули, не использующие протокол file:, не будут предоставлять его.

import.meta.url

  • Тип: <string> Абсолютный URL file: модуля.

Это свойство определено точно так же, как в браузерах, и содержит URL текущего файла модуля.

Это позволяет использовать полезные шаблоны, например загружать файлы относительно текущего:

import { readFileSync } from 'node:fs';
const buffer = readFileSync(new URL('./data.proto', import.meta.url)); copy

import.meta.main

Добавлено в: v24.2.0
Стабильность: 1.0 — Ранняя разработка
  • Тип: <boolean> true, если текущий модуль является точкой входа текущего процесса; в противном случае — false.

Эквивалент require.main === module в CommonJS.

Аналог __name__ == "__main__" в Python.

export function foo() {
  return 'Hello, world';
}

function main() {
  const message = foo();
  console.log(message);
}

if (import.meta.main) main();
// `foo` can be imported from another module without possible side-effects from `main` copy

import.meta.resolve(specifier)

История
Версия Изменения
v20.6.0, v18.19.0

Больше не требует флага CLI --experimental-import-meta-resolve, за исключением нестандартного параметра parentURL.

v20.6.0, v18.19.0

Этот API больше не вызывает исключение при работе с URL file:, которым не соответствует существующий файл в локальной файловой системе.

v20.0.0, v18.19.0

Теперь этот API синхронно возвращает строку вместо Promise.

v16.2.0, v14.18.0

Добавлена поддержка объекта WHATWG URL для параметра parentURL.

v13.9.0, v12.16.2

Добавлено в: v13.9.0, v12.16.2

Стабильность: 1.2 — Кандидат на выпуск
  • specifier <string> Спецификатор модуля, разрешаемый относительно текущего модуля.
  • Возвращает: <string> Абсолютная строка 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 эта функция принимает второй аргумент:

  • parent <string> | <URL> Необязательный абсолютный URL родительского модуля, относительно которого выполняется разрешение. По умолчанию: import.meta.url

Совместимость с CommonJS

Инструкции import

Инструкция import может ссылаться на модуль ES или модуль CommonJS. Инструкции import разрешены только в модулях ES, но динамические выражения import() поддерживаются в CommonJS для загрузки модулей ES.

При импорте модулей CommonJS объект module.exports предоставляется в качестве экспорта по умолчанию. Также могут быть доступны именованные экспорты, определяемые статическим анализом для повышения совместимости экосистемы.

require

В настоящее время модуль CommonJS require поддерживает загрузку только синхронных модулей ES (то есть модулей ES, в которых не используется await верхнего уровня).

Подробнее см. в разделе Загрузка модулей ECMAScript с помощью require().

Пространства имен CommonJS

История
Версия Изменения
v23.0.0

В пространства имен CJS добавлен маркер экспорта 'module.exports'.

v14.13.0

Добавлено в: v14.13.0

Модули CommonJS состоят из объекта module.exports, который может иметь любой тип.

Для этого при импорте CommonJS из модуля ECMAScript создается оболочка пространства имен для модуля CommonJS, которая всегда предоставляет ключ экспорта default, указывающий на значение module.exports модуля CommonJS.

Кроме того, исходный текст модуля CommonJS подвергается эвристическому статическому анализу, чтобы составить максимально полный статический список экспортов для предоставления в пространстве имен на основе значений из module.exports. Это необходимо, поскольку такие пространства имен должны быть созданы до выполнения модуля CJS.

Эти объекты пространства имен CommonJS также предоставляют экспорт default в качестве именованного экспорта 'module.exports', чтобы однозначно указать, что в CommonJS он представлен этим значением, а не значением пространства имен. Это соответствует семантике обработки имени экспорта 'module.exports' при поддержке совместимости с require(esm).

При импорте модуля CommonJS его можно надежно импортировать с помощью импорта ES по умолчанию или соответствующего синтаксического сокращения:

import { default as cjs } from 'cjs';
// Identical to the above
import cjsSugar from 'cjs';

console.log(cjs);
console.log(cjs === cjsSugar);
// Prints:
//   <module.exports>
//   true copy

Этот экзотический объект пространства имен модуля можно наблюдать напрямую с помощью import * as m from 'cjs' или динамического импорта:

import * as m from 'cjs';
console.log(m);
console.log(m === await import('cjs'));
// Prints:
//   [Module] { default: <module.exports>, 'module.exports': <module.exports> }
//   true copy

Для лучшей совместимости с существующими способами использования в экосистеме JS 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' },
//     'module.exports': { name: 'exported' },
//     name: 'exported'
//   } copy

Как видно из последнего примера с выводом экзотического объекта пространства имен модуля, при импорте модуля экспорт name копируется из объекта module.exports и напрямую добавляется в пространство имен модуля ES.

Обновления живых привязок и новые экспорты, добавленные в module.exports, для этих именованных экспортов не обнаруживаются.

Обнаружение именованных экспортов основано на распространенных синтаксических шаблонах, но не всегда позволяет правильно определить именованные экспорты. В таких случаях предпочтительнее использовать описанную выше форму импорта по умолчанию.

Обнаружение именованных экспортов охватывает множество распространенных шаблонов экспорта и повторного экспорта, а также результаты работы инструментов сборки и транспиляторов. Точная реализованная семантика описана в merve.

Различия между модулями ES и CommonJS

Нет require, exports или module.exports

В большинстве случаев для загрузки модулей CommonJS можно использовать import модуля ES.

При необходимости функцию require можно создать в модуле ES с помощью module.createRequire().

Нет __filename или __dirname

Эти переменные CommonJS недоступны в модулях ES.

Сценарии использования __filename и __dirname можно реализовать с помощью import.meta.filename и import.meta.dirname.

Загрузка дополнений не поддерживается

В настоящее время дополнения не поддерживаются при импорте модулей ES.

Вместо этого их можно загрузить с помощью module.createRequire() или process.dlopen.

Нет require.main

Для замены require.main === module предусмотрен API import.meta.main.

Нет 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

История
Версия Изменения
v23.1.0, v22.12.0, v20.18.3, v18.20.5

Модули JSON больше не являются экспериментальными.

На файлы JSON можно ссылаться с помощью import:

import packageConfig from './package.json' with { type: 'json' }; copy

Синтаксис with { type: 'json' } обязателен; см. раздел Атрибуты импорта.

Импортированный JSON предоставляет только экспорт default. Поддержка именованных экспортов отсутствует. В кэше CommonJS создается запись, чтобы избежать дублирования. Если модуль JSON уже импортирован по тому же пути, в CommonJS возвращается тот же объект.

Модули Wasm

История
Версия Изменения
v24.5.0

Для модулей Wasm больше не требуется флаг --experimental-wasm-modules.

Поддерживается импорт как экземпляров модулей WebAssembly, так и исходных фаз WebAssembly.

Обе эти интеграции соответствуют предложению об интеграции модулей ES для WebAssembly.

Импорт исходной фазы Wasm

Стабильность: 1.2 — Кандидат на выпуск
Добавлено в: v24.0.0

Предложение «Импорт исходной фазы» позволяет с помощью комбинации ключевых слов import source напрямую импортировать объект WebAssembly.Module вместо получения экземпляра модуля, уже созданного вместе с его зависимостями.

Это полезно, когда для Wasm требуется создавать экземпляры с нестандартными параметрами, сохраняя при этом возможность разрешать и загружать модуль через интеграцию модулей ES.

Например, для создания нескольких экземпляров модуля или передачи пользовательских импортов новому экземпляру library.wasm:

import source libraryModule from './library.wasm';

const instance1 = await WebAssembly.instantiate(libraryModule, importObject1);

const instance2 = await WebAssembly.instantiate(libraryModule, importObject2); copy

Помимо статической исходной фазы, существует также динамический вариант исходной фазы с помощью синтаксиса динамического импорта фазы import.source:

const dynamicLibrary = await import.source('./library.wasm');

const instance = await WebAssembly.instantiate(dynamicLibrary, importObject); copy

Встроенные строковые функции JavaScript

Стабильность: 1.2 — Кандидат на выпуск
Добавлено в: v24.5.0

При импорте модулей WebAssembly предложение о встроенных строковых функциях JS для WebAssembly автоматически включается посредством интеграции ESM. Это позволяет модулям WebAssembly напрямую использовать эффективные строковые функции, встроенные на этапе компиляции, из пространства имен wasm:js-string.

Например, следующий модуль Wasm экспортирует строковую функцию getLength с помощью встроенной функции wasm:js-string length:

(module
  ;; Compile-time import of the string length builtin.
  (import "wasm:js-string" "length" (func $string_length (param externref) (result i32)))

  ;; Define getLength, taking a JS value parameter assumed to be a string,
  ;; calling string length on it and returning the result.
  (func $getLength (param $str externref) (result i32)
    local.get $str
    call $string_length
  )

  ;; Export the getLength function.
  (export "getLength" (func $get_length))
) copy
import { getLength } from './string-len.wasm';
getLength('foo'); // Returns 3. copy

Встроенные функции Wasm — это импорты времени компиляции, которые связываются во время компиляции модуля, а не его создания. Они не ведут себя как обычные импорты графа модулей, и их нельзя проверить с помощью WebAssembly.Module.imports(mod) или виртуализировать, если только модуль не скомпилирован заново с помощью прямого API WebAssembly.compile с отключенными строковыми встроенными функциями.

Импорт модуля в исходной фазе до создания его экземпляра также автоматически задействует встроенные функции времени компиляции:

import source mod from './string-len.wasm';
const { exports: { getLength } } = await WebAssembly.instantiate(mod, {});
getLength('foo'); // Also returns 3. copy

Импорт фазы экземпляра Wasm

Стабильность: 1.1 — Активная разработка

Импорт экземпляров позволяет импортировать любые файлы .wasm как обычные модули, поддерживая в свою очередь и их импорты модулей.

Например, index.js, содержащий:

import * as M from './library.wasm';
console.log(M); copy

при выполнении в:

node index.mjs copy

предоставит интерфейс экспортов для создания экземпляра library.wasm.

Зарезервированные пространства имен Wasm

Добавлено в: v24.5.0

При импорте экземпляров модулей WebAssembly нельзя использовать имена модулей импорта, а также имена импорта и экспорта, начинающиеся с зарезервированных префиксов:

  • wasm-js: — зарезервирован во всех именах импорта модулей, именах модулей и именах экспортов.
  • wasm: — зарезервирован в именах импорта модулей и именах экспортов (импортируемые имена модулей разрешены для поддержки будущих встроенных полифилов).

При импорте модуля с использованием указанных выше зарезервированных имен будет вызвано исключение WebAssembly.LinkError.

await верхнего уровня

Добавлено в: v14.8.0

Ключевое слово 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 завершится с кодом состояния 13.

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

Загрузчики

Документация по прежним загрузчикам теперь доступна в разделе Модули: хуки настройки.

Алгоритм разрешения и загрузки

Особенности

Резолвер по умолчанию обладает следующими свойствами:

  • Разрешение на основе FileURL, как используется в модулях ES
  • Разрешение относительных и абсолютных URL
  • Отсутствие расширений по умолчанию
  • Отсутствие главных файлов каталогов
  • Поиск пакетов для голых спецификаторов через node_modules
  • Не завершается с ошибкой при неизвестных расширениях или протоколах
  • При необходимости может передать подсказку о формате на этап загрузки

Загрузчик по умолчанию обладает следующими свойствами

  • Поддержка загрузки встроенных модулей через URL node:
  • Поддержка загрузки «встроенных» модулей через URL data:
  • Поддержка загрузки модулей file:
  • Завершается с ошибкой при любом другом протоколе URL
  • Завершается с ошибкой при неизвестных расширениях для загрузки file: (поддерживаются только .cjs, .js и .mjs)

Алгоритм разрешения

Алгоритм загрузки спецификатора модуля ES задаётся приведённым ниже методом ESM_RESOLVE. Он возвращает разрешённый URL модуля относительно parentURL.

Алгоритм разрешения определяет полный разрешённый URL для загрузки модуля, а также предполагаемый формат модуля. Алгоритм разрешения не определяет, можно ли загружать URL с указанным протоколом и допустимы ли расширения файлов; вместо этого Node.js выполняет эти проверки на этапе загрузки (например, если запрошена загрузка URL с протоколом, отличным от file:, data: или node:).

Алгоритм также пытается определить формат файла по расширению (см. алгоритм ESM_FILE_FORMAT ниже). Если расширение файла не распознано (например, если это не .mjs, .cjs или .json), возвращается формат undefined, что приведёт к ошибке на этапе загрузки.

Алгоритм определения формата модуля для разрешённого URL задаётся методом ESM_FILE_FORMAT, который возвращает уникальный формат модуля для любого файла. Для модуля ECMAScript возвращается формат "module", а формат "commonjs" указывает на загрузку с помощью устаревшего загрузчика CommonJS. В будущих обновлениях могут быть добавлены дополнительные форматы, например "addon".

В приведённых ниже алгоритмах все ошибки подпрограмм передаются как ошибки этих процедур верхнего уровня, если не указано иное.

defaultConditions — это массив имён условного окружения, ["node", "import"].

Резолвер может выдавать следующие ошибки:

  • Недопустимый спецификатор модуля: спецификатор модуля является недопустимым URL, именем пакета или спецификатором подпути пакета.
  • Недопустимая конфигурация пакета: конфигурация package.json недопустима или содержит недопустимые настройки.
  • Недопустимая цель пакета: exports или imports пакета задают целевой модуль пакета с недопустимым типом или строковым значением.
  • Путь пакета не экспортирован: exports пакета не определяет или не разрешает целевой подпуть в пакете для данного модуля.
  • Импорт пакета не определён: imports пакета не определяет спецификатор.
  • Модуль не найден: запрошенный пакет или модуль не существует.
  • Импорт каталога не поддерживается: разрешённый путь соответствует каталогу, который не является допустимой целью для импорта модулей.

Спецификация алгоритма разрешения

ESM_RESOLVE(specifier, parentURL)

  1. Пусть resolved будет равен undefined.
  2. Если specifier является допустимым URL, то
    1. Установить resolved в результат разбора и повторной сериализации specifier как URL.
  3. Иначе, если specifier начинается с "/", "./" или "../", то
    1. Установить resolved в результат разрешения URL для specifier относительно parentURL.
  4. Иначе, если specifier начинается с "#", то
    1. Установить resolved в результат PACKAGE_IMPORTS_RESOLVE(specifier, parentURL, defaultConditions).
  5. Иначе
    1. Примечание: теперь specifier является голым спецификатором.
    2. Установить resolved в результат PACKAGE_RESOLVE(specifier, parentURL).
  6. Пусть format будет равен undefined.
  7. Если resolved является URL "file:", то
    1. Если resolved содержит любое процентное кодирование "/" или "\" (соответственно "%2F" и "%5C"), то
      1. Выдать ошибку Недопустимый спецификатор модуля.
    2. Если файл по адресу resolved является каталогом, то
      1. Выдать ошибку Импорт каталога не поддерживается.
    3. Если файл по адресу resolved не существует, то
      1. Выдать ошибку Модуль не найден.
    4. Установить resolved в реальный путь resolved, сохранив те же компоненты строки запроса и фрагмента URL.
    5. Установить format в результат ESM_FILE_FORMAT(resolved).
  8. Иначе
    1. Установить format в формат модуля для типа содержимого, связанного с URL resolved.
  9. Вернуть format и resolved на этап загрузки

PACKAGE_RESOLVE(packageSpecifier, parentURL)

  1. Пусть packageName будет равен undefined.
  2. Если packageSpecifier — пустая строка, то
    1. Выдать ошибку Недопустимый спецификатор модуля.
  3. Если packageSpecifier является именем встроенного модуля Node.js, то
    1. Вернуть строку "node:", объединённую с packageSpecifier.
  4. Если packageSpecifier не начинается с "@", то
    1. Установить packageName в подстроку packageSpecifier до первого разделителя "/" или до конца строки.
  5. Иначе
    1. Если packageSpecifier не содержит разделителя "/", то
      1. Выдать ошибку Недопустимый спецификатор модуля.
    2. Установить packageName в подстроку packageSpecifier до второго разделителя "/" или до конца строки.
  6. Если packageName начинается с "." или содержит "\" либо "%", то
    1. Выдать ошибку Недопустимый спецификатор модуля.
  7. Пусть packageSubpath будет равен ".", объединённой с подстрокой packageSpecifier, начиная с позиции, равной длине packageName.
  8. Пусть selfUrl будет результатом PACKAGE_SELF_RESOLVE(packageName, packageSubpath, parentURL).
  9. Если selfUrl не равен undefined, вернуть selfUrl.
  10. Пока parentURL не является корнем файловой системы,
    1. Пусть packageURL будет результатом разрешения URL "node_modules/", объединённого с packageName, относительно parentURL.
    2. Установить parentURL в URL родительской папки для parentURL.
    3. Если папка по адресу packageURL не существует, то
      1. Перейти к следующей итерации цикла.
    4. Пусть pjson будет результатом READ_PACKAGE_JSON(packageURL).
    5. Если pjson не равен null и pjson.exports не равен null или undefined, то
      1. Вернуть результат PACKAGE_EXPORTS_RESOLVE(packageURL, packageSubpath, pjson.exports, defaultConditions).
    6. Иначе, если packageSubpath равен ".", то
      1. Если pjson.main является строкой, то
        1. Вернуть результат разрешения URL main в packageURL.
    7. Иначе
      1. Вернуть результат разрешения URL packageSubpath в packageURL.
  11. Выдать ошибку Модуль не найден.

PACKAGE_SELF_RESOLVE(packageName, packageSubpath, parentURL)

  1. Пусть packageURL будет результатом LOOKUP_PACKAGE_SCOPE(parentURL).
  2. Если packageURL равен null, то
    1. Вернуть undefined.
  3. Пусть pjson будет результатом READ_PACKAGE_JSON(packageURL).
  4. Если pjson равен null или если pjson.exports равен null или undefined, то
    1. Вернуть undefined.
  5. Если pjson.name равен packageName, то
    1. Вернуть результат PACKAGE_EXPORTS_RESOLVE(packageURL, packageSubpath, pjson.exports, defaultConditions).
  6. Иначе вернуть undefined.

PACKAGE_EXPORTS_RESOLVE(packageURL, subpath, exports, conditions)

Примечание: эта функция вызывается непосредственно алгоритмом разрешения CommonJS.

  1. Если exports является объектом, содержащим ключ, начинающийся с ".", и ключ, не начинающийся с ".", выдать ошибку Недопустимая конфигурация пакета.
  2. Если subpath равен ".", то
    1. Пусть mainExport будет равен undefined.
    2. Если exports является строкой или массивом либо объектом, не содержащим ключей, начинающихся с ".", то
      1. Установить mainExport в значение exports.
    3. Иначе, если exports является объектом, содержащим свойство ".", то
      1. Установить mainExport в значение exports["."].
    4. Если mainExport не равен undefined, то
      1. Пусть resolved будет результатом PACKAGE_TARGET_RESOLVE( packageURL, mainExport, null, false, conditions).
      2. Если resolved не равен null или undefined, вернуть resolved.
  3. Иначе, если exports является объектом и все его ключи начинаются с ".", то
    1. Утверждение: subpath начинается с "./".
    2. Пусть resolved будет результатом PACKAGE_IMPORTS_EXPORTS_RESOLVE( subpath, exports, packageURL, false, conditions).
    3. Если resolved не равен null или undefined, вернуть resolved.
  4. Выдать ошибку Путь пакета не экспортирован.

PACKAGE_IMPORTS_RESOLVE(specifier, parentURL, conditions)

Примечание: эта функция вызывается непосредственно алгоритмом разрешения CommonJS.

  1. Утверждение: specifier начинается с "#".
  2. Если specifier в точности равен "#", то
    1. Выдать ошибку Недопустимый спецификатор модуля.
  3. Пусть packageURL будет результатом LOOKUP_PACKAGE_SCOPE(parentURL).
  4. Если packageURL не равен null, то
    1. Пусть pjson будет результатом READ_PACKAGE_JSON(packageURL).
    2. Если pjson.imports является ненулевым объектом, то
      1. Пусть resolved будет результатом PACKAGE_IMPORTS_EXPORTS_RESOLVE( specifier, pjson.imports, packageURL, true, conditions).
      2. Если resolved не равен null или undefined, вернуть resolved.
  5. Выдать ошибку Импорт пакета не определён.

PACKAGE_IMPORTS_EXPORTS_RESOLVE(matchKey, matchObj, packageURL, isImports, conditions)

  1. Если matchKey заканчивается на "/", то
    1. Выдать ошибку Недопустимый спецификатор модуля.
  2. Если matchKey является ключом matchObj и не содержит "*", то
    1. Пусть target будет значением matchObj[matchKey].
    2. Вернуть результат PACKAGE_TARGET_RESOLVE(packageURL, target, null, isImports, conditions).
  3. Пусть expansionKeys будет списком ключей matchObj, содержащих только один символ "*", отсортированным функцией сортировки PATTERN_KEY_COMPARE в порядке убывания специфичности.
  4. Для каждого ключа expansionKey в expansionKeys выполнить
    1. Пусть patternBase будет подстрокой expansionKey до первого символа "*", не включая его.
    2. Если matchKey начинается с patternBase, но не равен ему, то
      1. Пусть patternTrailer будет подстрокой expansionKey, начиная с индекса после первого символа "*".
      2. Если длина patternTrailer равна нулю или если matchKey заканчивается на patternTrailer и длина matchKey больше или равна длине expansionKey, то
        1. Пусть target будет значением matchObj[expansionKey].
        2. Пусть patternMatch будет подстрокой matchKey, начинающейся с индекса, равного длине patternBase, и заканчивающейся на длине matchKey за вычетом длины patternTrailer.
        3. Вернуть результат PACKAGE_TARGET_RESOLVE(packageURL, target, patternMatch, isImports, conditions).
  5. Вернуть null.

PATTERN_KEY_COMPARE(keyA, keyB)

  1. Утверждение: keyA содержит только один символ "*".
  2. Утверждение: keyB содержит только один символ "*".
  3. Пусть baseLengthA будет индексом символа "*" в keyA.
  4. Пусть baseLengthB будет индексом символа "*" в keyB.
  5. Если baseLengthA больше baseLengthB, вернуть -1.
  6. Если baseLengthB больше baseLengthA, вернуть 1.
  7. Если длина keyA больше длины keyB, вернуть -1.
  8. Если длина keyB больше длины keyA, вернуть 1.
  9. Вернуть 0.

PACKAGE_TARGET_RESOLVE(packageURL, target, patternMatch, isImports, conditions)

  1. Если target является строкой, то
    1. Если target не начинается с "./", то
      1. Если isImports равно false, или если target начинается с "../" или "/", или если target является допустимым URL, то
        1. Выдать ошибку Недопустимая цель пакета.
      2. Если patternMatch является строкой, то
        1. Вернуть результат PACKAGE_RESOLVE(target, в котором каждое вхождение "*" заменено на patternMatch, packageURL + "/").
      3. Вернуть результат PACKAGE_RESOLVE(target, packageURL + "/").
    2. Если при разделении target по "/" или "\" после первого сегмента "." обнаруживаются сегменты "", ".", ".." или "node_modules" (без учёта регистра и включая варианты с процентным кодированием), выдать ошибку Недопустимая цель пакета.
    3. Пусть resolvedTarget будет результатом разрешения URL для конкатенации packageURL и target.
    4. Утверждение: packageURL содержится в resolvedTarget.
    5. Если patternMatch равен null, то
      1. Вернуть resolvedTarget.
    6. Если при разделении patternMatch по "/" или "\" обнаруживаются сегменты "", ".", ".." или "node_modules" (без учёта регистра и включая варианты с процентным кодированием), выдать ошибку Недопустимый спецификатор модуля.
    7. Вернуть результат разрешения URL для resolvedTarget, в котором каждое вхождение "*" заменено на patternMatch.
  2. Иначе, если target является ненулевым объектом, то
    1. Если target содержит ключи-свойства индексов, определённые в ECMA-262 6.1.7 Индекс массива, выдать ошибку Недопустимая конфигурация пакета.
    2. Для каждого свойства p объекта target в порядке вставки свойств в объект
      1. Если p равен "default" или conditions содержит запись для p, то
        1. Пусть targetValue будет значением свойства p в target.
        2. Пусть resolved будет результатом PACKAGE_TARGET_RESOLVE( packageURL, targetValue, patternMatch, isImports, conditions).
        3. Если resolved равен undefined, перейти к следующей итерации цикла.
        4. Вернуть resolved.
    3. Вернуть undefined.
  3. Иначе, если target является массивом, то
    1. Если _target.length равен нулю, вернуть null.
    2. Для каждого элемента targetValue в target выполнить
      1. Пусть resolved будет результатом PACKAGE_TARGET_RESOLVE( packageURL, targetValue, patternMatch, isImports, conditions); при любой ошибке Недопустимая цель пакета перейти к следующей итерации цикла.
      2. Если resolved равен undefined, перейти к следующей итерации цикла.
      3. Вернуть resolved.
    3. Вернуть или выдать последнюю резервную ошибку разрешения либо вернуть null.
  4. Иначе, если target равен null, вернуть null.
  5. Иначе выдать ошибку Недопустимая цель пакета.

ESM_FILE_FORMAT(url)

  1. Утверждение: url соответствует существующему файлу.
  2. Если url заканчивается на ".mjs", то
    1. Вернуть "module".
  3. Если url заканчивается на ".cjs", то
    1. Вернуть "commonjs".
  4. Если url заканчивается на ".json", то
    1. Вернуть "json".
  5. Если url заканчивается на ".wasm", то
    1. Вернуть "wasm".
  6. Если включён --experimental-addon-modules и url заканчивается на ".node", то
    1. Вернуть "addon".
  7. Пусть packageURL будет результатом LOOKUP_PACKAGE_SCOPE(url).
  8. Пусть pjson будет результатом READ_PACKAGE_JSON(packageURL).
  9. Пусть packageType будет равен null.
  10. Если pjson?.type равен "module" или "commonjs", то
    1. Установить packageType в значение pjson.type.
  11. Если url заканчивается на ".js", то
    1. Если packageType не равен null, то
      1. Вернуть packageType.
    2. Если результат DETECT_MODULE_SYNTAX(source) равен true, то
      1. Вернуть "module".
    3. Вернуть "commonjs".
  12. Если у url нет расширения, то
    1. Если packageType равен "module" и файл по адресу url содержит заголовок типа содержимого "application/wasm" для модуля WebAssembly, то
      1. Вернуть "wasm".
    2. Если packageType не равен null, то
      1. Вернуть packageType.
    3. Если результат DETECT_MODULE_SYNTAX(source) равен true, то
      1. Вернуть "module".
    4. Вернуть "commonjs".
  13. Вернуть undefined (на этапе загрузки будет выдана ошибка).

LOOKUP_PACKAGE_SCOPE(url)

  1. Пусть scopeURL будет равен url.
  2. Пока scopeURL не является корнем файловой системы,
    1. Установить scopeURL в URL родительского каталога для scopeURL.
    2. Если scopeURL заканчивается сегментом пути "node_modules", вернуть null.
    3. Пусть pjsonURL будет результатом разрешения "package.json" внутри scopeURL.
    4. Если файл по адресу pjsonURL существует, то
      1. Вернуть scopeURL.
  3. Вернуть null.

READ_PACKAGE_JSON(packageURL)

  1. Пусть pjsonURL будет результатом разрешения "package.json" внутри packageURL.
  2. Если файл по адресу pjsonURL не существует, то
    1. Вернуть null.
  3. Если файл по адресу packageURL не разбирается как корректный JSON, то
    1. Выдать ошибку Недопустимая конфигурация пакета.
  4. Вернуть разобранное содержимое JSON-файла по адресу pjsonURL.

DETECT_MODULE_SYNTAX(source)

  1. Разобрать source как модуль ECMAScript.
  2. Если разбор выполнен успешно, то
    1. Если source содержит на верхнем уровне await, статические инструкции import или export либо import.meta, вернуть true.
    2. Если source содержит на верхнем уровне лексическое объявление (const, let или class) любой из переменных-обёрток CommonJS (require, exports, module, __filename или __dirname), вернуть true.
  3. Иначе вернуть false.

Настройка алгоритма разрешения спецификаторов ESM

Хуки настройки модулей предоставляют механизм настройки алгоритма разрешения спецификаторов ESM. Пример реализации разрешения спецификаторов ESM в стиле CommonJS см. в 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-v24.x/docs/api/esm.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API