Spec-Zone.ru › Node.js 18 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.

Node.js будет интерпретировать все остальные типы ввода, такие как файлы с расширением .js, где ближайший родительский файл package.json не содержит поле верхнего уровня "type", или строковый ввод без флага --input-type, как CommonJS. Это делается для обеспечения обратной совместимости. Однако, теперь, когда Node.js поддерживает как CommonJS, так и модули ES, лучше указывать тип явно, когда это возможно. Node.js будет интерпретировать следующее как CommonJS, когда это передаётся в node в качестве начального ввода или когда на них ссылаются инструкции import, выражения import(), или выражения require():

  • Файлы с расширением .cjs.

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

  • Строки, переданные в качестве аргумента функции --eval или --print, или перенаправленные в node через STDIN, с флагом --input-type=commonjs.

Авторы пакетов должны включать поле "type", даже в пакетах, где все источники являются CommonJS. Явное указание типа пакета позволит в будущем адаптировать пакет к случаям изменения стандартного типа Node.js, а также упростит работу инструментам сборки и загрузчикам в определении способа интерпретации файлов в пакете.

Загрузчики модулей

Node.js имеет две системы для разрешения спецификатора и загрузки модулей.

Существует загрузчик модулей CommonJS:

  • Он полностью синхронный.
  • Он отвечает за обработку вызовов require().
  • Он может быть подменяем.
  • Он поддерживает папки как модули.
  • При разрешении спецификатора, если точного совпадения нет, он попытается добавить расширения (.js, .json, и, наконец, .node) и затем попытается разрешить папки как модули.
  • Он обрабатывает .json как файлы JSON-текста.
  • Файлы .node интерпретируются как скомпилированные модули дополнений, загружаемые с помощью process.dlopen().
  • Все файлы, не имеющие расширений .json или .node, обрабатываются как текстовые файлы JavaScript.
  • Он не может использоваться для загрузки модулей ECMAScript (хотя возможно загрузка модулей ECMAScript из модулей CommonJS). При использовании для загрузки текстового файла JavaScript, который не является модулем ECMAScript, он загружает его как модуль CommonJS.

Существует загрузчик модулей ECMAScript:

  • Он асинхронный.
  • Он отвечает за обработку инструкций import и выражений import().
  • Он не может быть подменяем, может быть настроен с помощью схем загрузки.
  • Он не поддерживает папки как модули, индексы каталогов (например, './startup/index.js') должны быть полностью указаны.
  • Он не ищет расширений. Расширение файла должно быть указано, когда спецификатор является относительным или абсолютным URL файла.
  • Он может загружать модули JSON, но требуется утверждение импорта.
  • Он принимает только расширения .js, .mjs, и .cjs для текстовых файлов JavaScript.
  • Он может использоваться для загрузки модулей JavaScript CommonJS. Такие модули передаются через cjs-module-lexer для попытки определения именованных экспортов, которые доступны, если их можно определить с помощью статического анализа. Импортированные модули CommonJS преобразуют свои URL в абсолютные пути и затем загружаются через загрузчик модулей CommonJS.

package.json и расширения файлов

Внутри пакета поле package.json "type" определяет, как Node.js должен интерпретировать файлы .js. Если файл package.json не имеет поля "type", файлы .js обрабатываются как CommonJS.

Значение поля package.json "type" равное "module" сообщает Node.js интерпретировать файлы .js внутри пакета как использующие синтаксис модулей ES.

Поле "type" применяется не только к начальным точкам входа (node my-app.js), но и к файлам, на которые ссылаются инструкции import и выражения import().

// my-app.js, treated as an ES module because there is a package.json
// file in the same folder with "type": "module".

import './startup/init.js';
// Loaded as ES module since ./startup contains no package.json file,
// and therefore inherits the "type" value from one level up.

import 'commonjs-package';
// Loaded as CommonJS since ./node_modules/commonjs-package/package.json
// lacks a "type" field or contains "type": "commonjs".

import './node_modules/commonjs-package/index.js';
// Loaded as CommonJS since ./node_modules/commonjs-package/package.json
// lacks a "type" field or contains "type": "commonjs". copy

Файлы с расширением .mjs всегда загружаются как модули ES независимо от ближайшего родительского файла package.json.

Файлы с расширением .cjs всегда загружаются как CommonJS независимо от ближайшего родительского файла package.json.

import './legacy-file.cjs';
// Loaded as CommonJS since .cjs is always loaded as CommonJS.

import 'commonjs-package/src/index.mjs';
// Loaded as ES module since .mjs is always loaded as ES module. copy

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

  • Внутри пакета "type": "module" Node.js можно настроить на интерпретацию конкретного файла как CommonJS, присвоив ему расширение .cjs (поскольку как файлы с расширением .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 copy

Для полноты, существует также --input-type=commonjs, для явного запуска строкового ввода как CommonJS. Это поведение по умолчанию, если --input-type не указан.

Определение менеджера пакетов

Стабильность: 1 - Экспериментальная

Хотя ожидается, что все проекты Node.js будут устанавливаться всеми менеджерами пакетов после публикации, их разработчики часто вынуждены использовать один конкретный менеджер пакетов. Для облегчения этого процесса Node.js поставляется с инструментом под названием Corepack, который призван сделать все менеджеры пакетов прозрачно доступными в вашей среде — при условии установки Node.js.

По умолчанию Corepack не будет навязывать какой-либо конкретный менеджер пакетов и будет использовать общие версии «Последней исправной работы», связанные с каждым выпуском Node.js, но вы можете улучшить этот опыт, установив поле "packageManager" в файле package.json вашего проекта.

Точки входа в пакет

В файле пакета package.json две поля могут определять точки входа в пакет: "main" и "exports". Оба поля применяются к точкам входа модулей ES и CommonJS.

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

Поле "exports" предоставляет современную альтернативу полю "main", позволяя определять несколько точек входа, поддерживать условное разрешение точек входа в разных средах и предотвращать определение любых других точек входа, кроме тех, которые определены в поле "exports". Эта инкапсуляция позволяет авторам модулей четко определять публичный интерфейс своего пакета.

Для новых пакетов, ориентированных на текущие поддерживаемые версии Node.js, рекомендуется использовать поле "exports". Для пакетов, поддерживающих Node.js 10 и ниже, необходимо использовать поле "main". Если оба поля "exports" и "main" определены, поле "exports" имеет приоритет над полем "main" в поддерживаемых версиях Node.js.

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

Существующие пакеты, вводящие поле "exports", не позволят потребителям пакета использовать какие-либо точки входа, которые не определены, включая package.json (например, require('your-package/package.json'). Это, вероятно, будет изменением, нарушающим обратную совместимость.

Чтобы введение поля "exports" не нарушало обратную совместимость, убедитесь, что все ранее поддерживаемые точки входа экспортированы. Лучше явно указать точки входа, чтобы публичный API пакета был хорошо определен. Например, проект, который ранее экспортировал main, lib, feature, и package.json может использовать следующее package.exports:

{
  "name": "my-package",
  "exports": {
    ".": "./lib/index.js",
    "./lib": "./lib/index.js",
    "./lib/index": "./lib/index.js",
    "./lib/index.js": "./lib/index.js",
    "./feature": "./feature/index.js",
    "./feature/index": "./feature/index.js",
    "./feature/index.js": "./feature/index.js",
    "./package.json": "./package.json"
  }
} copy

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

{
  "name": "my-package",
  "exports": {
    ".": "./lib/index.js",
    "./lib": "./lib/index.js",
    "./lib/*": "./lib/*.js",
    "./lib/*.js": "./lib/*.js",
    "./feature": "./feature/index.js",
    "./feature/*": "./feature/*.js",
    "./feature/*.js": "./feature/*.js",
    "./package.json": "./package.json"
  }
} copy

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

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

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

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

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

При определении поля "exports", все подпути пакета инкапсулируются и больше не доступны импортерам. Например, require('pkg/subpath.js') вызывает ошибку ERR_PACKAGE_PATH_NOT_EXPORTED.

Эта инкапсуляция экспортов обеспечивает более надежные гарантии относительно интерфейсов пакетов для инструментов и при обработке обновлений semver для пакета. Это не сильная инкапсуляция, так как прямой require любого абсолютного подпути пакета, такого как require('/path/to/node_modules/pkg/subpath.js') , все равно загрузит subpath.js.

Все текущие поддерживаемые версии Node.js и современные инструменты сборки поддерживают поле "exports". Для проектов, использующих более старую версию Node.js или связанный инструмент сборки, совместимость может быть достигнута путем включения поля "main" вместе с "exports" , указывающим на тот же модуль:

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

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

Добавлен в: 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') в своих экспортах. Это гарантирует, что для каждого экспортированного модуля существует только один подпуть, таким образом, все зависимые компоненты импортируют один и тот же согласованный спецификатор, сохраняя контракт пакета ясным для потребителей и упрощая завершение подпутей пакета.

Традиционно пакеты использовали стиль без расширений, что выгодно с точки зрения читабельности и скрывает фактический путь файла внутри пакета.

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

Сахар для экспорта

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

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

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

может быть записано:

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

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

Добавлен в: 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

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

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

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

* отображения экспонируют вложенные подпути, поскольку это всего лишь синтаксис подстановки строк.

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

import featureX from 'es-module-package/features/x.js';
// Loads ./node_modules/es-module-package/src/features/x.js

import featureY from 'es-module-package/features/y/y.js';
// Loads ./node_modules/es-module-package/src/features/y/y.js

import internalZ from '#internal/z.js';
// Loads ./node_modules/es-module-package/src/internal/z.js copy

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

Свойство экспортов, являющихся статически перечисляемыми, сохраняется с шаблонами экспорта, поскольку отдельные экспорты для пакета могут быть определены путем обработки целевого шаблона справа как ** глоба относительно списка файлов внутри пакета. Поскольку пути 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 модули, так как require() их не поддерживает. Всегда взаимно исключающее с "import".
  • "default" - универсальный резервный вариант, который всегда соответствует. Может быть файлом CommonJS или ES модуля. Это условие должно всегда стоять последним.

Внутри объекта "exports" порядок ключей имеет значение. Во время сопоставления условий более ранние записи имеют более высокий приоритет и имеют преимущество перед более поздними. Общее правило состоит в том, что условия должны быть от самых конкретных к наименее конкретным в порядке объекта.

Использование условий "import" и "require" может привести к некоторым проблемам, которые подробно описаны в разделе dual CommonJS/ES module packages.

Условие "node-addons" может использоваться для предоставления точки входа, которая использует нативные дополнения C++. Однако это условие можно отключить с помощью флага --no-addons. При использовании "node-addons", рекомендуется рассматривать "default" как расширение, которое предоставляет более универсальную точку входа, например, используя WebAssembly вместо нативного дополнения.

Условные экспорты также можно расширить на подпути экспорта, например:

{
  "exports": {
    ".": "./index.js",
    "./feature.js": {
      "node": "./feature-node.js",
      "default": "./feature.js"
    }
  }
} copy

Определяет пакет, где require('pkg/feature.js') и import 'pkg/feature.js' могли бы предоставлять разные реализации в Node.js и других средах JS.

При использовании ветвей среды всегда включайте условие "default", где это возможно. Предоставление условия "default" гарантирует, что любые неизвестные среды JS смогут использовать эту универсальную реализацию, что помогает избежать необходимости этим средам JS притворяться существующими средами, чтобы поддерживать пакеты с условными экспортами. По этой причине использование условных ветвей "node" и "default" обычно предпочтительнее использования условных ветвей "node" и "browser".

Вложенные условия

Помимо прямых сопоставлений, Node.js также поддерживает вложенные объекты условий.

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

{
  "exports": {
    "node": {
      "import": "./feature-node.mjs",
      "require": "./feature-node.cjs"
    },
    "default": "./feature.mjs"
  }
} copy

Условия продолжают сопоставляться в порядке, как и с плоскими условиями. Если вложенное условие не имеет сопоставления, оно будет продолжать проверять оставшиеся условия родительского условия. Таким образом, вложенные условия ведут себя аналогично вложенным операторам JavaScript if.

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

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

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

node --conditions=development index.js copy

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

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

Определения условий сообщества

Строки условий, отличные от "import", "require", "node", "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

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

До появления поддержки ES-модулей в Node.js, авторы пакетов часто включали в свой пакет как CommonJS, так и ES-модульные JavaScript-источники, при этом package.json "main" указывал на точку входа CommonJS, а package.json "module" — на точку входа ES-модуля. Это позволяло Node.js запускать точку входа CommonJS, в то время как инструменты сборки, такие как бандлеры, использовали точку входа ES-модуля, так как Node.js игнорировал (и по-прежнему игнорирует) поле "module" верхнего уровня.

Теперь Node.js может запускать точки входа ES-модулей, и пакет может содержать как точки входа CommonJS, так и ES-модулей (либо через отдельные спецификаторы, такие как 'pkg' и 'pkg/es-module', либо оба в одном спецификаторе через Условные экспорты). В отличие от сценария, где "module" используется только бандлерами или ES-модульные файлы транспилируются в CommonJS на лету перед оценкой Node.js, файлы, на которые ссылается точка входа ES-модуля, оцениваются как ES-модули.

Опасность двойных пакетов

Когда приложение использует пакет, предоставляющий как CommonJS, так и ES-модульные источники, существует риск определённых ошибок, если обе версии пакета загружаются. Это потенциально связано с тем, что pkgInstance созданный const pkgInstance = require('pkg'), не совпадает с pkgInstance созданным import pkgInstance from 'pkg' (или альтернативным основным путём, например, 'pkg/module'). Это «опасность двойных пакетов», когда две версии одного пакета могут загружаться в одной среде выполнения. Хотя маловероятно, что приложение или пакет преднамеренно загрузит обе версии напрямую, приложение часто загружает одну версию, в то время как зависимость приложения загружает другую версию. Эта опасность может возникнуть, потому что Node.js поддерживает смешение CommonJS и ES-модулей и может привести к неожиданному поведению.

Если основной экспорт пакета — это конструктор, сравнение экземпляров, созданных двумя версиями, возвращает false, а если экспорт — это объект, свойства, добавленные к одному (например, pkgInstance.foo = 3), отсутствуют в другом. Это отличается от того, как работают операторы import и require в средах с использованием только CommonJS или только ES-модулей соответственно и поэтому может удивить пользователей. Это также отличается от поведения, с которым пользователи знакомы при использовании транспиляции с помощью инструментов, таких как Babel или esm.

Создание двойных пакетов с избеганием или минимизацией опасностей

Во-первых, опасность, описанная в предыдущем разделе, возникает, когда пакет содержит как CommonJS, так и ES-модульные источники, и оба источника предназначены для использования в Node.js, либо через отдельные главные точки входа, либо экспортируемые пути. Вместо этого пакет может быть написан так, чтобы любая версия Node.js получала только источники CommonJS, а любые отдельные ES-модульные источники, которые могут содержать пакет, предназначены только для других сред, таких как браузеры. Такой пакет будет пригоден для любой версии Node.js, поскольку import может ссылаться на файлы CommonJS; но он не предоставит никаких преимуществ использования синтаксиса ES-модулей.

Пакет также может переключиться с синтаксиса CommonJS на ES-модульный синтаксис в обновлении версии esm. Это имеет недостаток, что новейшая версия пакета будет пригодна только для поддерживающих ES-модули версий Node.js.

В каждом подходе есть свои компромиссы, но существуют два основных подхода, удовлетворяющие следующим условиям:

  1. Пакет пригоден для использования как с require, так и с import.
  2. Пакет пригоден для использования как в текущей Node.js, так и в более старых версиях Node.js, не поддерживающих ES-модули.
  3. Главная точка входа пакета, например, 'pkg', может использоваться как require для разрешения на файл CommonJS, так и import для разрешения на файл ES-модуля. (Аналогично для экспортируемых путей, например, 'pkg/feature'.)
  4. Пакет предоставляет именованные экспорты, например import { name } from 'pkg', а не import pkg from 'pkg'; pkg.name.
  5. Пакет потенциально пригоден для использования в других средах ES-модулей, таких как браузеры.
  6. Опасности, описанные в предыдущем разделе, избегаются или минимизируются.
Подход №1: Использование обёртки ES-модуля

Напишите пакет на CommonJS или транспилируйте ES-модульные источники в CommonJS и создайте файл обёртки ES-модулей, который определяет именованные экспорты. Используя условные экспорты, обёртка ES-модулей используется для import, а точка входа CommonJS — для require.

// ./node_modules/pkg/package.json
{
  "type": "module",
  "exports": {
    "import": "./wrapper.mjs",
    "require": "./index.cjs"
  }
} copy

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

// ./node_modules/pkg/index.cjs
exports.name = 'value'; copy
// ./node_modules/pkg/wrapper.mjs
import cjsModule from './index.cjs';
export const name = cjsModule.name; copy

В этом примере name из import { name } from 'pkg' — тот же синглтон, что и name из const { name } = require('pkg') . Поэтому === возвращает true при сравнении двух name и опасность различающихся спецификаторов устраняется.

Если модуль не просто список именованных экспортов, а содержит уникальный функцию или экспорт объекта, например, module.exports = function () { ... }, или если требуется поддержка шаблона import pkg from 'pkg' в обёртке, то обёртка должна быть написана так, чтобы экспортировать по умолчанию вместе с любыми именованными экспортами:

import cjsModule from './index.cjs';
export const name = cjsModule.name;
export default cjsModule; copy

Этот подход подходит для следующих случаев:

  • Пакет в настоящее время написан на CommonJS, и автору не хотелось бы переписывать его в синтаксис ES-модулей, но он хочет предоставить именованные экспорты для потребителей ES-модулей.
  • У пакета есть другие пакеты, которые зависят от него, и конечный пользователь может установить как этот пакет, так и эти другие пакеты. Например, пакет utilities используется непосредственно в приложении, а пакет utilities-plus добавляет несколько дополнительных функций к utilities . Поскольку обёртка экспортирует основополагающие файлы CommonJS, не важно, написан ли utilities-plus на CommonJS или ES-модульном синтаксисе; он будет работать в любом случае.
  • Пакет хранит внутреннее состояние, и автору пакета не хотелось бы переписывать пакет для изоляции управления состоянием. См. следующий раздел.

Вариант этого подхода, не требующий условных экспортов для потребителей, мог бы заключаться в добавлении экспорта, например, "./module", для указания версии пакета полностью на ES-модульном синтаксисе. Это можно использовать через import 'pkg/module' пользователями, которые уверены, что версия CommonJS не будет загружена нигде в приложении, например, зависимостями; или если версия CommonJS может быть загружена, но не влияет на версию ES-модуля (например, потому что пакет бессостоятельный):

// ./node_modules/pkg/package.json
{
  "type": "module",
  "exports": {
    ".": "./index.cjs",
    "./module": "./wrapper.mjs"
  }
} copy
Подход №2: Изоляция состояния

package.json файл может непосредственно определить отдельные точки входа CommonJS и ES-модулей:

// ./node_modules/pkg/package.json
{
  "type": "module",
  "exports": {
    "import": "./index.mjs",
    "require": "./index.cjs"
  }
} copy

Это можно сделать, если обе версии пакета CommonJS и ES-модулей эквивалентны, например, потому что одна является транспилированным выходом другой; и управление состоянием пакета тщательно изолировано (или пакет бессостоятельный).

Причина, по которой состояние является проблемой, заключается в том, что как версия CommonJS, так и версия ES-модулей пакета могут быть использованы в приложении; например, код приложения пользователя может import версию ES-модуля, а зависимость require версию CommonJS. Если это произойдёт, две копии пакета будут загружены в памяти, и, следовательно, будет присутствовать два отдельных состояния. Это, вероятно, приведёт к трудно диагностируемым ошибкам.

Помимо написания бессостоятельного пакета (если, например, Math JavaScript был пакетом, он был бы бессостоятельным, так как все его методы статические), существуют способы изоляции состояния, чтобы оно разделялось между потенциально загруженными экземплярами пакета CommonJS и ES-модуля:

  1. Если возможно, поместите всё состояние внутри экземпляра объекта. Например, Date JavaScript требует создания экземпляра для хранения состояния; если бы это был пакет, он использовался бы так:

    import Date from 'date';
    const someDate = new Date();
    // someDate contains state; Date does not copy

    Ключевое слово new не обязательно; функция пакета может возвращать новый объект или изменять переданный объект, чтобы состояние оставалось внешним для пакета.

  2. Изолируйте состояние в одном или нескольких файлах CommonJS, которые совместно используются между версиями пакета CommonJS и ES-модулей. Например, если точки входа CommonJS и ES-модулей — это index.cjs и index.mjs соответственно:

    // ./node_modules/pkg/index.cjs
    const state = require('./state.cjs');
    module.exports.state = state; copy
    // ./node_modules/pkg/index.mjs
    import state from './state.cjs';
    export {
      state,
    }; copy

    Даже если pkg используется как через require, так и через import в приложении (например, через import в коде приложения и через require в зависимости), каждая ссылка на pkg будет содержать одно и то же состояние; и изменение этого состояния в любой системе модулей будет применяться к обоим.

Любые плагины, которые подключаются к синглтону пакета, должны отдельно подключаться к обоим синглтонам CommonJS и ES-модулей.

Этот подход подходит для следующих случаев:

  • Пакет в настоящее время написан на ES-модульном синтаксисе, и автору пакета требуется, чтобы эта версия использовалась везде, где поддерживается такой синтаксис.
  • Пакет бессостоятельный или его состояние можно изолировать без особых трудностей.
  • Вряд ли у пакета будут другие публичные пакеты, которые от него зависят, или, если они есть, пакет бессостоятельный или имеет состояние, которое не обязательно должно делиться между зависимостями или с общим приложением.

Даже с изолированным состоянием всё равно есть затраты на возможную дополнительную обработку между версиями пакета CommonJS и ES-модулей.

Как и в предыдущем подходе, вариант этого подхода, не требующий условных экспортов для потребителей, может заключаться в добавлении экспорта, например, "./module", для указания версии пакета полностью на ES-модульном синтаксисе:

// ./node_modules/pkg/package.json
{
  "type": "module",
  "exports": {
    ".": "./index.cjs",
    "./module": "./index.mjs"
  }
} copy

Определения полей package.json в Node.js

В этом разделе описываются поля, используемые в среде выполнения Node.js. Другие инструменты (например, npm) используют дополнительные поля, которые игнорируются Node.js и здесь не документируются.

В файлах package.json используются следующие поля Node.js:

  • "name" - Актуально при использовании именованных импортов в пакете. Также используется менеджерами пакетов в качестве имени пакета.
  • "main" - По умолчанию модуль при загрузке пакета, если не указан exports, а также в версиях Node.js до появления exports.
  • "packageManager" - Рекомендуемый менеджер пакетов при внесении вклада в пакет. Используется плагинами Corepack.
  • "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

  • Тип: <строка>
{
  "name": "package-name"
} copy

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

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

"main"

Добавлен в: v0.4.0
  • Тип: <строка>
{
  "main": "./index.js"
} copy

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

Когда у пакета есть поле "exports", оно будет иметь приоритет над полем "main" при импорте пакета по имени.

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

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

"packageManager"

Добавлен в: v16.9.0, v14.19.0
Устойчивость: 1 - Экспериментально
  • Тип: <строка>
{
  "packageManager": "<package manager name>@<version>"
} copy

Поле "packageManager" определяет, какой менеджер пакетов ожидается при работе с текущим проектом. Оно может быть установлено на любой из поддерживаемых менеджеров пакетов и гарантирует, что ваши команды используют точно такие же версии менеджеров пакетов без дополнительных установок помимо Node.js.

Это поле на данный момент экспериментальное и требует включения; см. страницу Corepack для получения подробностей о процедуре.

"type"

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

Снять отметку --experimental-modules.

v12.0.0

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

  • Тип: <строка>

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

  • Тип: <Объект> | <строка> | <массив строк>
{
  "exports": "./index.js"
} copy

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

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

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

"imports"

Добавлен в: v14.6.0, v12.19.0
  • Тип: <Объект>
// 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-v18.x/docs/api/packages.html

Spec-Zone.ru

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