Модули: пакеты
Введение
Пакет — это дерево каталогов, описанное файлом 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, но для этого требуется утверждение импорта.
- Принимает только расширения
.js,.mjsи.cjsдля текстовых файлов JavaScript. - Может использоваться для загрузки модулей JavaScript CommonJS. Такие модули передаются через
cjs-module-lexerдля определения именованных экспортов, которые доступны, если их удаётся определить с помощью статического анализа. URL импортированных модулей CommonJS преобразуются в абсолютные пути, после чего модули загружаются через загрузчик модулей CommonJS.
package.json и расширения файлов
Внутри пакета поле package.json "type" определяет, как Node.js должен интерпретировать файлы .js. Если в файле package.json отсутствует поле "type", файлы .js рассматриваются как CommonJS.
Значение "module" поля package.json "type" указывает 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(поскольку файлы.jsи.mjsвнутри пакета"module"рассматриваются как ES-модули). -
Внутри пакета
"type": "commonjs"Node.js можно указать интерпретировать отдельный файл как ES-модуль, присвоив ему расширение.mjs(поскольку файлы.jsи.cjsвнутри пакета"commonjs"рассматриваются как 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 не задан.
Определение менеджера пакетов
Предполагается, что после публикации все проекты 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", — в поддерживаемых версиях 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
Если экспорт "." является единственным, поле "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.
Шаблоны подстрок путей
Для пакетов с небольшим числом экспортов или импортов рекомендуется явно указывать каждую запись экспорта подстроки пути. Однако для пакетов с большим количеством подстрок путей это может привести к разрастанию 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 ограничения на условия минимальны, но, в частности, действуют следующие требования:
- Условие должно содержать хотя бы один символ.
- Условие не может начинаться с ".", поскольку оно может встречаться в местах, где также допустимы относительные пути.
- Условие не может содержать ",", поскольку некоторые инструменты командной строки могут интерпретировать его как разделитель списка.
- Условие не может быть целочисленным ключом свойства, например "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.
В дальнейшем приведённые выше определения могут быть перенесены в отдельный реестр условий.
Самоссылка на пакет по его имени
Внутри пакета значения, определённые в поле package.json "exports" пакета, можно использовать, указав имя пакета. Например, пусть 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. -
"packageManager"— менеджер пакетов, рекомендуемый при работе с пакетом. Используется оболочками Corepack. -
"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
"packageManager"
- Тип: <string>
{
"packageManager": "<package manager name>@<version>"
} copy Поле "packageManager" определяет, какой менеджер пакетов следует использовать при работе с текущим проектом. В нём можно указать любой из поддерживаемых менеджеров пакетов. Это гарантирует, что команды будут использовать одни и те же версии менеджера пакетов, не устанавливая ничего, кроме Node.js.
Это поле пока является экспериментальным, и его использование необходимо включить отдельно; подробности процедуры см. на странице Corepack.
"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-v20.x/docs/api/packages.html