Spec-Zone.ru › Node.js

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

Добавлено в: v8.5.0
История
Версия Изменения
v23.1.0, v22.12.0, v20.18.3, v18.20.5

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

v22.0.0

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

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.

v15.3.0, v14.17.0, v12.22.0

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

v14.13.0, v12.20.0

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

v14.8.0

Снято ограничение на использование Top-Level Await.

v14.0.0, v13.14.0, v12.20.0

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

v13.2.0, v12.17.0

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

v12.0.0

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

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

Введение

Модули ECMAScript — это официальный стандартный формат для упаковки кода JavaScript с целью его повторного использования. Модули определяются с помощью различных инструкций import и export.

В следующем примере ES-модуль экспортирует функцию:

// addTwo.mjs
function addTwo(num) {
  return num + 2;
}

export { addTwo };
jscopy

В следующем примере ES-модуль импортирует функцию из addTwo.mjs:

// app.mjs
import { addTwo } from './addTwo.mjs';

// Prints: 6
console.log(addTwo(4));
jscopy

Node.js полностью поддерживает модули ECMAScript в соответствии с их текущей спецификацией и обеспечивает взаимодействие между ними и исходным форматом модулей — CommonJS.

Подключение

В Node.js есть две системы модулей: модули CommonJS и модули ECMAScript.

Авторы могут указать Node.js интерпретировать JavaScript как ES-модуль с помощью расширения файла .mjs, поля "type" в package.json со значением "module" или флага --input-type со значением "module". Это явные маркеры того, что код предназначен для выполнения как ES-модуль.

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

Корень тома можно указать как /, // или 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' };
jscopy

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

Импорт node:
Добавлено в: v14.13.1, v12.20.0
История
Версия Изменения
v16.0.0, v14.18.0

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

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

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

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

Добавлено в: v17.1.0, v16.14.0
История
Версия Изменения
v21.0.0, v20.10.0, v18.20.0

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

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

import fooData from './foo.json' with { type: 'json' };

const { default: barData } =
  await import('./bar.json', { with: { type: 'json' } });
jscopy

Node.js поддерживает только атрибут type со следующими допустимыми значениями:

Атрибут type Необходим для
'json' JSON-модули

При импорте JSON-модулей атрибут type: 'json' является обязательным.

Встроенные модули

Встроенные модули предоставляют именованные экспорты своего общедоступного API. Также предоставляется экспорт по умолчанию, представляющий собой значение экспортов CommonJS. Экспорт по умолчанию можно использовать, в частности, для изменения именованных экспортов. Именованные экспорты встроенных модулей обновляются только при вызове module.syncBuiltinESMExports().

import EventEmitter from 'node:events';
const e = new EventEmitter();
jscopy
import { readFile } from 'node:fs';
readFile('./foo.txt', (err, source) => {
  if (err) {
    console.error(err);
  } else {
    console.log(source);
  }
});
jscopy
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;
jscopy

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

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

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

import.meta

  • Тип: <Object>

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

import.meta.dirname

Добавлено в: v21.2.0, v20.11.0
История
Версия Изменения
v24.0.0, v22.16.0

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

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

Оно совпадает со значением path.dirname() для import.meta.filename.

Внимание: присутствует только для модулей file:.

import.meta.filename

Добавлено в: v21.2.0, v20.11.0
История
Версия Изменения
v24.0.0, v22.16.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));
jscopy

import.meta.main

Добавлено в: v24.2.0, v22.18.0

Стабильность: 1.0 — Ранняя разработка

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

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

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

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`
jscopy

import.meta.resolve(specifier)

Добавлено в: v13.9.0, v12.16.2
История
Версия Изменения
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.

Стабильность: 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
jscopy

Поддерживаются все возможности разрешения модулей 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

Добавлено в: v14.13.0
История
Версия Изменения
v23.0.0

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

Модули 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
jscopy

Этот экзотический объект пространства имен модуля можно непосредственно наблюдать при использовании 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
jscopy

Для лучшей совместимости с существующим использованием в экосистеме JS Node.js также пытается определить именованные экспорты CommonJS каждого импортированного модуля CommonJS, чтобы предоставить их как отдельные экспорты ES-модуля с помощью статического анализа.

Например, рассмотрим модуль CommonJS, записанный следующим образом:

// cjs.cjs
exports.name = 'exported';
cjscopy

Указанный выше модуль поддерживает именованный импорт в 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'
//   }
jscopy

Как видно из последнего примера с выводом в журнал экзотического объекта пространства имен модуля, экспорт 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' };
jscopy

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

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

Модули Wasm

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

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

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

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

Импорт Wasm на этапе исходного кода

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

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

Предложение «Импорт на этапе исходного кода» позволяет использовать сочетание ключевых слов 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);
jscopy

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

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

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

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

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

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

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

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

(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))
)
textcopy
import { getLength } from './string-len.wasm';
getLength('foo'); // Returns 3.
jscopy

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

Строковые константы также можно импортировать из встроенного URL импорта wasm:js/string-constants, что позволяет определять статические глобальные строки JS:

(module
  (import "wasm:js/string-constants" "hello" (global $hello externref))
)
textcopy

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

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

Импорт Wasm на этапе создания экземпляра

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

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

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

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

при выполнении с помощью:

node index.mjs
bashcopy

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

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

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

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

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

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

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

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

Ключевое слово await можно использовать в теле модуля ECMAScript верхнего уровня.

Предположим, имеется a.mjs со следующим содержимым:

export const five = await Promise.resolve(5);
jscopy

И b.mjs со следующим содержимым:

import { five } from './a.mjs';

console.log(five); // Logs `5`
jscopy
node b.mjs # works
bashcopy

Если выражение 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`
});
jscopy

Загрузчики

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

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

Возможности

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

  • Разрешение на основе 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 является объектом, не равным null, то
      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 является объектом, не равным null, то
    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/api/esm.html

Spec-Zone.ru

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