دورة المقدمة السريعة لـ Node.js: أدخل لتتعلم
قبل عامين كتبت مقالًا تعريفيًا بنظام الوحدة: فهم مفهوم الوحدات الأمامية: CommonJs وES6Module. المعرفة الواردة في هذه المقالة موجهة للمبتدئين وهي بسيطة نسبيًا. وهنا نقوم أيضًا بتصحيح بعض الأخطاء في المقال:
[الوحدة] و [نظام الوحدة] شيئان مختلفان. الوحدة هي وحدة في البرنامج، ونظام الوحدة عبارة عن مجموعة من بناء الجملة أو الأدوات. يسمح نظام الوحدة للمطورين بتحديد الوحدات النمطية واستخدامها في المشاريع.
اختصار وحدة ECMAScript هو ESM أو ESModule، وليس ES6Module.
تمت تغطية المعرفة الأساسية حول نظام الوحدة تقريبًا في المقالة السابقة، لذا ستركز هذه المقالة على المبادئ الداخلية لنظام الوحدة ومقدمة أكثر اكتمالاً للاختلافات بين أنظمة الوحدات المختلفة. محتوى المقالة السابقة موجود في هذا لن تتكرر مرة أخرى.
لا تحتوي جميع لغات البرمجة على نظام وحدات مدمج، ولم يكن لدى جافا سكريبت نظام وحدات لفترة طويلة بعد ولادتها.
في بيئة المتصفح، يمكنك فقط استخدام علامة لتقديم ملفات التعليمات البرمجية غير المستخدمة. تشترك هذه الطريقة في نطاق عالمي، ويمكن القول إنها مليئة بالمشكلات إلى جانب التطور السريع للواجهة الأمامية يعد يلبي الاحتياجات الحالية. قبل ظهور نظام الوحدات الرسمي، أنشأ مجتمع الواجهة الأمامية نظام الوحدات النمطية الخاص به التابع لجهة خارجية، والأكثر استخدامًا هو: تعريف الوحدة غير المتزامن AMD ، وتعريف الوحدة الشامل UMD ، وما إلى ذلك. بالطبع، أشهرها هو CommonJS .
نظرًا لأن Node.js هي بيئة تشغيل JavaScript، فيمكنها الوصول مباشرة إلى نظام الملفات الأساسي. لذلك اعتمده المطورون ونفذوا نظامًا نمطيًا وفقًا لمواصفات CommonJS.
في البداية، كان من الممكن استخدام CommonJS فقط على منصة Node.js. ومع ظهور أدوات تعبئة الوحدات مثل Browserify وWebpack، أصبح من الممكن تشغيل CommonJS أخيرًا على جانب المتصفح.
لم يكن هناك معيار رسمي لنظام الوحدة إلا بعد إصدار مواصفات ECMAScript6 في عام 2015، وكان يُطلق على نظام الوحدات المبني وفقًا لهذا المعيار اسم وحدة ECMAScript (ESM) للاختصار. ومنذ ذلك الحين، بدأت ESM في التوحيد بيئة Node.js وبيئة المتصفح. بالطبع، يوفر ECMAScript6 فقط بناء الجملة والدلالات. أما بالنسبة للتنفيذ، فالأمر متروك لموردي خدمات المتصفح ومطوري Node للعمل بجد. ولهذا السبب لدينا أداة بابل التي تحسدها لغات البرمجة الأخرى. إن تنفيذ نظام الوحدات ليس بالمهمة السهلة، حيث يتمتع Node.js بدعم مستقر نسبيًا لـ ESM في الإصدار 13.2.
ولكن مهما كان الأمر، فإن ESM هو "ابن" جافا سكريبت، ولا حرج في تعلمها!
في عصر زراعة القطع والحرق، تم استخدام جافا سكريبت لتطوير التطبيقات، ولا يمكن تقديم ملفات البرامج النصية إلا من خلال علامات البرامج النصية. إحدى المشاكل الأكثر خطورة هي عدم وجود آلية مساحة الاسم، مما يعني أن كل نص برمجي يشترك في نفس النطاق. هناك حل أفضل لهذه المشكلة في المجتمع: وحدة إحياء
const myModule = (() => { const _privateFn = () => {} const_privateAttr = 1 يعود { publicFn: () => {}، الاهتمام العام: 2 } })() console.log(myModule) console.log(myModule.publicFn, myModule._privateFn)
نتائج التشغيل هي كما يلي:
هذا النمط بسيط للغاية، استخدم IIFE لإنشاء نطاق خاص، واستخدم متغيرات الإرجاع ليتم كشفها. لا يمكن الوصول إلى المتغيرات الداخلية (مثل _privateFn، _privateAttr) من النطاق الخارجي.
تستفيد [وحدة الكشف] من هذه الميزات لإخفاء المعلومات الخاصة وتصدير واجهات برمجة التطبيقات التي يجب أن تكون مكشوفة للعالم الخارجي. تم أيضًا تطوير نظام الوحدات اللاحق بناءً على هذه الفكرة.
بناءً على الأفكار المذكورة أعلاه، قم بتطوير محمل الوحدة النمطية.
اكتب أولاً دالة تقوم بتحميل محتوى الوحدة، وقم بتغليف هذه الوظيفة في نطاق خاص، ثم قم بتقييمها من خلال eval() لتشغيل الوظيفة:
وظيفة تحميل الوحدة (اسم الملف، الوحدة النمطية، تتطلب) { ثابت ملفوفSrc = `(الوظيفة (الوحدة النمطية، الصادرات، تتطلب) { ${fs.readFileSync(filename, 'utf8)} }(الوحدة النمطية، الوحدة النمطية، الصادرات، تتطلب)` تقييم (ملفوفةSrc) }
مثل [كشف الوحدة]، يتم تغليف الكود المصدري للوحدة في دالة، والفرق هو أن سلسلة من المتغيرات (module، وmodule.exports، وrequire) يتم تمريرها أيضًا إلى الوظيفة.
تجدر الإشارة إلى أنه تتم قراءة محتوى الوحدة من خلال [readFileSync]. بشكل عام، يجب ألا تستخدم الإصدار المتزامن عند استدعاء واجهات برمجة التطبيقات التي تتضمن نظام الملفات. لكن هذه المرة مختلفة، لأن تحميل الوحدات من خلال نظام CommonJs نفسه يجب أن يتم تنفيذه كعملية متزامنة لضمان إمكانية إدخال وحدات متعددة بترتيب التبعية الصحيح.
ثم قم بمحاكاة الدالة require()، والتي تتمثل وظيفتها الرئيسية في تحميل الوحدة.
تتطلب الوظيفة (اسم الوحدة) { معرف const = require.resolve(moduleName) إذا (require.cache[id]) { إرجاع require.cache[id].exports } // البيانات التعريفية للوحدة const Module = { الصادرات: {}، بطاقة تعريف } // تحديث ذاكرة التخزين المؤقت require.cache[id] = Module // تحميل الوحدة النمطيةloadModule(المعرف، الوحدة، يتطلب) // إرجاع المتغيرات المصدرة return Module.exports } تتطلب ذاكرة التخزين المؤقت = {} require.resolve = (moduleName) => { // تحليل معرف الوحدة الكاملة بناءً على اسم الوحدة النمطية }
(1) بعد أن تتلقى الوظيفة اسم الوحدة، تقوم أولاً بتحليل المسار الكامل للوحدة وتعيينه للمعرف.
(2) إذا كانت cache[id]
صحيحة، فهذا يعني أنه تم تحميل الوحدة، وسيتم إرجاع نتيجة ذاكرة التخزين المؤقت مباشرة (3) وإلا سيتم تكوين بيئة للتحميل الأول. على وجه التحديد، قم بإنشاء كائن وحدة نمطية، بما في ذلك الصادرات (أي المحتوى المصدر) والمعرف (الوظيفة كما هو مذكور أعلاه)
(4) قم بتخزين الوحدة التي تم تحميلها لأول مرة (5) اقرأ الكود المصدري من الملف المصدر للوحدة من خلال LoadModule (6) أخيرًا، تُرجع return module.exports
المحتوى الذي تريد تصديره.
عند محاكاة الدالة المطلوبة، هناك تفصيل مهم جدًا: يجب أن تكون الدالة المطلوبة متزامنة . وظيفتها هي فقط إرجاع محتوى الوحدة مباشرةً، ولا تستخدم آلية رد الاتصال. وينطبق الشيء نفسه على الطلب في Node.js. لذلك، يجب أن تكون عملية التعيين لـ Module.exports متزامنة أيضًا، وإذا تم استخدام غير متزامن، فستحدث مشكلات:
// حدث خطأ ما setTimeout(() => { Module.exports = الدالة () {} }، 1000)
حقيقة أن الطلب يتطلب وظيفة متزامنة له تأثير مهم جدًا على طريقة تعريف الوحدات، لأنه يجبرنا على استخدام التعليمات البرمجية المتزامنة فقط عند تعريف الوحدات، بحيث يوفر Node.js إصدارات متزامنة من معظم واجهات برمجة التطبيقات غير المتزامنة لهذا الغرض.
كان لدى Node.js المبكر إصدار غير متزامن من الوظيفة المطلوبة، ولكن تمت إزالته بسرعة لأنه سيجعل الوظيفة معقدة للغاية.
يعد ESM جزءًا من مواصفات ECMAScript2015، التي تحدد نظام الوحدة الرسمي للغة JavaScript للتكيف مع بيئات التنفيذ المختلفة.
افتراضيًا، يتعامل Node.js مع الملفات ذات اللاحقة .js على أنها مكتوبة باستخدام بناء جملة CommonJS. إذا كنت تستخدم بناء جملة ESM مباشرة في ملف .js، فسيقوم المترجم بالإبلاغ عن خطأ.
هناك ثلاث طرق لتحويل مترجم Node.js إلى صيغة ESM:
1. قم بتغيير امتداد الملف إلى .mjs؛
2. أضف حقل نوع إلى أحدث ملف package.json بالقيمة "module"؛
3. يتم تمرير السلسلة إلى --eval
كمعلمة، أو يتم نقلها إلى العقدة عبر أنبوب STDIN مع العلامة --input-type=module
على سبيل المثال:
العقدة --input-type=module --eval "import { sep } from 'node:path'; console.log(سبتمبر);"
يمكن تحليل ESM وتخزينه مؤقتًا كعنوان URL (مما يعني أيضًا أنه يجب ترميز الأحرف الخاصة بنسبة مئوية). يدعم بروتوكولات URL مثل file:
node:
data:
ملف:URL
يتم تحميل الوحدة عدة مرات إذا كان محدد الاستيراد المستخدم لحل الوحدة يحتوي على استعلامات أو أجزاء مختلفة
// تعتبر وحدتين مختلفتين import './foo.mjs?query=1'; استيراد './foo.mjs?query=2';
البيانات: URL
يدعم الاستيراد باستخدام أنواع MIME:
text/javascript
لوحدات ES
application/json
لـ JSON
application/wasm
لـ Wasm
import 'data:text/javascript,console.log("hello!");'; import _ from 'data:application/json,"world!"' تأكيد { النوع: 'json' };
data:URL
بتحليل المحددات المجردة والمطلقة للوحدات المضمنة فقط. تحليل المحددات النسبية لا يعمل لأن data:
ليس بروتوكولًا خاصًا وليس له مفهوم التحليل النسبي.
تأكيد الاستيراد
تضيف هذه السمة بناء الجملة المضمن إلى بيان استيراد الوحدة النمطية لتمرير المزيد من المعلومات بجوار محدد الوحدة النمطية.
استيراد بيانات fooData من './foo.json' تأكيد { النوع: 'json' }؛ const { default: barData } = انتظار الاستيراد('./bar.json', { تأكيد: { type: 'json' } });
في الوقت الحالي، لا يتم دعم سوى وحدة JSON، كما أن صياغة assert { type: 'json' }
إلزامية.
استيراد وحدات الغسيل
يتم دعم استيراد وحدات WebAssembly تحت علامة --experimental-wasm-modules
، مما يسمح باستيراد أي ملف .wasm كوحدة عادية، مع دعم استيراد وحداتها أيضًا.
//index.mjs استيراد * كـ M من './module.wasm'؛ console.log(م)
استخدم الأمر التالي للتنفيذ:
العقدة - وحدات-wasm التجريبية-index.mjs
يمكن استخدام الكلمة الأساسية "انتظار" في المستوى الأعلى في ESM.
// a.mjs تصدير const خمسة = انتظار Promise.resolve(5) // ب.mjs استيراد {خمسة} من "./a.mjs" console.log(خمسة) // 5
كما ذكرنا سابقًا، فإن دقة بيان الاستيراد لتبعيات الوحدة ثابتة، لذا فهي تحتوي على قيدين مشهورين:
لا يمكن لمعرفات الوحدة النمطية الانتظار حتى وقت التشغيل لإنشائها؛
يجب كتابة عبارات استيراد الوحدة النمطية في أعلى الملف ولا يمكن دمجها في عبارات التحكم في التدفق؛
ومع ذلك، في بعض الحالات، يكون هذان القيدان صارمين للغاية بلا شك. على سبيل المثال، هناك متطلب شائع نسبيًا: التحميل البطيء :
عند مواجهة وحدة نمطية كبيرة، فأنت تريد فقط تحميل هذه الوحدة الضخمة عندما تحتاج حقًا إلى استخدام وظيفة معينة في الوحدة.
ولهذا الغرض، توفر الإدارة السليمة بيئياً آلية إدخال غير متزامنة. يمكن تحقيق عملية التقديم هذه من خلال عامل import()
عند تشغيل البرنامج. من وجهة نظر نحوية، فهي تعادل وظيفة تتلقى معرف الوحدة النمطية كمعلمة وترجع وعدًا بعد حل الوعد، ويمكن الحصول على كائن الوحدة النمطية الذي تم تحليله.
استخدم مثالاً على التبعية الدائرية لتوضيح عملية تحميل ESM:
//index.js استيراد * كـ foo من './foo.js'؛ استيراد * كشريط من "./bar.js"؛ console.log(foo); console.log(bar); // foo.js استيراد * كشريط من "./bar.js" تصدير السماح محملة = خطأ؛ شريط ثابت التصدير = شريط؛ محملة = صحيح؛ //bar.js استيراد * كـ Foo من './foo.js'؛ تصدير السماح محملة = خطأ؛ تصدير const foo = Foo; محملة = صحيح
دعونا نلقي نظرة على نتائج التشغيل أولاً:
يمكن ملاحظة أنه من خلال التحميل، يمكن لكل من الوحدتين foo وbar تسجيل معلومات الوحدة الكاملة المحملة. لكن الأمر مختلف في CommonJS. يجب أن تكون هناك وحدة نمطية لا يمكنها طباعة شكلها بعد تحميلها بالكامل.
دعنا نتعمق في عملية التحميل لنرى سبب حدوث هذه النتيجة.
يمكن تقسيم عملية التحميل إلى ثلاث مراحل:
المرحلة الأولى: التحليل
المرحلة الثانية: التصريح
المرحلة الثالثة: التنفيذ
مرحلة التحليل:
يبدأ المترجم من ملف الإدخال (أي، Index.js)، ويحلل التبعيات بين الوحدات، ويعرضها في شكل رسم بياني. ويسمى هذا الرسم البياني أيضًا برسم التبعية.
في هذه المرحلة، نركز فقط على بيانات الاستيراد ونقوم بتحميل كود المصدر المطابق للوحدات النمطية التي تريد هذه البيانات تقديمها. والحصول على الرسم البياني النهائي للتبعية من خلال التحليل المتعمق. خذ المثال أعلاه للتوضيح:
1. بدءًا من ملف Index.js، ابحث عن عبارة import * as foo from './foo.js'
وانتقل إلى ملف foo.js.
2. تابع التحليل من ملف foo.js وابحث عن import * as Bar from './bar.js'
، وبالتالي انتقل إلى bar.js.
3. تابع التحليل من bar.js وابحث عن import * as Foo from './foo.js'
، والتي تشكل تبعية دائرية، ومع ذلك، نظرًا لأن المترجم يقوم بالفعل بمعالجة وحدة foo.js، فلن يدخل إليها مرة أخرى، ومن ثم تابع.
4. بعد تحليل وحدة الشريط، وجد أنه لا يوجد بيان استيراد، لذلك يعود إلى foo.js ويستمر في التحليل. لم يتم العثور على بيان الاستيراد مرة أخرى، وتم إرجاع ملف Index.js.
5. تم العثور import * as bar from './bar.js'
في ملف Index.js، ولكن بما أن bar.js قد تم تحليله بالفعل، فسيتم تخطيه ويستمر التنفيذ.
وأخيرًا، يتم عرض الرسم البياني للتبعية بالكامل من خلال نهج العمق الأول:
مرحلة الإعلان:
يبدأ المترجم من الرسم البياني للتبعية الذي تم الحصول عليه ويعلن عن كل وحدة بالترتيب من الأسفل إلى الأعلى. على وجه التحديد، في كل مرة يتم فيها الوصول إلى وحدة نمطية، يتم البحث عن جميع الخصائص التي سيتم تصديرها بواسطة الوحدة ويتم الإعلان عن معرفات القيم المصدرة في الذاكرة. يرجى ملاحظة أنه يتم إصدار الإعلانات فقط في هذه المرحلة ولا يتم تنفيذ أي عمليات تخصيص.
1. يبدأ المترجم من الوحدة bar.js ويعلن عن معرفات Load وfoo.
2. قم بالتتبع احتياطيًا إلى وحدة foo.js وأعلن عن المعرفات المحملة والشريطية.
3. وصلنا إلى وحدة Index.js، لكن هذه الوحدة لا تحتوي على بيان تصدير، لذلك لم يتم الإعلان عن أي معرف.
بعد الإعلان عن جميع معرفات التصدير، انتقل عبر الرسم البياني للتبعية مرة أخرى لربط العلاقة بين الاستيراد والتصدير.
يمكن ملاحظة أنه تم إنشاء علاقة ربط مشابهة لـ const بين الوحدة المقدمة عن طريق الاستيراد والقيمة المصدرة عن طريق التصدير. يمكن لجانب الاستيراد القراءة فقط وليس الكتابة. علاوة على ذلك، فإن وحدة الشريط المقروءة في ملف Index.js ووحدة الشريط المقروءة في foo.js هما في الأساس نفس المثيل.
ولهذا السبب يتم إخراج نتائج التحليل الكاملة في نتائج هذا المثال.
وهذا يختلف جوهريًا عن النهج الذي يستخدمه نظام CommonJS. إذا قامت وحدة باستيراد وحدة CommonJS، فسيقوم النظام بنسخ كائن التصدير بالكامل للأخيرة ونسخ محتوياتها إلى الوحدة الحالية. في هذه الحالة، إذا قامت الوحدة المستوردة بتعديل متغير النسخ الخاص بها، فلن يتمكن المستخدم من رؤية القيمة الجديدة .
مرحلة التنفيذ:
في هذه المرحلة، سيقوم المحرك بتنفيذ كود الوحدة. لا يزال يتم الوصول إلى الرسم البياني للتبعية بترتيب من الأسفل إلى الأعلى ويتم تنفيذ الملفات التي تم الوصول إليها واحدًا تلو الآخر. يبدأ التنفيذ من الملف bar.js، إلى foo.js، وأخيرًا إلى ملف Index.js. في هذه العملية، يتم تحسين قيمة المعرف في جدول التصدير تدريجيًا.
لا يبدو أن هذه العملية تختلف كثيرًا عن CommonJS، ولكن هناك في الواقع اختلافات كبيرة. نظرًا لأن CommonJS ديناميكي، فإنه يقوم بتحليل الرسم البياني للتبعية أثناء تنفيذ الملفات ذات الصلة. لذا، طالما أنك ترى بيان الطلب، يمكنك التأكد من أنه عندما يصل البرنامج إلى هذا البيان، فقد تم تنفيذ جميع الرموز السابقة. لذلك، ليس من الضروري أن تظهر عبارة الطلب في بداية الملف، ولكن يمكن أن تظهر في أي مكان، ويمكن أيضًا إنشاء معرفات الوحدة من المتغيرات.
لكن ESM مختلف، حيث يتم فصل المراحل الثلاث المذكورة أعلاه عن بعضها البعض، ويجب أولاً إنشاء الرسم البياني للتبعية بالكامل قبل أن تتمكن من تنفيذ التعليمات البرمجية. لذلك، يجب أن تكون عمليات إدخال الوحدات وتصديرها ثابتة. لا تنتظر حتى يتم تنفيذ التعليمات البرمجية.
بالإضافة إلى الاختلافات العديدة التي ذكرناها سابقًا، هناك بعض الاختلافات الجديرة بالملاحظة:
عند استخدام الكلمة الأساسية للاستيراد في ESM لحل المحددات النسبية أو المطلقة، يجب توفير امتداد الملف ويجب تحديد فهرس الدليل ('./path/index.js') بالكامل. تسمح وظيفة CommonJS المطلوبة بحذف هذا الامتداد.
يتم تشغيل ESM في الوضع الصارم بشكل افتراضي، ولا يمكن تعطيل هذا الوضع الصارم. لذلك، لا يمكنك استخدام متغيرات غير معلنة، ولا يمكنك استخدام الميزات المتوفرة فقط في الوضع غير المقيد (مثل مع).
يوفر CommonJS بعض المتغيرات العامة، ولا يمكن استخدام هذه المتغيرات ضمن ESM. إذا حاولت استخدام هذه المتغيرات، فسيحدث خطأ مرجعي. يشمل
require
exports
module.exports
__filename
__dirname
من بينها، يشير __filename
إلى المسار المطلق لملف الوحدة النمطية الحالي، و__ __dirname
هو المسار المطلق للمجلد الذي يوجد به الملف. يعد هذان المتغيران مفيدًا جدًا عند إنشاء المسار النسبي للملف الحالي، لذلك توفر ESM بعض الطرق لتنفيذ وظائف المتغيرين.
في ESM، يمكنك استخدام كائن import.meta
للحصول على مرجع يشير إلى عنوان URL للملف الحالي. على وجه التحديد، يتم الحصول على مسار الملف للوحدة الحالية من خلال import.meta.url
تنسيق هذا المسار مشابه file:///path/to/current_module.js
. بناءً على هذا المسار، يتم إنشاء المسار المطلق المعبر عنه بواسطة __filename
و __dirname
:
استيراد { fileURLToPath } من "url" استيراد {dirname} من "المسار" const __filename = fileURLToPath(import.meta.url) const __dirname = dirname(__filename)
يمكنه أيضًا محاكاة الدالة require() في CommonJS
استيراد { createRequire } من "الوحدة النمطية" تتطلب const = createRequire(import.meta.url)
في النطاق العالمي ESM، هذا غير محدد، ولكن في نظام وحدة CommonJS، فهو مرجع للصادرات:
//ESM console.log(هذا) // غير محدد // CommonJS console.log (هذا === الصادرات) // صحيح
كما ذكر أعلاه، يمكن محاكاة وظيفة CommonJS require() في ESM لتحميل وحدات CommonJS. بالإضافة إلى ذلك، يمكنك أيضًا استخدام صيغة الاستيراد القياسية لتقديم وحدات CommonJS، لكن طريقة الاستيراد هذه يمكنها فقط استيراد الأشياء المصدرة افتراضيًا:
استيراد الحزمة الرئيسية من 'commonjs-package' // من الممكن تمامًا استيراد { الطريقة } من 'commonjs-package' // خطأ
تتطلب وحدة CommonJS التعامل دائمًا مع الملفات التي تشير إليها على أنها CommonJS. تحميل وحدات ES باستخدام الطلب غير مدعوم لأن وحدات ES لها تنفيذ غير متزامن. ولكن يمكنك استخدام import()
لتحميل وحدات ES من وحدات CommonJS.
على الرغم من إطلاق ESM لمدة 7 سنوات، إلا أن Node.js دعمته أيضًا بشكل ثابت. عندما نقوم بتطوير مكتبات المكونات، يمكننا فقط دعم ESM. ولكن لكي تكون متوافقة مع المشاريع القديمة، يعد دعم CommonJS ضروريًا أيضًا. هناك طريقتان مستخدمتان على نطاق واسع لجعل مكتبة المكونات تدعم التصدير من كلا نظامي الوحدات.
اكتب الحزم في CommonJS أو قم بتحويل الكود المصدري لوحدة ES إلى CommonJS وقم بإنشاء ملفات مجمعة لوحدة ES التي تحدد عمليات التصدير المسماة. استخدم التصدير المشروط، والاستيراد يستخدم غلاف الوحدة النمطية ES، والمطلوب يستخدم نقطة إدخال CommonJS. على سبيل المثال، في وحدة المثال
// package.json { "النوع": "الوحدة النمطية"، "الصادرات": { "استيراد": "./wrapper.mjs"، "تتطلب": "./index.cjs" } }
استخدم امتدادات العرض .cjs
و .mjs
، لأن استخدام .js
فقط سيؤدي إما إلى CommonJS افتراضيًا، أو "type": "module"
سوف يتسبب في التعامل مع هذه الملفات كوحدات ES.
// ./index.cjs Export.name = 'name'; // ./wrapper.mjs استيراد cjsModule من "./index.cjs" اسم ثابت التصدير = cjsModule.name؛
في هذا المثال:
// استخدم ESM لتقديم الاستيراد { name } من "المثال" // استخدم CommonJS لتقديم const { name } = require('example')
الاسم المقدم في كلا الاتجاهين هو نفس المفردة.
يمكن لملف package.json تحديد نقاط إدخال منفصلة لوحدة CommonJS وES مباشرةً:
// package.json { "النوع": "الوحدة النمطية"، "الصادرات": { "استيراد": "./index.mjs"، "تتطلب": "./index.cjs" } }
يمكن القيام بذلك إذا كانت إصدارات الحزمة CommonJS وESM متكافئة، على سبيل المثال لأن أحدهما عبارة عن مخرجات منقولة من الآخر ولأن إدارة حالة الحزمة معزولة بعناية (أو تكون الحزمة عديمة الحالة)
سبب المشكلة هو أنه يمكن استخدام إصداري CommonJS وESM للحزمة في التطبيق؛ على سبيل المثال، يمكن للكود المرجعي للمستخدم استيراد إصدار ESM، بينما تتطلب التبعية إصدار CommonJS. إذا حدث هذا، فسيتم تحميل نسختين من الحزمة في الذاكرة، وبالتالي ستحدث حالتان مختلفتان. يمكن أن يؤدي هذا إلى أخطاء يصعب حلها.
بالإضافة إلى كتابة الحزم عديمة الحالة (على سبيل المثال، إذا كانت JavaScript حزمة، فستكون عديمة الحالة لأن جميع أساليبها ثابتة)، هناك طرق لعزل الحالة بحيث يمكن استخدامها في CommonJS وESM التي يحتمل تحميلها ومشاركتها بين حالات الحزمة:
إذا كان ذلك ممكنًا، قم بتضمين كافة الحالات في الكائن الذي تم إنشاء مثيل له. على سبيل المثال، يحتاج تاريخ JavaScript إلى إنشاء مثيل له ليحتوي على حالة؛ إذا كان عبارة عن حزمة، فسيتم استخدامه على النحو التالي:
تاريخ الاستيراد من "التاريخ"؛ const someDate = new Date(); // someDate يحتوي على الحالة؛ التاريخ لا يحتوي عليه
الكلمة الأساسية الجديدة غير مطلوبة؛ يمكن لوظائف الحزمة إرجاع كائنات جديدة أو تعديل الكائنات التي تم تمريرها للحفاظ على الحالة خارج الحزمة.
عزل الحالة في واحد أو أكثر من ملفات CommonJS المشتركة بين إصدارات CommonJS وESM للحزمة. على سبيل المثال، نقاط الإدخال لـ CommonJS وESM هي Index.cjs وindex.mjs على التوالي:
//index.cjs حالة ثابتة = تتطلب ('./state.cjs') Module.exports.state = State; //index.mjs حالة الاستيراد من "./state.cjs" يصدّر { ولاية }
حتى إذا تم استخدام المثال في تطبيق عبر الطلب والاستيراد، فإن كل مرجع للمثال يحتوي على نفس الحالة وسيتم تطبيق التغييرات التي تطرأ على الحالة بواسطة أي من نظامي الوحدة على كليهما.
إذا كانت هذه المقالة مفيدة لك، فيرجى الإعجاب بها ودعمها. "الإعجاب" الخاص بك هو الدافع بالنسبة لي لمواصلة الإبداع.
تستشهد هذه المقالة بالمعلومات التالية:
الوثائق الرسمية لـNode.js
أنماط تصميم Node.js