Модули: пакеты
Введение
Пакет — это дерево папок, описанное файлом 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. -
Код, содержащий синтаксис, который успешно разбирается только как ES-модули, например операторы
importилиexportлибоimport.meta, без явного указания способа его интерпретации. Явными маркерами являются расширения.mjsили.cjs, поляpackage.json"type"со значениями"module"или"commonjs"либо флаги--input-typeили--experimental-default-type. Динамические выраженияimport()поддерживаются как в CommonJS, так и в ES-модулях и не заставляют считать файл ES-модулем. См. раздел Определение синтаксиса.
Node.js будет считать следующие файлы модулями CommonJS, если они переданы в node в качестве исходных данных или на них ссылаются операторы import либо выражения import():
-
Файлы с расширением
.cjs. -
Файлы с расширением
.js, если ближайший родительский файлpackage.jsonсодержит поле верхнего уровня"type"со значением"commonjs". -
Строки, переданные аргументом в
--evalили--printлибо перенаправленные вnodeчерезSTDINс флагом--input-type=commonjs.
Помимо этих явных случаев, в других ситуациях Node.js выбирает систему модулей по умолчанию на основе значения флага --experimental-default-type:
-
Файлы, заканчивающиеся на
.jsили не имеющие расширения, если в той же папке или в любой родительской папке нет файлаpackage.json. -
Файлы, заканчивающиеся на
.jsили не имеющие расширения, если в ближайшем родительском полеpackage.jsonотсутствует поле"type", за исключением случаев, когда папка находится внутри папкиnode_modules. (Области пакетов внутриnode_modulesвсегда считаются CommonJS, если в файлеpackage.jsonотсутствует поле"type", независимо от значения--experimental-default-type, для обеспечения обратной совместимости.) -
Строки, переданные аргументом в
--evalили перенаправленные вnodeчерезSTDIN, если--input-typeне задан.
В настоящее время по умолчанию для этого флага используется значение "commonjs", но в будущем оно может измениться на "module". Поэтому по возможности лучше указывать тип явно; в частности, авторам пакетов всегда следует включать поле "type" в файлы package.json, даже если все исходные файлы в пакете используют CommonJS. Явное указание type пакета защитит его от возможного изменения типа Node.js по умолчанию в будущем, а также упростит инструментам сборки и загрузчикам определение того, как следует интерпретировать файлы пакета.
Определение синтаксиса
Node.js проверяет исходный код неоднозначных входных данных, чтобы определить, содержит ли он синтаксис ES-модулей; если такой синтаксис обнаружен, входные данные будут считаться ES-модулем.
К неоднозначным входным данным относятся:
- Файлы с расширением
.jsили без расширения, если отсутствует управляющий файлpackage.jsonлибо в нём нет поляtype, и при этом не задан--experimental-default-type. - Строковые входные данные (
--evalили STDIN), если не заданы ни--input-type, ни--experimental-default-type.
Под синтаксисом ES-модулей понимается синтаксис, который вызвал бы ошибку при выполнении в CommonJS. К нему относятся:
-
операторы
import(но не выраженияimport(), допустимые в CommonJS). -
операторы
export. -
ссылки
import.meta. -
awaitна верхнем уровне модуля. - Лексические повторные объявления переменных обёртки CommonJS (
require,module,exports,__dirname,__filename).
Загрузчики модулей
В Node.js есть две системы для разрешения спецификаторов и загрузки модулей.
Загрузчик модулей CommonJS:
- Полностью синхронный.
- Отвечает за обработку вызовов
require(). - Поддерживает monkey patching.
- Поддерживает папки в качестве модулей.
- При разрешении спецификатора, если точное совпадение не найдено, он пытается добавить расширения (
.js,.jsonи, наконец,.node), а затем выполнить разрешение папок в качестве модулей. - Считает
.jsonтекстовыми файлами JSON. -
Файлы
.nodeинтерпретируются как скомпилированные модули-дополнения, загружаемые с помощьюprocess.dlopen(). - Считает все файлы без расширений
.jsonили.nodeтекстовыми файлами JavaScript. - Может использоваться для загрузки модулей ECMAScript из модулей CommonJS, только если граф модулей синхронный (то есть не содержит
awaitна верхнем уровне). При загрузке текстового файла JavaScript, не являющегося модулем ECMAScript, файл загружается как модуль CommonJS.
Загрузчик модулей ECMAScript:
- Асинхронный, кроме случаев загрузки модулей для
require(). - Отвечает за обработку операторов
importи выраженийimport(). - Не поддерживает monkey patching; его можно настраивать с помощью хуков загрузчика.
- Не поддерживает папки в качестве модулей; индексы каталогов (например,
'./startup/index.js') должны быть указаны полностью. - Не выполняет поиск по расширениям. Если спецификатор — относительный или абсолютный URL файла, расширение файла необходимо указать.
- Может загружать модули JSON, но для этого требуется атрибут типа импорта.
- Для текстовых файлов JavaScript принимает только расширения
.js,.mjsи.cjs. - Может загружать модули JavaScript CommonJS. Такие модули передаются в
cjs-module-lexerдля поиска именованных экспортов, которые доступны, если их можно определить статическим анализом. URL импортированных модулей CommonJS преобразуются в абсолютные пути, после чего модули загружаются с помощью загрузчика модулей CommonJS.
package.json и расширения файлов
Внутри пакета поле package.json "type" определяет, как Node.js должен интерпретировать файлы .js. Если файл package.json не содержит поля "type", файлы .js считаются модулями CommonJS.
Значение package.json "type", равное "module", указывает Node.js интерпретировать файлы .js в этом пакете с использованием синтаксиса ES-модулей.
Поле "type" применяется не только к начальным точкам входа (node my-app.js), но и к файлам, на которые ссылаются операторы import и выражения import().
// my-app.js, treated as an ES module because there is a package.json // file in the same folder with "type": "module". import './startup/init.js'; // Loaded as ES module since ./startup contains no package.json file, // and therefore inherits the "type" value from one level up. import 'commonjs-package'; // Loaded as CommonJS since ./node_modules/commonjs-package/package.json // lacks a "type" field or contains "type": "commonjs". import './node_modules/commonjs-package/index.js'; // Loaded as CommonJS since ./node_modules/commonjs-package/package.json // lacks a "type" field or contains "type": "commonjs". copy
Файлы с окончанием .mjs всегда загружаются как ES-модули, независимо от ближайшего родительского файла package.json.
Файлы с окончанием .cjs всегда загружаются как модули CommonJS, независимо от ближайшего родительского файла package.json.
import './legacy-file.cjs'; // Loaded as CommonJS since .cjs is always loaded as CommonJS. import 'commonjs-package/src/index.mjs'; // Loaded as ES module since .mjs is always loaded as ES module. copy
Расширения .mjs и .cjs можно использовать, чтобы смешивать типы в одном пакете:
-
Внутри пакета
"type": "module"Node.js можно указать интерпретировать определённый файл как модуль CommonJS, задав ему расширение.cjs(поскольку в пакете"module"файлы.jsи.mjsсчитаются ES-модулями). -
Внутри пакета
"type": "commonjs"Node.js можно указать интерпретировать определённый файл как ES-модуль, задав ему расширение.mjs(поскольку в пакете"commonjs"файлы.jsи.cjsсчитаются модулями CommonJS).
Флаг --input-type
Строки, переданные аргументом в --eval (или -e) либо перенаправленные в node через STDIN, считаются ES-модулями, если задан флаг --input-type=module.
node --input-type=module --eval "import { sep } from 'node:path'; console.log(sep);"
echo "import { sep } from 'node:path'; console.log(sep);" | node --input-type=module copy Для полноты также существует --input-type=commonjs, позволяющий явно выполнять строковые входные данные как CommonJS. Это поведение используется по умолчанию, если --input-type не задан.
Точки входа пакета
В файле package.json пакета два поля могут задавать точки входа пакета: "main" и "exports". Оба поля применяются как к точкам входа модулей ES, так и к точкам входа модулей CommonJS.
Поле "main" поддерживается во всех версиях Node.js, но его возможности ограничены: оно задаёт только основную точку входа пакета.
Поле "exports" представляет собой современную альтернативу полю "main": оно позволяет задавать несколько точек входа, поддерживает условное разрешение точек входа в разных средах и запрещает использовать любые точки входа, кроме определённых в "exports". Такая инкапсуляция позволяет авторам модулей чётко определять публичный интерфейс своего пакета.
Для новых пакетов, рассчитанных на поддерживаемые в настоящее время версии Node.js, рекомендуется поле "exports". Для пакетов, поддерживающих Node.js 10 и более ранние версии, требуется поле "main". Если заданы оба поля — "exports" и "main", — в поддерживаемых версиях Node.js поле "exports" имеет приоритет над полем "main".
Условные экспорты можно использовать в "exports", чтобы задавать разные точки входа пакета для разных сред, в том числе в зависимости от того, подключается ли пакет через require или через import. Дополнительную информацию о поддержке CommonJS и модулей ES в одном пакете см. в разделе Пакеты с поддержкой CommonJS и модулей ES.
Добавление поля "exports" в существующие пакеты лишит потребителей возможности использовать любые неуказанные точки входа, включая package.json (например, require('your-package/package.json')). Вероятно, это будет нарушающим обратную совместимость изменением.
Чтобы добавление поля "exports" не нарушало обратную совместимость, экспортируйте все ранее поддерживавшиеся точки входа. Лучше явно указывать точки входа, чтобы публичный API пакета был чётко определён. Например, проект, ранее экспортировавший main, lib, feature и package.json, может использовать следующий package.exports:
{
"name": "my-package",
"exports": {
".": "./lib/index.js",
"./lib": "./lib/index.js",
"./lib/index": "./lib/index.js",
"./lib/index.js": "./lib/index.js",
"./feature": "./feature/index.js",
"./feature/index": "./feature/index.js",
"./feature/index.js": "./feature/index.js",
"./package.json": "./package.json"
}
} copy В качестве альтернативы проект может экспортировать целые папки, как с расширениями в путях подмодулей, так и без них, используя шаблоны экспорта:
{
"name": "my-package",
"exports": {
".": "./lib/index.js",
"./lib": "./lib/index.js",
"./lib/*": "./lib/*.js",
"./lib/*.js": "./lib/*.js",
"./feature": "./feature/index.js",
"./feature/*": "./feature/*.js",
"./feature/*.js": "./feature/*.js",
"./package.json": "./package.json"
}
} copy При этом описанный выше способ обеспечивает обратную совместимость для любых минорных версий пакета, а в будущем крупном обновлении экспорты можно будет должным образом ограничить только определёнными экспортируемыми функциями:
{
"name": "my-package",
"exports": {
".": "./lib/index.js",
"./feature/*.js": "./feature/*.js",
"./feature/internal/*": null
}
} copy Экспорт основной точки входа
При создании нового пакета рекомендуется использовать поле "exports":
{
"exports": "./index.js"
} copy Если задано поле "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", указав в обоих один и тот же модуль:
{
"main": "./index.js",
"exports": "./index.js"
} copy Экспорты путей подмодулей
При использовании поля "exports" можно задавать собственные пути подмодулей наряду с основной точкой входа, рассматривая основную точку входа как путь подмодуля ".":
{
"exports": {
".": "./index.js",
"./submodule.js": "./src/submodule.js"
}
} copy Теперь потребитель может импортировать только указанный в "exports" путь подмодуля:
import submodule from 'es-module-package/submodule.js'; // Loads ./node_modules/es-module-package/src/submodule.js copy
При попытке импортировать другие пути подмодулей возникнет ошибка:
import submodule from 'es-module-package/private-module.js'; // Throws ERR_PACKAGE_PATH_NOT_EXPORTED copy
Расширения в путях подмодулей
Авторам пакетов следует указывать в экспортах пути подмодулей либо с расширениями (import 'pkg/subpath.js'), либо без расширений (import 'pkg/subpath'). Это гарантирует, что для каждого экспортируемого модуля будет существовать только один путь подмодуля, поэтому все зависимые пакеты будут импортировать модуль по одному и тому же спецификатору. Это делает контракт пакета понятным для потребителей и упрощает автодополнение путей подмодулей.
Традиционно пакеты предпочитали стиль без расширений: он повышает читаемость и скрывает фактический путь файла внутри пакета.
Теперь, когда карты импорта предоставляют стандарт разрешения пакетов в браузерах и других средах выполнения JavaScript, использование стиля без расширений может привести к разрастанию определений карт импорта. Явные расширения файлов позволяют избежать этой проблемы: карта импорта может использовать сопоставление папок пакетов для сопоставления нескольких путей подмодулей, если это возможно, вместо отдельной записи карты для каждого экспортируемого пути подмодуля пакета. Это также соответствует требованию использовать полный путь спецификатора в относительных и абсолютных спецификаторах импорта.
Правила путей и проверка целей экспорта
При задании путей в качестве целей в поле "exports" Node.js применяет несколько правил для обеспечения безопасности, предсказуемости и надлежащей инкапсуляции. Авторам, публикующим пакеты, важно понимать эти правила.
Цели должны быть относительными URL
Все пути целей в карте "exports" (значения, соответствующие ключам экспорта) должны быть относительными строками URL, начинающимися с ./.
// package.json
{
"name": "my-package",
"exports": {
".": "./dist/main.js", // Correct
"./feature": "./lib/feature.js", // Correct
// "./origin-relative": "/dist/main.js", // Incorrect: Must start with ./
// "./absolute": "file:///dev/null", // Incorrect: Must start with ./
// "./outside": "../common/util.js" // Incorrect: Must start with ./
}
} copy Причины такого поведения:
- Безопасность: предотвращает экспорт произвольных файлов за пределами каталога самого пакета.
- Инкапсуляция: гарантирует, что все экспортируемые пути разрешаются относительно корня пакета, благодаря чему пакет остаётся автономным.
Запрет выхода за пределы пути и недопустимых сегментов
Цели экспорта не должны разрешаться в расположение за пределами корневого каталога пакета. Кроме того, сегменты пути, такие как . (одна точка), .. (две точки) или node_modules (а также их URL-кодированные эквиваленты), как правило, запрещены в строке target после начального ./ и в любой части subpath, подставляемой в шаблон цели.
// package.json
{
"name": "my-package",
"exports": {
// ".": "./dist/../../elsewhere/file.js", // Invalid: path traversal
// ".": "././dist/main.js", // Invalid: contains "." segment
// ".": "./dist/../dist/main.js", // Invalid: contains ".." segment
// "./utils/./helper.js": "./utils/helper.js" // Key has invalid segment
}
} copy Сокращённая запись экспортов
Если экспорт "." является единственным экспортом, поле "exports" позволяет использовать сокращённую запись, в которой его значение задаётся непосредственно в поле "exports".
{
"exports": {
".": "./index.js"
}
} copy можно записать так:
{
"exports": "./index.js"
} copy Импорты путей подмодулей
Помимо поля "exports", существует поле пакета "imports", предназначенное для создания частных сопоставлений, применяемых только к спецификаторам импорта из самого пакета.
Записи в поле "imports" всегда должны начинаться с #, чтобы их можно было отличить от спецификаторов внешних пакетов.
Например, поле imports можно использовать, чтобы применять преимущества условных экспортов к внутренним модулям:
// package.json
{
"imports": {
"#dep": {
"node": "dep-node-native",
"default": "./dep-polyfill.js"
}
},
"dependencies": {
"dep-node-native": "^1.0.0"
}
} copy в этом случае import '#dep' не разрешается в путь внешнего пакета dep-node-native (включая его собственные экспорты), а в других средах вместо этого указывает на локальный файл ./dep-polyfill.js относительно пакета.
В отличие от поля "exports", поле "imports" допускает сопоставление с внешними пакетами.
В остальном правила разрешения для поля imports аналогичны правилам для поля exports.
Шаблоны путей подмодулей
Для пакетов с небольшим числом экспортов или импортов рекомендуется явно перечислять каждый путь подмодуля в exports. Но в пакетах с большим количеством путей подмодулей это может привести к package.json избыточности и проблемам с сопровождением.
В таких случаях можно использовать шаблоны экспорта путей подмодулей:
// ./node_modules/es-module-package/package.json
{
"exports": {
"./features/*.js": "./src/features/*.js"
},
"imports": {
"#internal/*.js": "./src/internal/*.js"
}
} copy Сопоставления * предоставляют доступ к вложенным путям подмодулей, поскольку используют только синтаксис замены строк.
Все вхождения * в правой части затем заменяются этим значением, в том числе если оно содержит разделители /.
import featureX from 'es-module-package/features/x.js'; // Loads ./node_modules/es-module-package/src/features/x.js import featureY from 'es-module-package/features/y/y.js'; // Loads ./node_modules/es-module-package/src/features/y/y.js import internalZ from '#internal/z.js'; // Loads ./node_modules/es-module-package/src/internal/z.js copy
Это прямое статическое сопоставление и замена без особой обработки расширений файлов. Наличие "*.js" с обеих сторон сопоставления ограничивает экспорты пакета только файлами JS.
Свойство статической перечислимости экспортов сохраняется и при использовании шаблонов экспорта: отдельные экспорты пакета можно определить, сопоставив целевой шаблон в правой части с файлами пакета при помощи шаблона glob **. Поскольку пути node_modules запрещены в целях экспорта, такое раскрытие шаблона зависит только от файлов самого пакета.
Чтобы исключить частные вложенные папки из шаблонов, можно использовать цели null:
// ./node_modules/es-module-package/package.json
{
"exports": {
"./features/*.js": "./src/features/*.js",
"./features/private-internal/*": null
}
} copy import featureInternal from 'es-module-package/features/private-internal/m.js'; // Throws: ERR_PACKAGE_PATH_NOT_EXPORTED import featureX from 'es-module-package/features/x.js'; // Loads ./node_modules/es-module-package/src/features/x.js copy
Условные экспорты
Условные экспорты позволяют сопоставлять разные пути в зависимости от определённых условий. Они поддерживаются как для импорта CommonJS, так и для импорта модулей ES.
Например, пакет, предоставляющий разные экспорты модулей ES для require() и import, можно описать так:
// package.json
{
"exports": {
"import": "./index-module.js",
"require": "./index-require.cjs"
},
"type": "module"
} copy В Node.js реализованы следующие условия, перечисленные в порядке от наиболее специфичного к наименее специфичному; именно в таком порядке их следует задавать:
-
"node-addons"— аналогично"node"; соответствует любой среде Node.js. Это условие можно использовать, чтобы предоставить точку входа с собственными дополнениями на C++ вместо более универсальной точки входа, не зависящей от собственных дополнений. Это условие можно отключить флагом--no-addons. -
"node"— соответствует любой среде Node.js. Это может быть файл CommonJS или модуль ES. В большинстве случаев явно указывать платформу Node.js не требуется. -
"import"— соответствует случаю, когда пакет загружается черезimportилиimport()либо посредством любой операции импорта или разрешения верхнего уровня загрузчиком модулей ECMAScript. Применяется независимо от формата целевого файла. Всегда взаимоисключающее с"require". -
"require"— соответствует случаю, когда пакет загружается черезrequire(). На указанный файл должна распространяться возможность загрузки с помощьюrequire(), хотя условие соответствует независимо от формата целевого файла. Поддерживаются форматы CommonJS, JSON, собственные дополнения и модули ES. Всегда взаимоисключающее с"import". -
"module-sync"— соответствует независимо от того, загружается ли пакет черезimport,import()илиrequire(). Ожидается, что это будут модули ES, в графе модулей которых нет await верхнего уровня. Если он есть, приrequire()модуля будет вызвано исключениеERR_REQUIRE_ASYNC_MODULE. -
"default"— универсальное резервное условие, которое всегда соответствует. Это может быть файл CommonJS или модуль ES. Это условие всегда должно идти последним.
В объекте "exports" порядок ключей имеет значение. При сопоставлении условий более ранние записи имеют более высокий приоритет и преобладают над последующими. Общее правило: условия в объекте следует располагать от наиболее специфичного к наименее специфичному.
Использование условий "import" и "require" может привести к некоторым проблемам; они подробнее описаны в разделе Пакеты с поддержкой CommonJS и модулей ES.
Условие "node-addons" можно использовать, чтобы предоставить точку входа с собственными дополнениями на C++. Однако это условие можно отключить флагом --no-addons. При использовании "node-addons" рекомендуется рассматривать "default" как улучшение, предоставляющее более универсальную точку входа, например использующую WebAssembly вместо собственного дополнения.
Условные экспорты можно также применять к путям подмодулей, например:
{
"exports": {
".": "./index.js",
"./feature.js": {
"node": "./feature-node.js",
"default": "./feature.js"
}
}
} copy Здесь определяется пакет, в котором require('pkg/feature.js') и import 'pkg/feature.js' могут предоставлять разные реализации для Node.js и других сред JavaScript.
При использовании ветвей для разных сред всегда по возможности включайте условие "default". Условие "default" позволяет любой неизвестной среде JavaScript использовать эту универсальную реализацию и помогает избежать необходимости выдавать себя за существующие среды ради поддержки пакетов с условными экспортами. Поэтому обычно предпочтительнее использовать ветви условий "node" и "default", а не "node" и "browser".
Вложенные условия
Помимо прямых сопоставлений, Node.js также поддерживает вложенные объекты условий.
Например, чтобы определить пакет с точками входа в двух режимах только для Node.js, но не для браузера:
{
"exports": {
"node": {
"import": "./feature-node.mjs",
"require": "./feature-node.cjs"
},
"default": "./feature.mjs"
}
} copy Условия сопоставляются в заданном порядке, как и при плоской структуре. Если для вложенного условия нет сопоставления, проверка продолжается для оставшихся условий родительского условия. Таким образом, вложенные условия работают аналогично вложенным инструкциям if в JavaScript.
Разрешение пользовательских условий
При запуске Node.js можно добавить пользовательские условия с помощью флага --conditions:
node --conditions=development index.js copy
Это позволит разрешать условие "development" в импортах и экспортах пакета, одновременно разрешая соответствующие условия "node", "node-addons", "default", "import" и "require".
С помощью повторения флага можно задать любое количество пользовательских условий.
Типичные условия должны содержать только буквенно-цифровые символы; при необходимости в качестве разделителей можно использовать «:», «-» или «=». Использование других символов может вызвать проблемы совместимости за пределами Node.js.
В Node.js для условий существует очень мало ограничений, но среди них есть следующие:
- Условие должно содержать хотя бы один символ.
- Оно не может начинаться с ".", поскольку может встречаться в местах, где также допускаются относительные пути.
- Оно не может содержать ",", поскольку некоторые инструменты CLI могут интерпретировать его как разделитель списка.
- Оно не может быть целочисленным ключом свойства, например "10", поскольку это может неожиданно повлиять на порядок ключей свойств объектов JS.
Определения условий сообщества
Строки условий, отличные от условий "import", "require", "node", "module-sync", "node-addons" и "default", реализованных в ядре Node.js, по умолчанию игнорируются.
На других платформах могут реализовываться другие условия, а пользовательские условия можно включить в Node.js с помощью флага --conditions / -C.
Поскольку пользовательские условия пакета требуют чётких определений для правильного применения, ниже приведён список распространённых известных условий пакетов и их строгих определений, который поможет согласовать их использование в экосистеме.
-
"types"— может использоваться системами проверки типов для выбора файла типов для указанного экспорта. Это условие всегда должно указываться первым. -
"browser"— любая среда веб-браузера. -
"development"— может использоваться для определения точки входа в среде разработки, например для предоставления дополнительного контекста отладки, такого как более подробные сообщения об ошибках. Всегда должно быть взаимоисключающим с"production". -
"production"— может использоваться для определения точки входа в производственной среде. Всегда должно быть взаимоисключающим с"development".
Для других сред выполнения определения ключей условий, специфичных для платформы, поддерживаются организацией WinterCG в спецификации предложения Runtime Keys.
В этот список можно добавлять определения новых условий, создав запрос на включение изменений в документацию Node.js по этому разделу. Требования к добавлению нового определения условия:
- Определение должно быть ясным и однозначным для всех, кто его реализует.
- Необходимость условия должна быть чётко обоснована соответствующим сценарием использования.
- Должно существовать достаточно уже имеющихся реализаций.
- Имя условия не должно конфликтовать с другим определением условия или с широко используемым условием.
- Добавление определения условия должно приносить экосистеме пользу за счёт координации, которую иначе было бы невозможно обеспечить. Например, это не обязательно относится к условиям, специфичным для компании или приложения.
- Условие должно быть таким, чтобы пользователь Node.js ожидал увидеть его в документации ядра Node.js. Условие
"types"— хороший пример: ему не совсем место в предложении Runtime Keys, но оно хорошо подходит для документации Node.js.
Со временем эти определения могут быть перенесены в отдельный реестр условий.
Обращение к пакету по его имени из самого пакета
Внутри пакета к значениям, определённым в поле "exports" файла package.json пакета, можно обращаться по имени пакета. Например, если package.json имеет следующий вид:
// package.json
{
"name": "a-package",
"exports": {
".": "./index.mjs",
"./foo.js": "./foo.js"
}
} copy любой модуль в этом пакете может обратиться к экспорту самого пакета:
// ./a-module.mjs
import { something } from 'a-package'; // Imports "something" from ./index.mjs. copy Обращение к самому пакету доступно только в том случае, если для package.json задано поле "exports", и позволяет импортировать только то, что разрешает это поле "exports" (в package.json). Поэтому приведённый ниже код вызовет ошибку времени выполнения для указанного выше пакета:
// ./another-module.mjs
// Imports "another" from ./m.mjs. Fails because
// the "package.json" "exports" field
// does not provide an export named "./m.mjs".
import { another } from 'a-package/m.mjs'; copy Обращаться к самому пакету можно и при использовании require — как в модуле ES, так и в модуле CommonJS. Например, также будет работать следующий код:
// ./a-module.js
const { something } = require('a-package/foo.js'); // Loads from ./foo.js. copy Наконец, обращение к самому пакету работает и с пакетами с областью действия. Например, также будет работать следующий код:
// package.json
{
"name": "@my/package",
"exports": "./index.js"
} copy // ./index.js module.exports = 42; copy
// ./other.js
console.log(require('@my/package')); copy $ node other.js 42 copy
Пакеты с поддержкой CommonJS и модулей ES
Подробную информацию см. в репозитории с примерами пакетов.
Определения полей package.json в Node.js
В этом разделе описаны поля, используемые средой выполнения Node.js. Другие инструменты (например, npm) используют дополнительные поля, которые игнорируются Node.js и здесь не документируются.
В Node.js используются следующие поля файлов package.json:
-
"name"— используется при именованных импортах внутри пакета. Также используется менеджерами пакетов в качестве имени пакета. -
"main"— модуль по умолчанию при загрузке пакета, если поле exports не задано, а также в версиях Node.js, предшествующих появлению поля exports. -
"type"— тип пакета, определяющий, следует ли загружать файлы.jsкак CommonJS или как модули ES. -
"exports"— экспорты пакета и условные экспорты. Если это поле задано, оно ограничивает список подмодулей, которые можно загрузить из пакета. -
"imports"— импорты пакета, используемые модулями внутри самого пакета.
"name"
- Тип: <string>
{
"name": "package-name"
} copy Поле "name" задаёт имя пакета. Для публикации в реестре npm необходимо имя, соответствующее определённым требованиям.
Поле "name" можно использовать вместе с полем "exports", чтобы обращаться к пакету по его имени из самого пакета.
"main"
- Тип: <string>
{
"main": "./index.js"
} copy Поле "main" задаёт точку входа пакета при импорте по имени через поиск node_modules. Его значение — путь.
Если у пакета есть поле "exports", при импорте пакета по имени оно имеет приоритет над полем "main".
Оно также задаёт скрипт, используемый, когда каталог пакета загружается через require().
// This resolves to ./path/to/directory/index.js.
require('./path/to/directory'); copy
"type"
- Тип: <string>
Поле "type" задаёт формат модулей, который Node.js использует для всех файлов .js, ближайшим родительским каталогом которых является каталог с этим файлом package.json.
Файлы с расширением .js загружаются как модули ES, если в ближайшем родительском файле package.json задано поле верхнего уровня "type" со значением "module".
Ближайший родительский файл package.json определяется как первый файл package.json, найденный при поиске в текущей папке, её родительской папке и далее вверх — до папки node_modules или корня тома.
// package.json
{
"type": "module"
} copy # In same folder as preceding package.json node my-app.js # Runs as ES module copy
Если в ближайшем родительском файле package.json отсутствует поле "type" или задано значение "type": "commonjs", файлы .js рассматриваются как CommonJS. Если поиск достигает корня тома и файл package.json не найден, файлы .js рассматриваются как CommonJS.
Инструкции import в файлах .js рассматриваются как инструкции модулей ES, если ближайший родительский файл package.json содержит "type": "module".
// my-app.js, part of the same example as above import './startup.js'; // Loaded as ES module because of package.json copy
Независимо от значения поля "type" файлы .mjs всегда рассматриваются как модули ES, а файлы .cjs — всегда как CommonJS.
"exports"
- Тип: <Object> | <string> | <string[]>
{
"exports": "./index.js"
} copy Поле "exports" позволяет задавать точки входа пакета при его загрузке по имени через поиск node_modules или при обращении к пакету по его собственному имени. Оно поддерживается в Node.js 12 и более поздних версиях как альтернатива полю "main" и позволяет задавать экспорты путей подмодулей и условные экспорты, одновременно инкапсулируя внутренние неэкспортируемые модули.
Условные экспорты можно также использовать в "exports", чтобы задавать разные точки входа пакета для разных сред, в том числе в зависимости от того, подключается ли пакет через require или через import.
Все пути, определённые в "exports", должны быть относительными URL файлов, начинающимися с ./.
"imports"
- Тип: <Object>
// package.json
{
"imports": {
"#dep": {
"node": "dep-node-native",
"default": "./dep-polyfill.js"
}
},
"dependencies": {
"dep-node-native": "^1.0.0"
}
} copy Записи в поле imports должны быть строками, начинающимися с #.
Импорты пакета допускают сопоставление с внешними пакетами.
Это поле задаёт импорты путей подмодулей для текущего пакета.
© Joyent, Inc. and other Node contributors
Licensed under the MIT License.
Node.js is a trademark of Joyent, Inc. and is used with its permission.
We are not endorsed by or affiliated with Joyent.
https://nodejs.org/dist/latest-v22.x/docs/api/packages.html