Spec-Zone.ru › webpack 5

Экспорт пакета

Поле exports в package.json пакета позволяет объявить, какой модуль следует использовать при запросах модулей, таких как import "package" или import "package/sub/path". Оно заменяет стандартную реализацию, которая возвращает поле main соответственно index.js файлы для "package" и поиск по файловой системе для "package/sub/path".

Когда указано поле exports, доступны только эти запросы модулей. Любые другие запросы приведут к ошибке ModuleNotFound.

Общая синтаксис

В общем случае, поле exports должно содержать объект, где каждое свойство определяет подпуть запроса модуля. Для примеров выше можно использовать следующие свойства: "." для import "package" и "./sub/path" для import "package/sub/path". Свойства, заканчивающиеся на /, перенаправят запрос с этим префиксом к старому алгоритму поиска по файловой системе. Для свойств, заканчивающихся на *, * может принимать любое значение, а любые * в значении свойства будут заменены принятым значением.

Пример:

{
  "exports": {
    ".": "./main.js",
    "./sub/path": "./secondary.js",
    "./prefix/": "./directory/",
    "./prefix/deep/": "./other-directory/",
    "./other-prefix/*": "./yet-another/*/*.js"
  }
}
Запрос модуля Результат
package .../package/main.js
package/sub/path .../package/secondary.js
package/prefix/some/file.js .../package/directory/some/file.js
package/prefix/deep/file.js .../package/other-directory/file.js
package/other-prefix/deep/file.js .../package/yet-another/deep/file/deep/file.js
package/main.js Ошибка

Альтернативы

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

Примечание: Будет использован только первый действительный результат, а не все действительные результаты.

Пример:

{
  "exports": {
    "./things/": ["./good-things/", "./bad-things/"]
  }
}

Здесь package/things/apple может быть найден в .../package/good-things/apple или в .../package/bad-things/apple.

предупреждение

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

Например, при следующей конфигурации:

{
  "exports": {
    ".": ["-bad-specifier-", "./non-existent.js", "./existent.js"]
  }
}

Webpack 5.94.0+ теперь выведет ошибку, так как non-existent.js не найден, в то время как предыдущее поведение привело бы к разрешению existent.js.

Условный синтаксис

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

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

Пример:

{
  "exports": {
    ".": {
      "red": "./stop.js",
      "yellow": "./stop.js",
      "green": {
        "free": "./drive.js",
        "default": "./wait.js"
      },
      "default": "./drive-carefully.js"
    }
  }
}

Это переводится примерно так:

if (red && valid('./stop.js')) return './stop.js';
if (yellow && valid('./stop.js')) return './stop.js';
if (green) {
  if (free && valid('./drive.js')) return './drive.js';
  if (valid('./wait.js')) return './wait.js';
}
if (valid('./drive-carefully.js')) return './drive-carefully.js';
throw new ModuleNotFoundError();

Доступные условия зависят от системы модулей и используемого инструмента.

Сокращение

При необходимости поддержки только одного элемента (".") в пакет, вложенность объекта { ".": ... } может быть опущена:

{
  "exports": "./index.mjs"
}
{
  "exports": {
    "red": "./stop.js",
    "green": "./drive.js"
  }
}

Примечания по порядку

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

Пример: { "red": "./stop.js", "green": "./drive.js" } != { "green": "./drive.js", "red": "./stop.js" } (если оба условия red и green установлены, будет использовано первое свойство)

В объекте, где каждый ключ является подпутем, порядок свойств (подпутей) не имеет значения. Более конкретные пути отдаются предпочтение менее конкретным.

Пример: { "./a/": "./x/", "./a/b/": "./y/", "./a/b/c": "./z" } == { "./a/b/c": "./z", "./a/b/": "./y/", "./a/": "./x/" } (порядок всегда будет: ./a/b/c > ./a/b/ > ./a/).

Поле exports предпочтительнее других полей ввода пакета, таких как main, module, browser или пользовательских.

Поддержка

Функция Поддерживается
свойство "." Node.js, webpack, rollup, esinstall, wmr
обычное свойство Node.js, webpack, rollup, esinstall, wmr
свойство, заканчивающееся на / Node.js(1), webpack, rollup, esinstall(2), wmr(3)
свойство, заканчивающееся на * Node.js, webpack, rollup, esinstall
Альтернативы Node.js, webpack, rollup, esinstall(4)
Сокращение только пути Node.js, webpack, rollup, esinstall, wmr
Сокращение только условий Node.js, webpack, rollup, esinstall, wmr
Условный синтаксис Node.js, webpack, rollup, esinstall, wmr
Вложенный условный синтаксис Node.js, webpack, rollup, wmr(5)
Порядок условий Node.js, webpack, rollup, wmr(6)
условие "default" Node.js, webpack, rollup, esinstall, wmr
Порядок путей Node.js, webpack, rollup
Ошибка при отсутствии сопоставления Node.js, webpack, rollup, esinstall, wmr(7)
Ошибка при смешивании условий и путей Node.js, webpack, rollup

(1) Удалено в Node.js 17. Используйте * вместо.

(2) "./" намеренно игнорируется как ключ.

(3) Значение свойства игнорируется, а ключ свойства используется в качестве целевого значения. Эффективно допускаются только сопоставления с одинаковым ключом и значением.

(4) Синтаксис поддерживается, но всегда используется первый элемент, что делает его непригодным для практического использования.

(5) Обработка обратного перехода к альтернативным условиям родительского элемента выполняется некорректно.

(6) Для условия require порядок объектов условий обрабатывается неправильно. Это намеренно, поскольку wmr не различает синтаксис ссылки.

(7) При использовании сокращения "exports": "./file.js", любой запрос, например, package/not-existing, будет разрешен до него. При отсутствии сокращения прямой доступ к файлам, например, package/file.js, не приведет к ошибке.

Условия

Синтаксис ссылки

Одно из этих условий устанавливается в зависимости от синтаксиса, используемого для ссылки на модуль:

Условие Описание Поддерживается
import Запрос отправлен из синтаксиса ESM или аналогичного. Node.js, webpack, rollup, esinstall(1), wmr(1)
require Запрос отправлен из синтаксиса CommonJs/AMD или аналогичного. Node.js, webpack, rollup, esinstall(1), wmr(1)
style Запрос отправлен со ссылкой на таблицу стилей.
sass Запрос отправлен со ссылкой на таблицу стилей Sass.
asset Запрос отправлен со ссылкой на ресурс.
script Запрос отправлен из обычного тега скрипта без системы модулей.

Эти условия также могут быть установлены дополнительно:

Условие Описание Поддерживается
module Все синтаксисы модулей, которые позволяют ссылаться на javascript, поддерживают ESM.
(только в сочетании с import или require)
webpack, rollup, wmr
esmodules Всегда устанавливается поддерживаемыми инструментами. wmr
types Запрос отправлен из TypeScript, который заинтересован в декларациях типов.

(1) import и require устанавливаются независимо от синтаксиса ссылки. require всегда имеет меньший приоритет.

import

Следующий синтаксис установит условие import.

  • Декларации ESM import в ESM
  • Выражение JS import()
  • HTML <script type="module"> в HTML
  • HTML <link rel="preload/prefetch"> в HTML
  • JS new Worker(..., { type: "module" })
  • WASM import раздел
  • ESM HMR (webpack) import.hot.accept/decline([...])
  • JS Worklet.addModule
  • Использование javascript в качестве точки входа

require

Следующий синтаксис установит условие require.

  • CommonJs require(...)
  • AMD define()
  • AMD require([...])
  • CommonJs require.resolve()
  • CommonJs (webpack) require.ensure([...])
  • CommonJs (webpack) require.context
  • CommonJs HMR (webpack) module.hot.accept/decline([...])
  • HTML <script src="...">

style

Следующий синтаксис установит условие style.

  • CSS @import
  • HTML <link rel="stylesheet">

asset

Следующий синтаксис установит условие asset.

  • CSS url()
  • ESM new URL(..., import.meta.url)
  • HTML <img src="...">

скрипт

Следующий синтаксис установит условие %%%CODE_BLOCK_107%%:

  • HTML <script src="...">

script следует устанавливать только тогда, когда система модулей не поддерживается. При предварительной обработке скрипта системой, поддерживающей CommonJs, следует установить require вместо этого.

Это условие следует использовать при поиске файла javascript, который может быть вставлен в виде тега script на HTML-странице без дополнительной предварительной обработки.

Оптимизации

Для различных оптимизаций установлены следующие условия:

Условие Описание Поддерживается
production В производственной среде.
Инструменты разработки не должны включаться.
webpack
development В среде разработки.
Инструменты разработки должны быть включены.
webpack

Примечание: Поскольку production и development не поддерживаются всеми, не следует делать никаких предположений, если ни одно из них не установлено.

Целевая среда

В зависимости от целевой среды установлены следующие условия:

Условие Описание Поддерживается
browser Код будет выполняться в браузере. webpack, esinstall, wmr
electron Код будет выполняться в electron.(1) webpack
worker Код будет выполняться в (Web)Worker.(1) webpack
worklet Код будет выполняться в Worklet.(1) -
node Код будет выполняться в Node.js. Node.js, webpack, wmr(2)
deno Код будет выполняться в Deno. -
react-native Код будет выполняться в react-native. -

(1) electron, worker и worklet комбинируются с node или browser, в зависимости от контекста.

(2) Это устанавливается для целевой среды браузера.

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

  • node: Смотрите поле engines для совместимости.
  • browser: Совместимо с текущим спецификацией и предложениями стадии 4 на момент публикации пакета. Поддержка полифиллов/транспиляции должна осуществляться со стороны потребителя.
    • Функции, которые невозможно полифилить или транспилировать, следует использовать с осторожностью, так как это ограничивает возможное использование.
  • deno: TBD
  • react-native: TBD

Условие: Препроцессор и среды выполнения

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

Условие Описание Поддерживается
webpack Обрабатывается webpack. webpack

К сожалению, нет условия node-js для среды выполнения Node.js. Это упростило бы создание исключений для Node.js.

Условие: Пользовательское

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

Инструмент Поддержка Примечания
Node.js да Используйте аргумент командной строки --conditions.
webpack да Используйте параметр конфигурации resolve.conditionNames.
rollup да Используйте параметр exportConditions для @rollup/plugin-node-resolve
esinstall нет
wmr нет

Для пользовательских условий рекомендуется следующая схема именования:

<company-name>:<condition-name>

Примеры: example-corp:beta, google:internal.

Общие шаблоны

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

Эти шаблоны следует использовать как руководство, а не как строгие правила. Их можно адаптировать к отдельным пакетам.

Эти шаблоны основаны на следующем списке целей/предположений:

  • Пакеты устаревают.
    • Мы предполагаем, что в какой-то момент пакеты больше не будут поддерживаться, но они будут продолжать использоваться.
    • exports должен быть написан для использования резервных вариантов для неизвестных будущих случаев. default условие можно использовать для этого.
    • Поскольку будущее неизвестно, мы предполагаем среду, похожую на браузеры, и систему модулей, похожую на ESM.
  • Не все условия поддерживаются каждым инструментом.
    • Для обработки таких случаев следует использовать резервные варианты.
    • Мы предполагаем, что следующие резервные варианты имеют смысл в общем:
      • ESM > CommonJs
      • Производство > Разработка
      • Браузер > node.js

В зависимости от цели пакета, может быть уместен другой подход, и в этом случае шаблоны следует адаптировать к нему. Например, для инструмента командной строки браузерная модель будущего и резервные варианты не имеют большого смысла, и в этом случае следует использовать модели среды и резервные варианты, подобные node.js.

Для сложных случаев необходимо комбинировать несколько шаблонов, вложенных друг в друга.

Пакеты, независимые от целевой среды

Эти шаблоны подходят для пакетов, которые не используют специфичные для среды API.

Предоставление только версии ESM

{
  "type": "module",
  "exports": "./index.js"
}

Примечание: Предоставление только ESM имеет ограничения для node.js. Такой пакет будет работать только в Node.js >= 14 и только при использовании import. Он не будет работать с require().

Предоставление версий CommonJs и ESM (бессостоятельный)

{
  "type": "module",
  "exports": {
    "node": {
      "module": "./index.js",
      "require": "./index.cjs"
    },
    "default": "./index.js"
  }
}

Большинство инструментов получают версию ESM. Node.js является исключением. Он получает версию CommonJs при использовании require(). Это приведет к двум экземплярам этого пакета при ссылке на него с require() и import, но это не повредит, поскольку пакет не имеет состояния.

Условие module используется в качестве оптимизации при предварительной обработке кода, предназначенного для Node.js, с помощью инструмента, который поддерживает ESM для require() (например, при связывании для Node.js). Для такого инструмента исключение пропущено. Это технически необязательно, но инструменты связывания в противном случае включили бы исходный код пакета дважды.

Вы также можете использовать бессостоятельный шаблон, если можете изолировать состояние своего пакета в JSON-файлах. JSON потребляемый как CommonJs, так и ESM без загрязнения графа другой системой модулей.

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

Предоставление версий CommonJs и ESM (состоятельный)

{
  "type": "module",
  "exports": {
    "node": {
      "module": "./index.js",
      "import": "./wrapper.js",
      "require": "./index.cjs"
    },
    "default": "./index.js"
  }
}
// wrapper.js
import cjs from './index.cjs';

export const A = cjs.A;
export const B = cjs.B;

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

Это не проблема для большинства инструментов, но Node.js снова является исключением. Для Node.js мы всегда используем версию CommonJs и предоставляем именованные экспорты в ESM с оболочкой ESM.

Мы снова используем условие module в качестве оптимизации.

Предоставление только версии CommonJs

{
  "type": "commonjs",
  "exports": "./index.js"
}

Предоставление "type": "commonjs" помогает статически определить файлы CommonJs.

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

{
  "type": "module",
  "exports": {
    "script": "./dist-bundle.js",
    "default": "./index.js"
  }
}

Обратите внимание, что несмотря на использование "type": "module" и .js для dist-bundle.js этот файл не в формате ESM. Он должен использовать глобальные переменные, чтобы разрешить прямое использование как тег script.

Предоставление инструментов разработки или оптимизаций для производства

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

Без обнаружения среды выполнения Node.js

{
  "type": "module",
  "exports": {
    "development": "./index-with-devtools.js",
    "default": "./index-optimized.js"
  }
}

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

С обнаружением среды выполнения Node.js

{
  "type": "module",
  "exports": {
    "development": "./index-with-devtools.js",
    "production": "./index-optimized.js",
    "node": "./wrapper-process-env.cjs",
    "default": "./index-optimized.js"
  }
}
// wrapper-process-env.cjs
if (process.env.NODE_ENV !== 'development') {
  module.exports = require('./index-optimized.cjs');
} else {
  module.exports = require('./index-with-devtools.cjs');
}

Мы отдаем предпочтение статическому обнаружению режима разработки/производства с помощью условия production или development.

Node.js позволяет обнаружить режим разработки/производства во время выполнения с помощью process.env.NODE_ENV, поэтому мы используем его в качестве резервного варианта в Node.js. Синхронный условный импорт ESM невозможен, и мы не хотим загружать пакет дважды, поэтому для обнаружения режима во время выполнения мы должны использовать CommonJs.

Если невозможно определить режим, мы возвращаемся к производственной версии.

Предоставление различных версий в зависимости от целевой среды

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

Предоставление версий Node.js, WebWorker и браузера

{
  "type": "module",
  "exports": {
    "node": "./index-node.js",
    "worker": "./index-worker.js",
    "default": "./index.js"
  }
}

Предоставление версий Node.js, браузера и electron

{
  "type": "module",
  "exports": {
    "electron": {
      "node": "./index-electron-node.js",
      "default": "./index-electron.js"
    },
    "node": "./index-node.js",
    "default": "./index.js"
  }
}

Комбинирование шаблонов

Пример 1

Это пример пакета, который имеет оптимизации для использования в производственной и разработке с обнаружением среды выполнения для process.env и также содержит версии CommonJs и ESM.

{
  "type": "module",
  "exports": {
    "node": {
      "development": {
        "module": "./index-with-devtools.js",
        "import": "./wrapper-with-devtools.js",
        "require": "./index-with-devtools.cjs"
      },
      "production": {
        "module": "./index-optimized.js",
        "import": "./wrapper-optimized.js",
        "require": "./index-optimized.cjs"
      },
      "default": "./wrapper-process-env.cjs"
    },
    "development": "./index-with-devtools.js",
    "production": "./index-optimized.js",
    "default": "./index-optimized.js"
  }
}

Пример 2

Это пример пакета, который поддерживает Node.js, браузер и electron, имеет оптимизации для использования в производственной и разработке с обнаружением среды выполнения для process.env и также содержит версии CommonJs и ESM.

{
  "type": "module",
  "exports": {
    "electron": {
      "node": {
        "development": {
          "module": "./index-electron-node-with-devtools.js",
          "import": "./wrapper-electron-node-with-devtools.js",
          "require": "./index-electron-node-with-devtools.cjs"
        },
        "production": {
          "module": "./index-electron-node-optimized.js",
          "import": "./wrapper-electron-node-optimized.js",
          "require": "./index-electron-node-optimized.cjs"
        },
        "default": "./wrapper-electron-node-process-env.cjs"
      },
      "development": "./index-electron-with-devtools.js",
      "production": "./index-electron-optimized.js",
      "default": "./index-electron-optimized.js"
    },
    "node": {
      "development": {
        "module": "./index-node-with-devtools.js",
        "import": "./wrapper-node-with-devtools.js",
        "require": "./index-node-with-devtools.cjs"
      },
      "production": {
        "module": "./index-node-optimized.js",
        "import": "./wrapper-node-optimized.js",
        "require": "./index-node-optimized.cjs"
      },
      "default": "./wrapper-node-process-env.cjs"
    },
    "development": "./index-with-devtools.js",
    "production": "./index-optimized.js",
    "default": "./index-optimized.js"
  }
}

Выглядит сложно, да. Мы уже смогли уменьшить сложность благодаря предположению: Только node требуется версия CommonJs и может определить производство/разработку с помощью process.env.

Рекомендации

  • Избегайте экспорта default. Он обрабатывается по-разному различными инструментами. Используйте только именованные экспорты.
  • Никогда не предоставляйте разные API или семантику для разных условий.
  • Пишите исходный код как ESM и транспонируйте в CJS с помощью babel, typescript или подобных инструментов.
  • Используйте .cjs или type: "commonjs" в package.json, чтобы четко обозначить исходный код как CommonJs. Это позволяет статически определять для инструментов, используется CommonJs или ESM. Это важно для инструментов, которые поддерживают только ESM и не поддерживают CommonJs.
  • ESM, используемый в пакетах, поддерживает следующие типы запросов:
    • Поддерживаются запросы модулей, указывающие на другие пакеты с package.json.
    • Поддерживаются относительные запросы, указывающие на другие файлы внутри пакета.
      • Они не должны указывать на файлы за пределами пакета.
    • data: Поддерживаются запросы URL.
    • Другие абсолютные или относительные к серверу запросы по умолчанию не поддерживаются, но они могут поддерживаться некоторыми инструментами или средами.

© JS Foundation and other contributors
Licensed under the Creative Commons Attribution License 4.0.
https://webpack.js.org/guides/package-exports

Spec-Zone.ru

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