Модули: модули 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'. Они напрямую и явно указывают полный путь.
Разрешение непrefixированных спецификаторов выполняется алгоритмом разрешения и загрузки модулей Node.js. Все остальные спецификаторы разрешаются только с использованием стандартной семантики разрешения относительных URL.
Как и в CommonJS, к файлам модулей внутри пакетов можно обращаться, добавив путь к имени пакета, если только поле package.json пакета не содержит поле "exports"; в этом случае к файлам внутри пакетов можно обращаться только по путям, определённым в "exports".
Подробнее о правилах разрешения пакетов, применяемых к непrefixированным спецификаторам в механизме разрешения модулей 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:
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:
URL node: поддерживаются как альтернативный способ загрузки встроенных модулей Node.js. Эта схема URL позволяет указывать встроенные модули с помощью корректных абсолютных URL.
import fs from 'node:fs/promises'; copy
Атрибуты импорта
Атрибуты импорта — это встроенный синтаксис для инструкций импорта модулей, позволяющий передавать дополнительную информацию вместе со спецификатором модуля.
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
Выражения import()
Динамический импорт import() поддерживается как в CommonJS, так и в модулях ES. В модулях CommonJS его можно использовать для загрузки модулей ES.
import.meta
- Тип: <Object>
Мета-свойство import.meta — это Object, содержащее следующие свойства. Оно поддерживается только в модулях ES.
import.meta.dirname
- Тип: <string> Имя каталога текущего модуля.
Это то же самое, что и path.dirname() для import.meta.filename.
Примечание: присутствует только в модулях
file:.
import.meta.filename
- Тип: <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
- Тип: <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)
-
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 эта функция принимает второй аргумент:
Совместимость с CommonJS
Инструкции import
Инструкция import может ссылаться на модуль ES или модуль CommonJS. Инструкции import допускаются только в модулях ES, однако динамические выражения import() поддерживаются в CommonJS для загрузки модулей ES.
При импорте модулей CommonJS объект module.exports предоставляется в качестве экспорта по умолчанию. Именованные экспорты также могут быть доступны: они предоставляются с помощью статического анализа для лучшей совместимости с экосистемой.
require
В настоящее время модуль CommonJS require поддерживает загрузку только синхронных модулей ES (то есть модулей ES, в которых не используется await верхнего уровня).
Подробнее см. в разделе Загрузка модулей ECMAScript с помощью require().
Пространства имён 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 Представление модуля CommonJS в пространстве имён модуля ECMAScript всегда представляет собой пространство имён с ключом экспорта default, указывающим на значение module.exports модуля CommonJS.
Этот экзотический объект пространства имён модуля можно непосредственно наблюдать при использовании 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 Для лучшей совместимости с существующим использованием в экосистеме 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' }, name: 'exported' } copy Как видно из последнего примера с выводом в журнал экзотического объекта пространства имён модуля, экспорт name копируется из объекта module.exports и напрямую устанавливается в пространство имён модуля ES при импорте модуля.
Обновления динамических привязок или новые экспорты, добавленные в module.exports, для этих именованных экспортов не обнаруживаются.
Обнаружение именованных экспортов основано на распространённых синтаксических шаблонах, но не всегда правильно определяет именованные экспорты. В таких случаях предпочтительнее использовать описанную выше форму импорта по умолчанию.
Обнаружение именованных экспортов охватывает множество распространённых шаблонов экспорта и реэкспорта, а также выходных данных инструментов сборки и транспиляторов. Точная реализованная семантика описана в cjs-module-lexer.
Различия между модулями 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
На файлы JSON можно ссылаться с помощью import:
import packageConfig from './package.json' with { type: 'json' }; copy Синтаксис with { type: 'json' } обязателен; см. раздел Атрибуты импорта.
Импортированный JSON предоставляет только экспорт default. Поддержка именованных экспортов отсутствует. В кэше CommonJS создаётся запись кэша, чтобы избежать дублирования. Если модуль JSON уже был импортирован из того же пути, в CommonJS возвращается тот же объект.
Модули Wasm
Поддерживается импорт как экземпляров модулей WebAssembly, так и импорт на этапе исходного кода WebAssembly.
Эта интеграция соответствует предложению по интеграции модулей ES для WebAssembly.
Модули Wasm
Поддерживается импорт модулей WebAssembly: любые файлы .wasm можно импортировать как обычные модули, сохраняя при этом поддержку импорта модулей.
Эта интеграция соответствует предложению по интеграции модулей ES для WebAssembly.
Например, index.mjs, содержащий:
import * as M from './module.wasm'; console.log(M); copy
при выполнении с помощью:
node index.mjs copy
предоставит интерфейс экспортов для создания экземпляра module.wasm.
Встроенные строковые средства JavaScript
При импорте модулей WebAssembly предложение WebAssembly JS String Builtins автоматически включается благодаря интеграции 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 при отключённых встроенных строковых средствах.
Зарезервированные пространства имён Wasm
При импорте модулей WebAssembly через интеграцию ESM нельзя использовать имена импортируемых модулей или имена импортов и экспортов, начинающиеся с зарезервированных префиксов:
-
wasm-js:— зарезервирован во всех именах импортируемых модулей, именах модулей и именах экспортов. -
wasm:— зарезервирован в именах импортов и экспортов (имена импортируемых модулей разрешены для поддержки будущих встроенных полифилов).
При импорте модуля с указанными выше зарезервированными именами будет вызвано исключение WebAssembly.LinkError.
await верхнего уровня
Ключевое слово 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"].
Резолвер может выдать следующие ошибки:
- Invalid Module Specifier: спецификатор модуля является недопустимым URL, именем пакета или спецификатором подпути пакета.
- Invalid Package Configuration: конфигурация package.json недопустима или содержит недопустимую конфигурацию.
- Invalid Package Target: экспорты или импорты пакета определяют целевой модуль пакета с недопустимым типом или строковым значением.
- Package Path Not Exported: экспорты пакета не определяют или не разрешают целевой подпуть в пакете для указанного модуля.
- Package Import Not Defined: импорты пакета не определяют спецификатор.
- Module Not Found: запрошенный пакет или модуль не существует.
- Unsupported Directory Import: разрешенный путь соответствует каталогу, который не является допустимой целью для импорта модулей.
Спецификация алгоритма разрешения
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"), то
- Выдать ошибку Invalid Module Specifier.
- Если файл по адресу resolved является каталогом, то
- Выдать ошибку Unsupported Directory Import.
- Если файл по адресу resolved не существует, то
- Выдать ошибку Module Not Found.
- Присвоить resolved реальный путь resolved, сохранив те же компоненты строки запроса и фрагмента URL.
- Присвоить format результат вызова ESM_FILE_FORMAT(resolved).
- В противном случае
- Присвоить format формат модуля, связанный с типом содержимого URL resolved.
- Вернуть format и resolved на этап загрузки
PACKAGE_RESOLVE(packageSpecifier, parentURL)
- Пусть packageName имеет значение undefined.
- Если packageSpecifier является пустой строкой, то
- Выдать ошибку Invalid Module Specifier.
- Если packageSpecifier является именем встроенного модуля Node.js, то
- Вернуть строку "node:", объединенную с packageSpecifier.
- Если packageSpecifier не начинается с "@", то
- Присвоить packageName подстроку packageSpecifier до первого разделителя "/" или до конца строки.
- В противном случае
- Если packageSpecifier не содержит разделитель "/", то
- Выдать ошибку Invalid Module Specifier.
- Присвоить packageName подстроку packageSpecifier до второго разделителя "/" или до конца строки.
- Если packageName начинается с "." либо содержит "\" или "%", то
- Выдать ошибку Invalid Module Specifier.
- Пусть packageSubpath — это ".", объединенная с подстрокой packageSpecifier, начиная с позиции, равной длине packageName.
- Пусть selfUrl — результат вызова PACKAGE_SELF_RESOLVE(packageName, packageSubpath, parentURL).
- Если selfUrl не равен undefined, вернуть selfUrl.
- Пока parentURL не является корнем файловой системы,
- Пусть packageURL — результат разрешения URL "node_modules/", объединенного с packageName, относительно parentURL.
- Присвоить parentURL URL родительской папки для parentURL.
- Если папка по адресу packageURL не существует, то
- Перейти к следующей итерации цикла.
- Пусть pjson — результат вызова READ_PACKAGE_JSON(packageURL).
- Если pjson не равен null, а pjson.exports не равен null или undefined, то
- Вернуть результат вызова PACKAGE_EXPORTS_RESOLVE(packageURL, packageSubpath, pjson.exports, defaultConditions).
- Иначе, если packageSubpath равен ".", то
- Если pjson.main является строкой, то
- Вернуть результат разрешения URL main в packageURL.
- В противном случае
- Вернуть результат разрешения URL packageSubpath в packageURL.
- Выдать ошибку Module Not Found.
PACKAGE_SELF_RESOLVE(packageName, packageSubpath, parentURL)
- Пусть packageURL — результат вызова LOOKUP_PACKAGE_SCOPE(parentURL).
- Если packageURL равен null, то
- Вернуть undefined.
- Пусть pjson — результат вызова READ_PACKAGE_JSON(packageURL).
- Если pjson равен null или если pjson.exports равен null или undefined, то
- Вернуть undefined.
- Если pjson.name равен packageName, то
- Вернуть результат вызова PACKAGE_EXPORTS_RESOLVE(packageURL, packageSubpath, pjson.exports, defaultConditions).
- В противном случае вернуть undefined.
PACKAGE_EXPORTS_RESOLVE(packageURL, subpath, exports, conditions)
Примечание: эта функция вызывается непосредственно алгоритмом разрешения CommonJS.
- Если exports является объектом, содержащим ключ, начинающийся с ".", и ключ, не начинающийся с ".", выдать ошибку Invalid Package Configuration.
- Если subpath равен ".", то
- Пусть mainExport имеет значение undefined.
- Если exports является строкой или массивом либо объектом, не содержащим ключей, начинающихся с ".", то
- Присвоить mainExport значение exports.
- Иначе, если exports является объектом, содержащим свойство ".", то
- Присвоить mainExport значение exports["."].
- Если mainExport не равен undefined, то
- Пусть resolved — результат вызова PACKAGE_TARGET_RESOLVE( packageURL, mainExport, null, false, conditions).
- Если resolved не равен null или undefined, вернуть resolved.
- Иначе, если exports является объектом и все ключи exports начинаются с ".", то
- Утверждение: subpath начинается с "./".
- Пусть resolved — результат вызова PACKAGE_IMPORTS_EXPORTS_RESOLVE( subpath, exports, packageURL, false, conditions).
- Если resolved не равен null или undefined, вернуть resolved.
- Выдать ошибку Package Path Not Exported.
PACKAGE_IMPORTS_RESOLVE(specifier, parentURL, conditions)
Примечание: эта функция вызывается непосредственно алгоритмом разрешения CommonJS.
- Утверждение: 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 оканчивается на "/", то
- Выдать ошибку Invalid Module Specifier.
- Если 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)
- Утверждение: keyA содержит ровно один символ "*".
- Утверждение: keyB содержит ровно один символ "*".
- Пусть baseLengthA — позиция символа "*" в keyA.
- Пусть baseLengthB — позиция символа "*" в keyB.
- Если baseLengthA больше baseLengthB, вернуть -1.
- Если baseLengthB больше baseLengthA, вернуть 1.
- Если длина keyA больше длины keyB, вернуть -1.
- Если длина keyB больше длины keyA, вернуть 1.
- Вернуть 0.
PACKAGE_TARGET_RESOLVE(packageURL, target, patternMatch, isImports, conditions)
- Если target является строкой, то
- Если target не начинается с "./", то
- Если isImports равен false или если target начинается с "../" или "/", либо если target является допустимым URL, то
- Выдать ошибку Invalid Package Target.
- Если patternMatch является строкой, то
- Вернуть результат вызова PACKAGE_RESOLVE(target, заменив каждый экземпляр "*" на patternMatch, packageURL + "/").
- Вернуть результат вызова PACKAGE_RESOLVE(target, packageURL + "/").
- Если при разделении target по "/" или "\" после первого сегмента "." обнаруживаются сегменты "", ".", ".." или "node_modules" (без учета регистра и включая варианты с процентным кодированием), выдать ошибку Invalid Package Target.
- Пусть resolvedTarget — результат разрешения URL для конкатенации packageURL и target.
- Утверждение: packageURL содержится в resolvedTarget.
- Если patternMatch равен null, то
- Вернуть resolvedTarget.
- Если при разделении patternMatch по "/" или "\" обнаруживаются сегменты "", ".", ".." или "node_modules" (без учета регистра и включая варианты с процентным кодированием), выдать ошибку Invalid Module Specifier.
- Вернуть результат разрешения URL для resolvedTarget, заменив каждый экземпляр "*" на patternMatch.
- Иначе, если target является ненулевым объектом, то
- Если target содержит ключи свойств с индексами, как определено в ECMA-262 6.1.7 Индекс массива, выдать ошибку Invalid Package Configuration.
- Для каждого свойства p объекта target в порядке вставки свойств
- Если p равен "default" или conditions содержит запись для p, то
- Пусть targetValue — значение свойства p в target.
- Пусть resolved — результат вызова PACKAGE_TARGET_RESOLVE( packageURL, targetValue, patternMatch, isImports, conditions).
- Если resolved равен undefined, продолжить цикл.
- Вернуть resolved.
- Вернуть undefined.
- Иначе, если target является массивом, то
- Если _target.length равен нулю, вернуть null.
- Для каждого элемента targetValue в target выполнить
- Пусть resolved — результат вызова PACKAGE_TARGET_RESOLVE( packageURL, targetValue, patternMatch, isImports, conditions); при ошибке Invalid Package Target продолжить цикл.
- Если resolved равен undefined, продолжить цикл.
- Вернуть resolved.
- Вернуть или выдать ошибку последнего резервного результата разрешения — возврата null или ошибки.
- Иначе, если target равен null, вернуть null.
- В противном случае выдать ошибку Invalid Package Target.
ESM_FILE_FORMAT(url)
- Утверждение: url соответствует существующему файлу.
- Если url оканчивается на ".mjs", то
- Вернуть "module".
- Если url оканчивается на ".cjs", то
- Вернуть "commonjs".
- Если url оканчивается на ".json", то
- Вернуть "json".
- Если url оканчивается на ".wasm", то
- Вернуть "wasm".
- Если включен
--experimental-addon-modulesи url оканчивается на ".node", то
- Вернуть "addon".
- Пусть packageURL — результат вызова LOOKUP_PACKAGE_SCOPE(url).
- Пусть pjson — результат вызова READ_PACKAGE_JSON(packageURL).
- Пусть packageType имеет значение null.
- Если pjson?.type равен "module" или "commonjs", то
- Присвоить packageType значение pjson.type.
- Если url оканчивается на ".js", то
- Если packageType не равен null, то
- Вернуть packageType.
- Если результат вызова DETECT_MODULE_SYNTAX(source) равен true, то
- Вернуть "module".
- Вернуть "commonjs".
- Если у url нет расширения, то
- Если packageType равен "module" и файл по адресу url содержит заголовок модуля WebAssembly, то
- Вернуть "wasm".
- Если packageType не равен null, то
- Вернуть packageType.
- Если результат вызова DETECT_MODULE_SYNTAX(source) равен true, то
- Вернуть "module".
- Вернуть "commonjs".
- Вернуть undefined (приведет к ошибке на этапе загрузки).
LOOKUP_PACKAGE_SCOPE(url)
- Пусть scopeURL имеет значение url.
- Пока scopeURL не является корнем файловой системы,
- Присвоить scopeURL родительский URL для scopeURL.
- Если scopeURL оканчивается сегментом пути "node_modules", вернуть null.
- Пусть pjsonURL — результат разрешения "package.json" внутри scopeURL.
- Если файл по адресу pjsonURL существует, то
- Вернуть scopeURL.
- Вернуть null.
READ_PACKAGE_JSON(packageURL)
- Пусть pjsonURL — результат разрешения "package.json" внутри packageURL.
- Если файл по адресу pjsonURL не существует, то
- Вернуть null.
- Если файл по адресу packageURL не разбирается как корректный JSON, то
- Выдать ошибку Invalid Package Configuration.
- Вернуть разобранный исходный JSON файла по адресу pjsonURL.
DETECT_MODULE_SYNTAX(source)
- Разобрать source как модуль ECMAScript.
- Если разбор выполнен успешно, то
- Если source содержит на верхнем уровне
await, статические инструкцииimportилиexportлибоimport.meta, вернуть true.- Если source содержит лексическое объявление верхнего уровня (
const,letилиclass) любой из переменных-оберток CommonJS (require,exports,module,__filenameили__dirname), вернуть true.- Иначе вернуть 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-v22.x/docs/api/esm.html