Модули: модули 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". Это явные маркеры того, что код предназначен для выполнения в качестве модуля 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:
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 При импорте встроенных модулей все именованные экспорты (то есть свойства объекта экспортов модуля) заполняются, даже если к ним не обращаются по отдельности. Поэтому первоначальный импорт встроенных модулей может быть немного медленнее, чем их загрузка с помощью
require()илиprocess.getBuiltinModule(), где объект экспортов модуля вычисляется сразу, но некоторые его свойства могут инициализироваться только при первом обращении к ним по отдельности.
Выражения import()
Динамический import() предоставляет асинхронный способ импорта модулей. Он поддерживается как в 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 из модуля 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
На файлы 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
Предложение «Импорт исходной фазы» позволяет с помощью комбинации ключевых слов 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
При импорте модулей 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
Импорт экземпляров позволяет импортировать любые файлы .wasm как обычные модули, поддерживая в свою очередь и их импорты модулей.
Например, index.js, содержащий:
import * as M from './library.wasm'; console.log(M); copy
при выполнении в:
node index.mjs copy
предоставит интерфейс экспортов для создания экземпляра library.wasm.
Зарезервированные пространства имен Wasm
При импорте экземпляров модулей WebAssembly нельзя использовать имена модулей импорта, а также имена импорта и экспорта, начинающиеся с зарезервированных префиксов:
-
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"].
Резолвер может выдавать следующие ошибки:
- Недопустимый спецификатор модуля: спецификатор модуля является недопустимым URL, именем пакета или спецификатором подпути пакета.
- Недопустимая конфигурация пакета: конфигурация package.json недопустима или содержит недопустимые настройки.
- Недопустимая цель пакета: exports или imports пакета задают целевой модуль пакета с недопустимым типом или строковым значением.
- Путь пакета не экспортирован: exports пакета не определяет или не разрешает целевой подпуть в пакете для данного модуля.
- Импорт пакета не определён: imports пакета не определяет спецификатор.
- Модуль не найден: запрошенный пакет или модуль не существует.
- Импорт каталога не поддерживается: разрешённый путь соответствует каталогу, который не является допустимой целью для импорта модулей.
Спецификация алгоритма разрешения
ESM_RESOLVE(specifier, parentURL)
- Пусть resolved будет равен undefined.
- Если specifier является допустимым URL, то
- Установить resolved в результат разбора и повторной сериализации specifier как URL.
- Иначе, если specifier начинается с "/", "./" или "../", то
- Установить resolved в результат разрешения URL для specifier относительно parentURL.
- Иначе, если specifier начинается с "#", то
- Установить resolved в результат PACKAGE_IMPORTS_RESOLVE(specifier, parentURL, defaultConditions).
- Иначе
- Примечание: теперь specifier является голым спецификатором.
- Установить resolved в результат PACKAGE_RESOLVE(specifier, parentURL).
- Пусть format будет равен undefined.
- Если resolved является URL "file:", то
- Если resolved содержит любое процентное кодирование "/" или "\" (соответственно "%2F" и "%5C"), то
- Выдать ошибку Недопустимый спецификатор модуля.
- Если файл по адресу resolved является каталогом, то
- Выдать ошибку Импорт каталога не поддерживается.
- Если файл по адресу resolved не существует, то
- Выдать ошибку Модуль не найден.
- Установить resolved в реальный путь resolved, сохранив те же компоненты строки запроса и фрагмента URL.
- Установить format в результат ESM_FILE_FORMAT(resolved).
- Иначе
- Установить format в формат модуля для типа содержимого, связанного с URL resolved.
- Вернуть format и resolved на этап загрузки
PACKAGE_RESOLVE(packageSpecifier, parentURL)
- Пусть packageName будет равен undefined.
- Если packageSpecifier — пустая строка, то
- Выдать ошибку Недопустимый спецификатор модуля.
- Если packageSpecifier является именем встроенного модуля Node.js, то
- Вернуть строку "node:", объединённую с packageSpecifier.
- Если packageSpecifier не начинается с "@", то
- Установить packageName в подстроку packageSpecifier до первого разделителя "/" или до конца строки.
- Иначе
- Если packageSpecifier не содержит разделителя "/", то
- Выдать ошибку Недопустимый спецификатор модуля.
- Установить packageName в подстроку packageSpecifier до второго разделителя "/" или до конца строки.
- Если packageName начинается с "." или содержит "\" либо "%", то
- Выдать ошибку Недопустимый спецификатор модуля.
- Пусть 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.
- Выдать ошибку Модуль не найден.
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 является объектом, содержащим ключ, начинающийся с ".", и ключ, не начинающийся с ".", выдать ошибку Недопустимая конфигурация пакета.
- Если 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 является объектом и все его ключи начинаются с ".", то
- Утверждение: subpath начинается с "./".
- Пусть resolved будет результатом PACKAGE_IMPORTS_EXPORTS_RESOLVE( subpath, exports, packageURL, false, conditions).
- Если resolved не равен null или undefined, вернуть resolved.
- Выдать ошибку Путь пакета не экспортирован.
PACKAGE_IMPORTS_RESOLVE(specifier, parentURL, conditions)
Примечание: эта функция вызывается непосредственно алгоритмом разрешения CommonJS.
- Утверждение: specifier начинается с "#".
- Если 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_IMPORTS_EXPORTS_RESOLVE(matchKey, matchObj, packageURL, isImports, conditions)
- Если matchKey заканчивается на "/", то
- Выдать ошибку Недопустимый спецификатор модуля.
- Если 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, то
- Выдать ошибку Недопустимая цель пакета.
- Если patternMatch является строкой, то
- Вернуть результат PACKAGE_RESOLVE(target, в котором каждое вхождение "*" заменено на patternMatch, packageURL + "/").
- Вернуть результат PACKAGE_RESOLVE(target, packageURL + "/").
- Если при разделении target по "/" или "\" после первого сегмента "." обнаруживаются сегменты "", ".", ".." или "node_modules" (без учёта регистра и включая варианты с процентным кодированием), выдать ошибку Недопустимая цель пакета.
- Пусть resolvedTarget будет результатом разрешения URL для конкатенации packageURL и target.
- Утверждение: packageURL содержится в resolvedTarget.
- Если patternMatch равен null, то
- Вернуть resolvedTarget.
- Если при разделении patternMatch по "/" или "\" обнаруживаются сегменты "", ".", ".." или "node_modules" (без учёта регистра и включая варианты с процентным кодированием), выдать ошибку Недопустимый спецификатор модуля.
- Вернуть результат разрешения URL для resolvedTarget, в котором каждое вхождение "*" заменено на patternMatch.
- Иначе, если target является ненулевым объектом, то
- Если target содержит ключи-свойства индексов, определённые в ECMA-262 6.1.7 Индекс массива, выдать ошибку Недопустимая конфигурация пакета.
- Для каждого свойства 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); при любой ошибке Недопустимая цель пакета перейти к следующей итерации цикла.
- Если resolved равен undefined, перейти к следующей итерации цикла.
- Вернуть resolved.
- Вернуть или выдать последнюю резервную ошибку разрешения либо вернуть null.
- Иначе, если target равен null, вернуть null.
- Иначе выдать ошибку Недопустимая цель пакета.
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 содержит заголовок типа содержимого "application/wasm" для модуля 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, то
- Выдать ошибку Недопустимая конфигурация пакета.
- Вернуть разобранное содержимое 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-v24.x/docs/api/esm.html