ECMAScript модули¶
Стабильность: 2 – Стабильная
АПИ является удовлетворительным. Совместимость с NPM имеет высший приоритет и не будет нарушена кроме случаев явной необходимости.
Введение¶
Модули ECMAScript - это официальный стандартный формат для упаковки кода JavaScript для повторного использования. Модули определяются с помощью различных операторов импорт
и экспорт
.
Следующий пример модуля ES экспортирует функцию:
1 2 3 4 5 6 |
|
Следующий пример модуля ES импортирует функцию из addTwo.mjs
:
1 2 3 4 5 |
|
Node.js полностью поддерживает модули ECMAScript в их нынешнем виде и обеспечивает взаимодействие между ними и оригинальным форматом модулей, CommonJS.
Включение¶
Node.js имеет две системы модулей: CommonJS модули и ECMAScript модули.
Авторы могут указать Node.js использовать загрузчик модулей ECMAScript через расширение файла .mjs
, поле package.json
"type"
, или флаг --input-type
. Вне этих случаев Node.js будет использовать загрузчик модулей CommonJS. Более подробную информацию смотрите в разделе Определение системы модулей.
Пакеты¶
Этот раздел был перемещен в Модули: Пакеты.
Спецификаторы импорта
¶
Терминология¶
Спецификатор оператора импорта
- это строка после ключевого слова from
, например, 'node:path'
в import { sep } from 'node:path'
. Спецификаторы также используются в операторах export from
и в качестве аргумента выражения import()
.
Существует три типа спецификаторов:
-
Относительные спецификаторы, такие как
'./startup.js'
или'../config.mjs'
. Они указывают путь относительно местоположения импортируемого файла. Для них всегда необходимо расширение файла. -
Голые спецификаторы, такие как
некоторый пакет
илинекоторый пакет/shuffle
. Они могут ссылаться на основную точку входа пакета по имени пакета или на конкретный функциональный модуль внутри пакета с префиксом имени пакета, как показано в примерах соответственно. Указание расширения файла необходимо только для пакетов без поляexports
. -
Абсолютные спецификаторы, такие как
'file:///opt/nodejs/config.js'
. Они прямо и однозначно ссылаются на полный путь.
Разрешение голых спецификаторов обрабатывается алгоритмом разрешения модулей Node.js. Все остальные разрешения спецификаторов всегда разрешаются только с помощью стандартной семантики разрешения относительных URL.
Как и в CommonJS, доступ к файлам модулей внутри пакетов можно получить, добавив путь к имени пакета, если только package.json
не содержит поле "exports"
, в этом случае доступ к файлам внутри пакетов можно получить только по путям, определенным в "exports"
.
Подробнее об этих правилах разрешения пакетов, которые применяются к голым спецификаторам в разрешении модулей Node.js, смотрите документацию packages.
Обязательные расширения файлов¶
Расширение файла должно быть указано при использовании ключевого слова import
для разрешения относительных или абсолютных спецификаторов. Индексы каталогов (например, './startup/index.js'
) также должны быть полностью указаны.
Это поведение соответствует тому, как import
ведет себя в среде браузера, предполагая типично настроенный сервер.
URLs¶
Модули ES разрешаются и кэшируются как URL-адреса. Это означает, что специальные символы должны быть percent-encoded, такие как #
с %23
и ?
с %3F
.
Поддерживаются схемы URL file:
, node:
и data:
. Спецификатор типа 'https://example.com/app.js'
не поддерживается в Node.js, если только не используется пользовательский HTTPS-загрузчик.
file:
URLs¶
Модули загружаются несколько раз, если спецификатор import
, используемый для их разрешения, имеет другой запрос или фрагмент.
1 2 |
|
На корень тома можно ссылаться через /
, //
или file:///
. Учитывая различия между URL и разрешением пути (например, детали кодировки процентов), рекомендуется использовать url.pathToFileURL при импорте пути.
data:
imports¶
data:
URLs поддерживаются для импорта со следующими MIME-типами:
text/javascript
для модулей ESapplication/json
для JSONapplication/wasm
для Wasm
1 2 |
|
data:
URL разрешают только голые спецификаторы для встроенных модулей и абсолютные спецификаторы. Разрешение относительных спецификаторов не работает, потому что data:
не является специальной схемой. Например, попытка загрузить ./foo
из data:text/javascript,import "./foo";
не удается, потому что не существует понятия относительного разрешения для data:
URL.
node:
imports¶
URL-адреса node:
поддерживаются как альтернативный способ загрузки встроенных модулей Node.js. Эта схема URL позволяет ссылаться на встроенные модули с помощью допустимых абсолютных строк URL.
1 |
|
Утверждения импорта¶
Стабильность: 1 – Экспериментальная
Экспериментальный
Предложение Import Assertions proposal добавляет встроенный синтаксис для операторов импорта модулей, чтобы передавать дополнительную информацию наряду со спецификатором модуля.
1 2 3 4 |
|
Node.js поддерживает следующие значения type
, для которых утверждение является обязательным:
Assertion type | Needed for |
---|---|
'json' | JSON modules |
Встроенные модули¶
Core modules предоставляют именованные экспорты своих публичных API. Также предоставляется экспорт по умолчанию, который является значением экспорта CommonJS. Экспорт по умолчанию можно использовать, в частности, для модификации именованных экспортов. Именованные экспорты встроенных модулей обновляются только вызовом module.syncBuiltinESMExports()
.
1 2 |
|
1 2 3 4 5 6 7 8 |
|
1 2 3 4 5 6 7 8 9 10 |
|
выражения import()
¶
Dynamic import()
поддерживается как в модулях CommonJS, так и в модулях ES. В модулях CommonJS его можно использовать для загрузки модулей ES.
import.meta
¶
Мета-свойство import.meta
представляет собой объект
, содержащий следующие свойства.
import.meta.url
¶
<string>
Абсолютныйфайл:
URL модуля.
Определяется точно так же, как и в браузерах, предоставляя URL текущего файла модуля.
Это позволяет использовать такие полезные шаблоны, как относительная загрузка файлов:
1 2 3 4 |
|
import.meta.resolve(specifier[, parent])
¶
Стабильность: 1 – Экспериментальная
Экспериментальная
Эта функция доступна только при включенном флаге команды --experimental-import-meta-resolve
.
specifier
<string>
Спецификатор модуля для разрешения относительноparent
.parent
<string>
|<URL>
Абсолютный URL родительского модуля для преобразования. Если не указан, то по умолчанию используется значениеimport.meta.url
.- Возвращает:
<Promise>
Предоставляет функцию разрешения относительно модуля, относящуюся к каждому модулю и возвращающую строку URL.
1 2 3 |
|
import.meta.resolve
также принимает второй аргумент, который является родительским модулем, из которого нужно выполнить resolve:
1 |
|
Эта функция является асинхронной, поскольку модульный резолвер ES в Node.js может быть асинхронным.
Взаимодействие с CommonJS¶
импортные
утверждения¶
Оператор импорта
может ссылаться на модуль ES или модуль CommonJS. Операторы импорта
разрешены только в модулях ES, но в CommonJS для загрузки модулей ES поддерживаются динамические выражения import()
.
При импорте модулей CommonJS в качестве экспорта по умолчанию предоставляется объект module.exports
. Могут быть доступны именованные экспорты, предоставляемые статическим анализом в качестве удобства для лучшей совместимости с экосистемой.
require
¶
Модуль CommonJS require
всегда рассматривает файлы, на которые он ссылается, как CommonJS.
Использование require
для загрузки модуля ES не поддерживается, поскольку модули ES имеют асинхронное выполнение. Вместо этого используйте import()
для загрузки модуля ES из модуля CommonJS.
Пространства имен CommonJS¶
Модули CommonJS состоят из объекта module.exports
, который может быть любого типа.
При импорте модуля CommonJS он может быть надежно импортирован с помощью стандартного импорта модуля ES или соответствующего сахарного синтаксиса:
1 2 3 4 5 6 7 8 9 10 11 |
|
Представление ECMAScript Module Namespace модуля CommonJS всегда является пространством имен с ключом экспорта default
, указывающим на значение CommonJS module.exports
.
Этот экзотический объект пространства имен модуля можно непосредственно наблюдать либо при использовании import * as m from 'cjs'
, либо при динамическом импорте:
1 2 3 4 5 6 |
|
Для лучшей совместимости с существующим использованием в экосистеме JS, Node.js дополнительно пытается определить именованные экспорты CommonJS каждого импортированного модуля CommonJS, чтобы предоставить их как отдельные экспорты модуля ES, используя процесс статического анализа.
Например, рассмотрим модуль CommonJS, написанный:
1 2 |
|
Предыдущий модуль поддерживает именованный импорт в модулях ES:
1 2 3 4 5 6 7 8 9 10 11 |
|
Как видно из последнего примера регистрации экзотического объекта пространства имен модуля, экспорт name
копируется из объекта module.exports
и устанавливается непосредственно в пространство имен модуля ES при импорте модуля.
Обновления живой привязки или новые экспорты, добавленные в module.exports
, не обнаруживаются для этих именованных экспортов.
Обнаружение именованных экспортов основано на общих синтаксических шаблонах, но не всегда правильно определяет именованные экспорты. В этих случаях использование формы импорта по умолчанию, описанной выше, может быть лучшим вариантом.
Обнаружение именованного экспорта охватывает многие общие шаблоны экспорта, шаблоны реэкспорта, а также выходы инструментов сборки и транспилятора. Точная семантика реализована в cjs-module-lexer.
Различия между модулями ES и CommonJS¶
Нет require
, exports
или module.exports
¶
В большинстве случаев для загрузки модулей CommonJS можно использовать модуль ES import
.
При необходимости функция require
может быть создана в модуле ES с помощью module.createRequire()
.
Нет __filename
или __dirname
¶
Эти переменные CommonJS недоступны в модулях ES.
Варианты использования __filename
и __dirname
могут быть воспроизведены через import.meta.url
.
Нет загрузки аддонов¶
Addons в настоящее время не поддерживаются импортом модулей ES.
Вместо этого они могут быть загружены с помощью module.createRequire()
или process.dlopen
.
Нет require.resolve
¶
Относительное разрешение может быть обработано через new URL('./local', import.meta.url)
.
Для полной замены require.resolve
существует экспериментальный API import.meta.resolve
.
В качестве альтернативы можно использовать module.createRequire()
.
Нет NODE_PATH
¶
NODE_PATH
не является частью разрешения спецификаторов импорта
. Пожалуйста, используйте симлинки, если такое поведение желательно.
Нет require.extensions
¶
require.extensions
не используется import
. Ожидается, что крючки загрузчика смогут обеспечить этот рабочий процесс в будущем.
Нет require.cache
¶
require.cache
не используется import
, поскольку загрузчик модулей ES имеет свой собственный отдельный кэш.
Модули JSON¶
Стабильность: 1 – Экспериментальная
Экспериментальный
Файлы JSON могут быть упомянуты в import
:
1 |
|
Синтаксис assert { type: 'json' }
является обязательным; смотрите Import Assertions.
Импортируемый JSON раскрывает только экспорт default
. Поддержка именованных экспортов отсутствует. Во избежание дублирования создается запись в кэше CommonJS. Тот же объект возвращается в CommonJS, если модуль JSON уже был импортирован по тому же пути.
Модули Wasm.¶
Стабильность: 1 – Экспериментальная
Экспериментальный
Импорт модулей WebAssembly поддерживается под флагом --experimental-wasm-modules
, что позволяет импортировать любые файлы .wasm
как обычные модули, поддерживая при этом импорт их модулей.
Эта интеграция соответствует ES Module Integration Proposal for WebAssembly.
Например, index.mjs
, содержащий:
1 2 |
|
выполненные под:
1 |
|
обеспечит интерфейс экспорта для инстанцирования module.wasm
.
Верхний уровень await
¶
Ключевое слово await
можно использовать в теле верхнего уровня модуля ECMAScript.
Предположим, что a.mjs
с
1 |
|
И b.mjs
с
1 2 3 |
|
1 |
|
Если выражение верхнего уровня await
никогда не разрешится, процесс node
завершится с 13
кодом состояния.
1 2 3 4 5 6 7 8 9 10 11 |
|
Импорт HTTPS и HTTP¶
Стабильность: 1 – Экспериментальная
Экспериментальный
Импорт сетевых модулей с использованием https:
и http:
поддерживается под флагом --experimental-network-imports
. Это позволяет импортировать модули, подобные веб-браузеру, в Node.js с некоторыми отличиями, связанными со стабильностью приложения и проблемами безопасности, которые отличаются при работе в привилегированной среде, а не в песочнице браузера.
Импорт ограничен HTTP/1¶
Автоматическое согласование протоколов для HTTP/2 и HTTP/3 пока не поддерживается.
HTTP ограничен адресами loopback¶
http:
уязвим для атак типа "человек посередине" и не может использоваться для адресов за пределами IPv4-адреса 127.0.0.0/8
(127.0.0.1
- 127.255.255.255
) и IPv6-адреса ::1
. Поддержка http:
предназначена для использования в локальных разработках.
Аутентификация никогда не отправляется на сервер назначения.¶
Заголовки Authorization
, Cookie
и Proxy-Authorization
не отправляются на сервер. Избегайте включения информации о пользователе в части импортируемых URL. В настоящее время разрабатывается модель безопасности для безопасного использования этих заголовков на сервере.
CORS никогда не проверяется на сервере назначения¶
CORS разработан для того, чтобы позволить серверу ограничить потребителей API определенным набором хостов. Это не поддерживается, так как не имеет смысла для реализации на базе сервера.
Невозможно загрузить несетевые зависимости.¶
Эти модули не могут получить доступ к другим модулям, которые не работают через http:
или https:
. Чтобы получить доступ к локальным модулям и избежать проблем с безопасностью, передавайте ссылки на локальные зависимости:
1 2 3 4 5 6 7 |
|
1 2 3 4 5 6 7 8 |
|
Загрузка по сети не включена по умолчанию.¶
Пока что флаг --experimental-network-imports
необходим для включения загрузки ресурсов через http:
или https:
. В будущем для обеспечения этого будет использоваться другой механизм. Opt-in требуется для предотвращения случайного использования переходных зависимостей с потенциально изменяемым состоянием, что может повлиять на надежность приложений Node.js.
Грузчики¶
Стабильность: 1 – Экспериментальная
Экспериментальный
В настоящее время этот API находится в стадии разработки и будет меняться.
Чтобы настроить разрешение модулей по умолчанию, можно опционально предоставить крючки загрузчика через аргумент --experimental-loader ./loader-name.mjs
в Node.js.
При использовании хуков они применяются к каждому последующему загрузчику, точке входа и всем вызовам import
. Они не применяются к вызовам require
; они по-прежнему следуют правилам CommonJS.
Загрузчики следуют шаблону --require
:
1 2 3 4 |
|
Они вызываются в следующей последовательности: cache-buster
вызывает http-to-https
, который вызывает unpkg
.
Крючки¶
Хуки являются частью цепочки, даже если эта цепочка состоит только из одного пользовательского (user-provided) хука и хука по умолчанию, который присутствует всегда. Функции хуков вложены друг в друга: каждая из них всегда должна возвращать простой объект, а цепочка происходит в результате вызова каждой функцией функции next<hookName>()
, которая является ссылкой на последующий хук загрузчика.
Хук, возвращающий значение, в котором отсутствует необходимое свойство, вызывает исключение. Хук, который возвращается без вызова next<hookName>()
и без возврата shortCircuit: true
, также вызывает исключение. Эти ошибки призваны помочь предотвратить непреднамеренный разрыв цепи.
resolve(specifier, context, nextResolve)
¶
В настоящее время API загрузчиков перерабатывается. Этот хук может исчезнуть или его сигнатура может измениться. Не полагайтесь на API, описанный ниже.
specifier
<string>
контекст
<Object>
условия
<string[]>
Условия экспорта соответствующегоpackage.json
.importAssertions
<Object>
Объект, пары ключ-значение которого представляют утверждения для импортируемого модуляparentURL
{string|undefined} Модуль, импортирующий данный модуль, или undefined, если это точка входа Node.js
nextResolve
<Function>
Следующий хукresolve
в цепочке, или хукresolve
по умолчанию Node.js после последнего пользовательского хукаresolve
.- Возвращает:
<Object>
формат
{string|null|undefined} Подсказка для крючка загрузки (может быть проигнорирована)'builtin' | 'commonjs' | 'json' | 'module' | 'wasm'
.importAssertions
{Object|undefined} Утверждения импорта для использования при кэшировании модуля (необязательно; если исключить, то будут использоваться входные данные)shortCircuit
{undefined|boolean} Сигнал о том, что этот хук намерен прервать цепочку хуковresolve
. По умолчанию:false
.url
<string>
Абсолютный URL, к которому разрешается данный вход.
Цепочка хуков resolve
отвечает за указание Node.js, где найти и как кэшировать заданный оператор import
или выражение. По желанию она может возвращать его формат (например, 'module'
) в качестве подсказки для хука load
. Если формат не указан, крючок load
в конечном итоге отвечает за предоставление окончательного значения формата
(и он может игнорировать подсказку, предоставленную resolve
); если resolve
предоставляет формат
, требуется пользовательский крючок load
, даже если только для передачи значения крючку Node.js по умолчанию load
.
Утверждения типов импорта являются частью ключа кэша для сохранения загруженных модулей во внутреннем кэше модулей. Хук resolve
отвечает за возврат объекта importAssertions
, если модуль должен быть кэширован с утверждениями, отличными от тех, что присутствуют в исходном коде.
Свойство conditions
в context
- это массив условий package exports conditions, которые применяются к данному запросу разрешения. Их можно использовать для поиска условных сопоставлений в других местах или для изменения списка при вызове логики разрешения по умолчанию.
Текущие условия package exports conditions всегда находятся в массиве context.conditions
, передаваемом в хук. Чтобы гарантировать дефолтное поведение разрешения спецификатора модуля Node.js при вызове defaultResolve
, массив context.conditions
, переданный ему, должен включать все элементы массива context.conditions
, изначально переданного в хук resolve
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
|
load(url, context, nextLoad)
¶
API загрузчиков находится в стадии переработки. Этот хук может исчезнуть или его сигнатура может измениться. Не полагайтесь на API, описанный ниже.
В предыдущей версии этого API эта функция была разделена на 3 отдельных, ныне устаревших хука (
getFormat
,getSource
иtransformSource
).
url
<string>
URL, возвращаемый цепочкойresolve
.context
<Object>
conditions
<string[]>
Условия экспорта соответствующегоpackage.json
.формат
{string|null|undefined} Формат, опционально предоставляемый цепочкой хуковresolve
.importAssertions
<Object>
nextLoad
<Function>
Следующийload
хук в цепочке, илиload
хук по умолчанию Node.js после последнего пользовательскогоload
хука.- Возвращает:
<Object>
формат
<string>
shortCircuit
{undefined|boolean} Сигнал о том, что этот хук намерен прервать цепочку хуковresolve
. По умолчанию:false
.source
{string|ArrayBuffer|TypedArray} Источник для оценки Node.js
Хук load
предоставляет возможность определить пользовательский метод определения того, как URL должен быть интерпретирован, получен и разобран. Он также отвечает за проверку утверждения об импорте.
Конечное значение format
должно быть одним из следующих:
format | Description | Acceptable types for source returned by load |
---|---|---|
'builtin' | Load a Node.js builtin module | Not applicable |
'commonjs' | Load a Node.js CommonJS module | Not applicable |
'json' | Load a JSON file | { string , ArrayBuffer , TypedArray } |
'module' | Load an ES module | { string , ArrayBuffer , TypedArray } |
'wasm' | Load a WebAssembly module | { ArrayBuffer , TypedArray } |
Значение source
игнорируется для типа 'builtin'
, потому что в настоящее время невозможно заменить значение встроенного (core) модуля Node.js. Значение source
игнорируется для типа 'commonjs'
, потому что загрузчик модуля CommonJS не предоставляет механизм для загрузчика модуля ES для переопределения возвращаемого значения модуля CommonJS. Это ограничение может быть преодолено в будущем.
Кавэат: ESM
load
hook и namespaced exports из модулей CommonJS несовместимы. Попытка использовать их вместе приведет к получению пустого объекта при импорте. Эта проблема может быть решена в будущем.Все эти типы соответствуют классам, определенным в ECMAScript.
- Конкретный объект
ArrayBuffer
является объектомSharedArrayBuffer
. - Конкретным объектом
TypedArray
являетсяUint8Array
.
Если исходное значение текстового формата (например, 'json'
, 'module'
) не является строкой, оно преобразуется в строку с помощью util.TextDecoder
.
Хук load
предоставляет возможность определить пользовательский метод для получения исходного кода спецификатора модуля ES. Это позволит загрузчику потенциально избежать чтения файлов с диска. Его также можно использовать для сопоставления нераспознанного формата с поддерживаемым, например, yaml
с module
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
В более продвинутом сценарии это также можно использовать для преобразования неподдерживаемого источника в поддерживаемый (см. Примеры ниже).
globalPreload()
¶
В настоящее время API загрузчиков перерабатывается. Этот хук может исчезнуть или его сигнатура может измениться. Не полагайтесь на API, описанный ниже.
В предыдущей версии этого API этот хук назывался
getGlobalPreloadCode
.
context
<Object>
Информация для помощи коду предварительной загрузкиport
{MessagePort}
- Возвращает:
<string>
Код для запуска перед стартом приложения
Иногда может потребоваться запустить некоторый код внутри той же глобальной области видимости, в которой запускается приложение. Этот хук позволяет вернуть строку, которая будет запущена как скрипт в небрежном режиме при запуске приложения.
Подобно тому, как работают обертки CommonJS, код запускается в неявной области видимости функции. Единственным аргументом является require
-подобная функция, которая может быть использована для загрузки встроенных модулей, таких как "fs": getBuiltin(request: string)
.
Если коду нужны более продвинутые функции require
, он должен создать свой собственный require
, используя module.createRequire()
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Для того чтобы обеспечить связь между приложением и загрузчиком, коду предварительной загрузки предоставляется еще один аргумент: port
. Он доступен в качестве параметра хука загрузчика и внутри исходного текста, возвращаемого хуком. Необходимо соблюдать некоторую осторожность, чтобы правильно вызвать port.ref()
и port.unref()
, чтобы предотвратить нахождение процесса в состоянии, в котором он не сможет нормально закрыться.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Примеры¶
Различные крючки загрузчика могут быть использованы вместе для выполнения широкой настройки поведения загрузки и оценки кода Node.js.
HTTPS-загрузчик¶
В текущем Node.js спецификаторы, начинающиеся с https://
, являются экспериментальными (см. HTTPS и HTTP imports).
Приведенный ниже загрузчик регистрирует хуки, чтобы обеспечить элементарную поддержку таких спецификаторов. Хотя это может показаться значительным улучшением основной функциональности Node.js, есть существенные недостатки фактического использования этого загрузчика: производительность намного ниже, чем при загрузке файлов с диска, нет кэширования, и нет безопасности.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
|
1 2 3 4 |
|
С предыдущим загрузчиком выполнение node --experimental-loader ./https-loader.mjs ./main.mjs
выводит текущую версию CoffeeScript для модуля по URL в main.mjs
.
Transpiler loader¶
Исходные тексты в форматах, которые Node.js не понимает, могут быть преобразованы в JavaScript с помощью хука load
. Однако прежде чем этот хук будет вызван, хук resolve
должен сказать Node.js, чтобы он не выдавал ошибку при неизвестных типах файлов.
Это менее эффективно, чем транспонирование исходных файлов перед запуском Node.js; загрузчик с транспонированием следует использовать только в целях разработки и тестирования.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
|
1 2 3 4 5 6 7 |
|
1 2 |
|
С предыдущим загрузчиком выполнение node --experimental-loader ./coffeescript-loader.mjs main.coffee
приводит к тому, что main.coffee
превращается в JavaScript после загрузки его исходного кода с диска, но до того, как Node.js выполнит его; и так далее для любых файлов .coffee
, .litcoffee
или .coffee.md
, на которые ссылаются операторы import
любого загруженного файла.
Алгоритм разрешения¶
Особенности¶
Резольвер обладает следующими свойствами:
- Разрешение на основе FileURL, как это используется в модулях ES
- Поддержка загрузки встроенных модулей
- Относительное и абсолютное разрешение URL
- Отсутствие расширений по умолчанию
- Отсутствие папки mains
- Поиск разрешения пакетов с голыми спецификаторами через node_modules
Алгоритм разрешителя¶
Алгоритм загрузки спецификатора модуля ES задается с помощью метода ESM_RESOLVE, приведенного ниже. Он возвращает разрешенный URL для спецификатора модуля относительно родительскогоURL.
Алгоритм определения формата модуля для разрешенного URL предоставляется методом ESM_FORMAT, который возвращает уникальный формат модуля для любого файла. Формат "module" возвращается для модуля ECMAScript, а формат "commonjs" используется для указания загрузки через старый загрузчик CommonJS. Дополнительные форматы, такие как "addon", могут быть расширены в будущих обновлениях.
В следующих алгоритмах все ошибки подпрограмм распространяются как ошибки этих подпрограмм верхнего уровня, если не указано иное.
defaultConditions - это массив имен условного окружения, ["node", "import"]
.
Резольвер может выдать следующие ошибки:
- Invalid Module Specifier: Спецификатор модуля является недопустимым URL, именем пакета или спецификатором подпути пакета.
- Invalid Package Configuration: конфигурация package.json недопустима или содержит недопустимую конфигурацию.
- Неверная цель пакета: Экспорт или импорт пакета определяет целевой модуль для пакета, который является недопустимым типом или строковым целевым модулем.
- Путь пакета не экспортирован: Экспорт пакетов не определяет или не разрешает целевой подпуть в пакете для данного модуля.
- Импорт пакета не определен: Импорт пакета не определяет спецификатор.
- Module Not Found: Запрашиваемый пакет или модуль не существует.
- Unsupported Directory Import: Разрешенный путь соответствует каталогу, который не является поддерживаемой целью для импорта модулей.
Спецификация алгоритма резольвера¶
ESM_RESOLVE(specifier, parentURL)
- Let resolved be undefined.
- If specifier is a valid URL, then
- Set resolved to the result of parsing and reserializing specifier as a URL.
- Otherwise, if specifier starts with “/”, “./”, or “../”, then
- Set resolved to the URL resolution of specifier relative to parentURL.
- Otherwise, if specifier starts with “#”, then
- Set resolved to the result of PACKAGE_IMPORTS_RESOLVE(specifier, parentURL, defaultConditions).
- Otherwise,
- Note: specifier is now a bare specifier.
- Set resolved the result of PACKAGE_RESOLVE(specifier, parentURL).
- Let format be undefined.
- If resolved is a “file:” URL, then
- If resolved contains any percent encodings of “/” or “\” (“%2F” and “%5C” respectively), then
- Throw an Invalid Module Specifier error.
- If the file at resolved is a directory, then
- Throw an Unsupported Directory Import error.
- If the file at resolved does not exist, then
- Throw a Module Not Found error.
- Set resolved to the real path of resolved, maintaining the same URL querystring and fragment components.
- Set format to the result of ESM_FILE_FORMAT(resolved).
- Otherwise,
- Set format the module format of the content type associated with the URL resolved.
- Load resolved as module format, format.
PACKAGE_RESOLVE(packageSpecifier, parentURL)
- Let packageName be undefined.
- If packageSpecifier is an empty string, then
- Throw an Invalid Module Specifier error.
- If packageSpecifier is a Node.js builtin module name, then
- Return the string “node:” concatenated with packageSpecifier.
- If packageSpecifier does not start with “@”, then
- Set packageName to the substring of packageSpecifier until the first “/” separator or the end of the string.
- Otherwise,
- If packageSpecifier does not contain a “/” separator, then
- Throw an Invalid Module Specifier error.
- Set packageName to the substring of packageSpecifier until the second “/” separator or the end of the string.
- If packageName starts with “.” or contains “\” or “%”, then
- Throw an Invalid Module Specifier error.
- Let packageSubpath be “.” concatenated with the substring of packageSpecifier from the position at the length of packageName.
- If packageSubpath ends in “/”, then
- Throw an Invalid Module Specifier error.
- Let selfUrl be the result of PACKAGE_SELF_RESOLVE(packageName, packageSubpath, parentURL).
- If selfUrl is not undefined, return selfUrl.
- While parentURL is not the file system root,
- Let packageURL be the URL resolution of “node_modules/” concatenated with packageSpecifier, relative to parentURL.
- Set parentURL to the parent folder URL of parentURL.
- If the folder at packageURL does not exist, then
- Continue the next loop iteration.
- Let pjson be the result of READ_PACKAGE_JSON(packageURL).
- If pjson is not null and pjson._exports_ is not null or undefined, then
- Return the result of PACKAGE_EXPORTS_RESOLVE(packageURL, packageSubpath, pjson.exports, defaultConditions).
- Otherwise, if packageSubpath is equal to “.”, then
- If pjson.main is a string, then
- Return the URL resolution of main in packageURL.
- Otherwise,
- Return the URL resolution of packageSubpath in packageURL.
- Throw a Module Not Found error.
PACKAGE_SELF_RESOLVE(packageName, packageSubpath, parentURL)
- Let packageURL be the result of LOOKUP_PACKAGE_SCOPE(parentURL).
- If packageURL is null, then
- Return undefined.
- Let pjson be the result of READ_PACKAGE_JSON(packageURL).
- If pjson is null or if pjson._exports_ is null or undefined, then
- Return undefined.
- If pjson.name is equal to packageName, then
- Return the result of PACKAGE_EXPORTS_RESOLVE(packageURL, packageSubpath, pjson.exports, defaultConditions).
- Otherwise, return undefined.
PACKAGE_EXPORTS_RESOLVE(packageURL, subpath, exports, conditions)
- If exports is an Object with both a key starting with “.” and a key not starting with “.”, throw an Invalid Package Configuration error.
- If subpath is equal to “.”, then
- Let mainExport be undefined.
- If exports is a String or Array, or an Object containing no keys starting with “.”, then
- Set mainExport to exports.
- Otherwise if exports is an Object containing a “.” property, then
- Set mainExport to exports[“.”].
- If mainExport is not undefined, then
- Let resolved be the result of PACKAGE_TARGET_RESOLVE( packageURL, mainExport, null, false, conditions).
- If resolved is not null or undefined, return resolved.
- Otherwise, if exports is an Object and all keys of exports start with “.”, then
- Let matchKey be the string “./” concatenated with subpath.
- Let resolved be the result of PACKAGE_IMPORTS_EXPORTS_RESOLVE( matchKey, exports, packageURL, false, conditions).
- If resolved is not null or undefined, return resolved.
- Throw a Package Path Not Exported error.
PACKAGE_IMPORTS_RESOLVE(specifier, parentURL, conditions)
- Assert: specifier begins with “#”.
- If specifier is exactly equal to “#” or starts with “#/”, then
- Throw an Invalid Module Specifier error.
- Let packageURL be the result of LOOKUP_PACKAGE_SCOPE(parentURL).
- If packageURL is not null, then
- Let pjson be the result of READ_PACKAGE_JSON(packageURL).
- If pjson.imports is a non-null Object, then
- Let resolved be the result of PACKAGE_IMPORTS_EXPORTS_RESOLVE( specifier, pjson.imports, packageURL, true, conditions).
- If resolved is not null or undefined, return resolved.
- Throw a Package Import Not Defined error.
PACKAGE_IMPORTS_EXPORTS_RESOLVE(matchKey, matchObj, packageURL, isImports, conditions)
- If matchKey is a key of matchObj and does not contain “*”, then
- Let target be the value of matchObj[matchKey].
- Return the result of PACKAGE_TARGET_RESOLVE(packageURL, target, null, isImports, conditions).
- Let expansionKeys be the list of keys of matchObj containing only a single “*”, sorted by the sorting function PATTERN_KEY_COMPARE which orders in descending order of specificity.
- For each key expansionKey in expansionKeys, do
- Let patternBase be the substring of expansionKey up to but excluding the first “*” character.
- If matchKey starts with but is not equal to patternBase, then
- Let patternTrailer be the substring of expansionKey from the index after the first “*” character.
- If patternTrailer has zero length, or if matchKey ends with patternTrailer and the length of matchKey is greater than or equal to the length of expansionKey, then
- Let target be the value of matchObj[expansionKey].
- Let patternMatch be the substring of matchKey starting at the index of the length of patternBase up to the length of matchKey minus the length of patternTrailer.
- Return the result of PACKAGE_TARGET_RESOLVE(packageURL, target, patternMatch, isImports, conditions).
- Return null.
PATTERN_KEY_COMPARE(keyA, keyB)
- Assert: keyA ends with “/” or contains only a single “*”.
- Assert: keyB ends with “/” or contains only a single “*”.
- Let baseLengthA be the index of “*” in keyA plus one, if keyA contains “*”, or the length of keyA otherwise.
- Let baseLengthB be the index of “*” in keyB plus one, if keyB contains “*”, or the length of keyB otherwise.
- If baseLengthA is greater than baseLengthB, return -1.
- If baseLengthB is greater than baseLengthA, return 1.
- If keyA does not contain “*”, return 1.
- If keyB does not contain “*”, return -1.
- If the length of keyA is greater than the length of keyB, return -1.
- If the length of keyB is greater than the length of keyA, return 1.
- Return 0.
PACKAGE_TARGET_RESOLVE(packageURL, target, patternMatch, isImports, conditions)
- If target is a String, then
- If target does not start with “./”, then
- If isImports is false, or if target starts with “../” or “/”, or if target is a valid URL, then
- Throw an Invalid Package Target error.
- If patternMatch is a String, then
- Return PACKAGE_RESOLVE(target with every instance of “*” replaced by patternMatch, packageURL + “/”).
- Return PACKAGE_RESOLVE(target, packageURL + “/”).
- If target split on “/” or “\” contains any "“, ”.“, ”..“, or ”node_modules" segments after the first “.” segment, case insensitive and including percent encoded variants, throw an Invalid Package Target error.
- Let resolvedTarget be the URL resolution of the concatenation of packageURL and target.
- Assert: resolvedTarget is contained in packageURL.
- If patternMatch is null, then
- Return resolvedTarget.
- If patternMatch split on “/” or “\” contains any "“, ”.“, ”..“, or ”node_modules" segments, case insensitive and including percent encoded variants, throw an Invalid Module Specifier error.
- Return the URL resolution of resolvedTarget with every instance of “*” replaced with patternMatch.
- Otherwise, if target is a non-null Object, then
- If exports contains any index property keys, as defined in ECMA-262 6.1.7 Array Index, throw an Invalid Package Configuration error.
- For each property p of target, in object insertion order as,
- If p equals “default” or conditions contains an entry for p, then
- Let targetValue be the value of the p property in target.
- Let resolved be the result of PACKAGE_TARGET_RESOLVE( packageURL, targetValue, patternMatch, isImports, conditions).
- If resolved is equal to undefined, continue the loop.
- Return resolved.
- Return undefined.
- Otherwise, if target is an Array, then
- If _target.length is zero, return null.
- For each item targetValue in target, do
- Let resolved be the result of PACKAGE_TARGET_RESOLVE( packageURL, targetValue, patternMatch, isImports, conditions), continuing the loop on any Invalid Package Target error.
- If resolved is undefined, continue the loop.
- Return resolved.
- Return or throw the last fallback resolution null return or error.
- Otherwise, if target is null, return null.
- Otherwise throw an Invalid Package Target error.
ESM_FILE_FORMAT(url)
- Assert: url corresponds to an existing file.
- If url ends in “.mjs”, then
- Return “module”.
- If url ends in “.cjs”, then
- Return “commonjs”.
- If url ends in “.json”, then
- Return “json”.
- Let packageURL be the result of LOOKUP_PACKAGE_SCOPE(url).
- Let pjson be the result of READ_PACKAGE_JSON(packageURL).
- If pjson?.type exists and is “module”, then
- If url ends in “.js”, then
- Return “module”.
- Throw an Unsupported File Extension error.
- Otherwise,
- Throw an Unsupported File Extension error.
LOOKUP_PACKAGE_SCOPE(url)
- Let scopeURL be url.
- While scopeURL is not the file system root,
- Set scopeURL to the parent URL of scopeURL.
- If scopeURL ends in a “node_modules” path segment, return null.
- Let pjsonURL be the resolution of “package.json” within scopeURL.
- if the file at pjsonURL exists, then
- Return scopeURL.
- Return null.
READ_PACKAGE_JSON(packageURL)
- Let pjsonURL be the resolution of “package.json” within packageURL.
- If the file at pjsonURL does not exist, then
- Return null.
- If the file at packageURL does not parse as valid JSON, then
- Throw an Invalid Package Configuration error.
- Return the parsed JSON source of the file at pjsonURL.
Настройка алгоритма разрешения спецификатора ESM¶
API Loaders API предоставляет механизм для настройки алгоритма разрешения спецификаторов ESM. Примером загрузчика, обеспечивающего разрешение ESM-спецификаторов в стиле CommonJS, является commonjs-extension-resolution-loader.