Spec-Zone.ru › Node.js 24 LTS

Модули: пакеты

История
Версия Изменения
v14.13.0, v12.20.0

Добавлена поддержка шаблонов "exports".

v14.6.0, v12.19.0

Добавлено поле пакета "imports".

v13.7.0, v12.17.0

Условные экспорты стали доступны без флага.

v13.7.0, v12.16.0

Удалён параметр --experimental-conditional-exports. В версии 12.16.0 условные экспорты по-прежнему доступны только с флагом --experimental-modules.

v13.6.0, v12.16.0

Снято ограничение на самообращение к пакету по его имени.

v12.7.0

Добавлено поле "exports" package.json — более мощная альтернатива классическому полю "main".

v12.0.0

Добавлена поддержка ES-модулей с расширением файла .js через поле package.json "type".

Введение

Пакет — это дерево папок, описанное файлом 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 по умолчанию в будущем, а также упростит инструментам сборки и загрузчикам определение того, как следует интерпретировать файлы в пакете.

Определение по синтаксису

История
Версия Изменения
v22.7.0, v20.19.0

Определение по синтаксису включено по умолчанию.

v21.1.0, v20.10.0

Добавлено в: v21.1.0, v20.10.0

Стабильность: 1.2 — кандидат на выпуск

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

Добавлено в: v12.0.0

Строки, переданные в качестве аргумента в --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

Экспорт подпутей

Добавлено в: v12.7.0

При использовании поля "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

Добавлено в: v12.11.0

Если экспорт "." — единственный экспорт, поле "exports" позволяет использовать сокращённую запись, задав значение поля "exports" напрямую.

{
  "exports": {
    ".": "./index.js"
  }
} copy

можно записать так:

{
  "exports": "./index.js"
} copy

Импорт подпутей

История
Версия Изменения
v24.14.0

Разрешён импорт подпутей, начинающихся с #/.

v14.6.0, v12.19.0

Добавлено в: v14.6.0, v12.19.0

Помимо поля "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.

Шаблоны подпутей

История
Версия Изменения
v16.10.0, v14.19.0

Добавлена поддержка суффиксов шаблонов в поле "imports".

v16.9.0, v14.19.0

Добавлена поддержка суффиксов шаблонов.

v14.13.0, v12.20.0

Добавлено в: v14.13.0, v12.20.0

Если в пакете используется небольшое число экспортов или импортов, рекомендуется явно указывать каждую запись подпути 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

Условный экспорт

История
Версия Изменения
v13.7.0, v12.16.0

Условный экспорт включён без флага.

v13.2.0, v12.16.0

Добавлено в: v13.2.0, v12.16.0

Условный экспорт позволяет сопоставлять разные пути в зависимости от заданных условий. Он поддерживается как при импорте 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.

Разрешение пользовательских условий

Добавлено в: v14.9.0, v12.19.0

При запуске Node.js пользовательские условия можно добавить с помощью флага --conditions:

node --conditions=development index.js copy

После этого условие "development" будет разрешаться при импорте и экспорте пакетов наряду с существующими условиями "node", "node-addons", "default", "import" и "require", применяемыми в соответствующих случаях.

С помощью повторяющихся флагов можно задать любое количество пользовательских условий.

Названия условий обычно должны содержать только буквенно-цифровые символы; при необходимости в качестве разделителей можно использовать ":", "-" или "=". Любые другие символы могут вызвать проблемы совместимости за пределами Node.js.

В Node.js для условий существует мало ограничений, однако в частности:

  1. Они должны содержать хотя бы один символ.
  2. Они не могут начинаться с ".", поскольку могут встречаться в местах, где также допустимы относительные пути.
  3. Они не могут содержать ",", поскольку некоторые инструменты командной строки могут интерпретировать их как список, разделённый запятыми.
  4. Они не могут быть целочисленными ключами свойств, такими как "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.

В дальнейшем приведённые выше определения могут быть перенесены в отдельный реестр условий.

Ссылка на пакет по его имени из самого пакета

История
Версия Изменения
v13.6.0, v12.16.0

Возможность ссылаться на пакет по его имени из самого пакета включена без флага.

v13.1.0, v12.16.0

Добавлено в: v13.1.0, v12.16.0

Внутри пакета к значениям, определённым в поле "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"

История
Версия Изменения
v13.6.0, v12.16.0

Удалён параметр --experimental-resolve-self.

v13.1.0, v12.16.0

Добавлено в: v13.1.0, v12.16.0

  • Тип: <string>
{
  "name": "package-name"
} copy

Поле "name" задаёт имя пакета. Для публикации в реестре npm требуется имя, соответствующее определённым требованиям.

Поле "name" можно использовать вместе с полем "exports", чтобы ссылаться на пакет из самого себя по его имени.

"main"

Добавлено в: v0.4.0
  • Тип: <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"

История
Версия Изменения
v13.2.0, v12.17.0

--experimental-modules включено без флага.

v12.0.0

Добавлено в: v12.0.0

  • Тип: <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"

История
Версия Изменения
v14.13.0, v12.20.0

Добавлена поддержка шаблонов "exports".

v13.7.0, v12.17.0

Условный экспорт включён без флага.

v13.7.0, v12.16.0

Реализован логический порядок условного экспорта.

v13.7.0, v12.16.0

Удалён параметр --experimental-conditional-exports. В версии 12.16.0 условный экспорт по-прежнему требует флаг --experimental-modules.

v13.2.0, v12.16.0

Реализован условный экспорт.

v12.7.0

Добавлено в: v12.7.0

  • Тип: <Object> | <string> | <string[]>
{
  "exports": "./index.js"
} copy

Поле "exports" позволяет определять точки входа пакета, импортируемого по имени через поиск node_modules или по ссылке из самого пакета на его собственное имя. Оно поддерживается в Node.js 12 и более поздних версиях и является альтернативой полю "main". В отличие от него, оно позволяет определять экспорты подпутей и условный экспорт, одновременно инкапсулируя внутренние модули, не включённые в экспорт.

Условный экспорт также можно использовать в "exports", чтобы определять разные точки входа пакета для разных сред, в том числе в зависимости от того, подключается ли пакет через require или через import.

Все пути, определённые в "exports", должны быть относительными URL файлов, начинающимися с ./.

"imports"

Добавлено в: v14.6.0, v12.19.0
  • Тип: <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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API