Spec-Zone.ru › Node.js

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

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

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

Добавлено в: v21.1.0, v20.10.0
История
Версия Изменения
v22.7.0, v20.19.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".
jscopy

Файлы с расширением .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.
jscopy

Расширения .mjs и .cjs позволяют смешивать типы в одном пакете:

  • В пакете "type": "module" можно указать Node.js, что определённый файл следует интерпретировать как CommonJS, присвоив ему расширение .cjs (так как файлы .js и .mjs в пакете "module" обрабатываются как ES-модули).

  • В пакете "type": "commonjs" можно указать Node.js, что определённый файл следует интерпретировать как ES-модуль, присвоив ему расширение .mjs (так как файлы .js и .cjs в пакете "commonjs" обрабатываются как 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
bashcopy

Для полноты картины существует также --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"
  }
}
jsoncopy

В качестве альтернативы проект может экспортировать целые каталоги — как с расширениями в подкаталогах, так и без них — используя шаблоны экспорта:

{
  "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"
  }
}
jsoncopy

Обеспечив таким образом обратную совместимость для всех минорных версий пакета, при следующем изменении мажорной версии можно будет надлежащим образом ограничить экспорты только конкретными экспортами функций:

{
  "name": "my-package",
  "exports": {
    ".": "./lib/index.js",
    "./feature/*.js": "./feature/*.js",
    "./feature/internal/*": null
  }
}
jsoncopy

Экспорт главной точки входа

При создании нового пакета рекомендуется использовать поле "exports":

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

Если определено поле "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"
}
jsoncopy

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

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

При использовании поля "exports" можно определять собственные подпути наряду с главной точкой входа, указав в качестве подпути главной точки входа ".":

{
  "exports": {
    ".": "./index.js",
    "./submodule.js": "./src/submodule.js"
  }
}
jsoncopy

Теперь потребитель может импортировать только подпуть, определённый в "exports":

import submodule from 'es-module-package/submodule.js';
// Loads ./node_modules/es-module-package/src/submodule.js
jscopy

При попытке использовать другие подпути возникнет ошибка:

import submodule from 'es-module-package/private-module.js';
// Throws ERR_PACKAGE_PATH_NOT_EXPORTED
jscopy
Расширения в подпутах

Авторам пакетов следует использовать в экспортируемых подпутях либо расширения (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 ./
  }
}
jsoncopy

Причины такого поведения:

  • Безопасность: предотвращает экспорт произвольных файлов за пределами каталога самого пакета.
  • Инкапсуляция: гарантирует, что все экспортируемые пути разрешаются относительно корневого каталога пакета, обеспечивая его автономность.
Запрет обхода каталогов и недопустимых сегментов

Целевые экспорты не должны разрешаться в расположения за пределами корневого каталога пакета. Кроме того, сегменты пути, такие как . (одна точка), .. (две точки) или 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
  }
}
jsoncopy

Сокращённая запись exports

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

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

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

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

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

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

Добавлено в: v14.6.0, v12.19.0
История
Версия Изменения
v25.4.0, v24.14.0

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

Помимо поля "exports", в пакете предусмотрено поле "imports" для создания частных сопоставлений, применимых только к спецификаторам импорта внутри самого пакета.

Записи в поле "imports" всегда должны начинаться с #, чтобы их можно было отличить от спецификаторов внешних пакетов.

Например, поле imports можно использовать, чтобы получить преимущества условных экспортов для внутренних модулей:

// package.json
{
  "imports": {
    "#dep": {
      "node": "dep-node-native",
      "default": "./dep-polyfill.js"
    }
  },
  "dependencies": {
    "dep-node-native": "^1.0.0"
  }
}
jsoncopy

в этом случае import '#dep' не разрешается в внешний пакет dep-node-native (включая его экспорты), а вместо этого в других средах разрешается в локальный файл ./dep-polyfill.js относительно пакета.

В отличие от поля "exports", поле "imports" допускает сопоставление с внешними пакетами.

В остальном правила разрешения для поля imports аналогичны правилам для поля exports.

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

Добавлено в: v14.13.0, v12.20.0
История
Версия Изменения
v16.10.0, v14.19.0

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

v16.9.0, v14.19.0

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

Для пакетов с небольшим числом экспортов или импортов рекомендуется явно перечислять каждый подпуть экспорта. Однако для пакетов с большим количеством подпутей это может привести к разрастанию package.json и проблемам с сопровождением.

В таких случаях можно использовать шаблоны экспорта подпутей:

// ./node_modules/es-module-package/package.json
{
  "exports": {
    "./features/*.js": "./src/features/*.js"
  },
  "imports": {
    "#internal/*.js": "./src/internal/*.js"
  }
}
jsoncopy

Шаблоны * открывают доступ к вложенным подпутям, поскольку выполняют только замену строк.

Все вхождения * в правой части будут заменены этим значением, в том числе если оно содержит разделители /.

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
jscopy

Это прямое статическое сопоставление и замена без специальной обработки расширений файлов. Наличие "*.js" в обеих частях сопоставления ограничивает экспортируемые подпути пакета только файлами JS.

Свойство статической перечислимости экспортов сохраняется и при использовании шаблонов экспорта, поскольку отдельные экспорты пакета можно определить, сопоставив целевой шаблон в правой части с помощью глоб-шаблона ** со списком файлов пакета. Поскольку пути node_modules запрещены в целевых экспортах, такое раскрытие зависит только от файлов самого пакета.

Чтобы исключить из шаблонов закрытые подкаталоги, можно использовать целевые значения null:

// ./node_modules/es-module-package/package.json
{
  "exports": {
    "./features/*.js": "./src/features/*.js",
    "./features/private-internal/*": null
  }
}
jsoncopy
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
jscopy

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

Добавлено в: v13.2.0, v12.16.0
История
Версия Изменения
v13.7.0, v12.16.0

Условные экспорты включены по умолчанию.

Условные экспорты позволяют сопоставлять запросы с разными путями в зависимости от определённых условий. Они поддерживаются как для CommonJS, так и для импорта ES-модулей.

Например, пакет, предоставляющий разные экспорты ES-модулей для require() и import, можно записать так:

// package.json
{
  "exports": {
    "import": "./index-module.js",
    "require": "./index-require.cjs"
  },
  "type": "module"
}
jsoncopy

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"
    }
  }
}
jsoncopy

Здесь определяется пакет, в котором 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"
  }
}
jsoncopy

Условия по-прежнему проверяются по порядку, как и в случае с плоским списком условий. Если для вложенного условия не найдено сопоставление, проверка продолжается для оставшихся условий родительского условия. В этом отношении вложенные условия ведут себя аналогично вложенным инструкциям JavaScript if.

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

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

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

node --conditions=development index.js
bashcopy

Это позволит разрешить условие "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.1.0, v12.16.0
История
Версия Изменения
v13.6.0, v12.16.0

Снято ограничение-флаг для ссылки на пакет по его имени.

Внутри пакета значения, определённые в поле package.json "exports" пакета, можно указывать по имени пакета. Например, если package.json имеет следующий вид:

// package.json
{
  "name": "a-package",
  "exports": {
    ".": "./index.mjs",
    "./foo.js": "./foo.js"
  }
}
jsoncopy

Тогда любой модуль в этом пакете может ссылаться на экспорт самого пакета:

// ./a-module.mjs
import { something } from 'a-package'; // Imports "something" from ./index.mjs.
jscopy

Ссылаться на пакет таким образом можно только в том случае, если 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';
jscopy

Ссылка на пакет по его имени также доступна при использовании require как в ES-модуле, так и в модуле CommonJS. Например, следующий код тоже будет работать:

// ./a-module.js
const { something } = require('a-package/foo.js'); // Loads from ./foo.js.
cjscopy

Наконец, ссылка на пакет по его имени работает и со scoped-пакетами. Например, следующий код тоже будет работать:

// package.json
{
  "name": "@my/package",
  "exports": "./index.js"
}
jsoncopy
// ./index.js
module.exports = 42;
cjscopy
// ./other.js
console.log(require('@my/package'));
cjscopy
$ node other.js
42
consolecopy

Пакеты с модулями 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.1.0, v12.16.0
История
Версия Изменения
v13.6.0, v12.16.0

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

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

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

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

"main"

Добавлено в: v0.4.0
  • Тип: <string>
{
  "main": "./index.js"
}
jsoncopy

Поле "main" задаёт точку входа пакета при импорте по имени с помощью разрешения node_modules. Его значение представляет собой путь.

Если существует поле "exports", при импорте пакета по имени оно имеет приоритет над полем "main".

Кроме того, оно задаёт скрипт, который используется, когда каталог пакета загружается через require().

// This resolves to ./path/to/directory/index.js.
require('./path/to/directory');
cjscopy

"type"

Добавлено в: v12.0.0
История
Версия Изменения
v13.2.0, v12.17.0

Снято ограничение-флаг для --experimental-modules.

  • Тип: <string>

Поле "type" определяет формат модулей, который Node.js использует для всех файлов .js, у которых этот файл package.json является ближайшим родительским файлом.

Файлы с расширением .js загружаются как ES-модули, если ближайший родительский файл package.json содержит поле верхнего уровня "type" со значением "module".

Ближайший родительский файл package.json — это первый файл package.json, найденный при поиске в текущей папке, родительской папке и так далее, пока не будет достигнута папка node_modules или корень тома.

// package.json
{
  "type": "module"
}
jsoncopy
# In same folder as preceding package.json
node my-app.js # Runs as ES module
bashcopy

Если в ближайшем родительском файле 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
jscopy

Независимо от значения поля "type", файлы .mjs всегда рассматриваются как ES-модули, а файлы .cjs всегда рассматриваются как CommonJS.

"exports"

Добавлено в: v12.7.0
История
Версия Изменения
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

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

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

Поле "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"
  }
}
jsoncopy

Записи в поле 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/api/packages.html

Spec-Zone.ru

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