صادرات الحزمة

يسمح حقل 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.

على سبيل المثال، عند وجود الإعداد التالي:

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

سيرمي Webpack 5.94.0+ الآن خطأ لأن non-existent.js غير موجود، بينما كان السلوك السابق سيحل الطلب إلى existent.js.

الصيغة الشرطية

بدلًا من تقديم النتائج مباشرة داخل حقل exports، يمكن لمؤلف الحزمة أن يترك نظام الوحدات يختار نتيجة بناءً على شروط تخص البيئة.

في هذه الحالة يجب استخدام كائن يربط الشروط بالنتائج. تُجرَّب الشروط حسب ترتيبها داخل الكائن. يتم تخطي الشروط التي تحتوي على نتائج غير صالحة. يمكن تداخل الشروط لإنشاء AND منطقي. وقد يكون الشرط الأخير في الكائن هو الشرط الخاص "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يصدر الطلب من وسم 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="...">

script

الصيغ التالية ستعيّن شرط script:

  • 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 وقت نشر الحزمة. يجب أن تُعالَج polyfilling أو transpiling من جهة المستهلك.
    • يجب استخدام الميزات التي لا يمكن توفير polyfill أو transpile لها بحذر، لأنها تحد من إمكانات الاستخدام.
  • deno: TBD
  • react-native: TBD

الشروط: المعالجات المسبقة وبيئات التشغيل

تُعيَّن الشروط التالية بحسب الأداة التي تعالج الكود المصدري مسبقًا.

الشرطالوصفمدعوم بواسطة
webpackتمت معالجته بواسطة webpack.webpack

للأسف لا يوجد شرط node-js لـ Node.js كبيئة تشغيل. كان ذلك سيُسهّل إنشاء استثناءات لـ Node.js.

الشروط: المخصصة

تدعم الأدوات التالية الشروط المخصصة:

الأداةمدعومملاحظات
Node.jsنعماستخدم وسيطة CLI --conditions.
webpackنعماستخدم خيار الإعداد resolve.conditionNames.
rollupنعماستخدم خيار exportConditions في @rollup/plugin-node-resolve
esinstallلا-
wmrلا-

يوصى باتباع نمط التسمية التالي للشروط المخصصة:

<company-name>:<condition-name>

أمثلة: example-corp:beta، google:internal.

الأنماط الشائعة

تُشرح كل الأنماط باستخدام إدخال واحد "." داخل الحزمة، لكن يمكن توسيعها لتشمل عدة إدخالات أيضًا بتكرار النمط لكل إدخال.

يجب استخدام هذه الأنماط كدليل إرشادي لا كقواعد صارمة. يمكن تكييفها مع الحزم الفردية.

تعتمد هذه الأنماط على قائمة الأهداف/الافتراضات التالية:

  • الحزم تتقادم.
    • نفترض أن بعض الحزم ستتوقف في مرحلة ما عن الصيانة، لكنها ستستمر قيد الاستخدام.
    • يجب كتابة exports بحيث تستخدم fallbacks للحالات المستقبلية غير المعروفة. يمكن استخدام شرط default لذلك.
    • بما أن المستقبل غير معروف، نفترض بيئة مشابهة للمتصفحات ونظام وحدات مشابهًا لـ ESM.
  • ليست كل الشروط مدعومة من كل أداة.
    • يجب استخدام fallbacks لمعالجة هذه الحالات.
    • نفترض أن الـ fallback التالي منطقي عمومًا:
      • ESM > CommonJs
      • Production > Development
      • Browser > node.js

بحسب غرض الحزمة قد يكون خيار آخر أكثر منطقية، وعندها يجب تعديل الأنماط بما يناسب ذلك. مثال: بالنسبة إلى أداة سطر أوامر، لا يكون افتراض مستقبل مشابه للمتصفح وfallback شبيه به منطقيًا كثيرًا، وفي هذه الحالة يجب استخدام بيئات وfallbacks شبيهة بـ 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 بواسطة أداة تدعم ESM مع require() (مثل bundler عند التجميع لـ 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

module.exports =
  process.env.NODE_ENV !== "development"
    ? require("./index-optimized.cjs")
    : require("./index-with-devtools.cjs");

نفضّل الاكتشاف الساكن لوضع الإنتاج/التطوير عبر شرط production أو development.

يسمح Node.js باكتشاف وضع الإنتاج/التطوير وقت التشغيل عبر process.env.NODE_ENV، لذلك نستخدم ذلك كـ fallback في Node.js. لا يمكن استيراد ESM شرطيًا بشكل متزامن، ولا نريد تحميل الحزمة مرتين، لذلك علينا استخدام CommonJs لاكتشاف وقت التشغيل.

عندما لا يكون اكتشاف الوضع ممكنًا، نرجع إلى إصدار الإنتاج.

توفير إصدارات مختلفة بحسب البيئة المستهدفة

يجب اختيار بيئة fallback تكون منطقية للحزمة لدعم البيئات المستقبلية. عمومًا يجب افتراض بيئة شبيهة بالمتصفح.

توفير إصدارات 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.
    • الطلبات النسبية مدعومة، وتشير إلى ملفات أخرى داخل الحزمة.
      • يجب ألا تشير إلى ملفات خارج الحزمة.
    • طلبات URL من نوع data: مدعومة.
    • الطلبات المطلقة الأخرى أو النسبية إلى الخادم غير مدعومة افتراضيًا، لكنها قد تكون مدعومة في بعض الأدوات أو البيئات.
Edit this page·

1 Contributor

arabpolice