Модули: Пакеты
Введение
Пакет — это древовидная структура папок, описываемая файлом package.json. Пакет состоит из папки, содержащей файл package.json, и всех подпапок до следующей папки, содержащей другой файл package.json или папки с именем node_modules.
Эта страница предоставляет руководство для авторов пакетов по написанию файлов package.json вместе со справочником по полям package.json, определённым Node.js.
Определение системы модулей
Node.js будет рассматривать следующее как ES-модули, когда это передаётся в node в качестве входных данных или когда на них ссылаются в коде ES-модулей с помощью 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 в качестве входных данных или когда на них ссылаются в коде ES-модулей с помощью import:
-
Файлы, заканчивающиеся на
.cjs. -
Файлы, заканчивающиеся на
.js, когда ближайший родительский файлpackage.jsonсодержит поле верхнего уровня"type"со значением"commonjs". -
Строки, передаваемые в качестве аргумента в
--evalили--print, или передаваемые вnodeчерезSTDIN, со флагом--input-type=commonjs.
Авторы пакетов должны включать поле "type", даже в пакетах, где все источники являются CommonJS. Явное указание типа пакета сделает пакет более устойчивым к будущим изменениям, в случае изменения типа по умолчанию Node.js, а также упростит определение инструментов сборки и загрузчиков того, как файлы в пакете должны интерпретироваться.
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".
Файлы, заканчивающиеся на .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.
Расширения .mjs и .cjs могут использоваться для смешивания типов внутри одного пакета:
-
В пакете
"type": "module", Node.js можно настроить на интерпретацию определённого файла как CommonJS, присвоив ему расширение.cjs(поскольку как файлы.js, так и файлы.mjsобрабатываются как ES-модули в пакете"module"). -
В пакете
"type": "commonjs", Node.js можно настроить на интерпретацию определённого файла как ES-модуль, присвоив ему расширение.mjs(поскольку как файлы.js, так и файлы.cjsобрабатываются как CommonJS в пакете"commonjs").
--input-type флаг
Строки, передаваемые в качестве аргумента в --eval (или -e), или передаваемые в node через STDIN, обрабатываются как ES-модули, когда установлен флаг --input-type=module.
node --input-type=module --eval "import { sep } from 'path'; console.log(sep);"
echo "import { sep } from 'path'; console.log(sep);" | node --input-type=module Для полноты, также есть --input-type=commonjs, для явного выполнения строковых входных данных как CommonJS. Это поведение по умолчанию, если --input-type не указано.
Точки входа в пакет
В файле package.json пакета две поля могут определять точки входа в пакет: "main" и "exports". Поле "main" поддерживается во всех версиях Node.js, но его возможности ограничены: оно только определяет главную точку входа в пакет.
Поле "exports" предоставляет альтернативу полю "main", где основная точка входа в пакет может быть определена, одновременно инкапсулируя пакет, предотвращая любые другие точки входа помимо тех, которые определены в "exports". Эта инкапсуляция позволяет авторам модулей определять публичный интерфейс для своего пакета.
Если оба поля "exports" и "main" определены, то поле "exports" имеет приоритет над "main". Поля "exports" не специфичны для ES-модулей или CommonJS; "main" переопределяется "exports", если оно существует. Таким образом, "main" не может использоваться в качестве резервного варианта для CommonJS, но может использоваться в качестве резервного варианта для устаревших версий Node.js, которые не поддерживают поле "exports".
Условные экспорты могут использоваться в "exports" для определения различных точек входа в пакет в зависимости от среды, включая то, ссылаются ли на пакет через require или через import. Для получения дополнительной информации о поддержке как CommonJS, так и ES Modules в одном пакете, обратитесь к разделу пакетов CommonJS/ES-модулей.
Предупреждение: Введение поля "exports" предотвращает использование потребителями пакета любых точек входа, которые не определены, включая package.json (например, require('your-package/package.json'). Это, вероятно, приведёт к нарушению совместимости.
Чтобы внедрение поля "exports" было безболезненным, убедитесь, что каждый ранее поддерживаемый пункт входа экспортирован. Лучше явно указать точки входа, чтобы публичный API пакета был хорошо определён. Например, проект, ранее экспортировавший main, lib, feature, и package.json, мог использовать следующий package.exports:
{
"name": "my-mod",
"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.js": "./feature/index.js",
"./package.json": "./package.json"
}
} В качестве альтернативы, проект может выбрать экспорт целых папок:
{
"name": "my-mod",
"exports": {
".": "./lib/index.js",
"./lib": "./lib/index.js",
"./lib/*": "./lib/*.js",
"./feature": "./feature/index.js",
"./feature/*": "./feature/*.js",
"./package.json": "./package.json"
}
} В крайнем случае, инкапсуляцию пакета можно отключить полностью, создав экспорт для корня пакета "./*": "./*". Это экспонирует каждый файл пакета, но при этом отключает инкапсуляцию и потенциальные преимущества, которые она предоставляет. Поскольку загрузчик ES-модулей в Node.js требует использования полного пути спецификатора, экспорт корня вместо явного указания точек входа менее выразителен, чем любой из предыдущих примеров. Не только инкапсуляция теряется, но и потребители модуля не могут import feature from 'my-mod/feature' так как им нужно предоставить полный путь import feature from 'my-mod/feature/index.js.
Экспорт основной точки входа
Для установки основной точки входа в пакет рекомендуется определить как поле "exports", так и поле "main" в файле package.json пакета:
{
"main": "./main.js",
"exports": "./main.js"
} Когда определено поле "exports", все подпути пакета инкапсулируются и больше недоступны импортерам. Например, require('pkg/subpath.js') вызывает ошибку ERR_PACKAGE_PATH_NOT_EXPORTED.
Эта инкапсуляция экспортов обеспечивает более надёжные гарантии по интерфейсам пакетов для инструментов и при обновлении пакетов semver. Это не жёсткая инкапсуляция, поскольку прямой require любого абсолютного подпути пакета, например, require('/path/to/node_modules/pkg/subpath.js') по-прежнему загрузит subpath.js.
Экспорты подпутей
При использовании поля "exports", пользовательские подпути могут быть определены наряду с основной точкой входа, рассматривая главную точку входа как подпуть ".":
{
"main": "./main.js",
"exports": {
".": "./main.js",
"./submodule": "./src/submodule.js"
}
} Теперь только определённый подпуть в "exports" может быть импортирован потребителем:
import submodule from 'es-module-package/submodule'; // Loads ./node_modules/es-module-package/src/submodule.js
В то время как другие подпути вызовут ошибку:
import submodule from 'es-module-package/private-module.js'; // Throws ERR_PACKAGE_PATH_NOT_EXPORTED
Импорты подпутей
Помимо поля "exports", можно определить внутренние карты импорта пакета, которые применяются только к спецификаторам импорта из самого пакета.
Записи в поле imports всегда должны начинаться с #, чтобы их можно было отличить от спецификаторов пакетов.
Например, поле imports можно использовать для получения преимуществ условных экспортов для внутренних модулей:
// package.json
{
"imports": {
"#dep": {
"node": "dep-node-native",
"default": "./dep-polyfill.js"
}
},
"dependencies": {
"dep-node-native": "^1.0.0"
}
} где import '#dep' не получает разрешения внешнего пакета dep-node-native (включая его экспорты), а вместо этого получает локальный файл ./dep-polyfill.js относительно пакета в других средах.
В отличие от поля "exports", поле "imports" допускает отображение на внешние пакеты.
Правила разрешения для поля imports аналогичны правилам для поля exports.
Паттерны подпутей
Для пакетов с небольшим количеством экспортов или импортов рекомендуется явно перечислять каждую запись подпути экспорта. Но для пакетов с большим количеством подпутей это может привести к package.json разбуханию и проблемам с обслуживанием.
Для этих случаев можно использовать шаблоны экспорта подпутей:
// ./node_modules/es-module-package/package.json
{
"exports": {
"./features/*": "./src/features/*.js"
},
"imports": {
"#internal/*": "./src/internal/*.js"
}
} Шаблон соответствия слева всегда должен заканчиваться *. Все экземпляры * справа будут затем заменены этим значением, включая, если оно содержит разделители /.
import featureX from 'es-module-package/features/x'; // Loads ./node_modules/es-module-package/src/features/x.js import featureY from 'es-module-package/features/y/y'; // Loads ./node_modules/es-module-package/src/features/y/y.js import internalZ from '#internal/z'; // Loads ./node_modules/es-module-package/src/internal/z.js
Это прямая статическая замена без специальной обработки расширений файлов. В предыдущем примере pkg/features/x.json было бы преобразовано в ./src/features/x.json.js в отображении.
Свойство экспортов, являющихся статически перечисляемыми, сохраняется с шаблонами экспорта, поскольку отдельные экспорты пакета могут быть определены путем обработки целевого шаблона справа как ** глоб против списка файлов в пакете. Так как пути node_modules запрещены в целевых объектах экспорта, это расширение зависит только от файлов самого пакета.
Упрощенное представление экспорта
Если экспорт "." является единственным экспортом, поле "exports" предоставляет упрощенное представление для этого случая, являясь прямым значением поля "exports".
Если экспорт "." имеет значения по умолчанию в виде массива или строки, то поле "exports" может быть установлено напрямую.
{
"exports": {
".": "./main.js"
}
} можно записать так:
{
"exports": "./main.js"
} Условные экспорты
Условные экспорты предоставляют способ отображения на разные пути в зависимости от определенных условий. Они поддерживаются для импортов CommonJS и модулей ES.
Например, пакет, который хочет предоставить разные экспорты модулей ES для require() и import, может быть записан:
// package.json
{
"main": "./main-require.cjs",
"exports": {
"import": "./main-module.js",
"require": "./main-require.cjs"
},
"type": "module"
} Node.js поддерживает следующие условия из коробки:
-
"import"- соответствует, когда пакет загружается черезimportилиimport(), или через любой импорт верхнего уровня или операцию разрешения загрузчиком модулей ECMAScript. Применяется независимо от формата модуля целевого файла. Всегда взаимно исключающие с"require". -
"require"- соответствует, когда пакет загружается черезrequire(). Ссылаемый файл должен быть загружаемым сrequire(), хотя условие соответствует независимо от формата модуля целевого файла. Ожидаемые форматы включают CommonJS, JSON и нативные плагины, но не модули ES, так какrequire()их не поддерживают. Всегда взаимно исключающие с"import". -
"node"- соответствует любой среде Node.js. Может быть файлом CommonJS или ES-модуля. Это условие должно всегда следовать за"import"или"require". -
"default"- общий случай по умолчанию, который всегда соответствует. Может быть файлом CommonJS или ES-модуля. Это условие должно всегда идти последним.
В объекте "exports" порядок ключей важен. При сопоставлении условий более ранние записи имеют более высокий приоритет и имеют преимущество перед более поздними записями. Общее правило состоит в том, что условия должны быть от самых конкретных к наименее конкретным в порядке следования объектов.
Другие условия, такие как "browser", "electron", "deno", "react-native", и т. д., неизвестны Node.js и, следовательно, игнорируются. Среды выполнения или инструменты, отличные от Node.js, могут использовать их по своему усмотрению. В будущем могут появиться дополнительные ограничения, определения или руководства по именам условий.
Использование условий "import" и "require" может привести к некоторым проблемам, которые подробно описаны в разделе пакеты CommonJS/ES-модулей двойного типа.
Условные экспорты также могут быть расширены на подпути экспортов, например:
{
"main": "./main.js",
"exports": {
".": "./main.js",
"./feature": {
"node": "./feature-node.js",
"default": "./feature.js"
}
}
} Определяет пакет, где require('pkg/feature') и import 'pkg/feature' могут предоставлять разные реализации в Node.js и других средах JS.
При использовании ветвей среды всегда включайте условие "default", где это возможно. Предоставление условия "default" гарантирует, что любые неизвестные среды JS смогут использовать эту универсальную реализацию, что помогает избежать необходимости этим средам JS выдавать себя за существующие среды для поддержки пакетов с условными экспортами. По этой причине использование ветвей условий "node" и "default" обычно предпочтительнее использования ветвей условий "node" и "browser".
Вложенные условия
Помимо прямых отображений, Node.js также поддерживает вложенные объекты условий.
Например, чтобы определить пакет, который имеет только точки входа двойного режима для использования в Node.js, но не в браузере:
{
"main": "./main.js",
"exports": {
"node": {
"import": "./feature-node.mjs",
"require": "./feature-node.cjs"
},
"default": "./feature.mjs",
}
} Условия по-прежнему сопоставляются в порядке, как и в случае с плоскими условиями. Если вложенное условное условие не имеет сопоставления, оно продолжает проверку оставшихся условий родительского условия. Таким образом, вложенные условия ведут себя аналогично вложенным операторам JavaScript if.
Разрешение пользовательских условий
При запуске Node.js пользовательские условия можно добавить с флагом --conditions.
node --conditions=development main.js
что затем разрешит условие "development" в импортах и экспортах пакета, одновременно разрешая существующие условия "node", "default", "import" и "require" соответствующим образом.
Можно задать любое количество пользовательских условий с повторяющимися флагами.
Ссылка на пакет по его имени
Внутри пакета значения, определенные в поле пакета package.json "exports", могут быть использованы через имя пакета. Например, предположим, что package.json это:
// package.json
{
"name": "a-package",
"exports": {
".": "./main.mjs",
"./foo": "./foo.js"
}
} Тогда любой модуль внутри этого пакета может ссылаться на экспорт в самом пакете:
// ./a-module.mjs
import { something } from 'a-package'; // Imports "something" from ./main.mjs. Самоссылка доступна только если 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'; Самоссылка также доступна при использовании require, как в ES-модуле, так и в CommonJS. Например, этот код также будет работать:
// ./a-module.js
const { something } = require('a-package/foo'); // Loads from ./foo.js. Пакеты 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-модулей, и может привести к неожиданному поведению.
Если экспорт пакета main является конструктором, сравнение экземпляров, созданных двумя версиями, возвращает 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 модулей в версии с изменениями, требующими переписывания (breaking change). Это имеет недостаток, что новейшая версия пакета будет пригодна только для версий Node.js, поддерживающих ES модули.
Каждый шаблон имеет свои компромиссы, но есть два основных подхода, которые удовлетворяют следующим условиям:
- Пакет пригоден для использования как через
require, так и черезimport. - Пакет пригоден для использования как в текущих версиях Node.js, так и в более старых версиях Node.js, не поддерживающих ES модули.
- Главная точка входа пакета, например,
'pkg', может использоваться какrequire, чтобы разрешить ссылку на файл CommonJS, так иimport, чтобы разрешить ссылку на файл ES модуля. (Точно так же для экспортированных путей, например,'pkg/feature'.) - Пакет предоставляет именованные экспорты, например,
import { name } from 'pkg', а неimport pkg from 'pkg'; pkg.name. - Пакет потенциально пригоден для использования в других средах ES модулей, таких как браузеры.
- Описанные в предыдущем разделе опасности избегаются или сводятся к минимуму.
Подход №1: Использование обертки ES модуля
Напишите пакет на CommonJS или транспилируйте источники ES модулей в CommonJS, и создайте файл обертки ES модуля, который определяет именованные экспорты. Используя условные экспорты, обертка ES модуля используется для import, а точка входа CommonJS — для require.
// ./node_modules/pkg/package.json
{
"type": "module",
"main": "./index.cjs",
"exports": {
"import": "./wrapper.mjs",
"require": "./index.cjs"
}
} В приведенном выше примере используются явные расширения .mjs и .cjs. Если ваши файлы используют расширение .js, то "type": "module" приведет к тому, что такие файлы будут обрабатываться как ES модули, точно так же, как "type": "commonjs" приведет к тому, что они будут обрабатываться как CommonJS. См. Включение.
// ./node_modules/pkg/index.cjs exports.name = 'value';
// ./node_modules/pkg/wrapper.mjs import cjsModule from './index.cjs'; export const name = cjsModule.name;
В этом примере 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;
Этот подход подходит для любого из следующих случаев использования:
- Пакет в настоящее время написан на 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",
"main": "./index.cjs",
"exports": {
".": "./index.cjs",
"./module": "./wrapper.mjs"
}
} Подход №2: Изолировать состояние
Файл package.json может напрямую определять отдельные точки входа CommonJS и ES модулей:
// ./node_modules/pkg/package.json
{
"type": "module",
"main": "./index.cjs",
"exports": {
"import": "./index.mjs",
"require": "./index.cjs"
}
} Это можно сделать, если обе версии пакета CommonJS и ES модулей эквивалентны, например, потому что одна является транспилированным выводом другой; и управление состоянием пакета тщательно изолировано (или пакет бессостоятелен).
Причина, по которой состояние является проблемой, заключается в том, что обе версии пакета, CommonJS и ES модулей, могут быть использованы в приложении; например, код приложения пользователя может import использовать версию ES модуля, в то время как зависимость require использует версию CommonJS. Если это произойдет, две копии пакета будут загружены в память, и, следовательно, будут присутствовать два отдельных состояния. Это, вероятно, вызовет трудноотслеживаемые ошибки.
Помимо написания бессостоятельного пакета (если, например, JavaScript’s Math был пакетом, он был бы бессостоятельным, поскольку все его методы статические), есть способы изоляции состояния таким образом, чтобы оно разделялось между потенциально загруженными экземплярами пакета CommonJS и ES модулей:
-
Если это возможно, поместите всё состояние в экземпляр объекта. Например, JavaScript’s
Date, например, нужно инициализировать для хранения состояния; если бы это был пакет, он использовался бы так:import Date from 'date'; const someDate = new Date(); // someDate contains state; Date does not
Ключевое слово
newне требуется; функция пакета может возвращать новый объект или изменять переданный объект, чтобы состояние оставалось внешним по отношению к пакету. -
Изолируйте состояние в одном или нескольких файлах CommonJS, которые совместно используются между версиями CommonJS и ES модулей пакета. Например, если точки входа CommonJS и ES модулей — это
index.cjsиindex.mjs, соответственно:// ./node_modules/pkg/index.cjs const state = require('./state.cjs'); module.exports.state = state;// ./node_modules/pkg/index.mjs import state from './state.cjs'; export { state };Даже если
pkgиспользуется черезrequireиimportв приложении (например, черезimportв коде приложения и черезrequireзависимостью) каждая ссылка наpkgбудет содержать одно и то же состояние; и изменение этого состояния из любой системы модулей будет применяться к обеим.
Любые плагины, которые присоединяются к синглтону пакета, должны отдельно присоединяться как к синглтону CommonJS, так и к синглтону ES модулей.
Этот подход подходит для любого из следующих случаев использования:
- Пакет в настоящее время написан на синтаксисе ES модулей, и автор пакета хочет, чтобы эта версия использовалась там, где такой синтаксис поддерживается.
- Пакет бессостоятелен или его состояние может быть изолировано без особых затруднений.
- Вряд ли у пакета будут другие публичные пакеты, от которых он зависит, или, если будут, пакет бессостоятелен или имеет состояние, которое не нужно разделять между зависимостями или со всем приложением.
Даже с изолированным состоянием есть затраты на возможную дополнительную обработку кода между версиями пакета CommonJS и ES модулей.
Как и в предыдущем подходе, вариант этого подхода, не требующий условных экспортов для потребителей, мог бы заключаться в добавлении экспорта, например, "./module", для указания на версию пакета, написанного целиком на синтаксисе ES модулей:
// ./node_modules/pkg/package.json
{
"type": "module",
"main": "./index.cjs",
"exports": {
".": "./index.cjs",
"./module": "./index.mjs"
}
} Определения полей Node.js package.json
В этом разделе описываются поля, используемые средой выполнения Node.js. Другие инструменты (например, npm) используют дополнительные поля, которые игнорируются Node.js и здесь не документируются.
В файлах package.json используются следующие поля Node.js:
-
"name"— Актуально при использовании именованных импортов внутри пакета. Также используется менеджерами пакетов в качестве имени пакета. -
"type"— Тип пакета, определяющий, загружать ли файлы.jsкак CommonJS или ES модули. -
"exports"— Экспорты пакета и условные экспорты. При наличии ограничивает, какие подмодули могут быть загружены изнутри пакета. -
"main"— Модуль по умолчанию при загрузке пакета, если exports не указан, и в версиях Node.js до появления exports. -
"imports"— Импорты пакета для использования модулями внутри самого пакета.
"name"
- Тип: <строка>
{
"name": "package-name"
} Поле "name" определяет имя вашего пакета. Публикация в реестре npm требует имени, которое соответствует определённым требованиям.
Поле "name" может использоваться дополнительно к полю "exports" для ссылок на пакет по его имени.
"type"
- Тип: <строка>
Поле "type" определяет формат модуля, который Node.js использует для всех файлов .js, у которых файл с этим package.json полем является ближайшим родительским файлом.
Файлы с расширением .js загружаются как ES модули, когда ближайший родительский файл package.json содержит поле верхнего уровня "type" со значением "module".
Ближайший родительский файл package.json определяется как первый файл package.json, найденный при поиске в текущей папке, папке родителя и так далее до папки node_modules или корня тома.
// package.json
{
"type": "module"
} # In same folder as preceding package.json node my-app.js # Runs as ES module
Если ближайший родительский файл 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
Независимо от значения поля "type", .mjs файлы всегда обрабатываются как модули ES, а .cjs файлы — всегда как CommonJS.
"exports"
- Тип: <Объект> | <строка> | <массив строк>
{
"exports": "./index.js"
} Поле "exports" позволяет определять точки входа (entry points) пакета при импорте по имени, загружаемого либо с помощью поиска node_modules, либо с помощью самоссылок на собственное имя. Оно поддерживается в Node.js 12+ как альтернатива "main", которое может поддерживать определение экспортов подпутей и условных экспортов при одновременном инкапсулировании внутренних неэкспортируемых модулей.
Условные экспорты также могут быть использованы внутри "exports" для определения различных точек входа пакета в разных средах, включая то, ссылается ли на пакет require или через import.
Все пути, определённые в поле "exports", должны быть относительными URL файлов, начинающимися с ./.
"main"
- Тип: <строка>
{
"main": "./main.js"
} Поле "main" определяет скрипт, который используется, когда директория пакета загружается с помощью require(). Его значение интерпретируется как путь.
require('./path/to/directory'); // This resolves to ./path/to/directory/main.js. Когда у пакета есть поле "exports", это поле будет иметь приоритет над полем "main" при импорте пакета по имени.
"imports"
- Тип: <Объект>
// package.json
{
"imports": {
"#dep": {
"node": "dep-node-native",
"default": "./dep-polyfill.js"
}
},
"dependencies": {
"dep-node-native": "^1.0.0"
}
} Элементы поля 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-v12.x/docs/api/packages.html