Пакеты¶
Введение¶
Пакет - это дерево папок, описываемое файлом package.json
. Пакет состоит из папки, содержащей файл package.json
, и всех вложенных папок до следующей папки, содержащей другой файл package.json
, или папки с именем node_modules
.
Эта страница содержит руководство для авторов пакетов, пишущих файлы package.json
, а также ссылку на поля package.json
, определенные Node.js.
Определение системы модулей¶
Node.js будет рассматривать следующие файлы как ES модули, когда они передаются в node
в качестве начального ввода, или когда на них ссылаются операторы import
или выражения import()
:
-
Файлы с расширением
.mjs
. -
Файлы с расширением
.js
, если ближайший родительский файлpackage.json
содержит поле верхнего уровня"type"
со значением"module"
. -
Строки, переданные в качестве аргумента в
--eval
, или переданные вnode
черезSTDIN
с флагом--input-type=module
.
Node.js будет рассматривать как CommonJS все другие формы ввода, такие как файлы .js
, где ближайший родительский файл package.json
не содержит поля верхнего уровня "type"
, или строковый ввод без флага --input-type
. Такое поведение сохраняет обратную совместимость. Однако теперь, когда Node.js поддерживает как CommonJS, так и ES модули, лучше быть явным, когда это возможно. Node.js будет рассматривать следующие файлы как CommonJS, когда они передаются в node
в качестве начального ввода, или когда на них ссылаются заявления import
, выражения import()
или выражения require()
:
-
Файлы с расширением
.cjs
. -
Файлы с расширением
.js
, если ближайший родительский файлpackage.json
содержит поле верхнего уровня"type"
со значением"commonjs"
. -
Строки, переданные в качестве аргумента в
--eval
или--print
, или переданные вnode
черезSTDIN
с флагом--input-type=commonjs
.
Авторы пакетов должны включать поле тип
, даже в пакетах, где все источники являются CommonJS. Явное указание типа
пакета защитит пакет в будущем на случай, если тип Node.js по умолчанию когда-либо изменится, а также облегчит инструментам сборки и загрузчикам определение того, как следует интерпретировать файлы в пакете.
Загрузчики модулей¶
Node.js имеет две системы для разрешения спецификатора и загрузки модулей.
Существует загрузчик модулей CommonJS:
- Он полностью синхронный.
- Он отвечает за обработку вызовов
require()
. - Его можно патчить по-обезьяньи.
- Он поддерживает папки как модули.
- При разрешении спецификатора, если не найдено точного соответствия, он попытается добавить расширения (
.js
,.json
, и наконец.node
), а затем попытается разрешить папки как модули. - Он рассматривает
.json
как текстовые файлы JSON. - Файлы
.node
интерпретируются как скомпилированные модули аддонов, загруженные с помощьюprocess.dlopen()
. - Все файлы, не имеющие расширений
.json
или.node
, рассматриваются как текстовые файлы JavaScript. - Его нельзя использовать для загрузки модулей ECMAScript (хотя можно загрузить модули ECMASCript из модулей CommonJS). При использовании для загрузки текстового файла JavaScript, который не является модулем ECMAScript, он загружается как модуль CommonJS.
Существует загрузчик модулей ECMAScript:
- Он является асинхронным.
- Он отвечает за обработку утверждений
import
и выраженийimport()
. - Он не поддается обезьяньим исправлениям, может быть настроен с помощью loader hooks.
- Не поддерживает папки в качестве модулей, индексы директорий (например,
'./startup/index.js'
) должны быть полностью указаны. - Поиск расширений не производится. Расширение файла должно быть указано, если спецификатор является относительным или абсолютным URL файла.
- Он может загружать модули JSON, но при этом требуется утверждение импорта.
- Он принимает только расширения
.js
,.mjs
и.cjs
для текстовых файлов JavaScript. - Он может использоваться для загрузки модулей JavaScript CommonJS. Такие модули пропускаются через
cjs-module-lexer
, чтобы попытаться определить именованные экспорты, которые доступны, если они могут быть определены с помощью статического анализа. Импортированные модули CommonJS преобразуют свои URL в абсолютные пути и затем загружаются через загрузчик модулей CommonJS.
package.json
и расширения файлов¶
Внутри пакета поле package.json
"type"
определяет, как Node.js должен интерпретировать файлы .js
. Если файл package.json
не содержит поля "type"
, файлы .js
рассматриваются как CommonJS.
Значение package.json
"type"
в "module"
указывает Node.js интерпретировать .js
файлы внутри этого пакета как использующие синтаксис ES module.
Поле "type"
применяется не только к начальным точкам входа (node my-app.js
), но и к файлам, на которые ссылаются операторы import
и выражения import()
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Файлы, заканчивающиеся на .mjs
, всегда загружаются как ES модули, независимо от ближайшего родителя package.json
.
Файлы, заканчивающиеся на .cjs
, всегда загружаются как CommonJS независимо от ближайшего родителя package.json
.
1 2 3 4 5 |
|
Расширения .mjs
и .cjs
могут быть использованы для смешивания типов в одном пакете:
-
Внутри пакета
`тип'': "module"
пакета, Node.js можно проинструктировать интерпретировать определенный файл как CommonJS, назвав его с расширением.cjs
(поскольку файлы.js
и.mjs
рассматриваются как ES модули в пакете"module"
). -
Внутри пакета
"type": "commonjs"
пакета, Node.js можно указать интерпретировать определенный файл как ES модуль, назвав его с расширением.mjs
(поскольку файлы.js
и.cjs
рассматриваются как CommonJS в пакете"commonjs"
).
флаг --input-type
¶
Строки, переданные в качестве аргумента в --eval
(или -e
), или переданные в node
через STDIN
, рассматриваются как ES модули, если установлен флаг --input-type=module
.
1 2 |
|
Для полноты картины существует также --input-type=commonjs
, для явного запуска строкового ввода как CommonJS. Это поведение по умолчанию, если --input-type
не указан.
Определение менеджера пакетов¶
Стабильность: 1 – Экспериментальная
Фича изменяется и не допускается флагом командной строки. Может быть изменена или удалена в последующих версиях.
Хотя ожидается, что все проекты Node.js будут устанавливаться всеми пакетными менеджерами после публикации, их команды разработчиков часто должны использовать один конкретный пакетный менеджер. Чтобы облегчить этот процесс, Node.js поставляется с инструментом под названием Corepack, цель которого - сделать все пакетные менеджеры прозрачно доступными в вашей среде - при условии, что у вас установлен Node.js.
По умолчанию Corepack не будет применять какой-либо конкретный менеджер пакетов и будет использовать общие версии "Last Known Good", связанные с каждым выпуском Node.js, но вы можете улучшить этот опыт, установив поле packageManager
в package.json
вашего проекта.
Точки входа в пакет¶
В файле package.json
пакета два поля могут определять точки входа в пакет: "main"
и "exports"
. Оба поля применимы как к точкам входа ES-модуля, так и модуля CommonJS.
Поле main
поддерживается во всех версиях Node.js, но его возможности ограничены: оно определяет только главную точку входа пакета.
Поле exports
представляет собой современную альтернативу "main"
, позволяя определять несколько точек входа, поддерживать условное разрешение входа между окружениями и предотвращать любые другие точки входа, кроме определенных в exports
. Такая инкапсуляция позволяет авторам модулей четко определить публичный интерфейс для своего пакета.
Для новых пакетов, предназначенных для поддерживаемых в настоящее время версий Node.js, рекомендуется использовать поле exports
. Для пакетов, поддерживающих Node.js 10 и ниже, поле main
является обязательным. Если определены и exports
, и main
, поле exports
имеет приоритет над main
в поддерживаемых версиях Node.js.
Conditional exports можно использовать внутри "exports"
для определения различных точек входа в пакет в зависимости от окружения, включая то, ссылается ли пакет через require
или через import
. Для получения дополнительной информации о поддержке модулей CommonJS и ES в одном пакете обратитесь к разделу о двойных пакетах модулей CommonJS/ES.
Существующие пакеты, вводящие поле exports
, не позволят потребителям пакета использовать любые точки входа, которые не определены, включая package.json
(например, require('your-package/package.json')
). *Это, вероятно, будет ломающим изменением.
Чтобы сделать введение exports
не ломающим, убедитесь, что каждая ранее поддерживаемая точка входа экспортируется. Лучше всего явно указать точки входа, чтобы публичный API пакета был четко определен. Например, проект, который ранее экспортировал main
, lib
, feature
и package.json
, может использовать следующий package.exports
:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
В качестве альтернативы проект может выбрать экспорт целых папок как с расширенными подпапками, так и без них, используя шаблоны экспорта:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Благодаря тому, что вышеприведенное обеспечивает обратную совместимость для любых второстепенных версий пакета, будущее серьезное изменение для пакета может затем правильно ограничить экспорт только определенным экспортом функций:
1 2 3 4 5 6 7 8 |
|
Экспорт главной точки входа¶
При написании нового пакета рекомендуется использовать поле exports
:
1 2 3 |
|
Когда определено поле exports
, все подпакеты пакета инкапсулируются и больше не доступны для импортеров. Например, require('pkg/subpath.js')
вызывает ошибку ERR_PACKAGE_PATH_NOT_EXPORTED
.
Такая инкапсуляция экспорта обеспечивает более надежные гарантии относительно интерфейсов пакетов для инструментов и при обработке обновлений semver для пакета. Это не сильная инкапсуляция, поскольку прямой require любого абсолютного подпути пакета, например require('/path/to/node_modules/pkg/subpath.js')
, все равно загрузит subpath.js
.
Все поддерживаемые в настоящее время версии Node.js и современные инструменты сборки поддерживают поле "exports"
. Для проектов, использующих более старую версию Node.js или соответствующий инструмент сборки, совместимость может быть достигнута включением поля "main"
наряду с "exports"
, указывающим на тот же модуль:
1 2 3 4 |
|
Экспорт подпутей¶
При использовании поля exports
пользовательские подпути могут быть определены вместе с основной точкой входа, рассматривая основную точку входа как "."
подпуть:
1 2 3 4 5 6 |
|
Теперь только определенный подпуть в exports
может быть импортирован потребителем:
1 2 |
|
В то время как другие подпути приведут к ошибке:
1 2 |
|
Расширения в подпакетах¶
Авторы пакетов должны предоставлять либо расширенные (import 'pkg/subpath.js'
), либо нерасширенные (import 'pkg/subpath'
) подпути в своих экспортируемых модулях. Это гарантирует, что для каждого экспортируемого модуля существует только один подпуть, так что все зависимые модули импортируют один и тот же согласованный спецификатор, сохраняя контракт пакета понятным для потребителей и упрощая заполнение подпутей пакета.
Традиционно в пакетах обычно используется стиль без расширения, который имеет преимущества в удобстве чтения и маскировке истинного пути к файлу внутри пакета.
Поскольку import maps теперь является стандартом для разрешения пакетов в браузерах и других средах выполнения JavaScript, использование стиля без расширения может привести к раздутым определениям карт импорта. Явные расширения файлов позволяют избежать этой проблемы, позволяя карте импорта использовать packages folder mapping для отображения нескольких подпутей, где это возможно, вместо отдельной записи карты для экспорта каждого подпути пакета. Это также отражает требование использования полного пути спецификатора в относительных и абсолютных спецификаторах импорта.
Экспорт сахара¶
Если экспорт "."
является единственным экспортом, поле exports
предоставляет сахар для этого случая, являясь прямым значением поля exports
.
1 2 3 4 5 |
|
можно записать:
1 2 3 |
|
Подпункт импорта¶
В дополнение к полю exports
, существует поле пакета imports
для создания частных отображений, которые применяются только к спецификаторам импорта из самого пакета.
Записи в поле "imports"
всегда должны начинаться с #
, чтобы гарантировать, что они не отличаются от внешних спецификаторов пакетов.
Например, поле imports можно использовать для получения преимуществ условного экспорта для внутренних модулей:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
где import '#dep'
не получает разрешение внешнего пакета dep-node-native
(включая его экспорт в свою очередь), а вместо этого получает локальный файл ./dep-polyfill.js
относительно пакета в других окружениях.
В отличие от поля "exports"
, поле "imports"
допускает сопоставление с внешними пакетами.
Правила разрешения для поля imports в остальном аналогичны полю exports.
Шаблоны подпутей¶
Для пакетов с небольшим количеством экспортов или импортов мы рекомендуем явно перечислять каждую запись подпути exports. Но для пакетов с большим количеством подпутей это может привести к раздуванию package.json
и проблемам с обслуживанием.
Для таких случаев вместо этого можно использовать шаблоны экспорта подпутей:
1 2 3 4 5 6 7 8 9 |
|
*
отображает вложенные подпути, поскольку это синтаксис замены только строк..
Все экземпляры *
в правой части будут заменены на это значение, включая те, которые содержат разделители /
.
1 2 3 4 5 6 7 8 |
|
Это прямое статическое сопоставление и замена без какой-либо специальной обработки расширений файлов. Включение *.js
с обеих сторон сопоставления ограничивает экспорт экспортируемых пакетов только JS-файлами.
Свойство экспорта быть статически перечислимым сохраняется в шаблонах exports, поскольку индивидуальный экспорт для пакета можно определить, рассматривая правый целевой шаблон как **
glob в списке файлов пакета. Поскольку пути node_modules
запрещены в целях exports, это расширение зависит только от файлов самого пакета.
Чтобы исключить личные подпапки из шаблонов, можно использовать цели null
:
1 2 3 4 5 6 7 |
|
1 2 3 4 5 |
|
Условный экспорт¶
Условные экспорты обеспечивают возможность сопоставления с различными путями в зависимости от определенных условий. Они поддерживаются как для импорта модулей CommonJS, так и для импорта модулей ES.
Например, можно написать пакет, который хочет предоставить разные экспорты ES-модулей для require()
и import
:
1 2 3 4 5 6 7 8 |
|
Node.js реализует следующие условия, перечисленные в порядке от наиболее специфичных к наименее специфичным, поскольку условия должны быть определены:
node-addons
- аналогичноnode
и подходит для любого окружения Node.js. Это условие может быть использовано для предоставления точки входа, которая использует родные C++ аддоны, в отличие от точки входа, которая является более универсальной и не полагается на родные аддоны. Это условие может быть отключено с помощью флага--no-addons
.node
- соответствует любой среде Node.js. Может быть файлом модуля CommonJS или ES. В большинстве случаев явное указание платформы Node.js не требуется.import
- соответствует, когда пакет загружается черезimport
илиimport()
, или через любую операцию верхнего уровня import или resolve загрузчика модулей ECMAScript. Применяется независимо от формата модуля в целевом файле. *Всегда взаимоисключающие сrequire
.require
- соответствует, когда пакет загружается черезrequire()
. Ссылаемый файл должен быть загружаемым с помощьюrequire()
, хотя условие выполняется независимо от формата модуля целевого файла. Ожидаемые форматы включают CommonJS, JSON и нативные аддоны, но не модули ES, посколькуrequire()
их не поддерживает. *Всегда взаимоисключающий сimport
.default
- общий запасной вариант, который всегда соответствует. Может быть файлом модуля CommonJS или ES. Это условие всегда должно быть последним.
В объекте exports
порядок ключей имеет значение. При подборе условия более ранние записи имеют более высокий приоритет и имеют приоритет над более поздними записями. Общее правило заключается в том, что условия должны располагаться в порядке следования объектов от наиболее специфичных к наименее специфичным.
Использование условий import
и require
может привести к некоторым опасностям, которые более подробно описаны в разделе двойные пакеты модулей CommonJS/ES.
Условие node-addons
может быть использовано для обеспечения точки входа, которая использует родные C++ аддоны. Однако это условие может быть отключено с помощью флага --no-addons
. При использовании node-addons
рекомендуется рассматривать default
как усовершенствование, обеспечивающее более универсальную точку входа, например, использование WebAssembly вместо родного аддона.
Условный экспорт также может быть расширен на экспортируемые подпути, например:
1 2 3 4 5 6 7 8 9 |
|
Определяет пакет, в котором require('pkg/feature.js')
и import 'pkg/feature.js'
могут обеспечить различные реализации между Node.js и другими средами JS.
При использовании ветвей окружения всегда включайте условие "по умолчанию", где это возможно. Предоставление условия "по умолчанию" гарантирует, что любые неизвестные JS-окружения смогут использовать эту универсальную реализацию, что помогает избежать необходимости этим JS-окружениям притворяться существующими окружениями для поддержки пакетов с условным экспортом. По этой причине использование ветвей условий "узел" и "по умолчанию" обычно предпочтительнее, чем использование ветвей условий "узел" и "браузер".
Вложенные условия¶
В дополнение к прямым сопоставлениям, Node.js также поддерживает вложенные объекты условий.
Например, чтобы определить пакет, который имеет точки входа только в двойном режиме для использования в Node.js, но не в браузере:
1 2 3 4 5 6 7 8 9 |
|
Условия продолжают сопоставляться в том же порядке, что и в плоских условиях. Если вложенное условие не имеет сопоставления, оно продолжает проверять оставшиеся условия родительского условия. Таким образом, вложенные условия ведут себя аналогично вложенным операторам JavaScript if
.
Разрешение пользовательских условий¶
При работе Node.js пользовательские условия могут быть добавлены с помощью флага --conditions
:
1 |
|
что позволит разрешить условие development
в импорте и экспорте пакетов, одновременно разрешая существующие условия node
, node-addons
, default
, import
и require
.
Любое количество пользовательских условий может быть задано с помощью флагов повтора.
Определения условий сообщества¶
Строки условий, отличные от условий "import"
, "require"
, "node"
, "node-addons"
и "default"
реализованных в ядре Node.js, по умолчанию игнорируются.
Другие платформы могут реализовать другие условия, а пользовательские условия могут быть включены в Node.js с помощью флага --conditions
/ -C
.
Поскольку пользовательские условия пакетов требуют четких определений для обеспечения правильного использования, ниже приведен список общих известных условий пакетов и их строгих определений для помощи в координации экосистемы.
types
- может использоваться системами типизации для разрешения файла типизации для данного экспорта. Это условие всегда должно быть включено первым.deno
- указывает на вариацию для платформы Deno.browser
- любая среда веб-браузера.react-native
- будет соответствовать фреймворку React Native (все платформы). Чтобы нацелить React Native для Web,browser
должен быть указан перед этим условием.development
- может использоваться для определения точки входа в среду только для разработки, например, для обеспечения дополнительного отладочного контекста, такого как лучшие сообщения об ошибках при запуске в режиме разработки. *Всегда должно быть взаимоисключающим сproduction
.production
- может использоваться для определения точки входа в производственную среду. *Всегда должно быть взаимоисключающим сdevelopment
.
Новые определения условий могут быть добавлены в этот список путем создания pull request в документации Node.js для этого раздела. Требования для включения нового определения условия в этот список следующие:
- Определение должно быть ясным и недвусмысленным для всех исполнителей.
- Должно быть четко обосновано, почему условие необходимо.
- Должно существовать достаточное количество существующих реализаций.
- Имя условия не должно конфликтовать с другим определением условия или условием в широком использовании.
- Перечисление определения условия должно обеспечивать координационную выгоду для экосистемы, которая иначе была бы невозможна. Например, это не обязательно относится к условиям, специфичным для компании или приложения.
Приведенные выше определения могут быть со временем перенесены в специальный реестр условий.
Самостоятельная ссылка на пакет с помощью его имени¶
Внутри пакета на значения, определенные в поле package.json
exports
, можно ссылаться через имя пакета. Например, если package.json
имеет вид:
1 2 3 4 5 6 7 8 |
|
Затем любой модуль в этом пакете может ссылаться на экспорт в самом пакете:
1 2 |
|
Самостоятельная ссылка доступна только если в package.json
есть "exports"
, и позволит импортировать только то, что разрешено этим "exports"
(в package.json
). Таким образом, приведенный ниже код, учитывая предыдущий пакет, выдаст ошибку во время выполнения:
1 2 3 4 5 6 |
|
Самостоятельные ссылки также доступны при использовании require
, как в модуле ES, так и в модуле CommonJS. Например, этот код также будет работать:
1 2 |
|
Наконец, самоссылка также работает с пакетами, имеющими скопированную структуру. Например, этот код также будет работать:
1 2 3 4 5 |
|
1 2 |
|
1 2 |
|
1 2 |
|
Двойные пакеты модулей CommonJS/ES¶
До появления поддержки ES-модулей в Node.js для авторов пакетов было обычной практикой включать в пакет исходные тексты JavaScript как CommonJS, так и ES-модуля, причем package.json
"main"
указывал точку входа CommonJS, а package.json
"module"
указывал точку входа ES-модуля. Это позволяло Node.js запускать точку входа CommonJS, в то время как инструменты сборки, такие как бандлеры, использовали точку входа модуля ES, поскольку Node.js игнорировал (и все еще игнорирует) поле верхнего уровня "module"
.
Теперь Node.js может запускать точки входа ES-модулей, и пакет может содержать как точки входа CommonJS, так и ES-модулей (либо через отдельные спецификаторы, такие как 'pkg'
и 'pkg/es-module'
, либо оба в одном спецификаторе через Conditional exports). В отличие от сценария, когда "module"
используется только бандлерами, или файлы модулей ES транслируются в CommonJS на лету перед оценкой Node.js, файлы, на которые ссылается точка входа модуля ES, оцениваются как модули ES.
Опасность двойного пакета¶
Когда приложение использует пакет, предоставляющий исходные тексты модулей CommonJS и ES, существует риск возникновения определенных ошибок, если загружаются обе версии пакета. Эта возможность возникает из-за того, что pkgInstance
, созданный const pkgInstance = require('pkg')
, не совпадает с pkgInstance
, созданным import pkgInstance from 'pkg'
(или альтернативным основным путем, например 'pkg/module'
). Это "опасность двойного пакета", когда две версии одного и того же пакета могут быть загружены в одной среде выполнения. Хотя маловероятно, что приложение или пакет будут намеренно загружать обе версии напрямую, часто бывает, что приложение загружает одну версию, а зависимость приложения загружает другую версию. Эта опасность может возникнуть, поскольку Node.js поддерживает смешивание модулей CommonJS и ES, и может привести к неожиданному поведению.
Если основной экспорт пакета является конструктором, то сравнение instanceof
экземпляров, созданных двумя версиями, возвращает false
, а если экспорт является объектом, то свойства, добавленные в один из них (например, pkgInstance.foo = 3
), не будут присутствовать в другом. Это отличается от того, как операторы import
и require
работают в средах модулей all-CommonJS или all-ES, соответственно, и поэтому вызывает удивление у пользователей. Это также отличается от поведения, с которым пользователи знакомы при использовании транспиляции с помощью таких инструментов, как Babel или esm
.
Написание двойных пакетов, избегая или минимизируя опасности¶
Во-первых, опасность, описанная в предыдущем разделе, возникает, когда пакет содержит исходные тексты модулей CommonJS и ES, и оба источника предоставляются для использования в Node.js либо через отдельные основные точки входа, либо через экспортируемые пути. Вместо этого можно написать пакет, в котором любая версия Node.js получает только источники CommonJS, а любые отдельные источники модуля ES, которые может содержать пакет, предназначены только для других сред, таких как браузеры. Такой пакет будет доступен для использования в любой версии Node.js, поскольку import
может ссылаться на файлы CommonJS; но он не будет предоставлять никаких преимуществ использования синтаксиса ES-модулей.
Пакет также может перейти с синтаксиса CommonJS на синтаксис ES-модулей при breaking change изменении версии. Это имеет тот недостаток, что самая новая версия пакета может быть использована только в версиях Node.js, поддерживающих ES-модули.
Каждый паттерн имеет свои компромиссы, но есть два общих подхода, которые удовлетворяют следующим условиям:
- Пакет можно использовать как через
require
, так и черезimport
. - Пакет можно использовать как в текущей версии Node.js, так и в старых версиях Node.js, в которых отсутствует поддержка ES-модулей.
- Главная точка входа пакета, например,
'pkg'
, может использоваться какrequire
для разрешения на файл CommonJS, так иimport
для разрешения на файл ES-модуля. (Аналогично для экспортируемых путей, например,'pkg/feature'
). - Пакет предоставляет именованные экспорты, например,
import { name } from 'pkg'
, а неimport pkg from 'pkg'; pkg.name
. - Пакет потенциально может быть использован в других средах ES-модулей, например, в браузерах.
- Опасности, описанные в предыдущем разделе, исключены или сведены к минимуму.
Подход #1: Использовать обертку ES-модуля¶
Напишите пакет в CommonJS или транспилируйте исходники ES-модуля в CommonJS и создайте файл-обертку ES-модуля, определяющий именованные экспорты. Используя Conditional exports, обертка модуля ES используется для import
и точки входа CommonJS для require
.
1 2 3 4 5 6 7 8 |
|
В предыдущем примере явно используются расширения .mjs
и .cjs
. Если ваши файлы используют расширение .js
, то "type": "module"
приведет к тому, что такие файлы будут рассматриваться как модули ES, так же как "type": "commonjs"
приведет к тому, что они будут рассматриваться как CommonJS. См. Enabling.
1 2 |
|
1 2 3 |
|
В этом примере name
из import { name } from 'pkg'
является тем же синглтоном, что и name
из const { name } = require('pkg')
. Поэтому ===
возвращает true
при сравнении двух name
и опасность расхождения спецификаторов исключается.
Если модуль представляет собой не просто список именованных экспортов, а содержит уникальный экспорт функции или объекта, например module.exports = function () { ... }
, или если в обертке требуется поддержка шаблона import pkg from 'pkg'
, то вместо этого обертку следует написать для экспорта по умолчанию опционально вместе с любыми именованными экспортами:
1 2 3 |
|
Этот подход подходит для любого из следующих случаев использования:
- Пакет в настоящее время написан на CommonJS, и автор предпочел бы не рефакторить его в синтаксис ES-модуля, но хотел бы обеспечить именованный экспорт для потребителей ES-модуля.
- Пакет имеет другие пакеты, которые зависят от него, и конечный пользователь может установить как этот пакет, так и другие пакеты. Например, пакет
utilities
используется непосредственно в приложении, а пакетutilities-plus
добавляет несколько дополнительных функций кutilities
. Поскольку обертка экспортирует базовые файлы CommonJS, не имеет значения, написан лиutilities-plus
в синтаксисе CommonJS или синтаксисе модуля ES; он будет работать в любом случае. - Пакет хранит внутреннее состояние, и автор пакета предпочел бы не рефакторить пакет, чтобы изолировать его управление состоянием. См. следующий раздел.
Вариантом этого подхода, не требующим условных экспортов для потребителей, может быть добавление экспорта, например, "./module"
, для указания на версию пакета с синтаксисом всех модулей ES. Это может использоваться через import 'pkg/module'
пользователями, которые уверены, что версия CommonJS не будет загружена нигде в приложении, например, зависимостями; или если версия CommonJS может быть загружена, но не влияет на версию ES-модуля (например, потому что пакет не имеет состояния):
1 2 3 4 5 6 7 8 |
|
Подход #2: Изолировать состояние¶
Файл package.json
может напрямую определять отдельные точки входа модулей CommonJS и ES:
1 2 3 4 5 6 7 8 |
|
Это можно сделать, если обе версии пакета - CommonJS и ES-модуля - эквивалентны, например, потому что одна является транспилированным результатом другой; и управление состоянием пакета тщательно изолировано (или пакет не имеет состояния).
Причина, по которой состояние является проблемой, заключается в том, что обе версии пакета - CommonJS и ES-модуля - могут использоваться в приложении; например, пользовательский код приложения может "импортировать" версию ES-модуля, в то время как зависимость "требует" версию CommonJS. В этом случае в память будут загружены две копии пакета и, следовательно, будут присутствовать два разных состояния. Это, скорее всего, привело бы к трудноустранимым ошибкам.
Помимо написания пакета без состояния (если бы, например, JavaScript Math
был пакетом, он был бы без состояния, так как все его методы статичны), есть несколько способов изолировать состояние, чтобы оно было общим для потенциально загруженных экземпляров пакета CommonJS и модуля ES:
- Если возможно, содержать все состояние внутри инстанцированного объекта. Например, объект JavaScript
Date
должен быть инстанцирован, чтобы содержать состояние; если бы это был пакет, он использовался бы следующим образом:
1 2 3 |
|
Ключевое слово new
не обязательно; функция пакета может вернуть новый объект или модифицировать переданный объект, чтобы сохранить состояние, внешнее по отношению к пакету.
- Изолируйте состояние в одном или нескольких файлах CommonJS, которые являются общими для версий пакета CommonJS и ES-модуля. Например, если точками входа в CommonJS и ES-модуль являются
index.cjs
иindex.mjs
соответственно:
1 2 3 |
|
1 2 3 |
|
Даже если pkg
используется через require
и import
в приложении (например, через import
в коде приложения и через require
зависимостью), каждая ссылка pkg
будет содержать одно и то же состояние; и изменение этого состояния из любой модульной системы будет применяться к обеим.
Любые плагины, которые присоединяются к синглтону пакета, должны будут отдельно присоединяться к синглтонам модулей CommonJS и ES.
Этот подход подходит для любого из следующих случаев использования:
- Пакет в настоящее время написан в синтаксисе модуля ES, и автор пакета хочет, чтобы эта версия использовалась везде, где поддерживается такой синтаксис.
- Пакет не имеет состояния или его состояние может быть изолировано без особых трудностей.
Определения полей Node.js package.json
¶
В этом разделе описаны поля, используемые средой выполнения Node.js. Другие инструменты (например, npm) используют дополнительные поля, которые игнорируются Node.js и не документируются здесь.
В Node.js используются следующие поля в файлах package.json
:
имя
- Актуально при использовании именованных импортов внутри пакета. Также используется менеджерами пакетов в качестве имени пакета.main
- Модуль по умолчанию при загрузке пакета, если не указан exports, и в версиях Node.js до введения exports.packageManager
- Менеджер пакетов, рекомендуемый при внесении вклада в пакет. Используется шеймами Corepack.type
- Тип пакета, определяющий, загружать ли файлы.js
как модули CommonJS или ES.exports
- Экспорт пакетов и условный экспорт. Когда присутствует, ограничивает, какие подмодули могут быть загружены из пакета.imports
- Импорт пакета, для использования модулями внутри самого пакета.
name
¶
- Тип:
<string>
1 2 3 |
|
Поле имя
определяет имя вашего пакета. Для публикации в реестре npm требуется имя, удовлетворяющее определенным требованиям.
Поле имя
можно использовать в дополнение к полю экспорты
для самоссылки пакета, используя его имя.
main
¶
- Тип:
<string>
1 2 3 |
|
Поле "main"
определяет точку входа пакета при импорте по имени через поиск node_modules
. Его значением является путь.
Если пакет имеет поле "exports"
, оно будет иметь приоритет над полем "main"
при импорте пакета по имени.
Он также определяет сценарий, который используется, когда каталог пакета загружается через require()
.
1 2 |
|
packageManager
¶
Стабильность: 1 – Экспериментальная
Фича изменяется и не допускается флагом командной строки. Может быть изменена или удалена в последующих версиях.
- Тип:
<string>
1 2 3 |
|
Поле packageManager
определяет, какой менеджер пакетов будет использоваться при работе над текущим проектом. Оно может быть установлено в любой из поддерживаемых менеджеров пакетов и гарантирует, что ваши команды используют одинаковые версии менеджеров пакетов без необходимости устанавливать что-либо еще, кроме Node.js.
В настоящее время это поле является экспериментальным, и его необходимо активировать; ознакомьтесь со страницей Corepack для получения подробной информации о процедуре.
type
¶
- Тип:
<string>
Поле "type"
определяет формат модуля, который Node.js использует для всех файлов .js
, ближайшим родителем которых является файл package.json
.
Файлы, заканчивающиеся на .js
, загружаются как модули ES, если ближайший родительский файл package.json
содержит поле верхнего уровня "type"
со значением "module"
.
Ближайший родительский package.json
определяется как первый package.json
, найденный при поиске в текущей папке, родительской папке этой папки и так далее, пока не будет достигнута папка node_modules
или корень тома.
1 2 3 4 |
|
1 2 |
|
Если в ближайшем родительском package.json
отсутствует поле "type"
или содержится "type": "commonjs"
, файлы .js
рассматриваются как CommonJS. Если достигнут корень тома и не найден package.json
, файлы .js
рассматриваются как CommonJS.
Операторы импорта
файлов .js
рассматриваются как модули ES, если ближайший родительский package.json
содержит "type": "module"
.
1 2 |
|
Независимо от значения поля тип
, файлы .mjs
всегда рассматриваются как модули ES, а файлы .cjs
всегда рассматриваются как CommonJS.
exports
¶
- Type:
<Object>
|<string>
|<string[]>
1 2 3 |
|
Поле "exports"
позволяет определить точки входа пакета при импорте по имени, загруженному либо через поиск node_modules
, либо самоссылкой на его собственное имя. Поддерживается в Node.js 12+ как альтернатива главному
, который может поддерживать определение subpath exports и conditional exports при инкапсуляции внутренних неэкспортированных модулей.
Условный экспорт также может быть использован в "exports"
для определения различных точек входа в пакет для каждого окружения, включая то, ссылается ли пакет через require
или через import
.
Все пути, определенные в "exports"
, должны быть относительными URL файлов, начинающимися с ./
.
imports
¶
- Тип:
<Object>
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Записи в поле imports
должны быть строками, начинающимися с #
.
Импорт пакетов разрешает сопоставление с внешними пакетами.
Это поле определяет subpath imports для текущего пакета.