Модули: пакеты
Введение
Пакет — это дерево папок, описанное файлом 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. Динамические выраженияimport()поддерживаются как в CommonJS, так и в ES-модулях и сами по себе не заставляют считать файл ES-модулем. См. раздел Определение по синтаксису.
Node.js будет считать следующие файлы модулями CommonJS, если они переданы в node в качестве входных данных или на них ссылаются инструкции import либо выражения import():
-
Файлы с расширением
.cjs. -
Файлы с расширением
.js, если ближайший родительский файлpackage.jsonсодержит поле верхнего уровня"type"со значением"commonjs". -
Строки, переданные в качестве аргумента в
--evalили--printлибо перенаправленные вnodeчерезSTDINс флагом--input-type=commonjs. -
Файлы с расширением
.js, у которых нет родительского файлаpackage.jsonили в ближайшем родительском файлеpackage.jsonотсутствует полеtype, если код успешно выполняется как CommonJS. Иными словами, Node.js сначала пытается выполнить такие «неоднозначные» файлы как CommonJS и повторяет попытку интерпретировать их как ES-модули, если выполнение в качестве CommonJS завершается ошибкой из-за того, что анализатор обнаружил синтаксис ES-модулей.
Использование синтаксиса ES-модулей в «неоднозначных» файлах снижает производительность, поэтому авторам рекомендуется везде, где это возможно, указывать тип явно. В частности, авторам пакетов всегда следует включать поле "type" в файлы package.json, даже в пакетах, где весь исходный код написан для CommonJS. Явное указание type пакета защитит его от возможного изменения типа Node.js по умолчанию в будущем, а также упростит инструментам сборки и загрузчикам определение того, как следует интерпретировать файлы в пакете.
Определение по синтаксису
Node.js анализирует исходный код неоднозначных входных данных, чтобы определить, содержит ли он синтаксис ES-модулей; если такой синтаксис обнаружен, входные данные будут интерпретированы как ES-модуль.
К неоднозначным входным данным относятся:
- Файлы с расширением
.jsили без расширения, если у них нет управляющего файлаpackage.jsonлибо в нём отсутствует полеtype. - Строковые входные данные (
--evalилиSTDIN), если--input-typeне задан.
Синтаксисом ES-модулей считается синтаксис, который вызвал бы ошибку при выполнении в качестве CommonJS. К нему относятся:
-
инструкции
import(но не выраженияimport(), допустимые в CommonJS). -
инструкции
export. -
ссылки
import.meta. -
awaitна верхнем уровне модуля. - Лексические повторные объявления переменных обёртки CommonJS (
require,module,exports,__dirname,__filename).
Разрешение модулей и загрузка
В Node.js существуют два способа разрешения и загрузки модулей; используемый способ зависит от того, как запрашивается модуль.
Когда модуль запрашивается через require() (доступен по умолчанию в модулях CommonJS и может быть динамически сформирован с помощью createRequire() как в CommonJS, так и в ES-модулях):
- Разрешение:
- Разрешение, инициированное через
require(), поддерживает папки в качестве модулей. - При разрешении спецификатора, если точное совпадение не найдено,
require()попытается добавить расширения (.js,.jsonи, наконец,.node), а затем разрешить папки в качестве модулей. - По умолчанию URL-адреса не поддерживаются в качестве спецификаторов.
- Разрешение, инициированное через
- Загрузка:
-
Файлы
.jsonсчитаются текстовыми файлами JSON. -
Файлы
.nodeинтерпретируются как скомпилированные модули-дополнения, загружаемые с помощьюprocess.dlopen(). -
Файлы
.ts,.mtsи.ctsсчитаются текстовыми файлами TypeScript. - Файлы с любым другим расширением или без расширения считаются текстовыми файлами JavaScript.
-
require()можно использовать для загрузки модулей ECMAScript из модулей CommonJS, только если модуль ECMAScript и его зависимости являются синхронными (то есть не содержатawaitна верхнем уровне).
-
Файлы
Когда модуль запрашивается с помощью статических инструкций import (доступны только в ES-модулях) или выражений import() (доступны как в CommonJS, так и в ES-модулях):
- Разрешение:
- Разрешение
import/import()не поддерживает папки в качестве модулей: индексы каталогов (например,'./startup/index.js') должны быть указаны полностью. - Поиск расширений не выполняется. Если спецификатор является относительным или абсолютным URL-адресом файла, расширение файла необходимо указывать.
- По умолчанию URL-адреса
file://иdata:поддерживаются в качестве спецификаторов.
- Разрешение
- Загрузка:
-
Файлы
.jsonсчитаются текстовыми файлами JSON. При импорте модулей JSON требуется атрибут типа импорта (например,import json from './data.json' with { type: 'json' }). -
Файлы
.nodeинтерпретируются как скомпилированные модули-дополнения, загружаемые с помощьюprocess.dlopen(), если включён параметр--experimental-addon-modules. -
Файлы
.ts,.mtsи.ctsсчитаются текстовыми файлами TypeScript. - Для текстовых файлов JavaScript принимаются только расширения
.js,.mjsи.cjs. -
Файлы
.wasmсчитаются модулями WebAssembly. - Любое другое расширение файла приведёт к ошибке
ERR_UNKNOWN_FILE_EXTENSION. Дополнительные расширения файлов можно добавить с помощью хуков настройки. -
import/import()можно использовать для загрузки модулей JavaScript CommonJS. Такие модули обрабатываются с помощью merve для определения именованных экспортов; они становятся доступны, если их удаётся определить посредством статического анализа.
-
Файлы
Независимо от способа запроса модуля, процесс разрешения и загрузки можно настроить с помощью хуков настройки.
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(поскольку в пакете"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('/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" напрямую.
{
"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 ./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, например:
{
"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.
В дальнейшем приведённые выше определения могут быть перенесены в отдельный реестр условий.
Ссылка на пакет по его имени из самого пакета
Внутри пакета к значениям, определённым в поле "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-v24.x/docs/api/packages.html