اذهب إلى المحتوى

Hassan Hedr

الأعضاء
  • المساهمات

    2846
  • تاريخ الانضمام

  • تاريخ آخر زيارة

  • عدد الأيام التي تصدر بها

    38

كل منشورات العضو Hassan Hedr

  1. يمكن تعريف تابع يعيد اسم المتجر المُسجل دخول إليه حاليًا، وذلك حسب طريقة الاستيثاق الخاصة بتطبيقك: function auth_store_name(){ return ...; // اسم المتجر } بعدها يمكنك استخدامه عند تعريف البادئة Prefix لمجموعة المسارات التي يحتاج لها كالتالي: Route::group([ 'prefix' => auth_store_name() ], function () { //.. })
  2. البرمجة المتزامنة synchronous programming في جافاسكربت تعني تنفيذ التعليمات في الأسطر البرمجية سطرًا تلو الآخر بحسب ترتيب كتابتها تمامًا، ولكن لا حاجة للالتزام بترتيب التنفيذ هذا دومًا، فمثلًا عند إرسال طلب عبر الشبكة ستضطر الإجرائية التي يُنفذ فيها البرنامج إلى انتظار رد ذلك الطلب ووصول جوابه قبل أن نتمكن من إكمال تنفيذ باقي البرنامج، حيث وقت انتظار إتمام الطلب هذا هو وقت مهدور، هنا يأتي دور البرمجة اللامتزامنة asynchronous programming لتحل هذه المشكلة، حيث تُنفذ فيها الأسطر البرمجية للبرنامج بترتيب مختلف عن ترتيب كتابتها الأصلي، فيصبح بإمكاننا مثلًا في مثالنا السابق تنفيذ تعليمات برمجية أخرى في أثناء انتظار إتمام عملية إرسال الطلب ووصول جوابه المنتظر مع البيانات المطلوبة. تٌنفَذ شيفرة جافاسكربت ضمن خيط وحيد thread ضمن الإجرائية، حيث تعالج شيفراتها بشكل متزامن ضمن ذلك الخيط عبر تنفيذ تعليمة واحدة فقط في كل لحظة، ويتوضح أثر البرمجة المتزامنة في هذه الحالة أكثر، فعند تنفيذ المهام التي تحتاج لوقت كبير ضمن ذلك الخيط سيُعيق ذلك تنفيذ كل الشيفرات اللاحقة لحين انتهاء تلك المهمة، لذا وبالاستفادة من مزايا برمجة جافاسكربت اللامتزامنة يمكننا إزاحة المهام التي تأخذ وقتًا طويلًا في التنفيذ إلى خيط آخر في الخلفية وبالتالي حل المشكلة، وبعد انتهاء تلك المهمة الطويلة تُنفذ الشيفرات المتعلقة بمعالجة بياناتها ضمن الخيط الأساسي لشيفرة جافاسكربت مجددًا. سنتعلم في هذا المقال طرق إدارة المهام اللامتزامنة باستخدام حلقة الأحداث Event Loop الخاصة بجافاسكربت والتي تُنهي بواسطتها مهامًا جديدة أثناء انتظار انتهاء المهام الأخرى، ولذلك سنطور برنامجًا يستفيد من البرمجة اللامتزامنة لطلب قائمة من الأفلام من الواجهة البرمجية لاستديو Ghibli وحفظ بياناتها ضمن ملف CSV، حيث سننفذ ذلك بثلاثة طرق وهي دوال رد النداء callback functions والوعود promises وأخيرًا باستخدام اللاتزامن والانتظار async/await ومع أنه من غير الشائع حاليًا استخدام دوال رد النداء في البرمجة اللامتزامنة في جافاسكربت، إلا أنه من المهم تعلم تلك الطريقة لفهم تاريخ الانتقال لاستخدام الوعود ووجودها أساسًا، ثم تأتي آلية اللاتزامن والانتظار لتسمح باستخدام الوعود بطريقة أبسط، وهي الطريقة المعتمدة حاليًا عند كتابة الشيفرات اللامتزامنة في جافاسكربت. المستلزمات تثبيت بيئة Node.js على الجهاز، حيث استخدمنا في هذا المقال الإصدار رقم 10.17.0، ويمكنك الاطلاع على مقال تثبيت Node.js على نظام أبونتو 18.04 للتعرف على طريقة تثبيته. معرفة طريقة تثبيت الحزم ضمن المشروع باستخدام npm. معرفة طرق تعريف وتنفيذ الدوال البسيطة في جافاسكربت قبل تعلم تنفيذها بالطريقة اللامتزامنة. حلقة الأحداث Event Loop لنتعرف بدايةً على الطريقة التي ينفذ بها جافاسكربت الدوال داخليًا، ما سيسمح لنا لاحقًا بفهم أكثر عند كتابة الشيفرات اللامتزامنة وتزيد قدرتنا على استكشاف الأخطاء وتصحيحها حين حدوثها، حيث يضيف مفسر جافاسكربت كل دالة تُنفَّذ إلى مكدس الاستدعاءات call stack، وهو هيكلية بيانات شبيهة بالقائمة بحيث يمكن إضافة أو حذف العناصر منه من الأعلى فقط أي تعتمد مبدأ الداخل آخرًا يخرج أولًا LIFO -اختصارًا إلى Last in, first out- فعند إضافة عنصرين إلى المكدس مثلًا يمكن حذف آخر عنصر تمت إضافته أولًا، فمثلًا عند استدعاء الدالة ‎functionA()‎ سيُضاف ذلك إلى مكدس الاستدعاء، وإذا استدعت الدالة functionA()‎ داخلها دالة أخرى مثلًا functionB()‎ فسيضاف الاستدعاء الأخير لأعلى مكدس الاستدعاء، وبعد الانتهاء من تنفيذه سيُزال من أعلى مكدس الاستدعاء، أي ينفذ جافاسكربت أولًا الدالة functionB()‎ ثم يزيلها من المكدس عند انتهائها، ثم يُنهي تنفيذ الدالة الأب functionA()‎ ثم يزيلها أيضًا من مكدس الاستدعاء، لهذا يتم دومًا تنفيذ الدوال الأبناء أو الداخلية قبل الدوال الآباء أو الخارجية. عندما يُنفذ جافاسكربت عملية لا متزامنة ككتابة البيانات إلى ملف مثلًا، فسيضيفها إلى جدول خاص ضمن الذاكرة يُخزَّن فيه العملية وشرط اكتمالها والدالة التي ستُستدعى عند اكتمالها، وبعد اكتمال العملية ستضاف تلك الدالة إلى رتل الرسائل message queue، وهو هيكلية بيانات تشبه القائمة أيضًا تُضاف إليها العناصر من الأسفل وتزال من الأعلى فقط أي تعتمد مبدأ الداخل أولًا يخرج أولًا FIFO -اختصارًا إلى First in, First out- وحين انتهاء عمليتين لا متزامنتين والتجهيز لاستدعاء الدوال الخاصة بهما سيتم استدعاء الدالة الخاصة بالعملية التي انتهت أولًا، حيث تنتظر الدوال ضمن رتل الرسائل إضافتها إلى مكدس الاستدعاء، وتبقى حلقة الأحداث في فحص دائم لمكدس الاستدعاء بانتظار فراغه، عندها يُنقل أول عنصر من رتل الرسائل إلى مكدس الاستدعاء، ويعطي جافاسكربت الأولوية للدوال ضمن رتل الرسائل بدلًا من استدعاءات الدوال الجديدة التي يفسرها ضمن الشيفرة، وبذلك تسمح تركيبة عمل مكدس الاستدعاء ورتل الرسائل وحلقة الأحداث بتنفيذ شيفرات جافاسكربت أثناء معالجة المهام اللامتزامنة. والآن بعد أن ألقينا نظرة عامة على حلقة الأحداث وتعرفنا فيها على طريقة تنفيذ الشيفرات اللامتزامنة في جافاسكربت يمكننا البدء بكتابة شيفرات لا متزامنة باستخدام إحدى الطرق لذلك، إما بدوال رد النداء أو الوعود أو باستخدام اللاتزامن والانتظار async/await. البرمجة اللامتزامنة باستخدام دوال رد النداء تُمرَّر دالة رد النداء callback function كمعامل للدوال الأخرى وتُنفَذ عند انتهاء تنفيذ الدالة المُمررة لها، وتحوي دالة رد النداء عادة شيفرات لمعالجة نتيجة تلك العملية أو شيفرات لتنفيذها بعد انتهاء تنفيذ العملية اللامتزامنة، حيث استخدمت هذه الطريقة لفترة طويلة وكانت أشيع طريقة مستخدمة لكتابة الشيفرات اللامتزامنة، ولكنها لم تعد مستخدمة حاليًا لأنها تُصعّب قراءة ووضوح الشيفرة، لكن سنستخدمها في هذه الفقرة لكتابة شيفرة جافاسكربت لا متزامنة لنتعرف بذلك على كل الطرق الممكنة ونلاحظ الفروقات بينها وميزة كل منها، حيث نستخدم دوال رد النداء بأكثر من طريقة ضمن الدوال الأخرى، وعادة ما نُمرِّرها كآخر معامل للدالة اللامتزامنة كالتالي: function asynchronousFunction([ Function Arguments ], [ Callback Function ]) { [ Action ] } لكننا لسنا ملزومين باتباع هذه البنية عند كتابة الدوال اللامتزامنة، ولكن شاع تمرير دالة رد النداء كآخر معامل للدالة اللامتزامنة ليسهل التعرف عليه بين المبرمجين، وتُمرَّر عادة دالة رد النداء كدالة مجهول الاسم -التي تُعرّف بلا اسم- إذ يُحسِّن تمريرها كآخر معامل قراءة الشيفرة، ولنفهم ذلك أكثر سننشئ وحدة برمجية في نود وظيفتها كتابة قائمة من أفلام استوديو Ghibli إلى ملف، فنبدأ بإنشاء مجلد سيحوي ملف جافاسكربت للبرنامج وملف الخرج النهائي كالتالي: mkdir ghibliMovies ندخل إلى المجلد: cd ghibliMovies سنرسل بدايةً طلب HTTP للواجهة البرمجية لاستديو Ghibli ونطبع داخل دالة رد النداء نتيجة ذلك الطلب، ولنتمكن من ذلك نحتاج إلى مكتبة تساعدنا في إرسال طلبات HTTP والوصول إلى البيانات ضمن الرد باستخدام دالة رد نداء، لذا نُهيئ ملف الحزمة للوحدة بتنفيذ الأمر التالي: npm init -y ونثبت مكتبة request بتنفيذ الأمر: npm i request --save ننشئ ملفًا جديدًا بالاسم callbackMovies.js ونفتحه باستخدام أي محرر نصوص: nano callbackMovies.js ونكتب داخله الشيفرة التالية والتي سترسل طلب HTTP باستخدام مكتبة request السابقة: const request = require('request'); request('https://ghibliapi.herokuapp.com/films'); نُحمّل في أول سطر مكتبة request التي ثبتناها، حيث ستعيد المكتبة دالة يمكن استدعاؤها لإنشاء طلبات HTTP نخزنها ضمن الثابت request، ثم نرسل طلب HTTP باستدعاء الدالة request()‎ وتمرير عنوان الواجهة البرمجية API له. لنطبع الآن البيانات من نتيجة الطلب إلى الطرفية بإضافة الأسطر كالتالي: const request = require('request'); request('https://ghibliapi.herokuapp.com/films', (error, response, body) => { if (error) { console.error(`Could not send request to API: ${error.message}`); return; } if (response.statusCode != 200) { console.error(`Expected status code 200 but received ${response.statusCode}.`); return; } console.log('Processing our list of movies'); movies = JSON.parse(body); movies.forEach(movie => { console.log(`${movie['title']}, ${movie['release_date']}`); }); }); مررنا للدالة request()‎ معاملان هما عنوان URL للواجهة البرمجية API لإرسال الطلب إليها، ودالة رد نداء سهمية لمعالجة أي أخطاء قد تحدث أو معالجة نتيجة إرسال الطلب عند نجاحه بعد انتهاء تنفيذه. لاحظ أن دالة رد النداء أخذت ثلاثة معاملات وهي كائن الخطأ error و كائن الرد response وبيانات جسم الطلب body، فبعد اكتمال الطلب سيُعيَّن قيم لتلك المعاملات بناءً على النتيجة، ففي حال فشل الطلب سيتم تعيين قيمة كائن للمعامل error، وتعيين القيمة null لكل من response و body، وعند نجاح الطلب سيتم تعيين قيمة الرد للمعامل response، وفي حال احتوى الرد على بيانات في جسم الطلب ستُعيَّن كقيمة للمعامل body. ونستفيد من تلك المعاملات داخل دالة رد النداء التي مَرَّرناها لها للتحقق من وجود الخطأ أولًا، ويفضل التحقق من ذلك دومًا ضمن دوال رد النداء بحيث لا نكمل تنفيذ باقي التعليمات عند حدوث خطأ، وفي حال وجود خطأ سنطبع رسالة الخطأ إلى الطرفية وننهي تنفيذ الدالة، بعدها نتحقق من رمز الحالة للرد المرسل ففي حال عدم توافر الخادم للرد على الطلب أو تغيير الواجهة البرمجية أو إرسال طلبية خاطئة سنلاحظ ذلك من رمز الرد ويمكن التحقق من نجاح العملية وسلامة الرد بالتحقق من أن رمز الحالة يساوي 200 ويمكننا بذلك متابعة معالجة الطلب، وفي حالتنا عالجنا الطلب بتحليل الرد وتحويله إلى مصفوفة ثم طبع كل عنصر من عناصرها -التي تمثل الأفلام- على شكل اسم الفلم وتاريخ إصداره، والآن نحفظ الملف وتخرج منه وننفذه كالتالي: node callbackMovies.js ليظهر الخرج كالتالي: Castle in the Sky, 1986 Grave of the Fireflies, 1988 My Neighbor Totoro, 1988 Kiki's Delivery Service, 1989 Only Yesterday, 1991 Porco Rosso, 1992 Pom Poko, 1994 Whisper of the Heart, 1995 Princess Mononoke, 1997 My Neighbors the Yamadas, 1999 Spirited Away, 2001 The Cat Returns, 2002 Howl's Moving Castle, 2004 Tales from Earthsea, 2006 Ponyo, 2008 Arrietty, 2010 From Up on Poppy Hill, 2011 The Wind Rises, 2013 The Tale of the Princess Kaguya, 2013 When Marnie Was There, 2014 حصلنا على قائمة بأفلام من إنتاج استوديو Ghibli مع تواريخ إصدارها بنجاح، والآن نريد من البرنامج كتابة القائمة إلى ملف، لذا نعدل الملف callbackMovies.js ضمن محرر النصوص ونضيف الأسطر التالية لإنشاء ملف بصيغة CSV يحوي بيانات الأفلام المجلوبة: const request = require('request'); const fs = require('fs'); request('https://ghibliapi.herokuapp.com/films', (error, response, body) => { if (error) { console.error(`Could not send request to API: ${error.message}`); return; } if (response.statusCode != 200) { console.error(`Expected status code 200 but received ${response.statusCode}.`); return; } console.log('Processing our list of movies'); movies = JSON.parse(body); let movieList = ''; movies.forEach(movie => { movieList += `${movie['title']}, ${movie['release_date']}\n`; }); fs.writeFile('callbackMovies.csv', movieList, (error) => { if (error) { console.error(`Could not save the Ghibli movies to a file: ${error}`); return; } console.log('Saved our list of movies to callbackMovies.csv');; }); }); نلاحظ بدايةً استيراد الوحدة البرمجية fs والتي توفرها بيئة نود للتعامل مع الملفات حيث نريد التابع writeFile()‎ منها لكتابة البيانات إلى ملف بطريقة لا متزامنة، وبدلًا من طباعة البيانات إلى الطرفية، يمكننا إضافتها إلى السلسلة النصية للمتغير movieList ثم نستدعي التابع writeFile()‎ لحفظ قيمة movieList النهائية إلى ملف جديد بالاسم callbackMovies.csv، ثم نمرر أخيرًا دالة رد نداء للتابع writeFile()‎ حيث سيُمرَّر لها معاملًا وحيدًا وهو كائن الخطأ error نعرف بالتحقق منه إذا ما فشلت عملية الكتابة إلى الملف، وذلك مثلًا عندما لا يملك المستخدم الحالي الذي يُنفِّذ إجرائية نود صلاحيات كافية لإنشاء ملف جديد ضمن المسار الحالي. والآن نحفظ الملف وننفذه مرة أخرى: node callbackMovies.js سنلاحظ ظهور ملف جديد ضمن مجلد المشروع ghibliMovies بالاسم callbackMovies.csv يحوي قائمة أفلام تشبه القائمة التالية: Castle in the Sky, 1986 Grave of the Fireflies, 1988 My Neighbor Totoro, 1988 Kiki's Delivery Service, 1989 Only Yesterday, 1991 Porco Rosso, 1992 Pom Poko, 1994 Whisper of the Heart, 1995 Princess Mononoke, 1997 My Neighbors the Yamadas, 1999 Spirited Away, 2001 The Cat Returns, 2002 Howl's Moving Castle, 2004 Tales from Earthsea, 2006 Ponyo, 2008 Arrietty, 2010 From Up on Poppy Hill, 2011 The Wind Rises, 2013 The Tale of the Princess Kaguya, 2013 When Marnie Was There, 2014 نلاحظ أننا كتبنا ذلك المحتوى إلى ملف CSV ضمن دالة رد النداء لطلب HTTP المرسل، حيث أن الشيفرات ضمن تلك الدالة ستُنفَذ بعد انتهاء عملية إرسال الطلب فقط، وفي حال أردنا الاتصال بقاعدة بيانات بعد كتابة محتوى ملف CSV السابق يجب إنشاء دالة لا متزامنة أخرى تُستدعَى ضمن دالة رد نداء التابع writeFile()‎، وكلما أردنا تنفيذ عمليات لا متزامنة متلاحقة يجب تغليف المزيد من دوال رد النداء داخل بعضها البعض، فإذا أردنا مثلًا تنفيذ خمس عمليات لا متزامنة متتالية بحيث تُنفًّذ كل منها بعد انتهاء العملية التي تسبقها وسنحصل في النهاية على شيفرة بنيتها تشبه التالي: doSomething1(() => { doSomething2(() => { doSomething3(() => { doSomething4(() => { doSomething5(() => { // العملية النهائية }); }); }); }); }); وبذلك ستصبح داول رد النداء المتداخلة هذه معقدة وصعبة القراءة خصوصًا إن احتوت على أسطر تعليمات عديدة، ويتوضح ذلك خصوصًا في المشاريع الكبيرة والمعقدة نسبيًا، فيصبح من الصعب التعامل مع تلك العمليات وهي نقطة ضعف هذه الطريقة والسبب في عدم استخدامها لمعالجة العمليات اللامتزامنة في وقتنا الحالي، وهنا جاءت الوعود لتحل محلها وتوفر صيغة أفضل في كتابة الشيفرات اللامتزامنة وهذا ما سنتعرف عليه في الفقرة التالية. استخدام الوعود لاختصار الشيفرات اللامتزامنة الوعد Promise هو كائن توفره جافاسكربت وظيفته إرجاع قيمة ما مستقبلًا ومن هنا جاءت تسمية الوعد من أنه يعدك بإعادة قيمة ما لاحقًا، ويمكن للدوال اللامتزامنة أن تُعيد كائن وعد من هذا النوع بدلًا من إرجاع القيمة النهائية لتنفيذها، وعند تحقق هذا الوعد مستقبلًا fulfilled سنحصل على القيمة النهائية للعملية وإلا سنحصل على خطأ ويكون الوعد قد رُفض rejected، أما خلال التنفيذ يكون الوعد في حالة الانتظار ويتم معالجته. تستخدم الوعود بالصيغة التالية: promiseFunction() .then([ رد نداء يُنفَّذ عند تحقق الوعد ]) .catch([ رد نداء يُنفَّذ عند رفض الوعد ]) نلاحظ أن الوعود تستخدم دوال رد النداء هي أيضًا، حيث نُمرِّر للتابع then()‎ دالة رد نداء تُستدعى عند نجاح التنفيذ، ونُمرِّر للتابع catch()‎ دالة رد نداء أخرى تُستدعى لمعالجة الأخطاء عند حدوثها أثناء عملية تنفيذ ذلك الوعد. ولنتعرف على الوعود أكثر سنطور برنامجنا السابق لاستخدام طريقة الوعود بدلًا من دوال رد النداء، ونبدأ بتثبيت مكتبة Axios التي تعتمد على الوعود في عملياتها لإرسال طلبات HTTP: npm i axios --save نُنشئ ملفًا جديدًا بالاسم promiseMovies.js سيحوي النسخة الجديدة من البرنامج: nano promiseMovies.js سنرسل طلب HTTP باستخدام مكتبة axios هذه المرة، وباستخدام نسخة خاصة من وحدة fs تعتمد في عملها على الوعود سنحفظ النتيجة ضمن ملف CSV كما فعلنا سابقًا، ونبدأ بكتابة الشيفرة التالية ضمن الملف لتحميل مكتبة Axios وإرسال طلب HTTP للواجهة البرمجية للحصول على قائمة الأفلام: const axios = require('axios'); axios.get('https://ghibliapi.herokuapp.com/films'); حملنا في أول سطر مكتبة axios وحفظنا الناتج ضمن الثابت axios وبعدها استدعينا التابع axios.get()‎ لإرسال طلب HTTP إلى الواجهة البرمجية، حيث سيعيد التابع axios.get()‎ وعدًا يمكننا ربطه مع دالة لطباعة الأفلام إلى الطرفية عند نجاح الطلب كالتالي: const axios = require('axios'); axios.get('https://ghibliapi.herokuapp.com/films') .then((response) => { console.log('Successfully retrieved our list of movies'); response.data.forEach(movie => { console.log(`${movie['title']}, ${movie['release_date']}`); }); }) بعد إرسال طلب HTTP من نوع GET باستخدام التابع axios.get()‎ استخدمنا التابع then()‎ والذي سيُنفذ عند نجاح الطلب فقط، وطبعنا داخله الأفلام إلى الطرفية كما فعلنا في الفقرة السابقة، والآن نطور البرنامج لكتابة تلك البيانات إلى ملف جديد باستخدام واجهة للتعامل مع نظام الملفات قائمة على الوعود كالتالي: const axios = require('axios'); const fs = require('fs').promises; axios.get('https://ghibliapi.herokuapp.com/films') .then((response) => { console.log('Successfully retrieved our list of movies'); let movieList = ''; response.data.forEach(movie => { movieList += `${movie['title']}, ${movie['release_date']}\n`; }); return fs.writeFile('promiseMovies.csv', movieList); }) .then(() => { console.log('Saved our list of movies to promiseMovies.csv'); }) استوردنا الوحدة البرمجية fs مجددًا لكن نلاحظ استخدام الخاصية ‎.promises منها، وهي النسخة الخاصة من وحدة fs التي تستخدم الوعود كنتيجة لتنفيذ دوالها بدلًا من طريقة دوال رد النداء، وسبب إتاحتها كنسخة منفصلة هو دعم المشاريع التي لازالت تستخدم الطريقة القديمة. ونلاحظ كيف أصبح أول استدعاء للتابع then()‎ يعالج رد الطلب HTTP الوارد ثم يستدعي التابع fs.writeFile()‎ بدلًا من طباعة البيانات إلى الطرفية، وبما أننا نستخدم نسخة الوعود من fs فسيعيد التابع writeFile()‎ عند استدعائه وعدًا آخر، يجري معالجته باستدعاء then()‎ مرة أخرى والتي بدورها تأخذ دالة رد النداء تُنفَّذ عند نجاح تنفيذ ذلك الوعد -المُعاد من التابع writeFile()‎. نلاحظ أيضًا مما سبق أنه يمكن إعادة وعد من داخل وعد آخر، ما سيسمح بتنفيذ تلك الوعود الواحد تلو الآخر، ويوفر لنا ذلك طريقة لتنفيذ عدد من العمليات اللامتزامنة خلف بعضها البعض، وندعو هذه العملية باسم سلسلة الوعود promise chaining وهي بديل عن استخدام دوال رد النداء المتداخلة التي تعرفنا عليها في الفقرة السابقة، بحيث يُستدعى التابع then()‎ الموالي عند تحقق الوعد المعاد من سابقه وهكذا وعند رفض أحد الوعود يُستدعى التابع catch()‎ مباشرةً آنذاك وتتوقع السلسلة عن العمل. ملاحظة: لم نتحقق في هذا المثال من رمز الرد لطلب HTTP الوارد كما فعلنا سابقًا، حيث لن يُلبي axios تلقائيًا الوعد الذي يعيده في حال كان رمز الرد الوارد يمثل أي خطأ، ولذلك لم نعد مضطرين للتحقق منه بأنفسنا. والآن نضيف التابع catch()‎ في نهاية البرنامج لإكماله كالتالي: const axios = require('axios'); const fs = require('fs').promises; axios.get('https://ghibliapi.herokuapp.com/films') .then((response) => { console.log('Successfully retrieved our list of movies'); let movieList = ''; response.data.forEach(movie => { movieList += `${movie['title']}, ${movie['release_date']}\n`; }); return fs.writeFile('promiseMovies.csv', movieList); }) .then(() => { console.log('Saved our list of movies to promiseMovies.csv'); }) .catch((error) => { console.error(`Could not save the Ghibli movies to a file: ${error}`); }); في حال فشل أي وعد من سلسلة الوعود تلك سيُنفذ التابع catch()‎ تلقائيًا كما أشرنا متجاوزًا أي دوال تسبقه، لذا يمكننا إضافة استدعاء التابع catch()‎ مرة واحدة فقط في النهاية لمعالجة أي خطأ قد يحدث من أي عملية سابقة حتى لو كنا ننفذ عدة عمليات غير متزامنة متتالية. والآن لنتحقق من صحة عمل البرنامج بتنفيذه كالتالي: node promiseMovies.js نلاحظ ظهور نفس البيانات السابقة ضمن الملف promiseMovies.csv: Castle in the Sky, 1986 Grave of the Fireflies, 1988 My Neighbor Totoro, 1988 Kiki's Delivery Service, 1989 Only Yesterday, 1991 Porco Rosso, 1992 Pom Poko, 1994 Whisper of the Heart, 1995 Princess Mononoke, 1997 My Neighbors the Yamadas, 1999 Spirited Away, 2001 The Cat Returns, 2002 Howl's Moving Castle, 2004 Tales from Earthsea, 2006 Ponyo, 2008 Arrietty, 2010 From Up on Poppy Hill, 2011 The Wind Rises, 2013 The Tale of the Princess Kaguya, 2013 When Marnie Was There, 2014 نلاحظ كيف اختصر استخدام الوعود كتابة الكثير من الشيفرات، وكيف أن عملية سَلسَلة الوعود أسهل وأبسط ومقروءة أكثر من طريقة دوال رد النداء المتداخلة، ولكن حتى مع تلك المزايا الجديدة هنالك صعوبات تحصل في حال أردنا تنفيذ العديد من العمليات اللامتزامنة أي ستزداد صعوبة الشيفرة المكتوبة بازدياد طول سلسلة الوعود. وتحتاج كلا الطريقتين السابقتين سواء دوال رد النداء أو الوعود لإنشاء دوال رد نداء تُعالج ناتج العملية اللامتزامنة، والطريقة الأفضل من ذلك هي انتظار نتيجة العملية اللامتزامنة وتخزينها ضمن متغير خارج أي دالة، وبذلك يمكننا استخدام النتائج ضمن المتغيرات مباشرةً ودون الحاجة لإنشاء الكثير من الدوال في كل مرة، وهذا تحديدًا ما يميز عملية اللاتزامن والانتظار باستخدام async و await في جافاسكربت وهي ما سنتعرف عليه في الفقرة التالية. ملاحظة: إن أردت تعلم المزيد حول الوعود، فارجع إلى توثيق واجهة الوعود Promise في موسوعة حسوب. التعامل مع الوعود باستخدام طريقة اللاتزامن والانتظار async/await تتيح الكلمة المفتاحية async اللاتزامن والكلمة المفتاحية await الانتظار صيغة بديلة أبسط للتعامل مع الوعود، إذ ستُعاد النتيجة مباشرةً كقيمة بدلًا من تمريرها على شكل وعد إلى التابع then()‎ لمعالجتها وكأننا نستدعي تابع متزامن عادي في جافاسكربت. ولنخبر جافاسكربت أن دالة ما هي دالة لا متزامنة تُعيد وعدًا، نعرفها بوضع الكلمة المفتاحية async قبلها، وبعدها يمكننا استخدام الكلمة المفتاحية await داخلها لإخبار جافاسكربت بإرجاع ناتج الوعد المُعاد عند نجاحه بدلًا من إرجاع الوعد نفسه كقيمة، أي تكون صيغة استخدام async/await كالتالي: async function() { await [عملية غير متزامنة] } لنطبق استخدامها على برنامجنا ونلاحظ الفرق، لننشئ ملفًا للبرنامج الجديد بالاسم asyncAwaitMovies.js: nano asyncAwaitMovies.js نستورد داخل ذلك الملف نفس الوحدات التي استخدمناها سابقًا لأن طريقة async/await تعتمد على الوعود في عملها: const axios = require('axios'); const fs = require('fs').promises; والآن نعرّف دالة باستخدام الكلمة المفتاحية async للدلالة على أنها دالة لا متزامنة كالتالي: const axios = require('axios'); const fs = require('fs').promises; async function saveMovies() {} عرفنا الدالة saveMovies()‎ باستخدام الكلمة المفتاحية async، بهذا نستطيع استخدام الكلمة المفتاحية await داخلها، أي ضمن الدوال اللامتزامنة التي نعرفها بنفس تلك الطريقة، والآن نستخدم الكلمة المفتاحية await لإرسال طلب HTTP إلى الواجهة البرمجية لجلب قائمة الأفلام: const axios = require('axios'); const fs = require('fs').promises; async function saveMovies() { let response = await axios.get('https://ghibliapi.herokuapp.com/films'); let movieList = ''; response.data.forEach(movie => { movieList += `${movie['title']}, ${movie['release_date']}\n`; }); } نرسل طلب HTTP باستخدام axios.get()‎ من داخل الدالة saveMovies()‎ كما فعلنا سابقًا، لكن لاحظ أنه بدلًا من استدعاء التابع then()‎ أضفنا الكلمة المفتاحية await قبل الاستدعاء، سينفذ حينها جافاسكربت الشيفرة في الأسطر اللاحقة فقط عند نجاح تنفيذ التابع axios.get()‎، وستُعيَّن القيمة التي يعيدها إلى المتغير response، والآن نضيف الشيفرة المسؤولة عن كتابة البيانات الواردة إلى ملف CSV: const axios = require('axios'); const fs = require('fs').promises; async function saveMovies() { let response = await axios.get('https://ghibliapi.herokuapp.com/films'); let movieList = ''; response.data.forEach(movie => { movieList += `${movie['title']}, ${movie['release_date']}\n`; }); await fs.writeFile('asyncAwaitMovies.csv', movieList); } نلاحظ استخدامنا للكلمة المفتاحية await عند استدعاء التابع fs.writeFile()‎ أيضًا لكتابة محتويات الملف، والآن ننهي كتابة الدالة بالتقاط ومعالجة أي أخطاء قد ترميها تلك العمليات باستخدام try/catch كما نفعل عادةً في جافاسكربت لالتقاط الأخطاء المرمية: const axios = require('axios'); const fs = require('fs').promises; async function saveMovies() { try { let response = await axios.get('https://ghibliapi.herokuapp.com/films'); let movieList = ''; response.data.forEach(movie => { movieList += `${movie['title']}, ${movie['release_date']}\n`; }); await fs.writeFile('asyncAwaitMovies.csv', movieList); } catch (error) { console.error(`Could not save the Ghibli movies to a file: ${error}`); } } وبذلك نضمن معالجة الأخطاء عند حدوثها في العمليات اللامتزامنة داخل جسم try، بما فيها أي أخطاء قد تحدث عند إرسال طلب HTTP أو عند فشل الكتابة إلى الملف. والآن نستدعي الدالة saveMovies()‎ اللامتزامنة لضمان تنفيذها عند تنفيذ البرنامج باستخدام نود: const axios = require('axios'); const fs = require('fs').promises; async function saveMovies() { try { let response = await axios.get('https://ghibliapi.herokuapp.com/films'); let movieList = ''; response.data.forEach(movie => { movieList += `${movie['title']}, ${movie['release_date']}\n`; }); await fs.writeFile('asyncAwaitMovies.csv', movieList); } catch (error) { console.error(`Could not save the Ghibli movies to a file: ${error}`); } } saveMovies(); لا يوجد فروقات كبيرة بين هذه الطريقة وبين الصيغة العادية لكتابة واستدعاء شيفرات جافاسكربت المتزامنة، حيث لم نحتاج لتعريف العديد من الدوال -تحديدًا دوال ردود النداء- وتمريرها كما فعلنا سابقًا، وتتوضح بذلك ميزة استخدام async/await تسهيل قراءة واستخدام الشيفرات اللامتزامنة أفضل من الطرق الأخرى. والآن ننفذ هذا البرنامج ونختبر عمله: node asyncAwaitMovies.js نلاحظ ظهور ملف جديد بالاسم asyncAwaitMovies.csv ضمن مجلد المشروع ghibliMovies يحوي داخله على التالي: Castle in the Sky, 1986 Grave of the Fireflies, 1988 My Neighbor Totoro, 1988 Kiki's Delivery Service, 1989 Only Yesterday, 1991 Porco Rosso, 1992 Pom Poko, 1994 Whisper of the Heart, 1995 Princess Mononoke, 1997 My Neighbors the Yamadas, 1999 Spirited Away, 2001 The Cat Returns, 2002 Howl's Moving Castle, 2004 Tales from Earthsea, 2006 Ponyo, 2008 Arrietty, 2010 From Up on Poppy Hill, 2011 The Wind Rises, 2013 The Tale of the Princess Kaguya, 2013 When Marnie Was There, 2014 وبذلك نكون تعرفنا على طريقة عملها استخدام ميزة async/await في جافاسكربت. ختامًا تعرفنا في هذا المقال على الطريقة التي تعالج بها جافاسكربت الدوال وتدير العمليات اللامتزامنة باستخدام حلقة الأحداث، وكتبنا برنامجًا لإنشاء ملف بصيغة CSV بالاعتماد على بيانات واردة من واجهة برمجية API بإرسال طلب HTTP نطلب فيه بيانات عدد من الأفلام مستخدمين بذلك كل طرق البرمجة اللامتزامنة المتوفرة في جافاسكربت، حيث بدأنا ذلك باستخدام طريقة دوال رد النداء القديمة وبعدها تعرفنا على الوعود وطريقة استخدامها، ثم طورنا ذلك باستخدام طريقة اللاتزامن والانتظار async/await لتصبح الشيفرة أبسط وأوضح. ويمكنك الآن بعد ما تعلمته ضمن هذا المقال استخدام التقنيات التي تعلمتها لكتابة البرامج التي تستخدم العمليات اللامتزامنة، ويمكنك الاستفادة من قائمة الواجهات البرمجية العامة المتاحة لتطوير ما قد يفيدك، وذلك بإرسال طلبات HTTP لا متزامنة إليها كما فعلنا في هذا المقال. ترجمة -وبتصرف- للمقال How To Write Asynchronous Code in Node.js. اقرأ أيضًا البرمجة غير المتزامنة في Node.js واجهة الوعود البرمجية Promise API في جافاسكربت الدوال الواعدة: تحويل الدوال إلى وعود Promisification في جافاسكربت التعامل مع أخطاء الوعود في جافاسكربت
  3. لأي تفاصيل بخصوص الاشتراك في الدورات وطريقة الدفع وإمكانية الدفع على أقساط تواصل مع مركز المساعدة واشرح لهم مشكلتك والأقساط التي يمكنك دفعها والفترة المتوقعة، وسيجيبون في حال توفر إمكانية لذلك ويساعدونك في الاشتراك إن أمكن، قد يستغرق الرد بعض الوقت لذا أرسل رسالة لهم تشرح بها كل التفاصيل وابقى على اطلاع على المحادثة لحين الرد
  4. الوحدة البرمجية module في نود Node.js هي أجزاء من شيفرات جافاسكربت منعزلة قابلة للاستخدام في أكثر من تطبيق، حيث يعد الغرض من الوحدة البرمجية هو تقسيم منطقي لوظيفة عمل الشيفرة، فأي ملف أو مجموعة ملفات يمكن اعتبارها وحدة برمجية في حال أمكن استخدام البيانات والتوابع فيها من قبل برامج أخرى خارجية. ويَنشأ عن التقسيم الوظيفي للشيفرات بتلك الطريقة وحدات برمجية أخرى يمكن إعادة استخدامها في عدة مشاريع أكبر أو مع مطورين آخرين أي أن الوحدات تبنى باعتماد بعضها على بعضها الآخر بطريقة هرمية، ما يؤدي لتطوير برمجيات غير مترابطة سهلة التطوير والتوسع وتوفر درجة من التعقيد أعلى من الوحدات المكونة لها، ما يفتح بابًا للمساهمة بمشاركة تلك الوحدات البرمجية والتي توفر بيانات وتوابع مفيدة مع مجتمع نود، وهي الطريقة التي جرى فيها تحزيم ونشر كل الوحدات البرمجية على مستودع npm، لهذا كمبرمج نود من الضروري أن تتعلم طريقة إنشاء الوحدات البرمجية. أخذنا في المقال السابق: إدارة الوحدات البرمجية في Node.js باستخدام npm وملف package.json فكرة أساسية عن ماهية الوحدات في نود وتعرفنا على مدير حزم نود npm وأهمية الملف package.json لإدارة الوحدات التي يعتمد عليها مشروعنا، وسنتعلم في هذا المقال كيفية إنشاء وحدة برمجية وظيفتها اقتراح الألوان على مطور الويب لاستخدامها في التصميم، فسنخزن الألوان المتاحة في مصفوفة داخل الوحدة وسنوفر تابعًا للمستخدمين يختار لهم إحداها عشوائيًا، بعدها سنتعلم عدة طرق يمكننا بها استيراد تلك الوحدة واستخدامها ضمن تطبيقات ومشاريع نود الأخرى. يلزمك في هذا المقال معرفةً باستخدام حلقة REPL التي يوفرها نود، حيث سنستخدمها لاختبار الوحدة التي سنطورها، لذا يفضل الاطلاع على مقال استخدام الوضع التفاعلي REPL في Node.js من هذه السلسلة "دليل تعلم Node.js" إن لم تطلع عليه مسبقًا. إنشاء وحدة برمجية في Node.js سنشرح في هذه الفقرة طريقة إنشاء وحدة برمجية جديدة في نود، حيث ستحتوي الوحدة التي سنطورها على مصفوفة من الألوان وتابع يختار إحداها عشوائيًا ويعيده للمستخدم، وسنستخدم في ذلك خاصية التصدير exports في نود لإتاحة التابع والمصفوفة للبرامج الخارجية. بدايةً، لنعتمد هيكلية معينة للبيانات التي سنخزنها ضمن الوحدة، حيث سنمثل كل لون بكائن سيحوي الخاصية name التي تعبر عن اسم ذلك اللون بصيغة مقروءة، والخاصية code وهي سلسلة نصية تمثل ترميز ذلك اللون لاستخدامه في HTML، والصيغة المعتمدة لتمثيل الألوان في HTML هي ستة أرقام بالترميز الست عشري. نبدأ باختيار بعض تلك الألوان التي ستوفرها وحدتنا البرمجية ونضعها في مصفوفة بالاسم allColors وليكن عددها ستة ألوان كما ستحتوي وحدتنا على تابع بالاسم getRandomColor()‎ لاختيار لون عشوائي من تلك المصفوفة وإعادته. ننتقل إلى الخطوات العملية، ننشئ مجلدًا جديدًا لاحتواء المشروع نسميه colors وننتقل إليه كالتالي: mkdir colors cd colors نُهيئ ملف الحزمة package.json ضمن مجلد المشروع لتتمكن باقي البرامج من استيراده واستخدامه لاحقًا كالتالي: npm init -y يمكن باستخدام الخيار ‎-y تخطي الأسئلة التي تظهر عادةً عند تخصيص محتوى ملف الحزمة package.json، وفي حال كنا ننوي نشر تلك الوحدة يجب تخصيص القيم داخل ذلك الملف كما شرحنا في المقال السابق. سنحصل بعد تنفيذ الأمر على الخرج التالي: { "name": "colors", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" } الآن نُنشئ ملف جافاسكربت جديد سيحوي على شيفرة الوحدة البرمجية وسيكون المدخل لها، ونفتحه باستخدام أي محرر نصوص أو شيفرات برمجية، مثلًا باستخدام nano كالتالي: nano index.js نبدأ بتعريف الصنف Color والذي سنمرر له اسم اللون وترميزه الذي سيستخدم ضمن HTML كالتالي: class Color { constructor(name, code) { this.name = name; this.code = code; } } بعد تعريف هيكلية البيانات التي ستمثل اللون، نُنشئ من ذلك الصنف بعض الكائنات ونخزنها ضمن مصفوفة الألوان كالتالي: class Color { constructor(name, code) { this.name = name; this.code = code; } } const allColors = [ new Color('brightred', '#E74C3C'), new Color('soothingpurple', '#9B59B6'), new Color('skyblue', '#5DADE2'), new Color('leafygreen', '#48C9B0'), new Color('sunkissedyellow', '#F4D03F'), new Color('groovygray', '#D7DBDD'), ]; بعدها نُعرّف الدالة التي ستجلب لنا لونًا عشوائيًا عند استدعاءها، لتصبح الشيفرة بالكامل كالتالي: class Color { constructor(name, code) { this.name = name; this.code = code; } } const allColors = [ new Color('brightred', '#E74C3C'), new Color('soothingpurple', '#9B59B6'), new Color('skyblue', '#5DADE2'), new Color('leafygreen', '#48C9B0'), new Color('sunkissedyellow', '#F4D03F'), new Color('groovygray', '#D7DBDD'), ]; exports.getRandomColor = () => { return allColors[Math.floor(Math.random() * allColors.length)]; } exports.allColors = allColors; تشير الكلمة المفتاحية exports إلى كائن عام توفره نود لكل وحدة برمجية، حيث ستكون كل الكائنات والتوابع المُعرّفة كخصائص ضمن ذلك الكائن متاحة عند استيراد هذه الوحدة واستخدامها من قبل الوحدات البرمجية الأخرى، ولذلك لاحظ كيف عرّفنا التابع getRandomColor()‎ مباشرةً كخاصية ضمن الكائن exports، وبعدها أضفنا الخاصية allColors ضمن ذلك الكائن التي تشير قيمتها إلى مصفوفة الألوان allColors المُنشئة سابقًا. بناءً على ما سبق، ستتمكن أي وحدة برمجية أخرى بعد استيرادها لهذه الوحدة من الوصول إلى التابع getRandomColor()‎ والمصفوفة allColors واستخدامهما، وبهذا نكون قد أنشأنا وحدة برمجية توفر للوحدات الأخرى مصفوفة من الألوان وتابعًا يختار إحداها عشوائيًا لتتمكن من استخدامهما. سنستخدم في الفقرة التالية الوحدة التي طورناها ضمن تطبيق آخر لنفهم فائدة الكائن export أكثر. اختبار الوحدة البرمجية باستخدام REPL يفضل قبل البدء باستخدام هذه الوحدة اختبارها أولًا للتأكد من صحة عملها، فسنستخدم في هذه الفقرة الوضع التفاعلي REPL لتحميل الوحدة colors واستدعاء التابع getRandomColor()‎ التي توفره لنختبر صحة عمله. نبدأ أولًا جلسة REPL جديدة ضمن مجلد المشروع الحاوي على الملف index.js كالتالي: node نلاحظ ظهور الرمز ‎>‎ في بداية السطر عند الدخول إلى وضع REPL ويمكن الآن إدخال أوامر وشيفرات جافاسكربت لتنفيذها فورًا كما يلي: colors = require('./index'); سيُحمّل التابع ‎require()‎ الوحدة colors وتحديدًا ملف المدخل entry point لها بعد الضغط على زر الإدخال ENTER لتنفيذ السطر السابق ونلاحظ ظهور الخرج التالي: { getRandomColor: [Function], allColors: [ Color { name: 'brightred', code: '#E74C3C' }, Color { name: 'soothingpurple', code: '#9B59B6' }, Color { name: 'skyblue', code: '#5DADE2' }, Color { name: 'leafygreen', code: '#48C9B0' }, Color { name: 'sunkissedyellow', code: '#F4D03F' }, Color { name: 'groovygray', code: '#D7DBDD' } ] } ظهرت لنا قيمة الوحدة البرمجية colors التي تم استيرادها، وهي عبارة عما صدّرناه منها، حيث يعيد التابع require عند استدعائه قيمة الكائن exports من الوحدة المستوردة وهي colors في حالتنا، والذي أضفنا إليه داخلها تابعًا بالاسم getRandomColor()‎ وخاصيةً بالاسم allColors، وهو ما ظهر ضمن الخرج، ويمكننا الآن اختبار التابع getRandomColor()‎ كالتالي: colors.getRandomColor(); نلاحظ كيف أعاد لنا لونًا عشوائيًا: Color { name: 'groovygray', code: '#D7DBDD' } سيظهر لك لونًا مختلفًا عند تنفيذ الأمر في كل مرة، وذلك لأن الاختيار عشوائي، والآن بعد إتمام الاختبار يمكننا الخروج من جلسة REPL بتنفيذ أمر الخروج التالي الذي سيعيدنا إلى سطر الأوامر: .exit تحققنا في هذه الفقرة من صحة عمل الوحدة البرمجية التي أنشأناها سابقًا وذلك باستخدام REPL، وسنطبق في الفقرة التالية نفس الخطوات لاستيراد واستخدام الوحدة لكن هذه المرة ضمن مشروع حقيقي. تثبيت وحدة منشأة محليًا كاعتمادية استوردنا الوحدة البرمجية أثناء اختبارها ضمن صدفة REPL في الفقرة السابقة بذكر المسار النسبي لها، أي ذكرنا مسار مجلد الملف index.js بدءًا من المسار الحالي، ولا تُعتمد طريقة الاستيراد هذه إلا في حالات خاصة إذ تُستورد الوحدات بذكر أسمائها لتجنب المشاكل التي قد تحدث عند نقل مجلدات المشاريع التي نعمل عليها أو تعديل مساراتها، وسنثبت في هذه الفقرة الوحدة البرمجية colors باستخدام أمر التثبيت install من npm، لذلك ننشئ بدايةً وحدة برمجية جديدة خارج مجلد الوحدة colors، بالرجوع إلى المجلد الأب له وإنشاء مجلد جديد كالتالي: cd .. mkdir really-large-application وننتقل لمجلد المشروع الجديد: cd really-large-application ثم نُهيئ كما تعلمنا سابقًا ملف الحزمة package.json لهذا المشروع بتنفيذ الأمر: npm init -y سيتم توليد ملف package.json بالمحتوى التالي: { "name": "really-large-application", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" } نثبت الآن الوحدة colors كالتالي: npm install --save ../colors . بذلك نكون قد ثبتنا الوحدة colors ضمن المشروع الجديد، ونعاين الآن الملف package.json لنرى كيف تُحفَظ الاعتماديات المحلية فيه: nano package.json نُلاحظ إضافة سطر جديد ضمن الخاصية dependencies يُذكر فيه اسم الوحدة ومسارها النسبي: { "name": "really-large-application", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "colors": "file:../colors" } } حيث نُسخِت الوحدة colors إلى مجلد الاعتماديات node_modules للمشروع الجديد، ويمكننا التأكد من ذلك باستعراض محتوياته باستخدام الأمر التالي: ls node_modules يظهر اسم مجلد الاعتمادية موجودًا ضمنه: colors يمكن الآن استخدام تلك الوحدة ضمن هذا المشروع، لذلك نُنشئ ملف جافاسكربت جديد: nano index.js ونستورد بدايةً الوحدة colors ونستخدم منها الدالة getRandomColor()‎ لاختيار لون عشوائي، ثم نطبع رسالة إلى الطرفية تخبر المستخدم باللون الذي يمكنه استخدامه، لذا نكتب داخل الملف index.js الشيفرة التالية: const colors = require('colors'); const chosenColor = colors.getRandomColor(); console.log(`You should use ${chosenColor.name} on your website. It's HTML code is ${chosenColor.code}`); نحفظ الملف ونخرج منه، والآن عند تنفيذ هذا البرنامج سيخبرنا بلون عشوائي يمكننا استخدامه: node index.js نحصل على خرج مشابه للتالي: You should use leafygreen on your website. It's HTML code is #48C9B0 بهذا نكون ثبتنا الوحدة البرمجية colors ضمن المشروع ويمكننا التعامل معها وإدارتها كأي اعتمادية أخرى ضمن المشروع، لكن يجب الانتباه أنه في كل مرة نعدل شيفرة الوحدة colors مثلًا لإضافة ألوان جديدة يجب علينا حينها تنفيذ أمر الترقية npm update ضمن مشروع التطبيق لتحديث الاعتمادية واستخدام المزايا الجديدة، ولتجنب تكرار تنفيذ ذلك عند كل تعديل في الفقرة التالية سنستخدم الوحدة colors بطريقة مختلفة تمكننا من استخدام أحدث إصدار لها ضمن المشاريع المعتمدة عليها أثناء العمل عليها وتطويرها. ربط وحدة محلية قد نمر في حالة نعمل فيها على تطوير وحدة برمجية محليًا ونستخدمها في الوقت نفسه ضمن مشروع آخر، وسيصعب آنذاك ترقيتها باستمرار ضمن المشروع كما أشرنا سابقًا، والحل يكمن في ربط الوحدات البرمجية بدلًا من تثبيتها لاستخدامها مباشرة وهي قيد التطوير والبناء. سنتعلم ذلك في هذه الفقرة عن طريق ربط الوحدة colors ضمن التطبيق الذي يستخدمها، وسنختبر الربط بإجراء تعديلات على الوحدة colors ونتحقق من التحديث الآني لتلك التعديلات ضمن اعتمادية التطبيق دون الحاجة للترقية أو لتثبيت الوحدة من جديد، لذلك نزيل بدايةً تثبيت الوحدة من التطبيق بتنفيذ الأمر التالي: npm un colors يربط مدير الحزم npm الوحدات البرمجية مع بعضها باستخدام الوصلات الرمزية symbolic links والتي تمثل مؤشرًا يشير إلى ملف أو مجلد ما ضمن نظام الملفات، ويُنفذ الربط هذا على مرحلتين: إنشاء وصلة أو رابط عام global link للوحدة حيث يُنشئ npm وصلة رمزية بين مجلد الوحدة البرمجية ومجلد الاعتماديات العام node_modules الذي تُثبَّت فيه كل الحزم العامة على مستوى النظام كله، أي الحزم المُثبَّتة باستخدام الخيار ‎-g. إنشاء وصلة محلية local link بحيث يُنشئ npm وصلة رمزية بين المشروع المحلي وبين الرابط العام للوحدة البرمجية المراد استخدامها فيه. ننشئ الرابط العام بالدخول إلى مجلد الوحدة colors واستخدام الأمر link كالتالي: cd ../colors sudo npm link سيظهر لنا خرج كالتالي: /usr/local/lib/node_modules/colors -> /home/hassan/colors أنشِئت بذلك وصلة رمزية في مجلد node_modules العام تشير إلى مجلد الوحدة colors، والآن نعود إلى مجلد المشروع really-large-application لربط الوحدة ضمنه كالتالي: cd ../really-large-application sudo npm link colors سنلاحظ ظهور خرج مشابه للتالي: /home/hassan/really-large-application/node_modules/colors -> /usr/local/lib/node_modules/colors -> /home/hassan/colors ملاحظة: يمكن اختصار الأمر link بكتابة ln بدلًا منه، ليصبح أمر الربط كالتالي npm ln colors وسنحصل على نفس النتيجة. وكما يُظهر خرج أمر الربط السابق فقد أنشِئت وصلة رمزية في مجلد node_modules للمشروع really-large-application تشير إلى الوصلة الرمزية لمجلد الوحدة colors الموجودة في مجلد node_modules العام على مستوى النظام، والتي بدورها تشير إلى مجلد الوحدة colors الفعلي، وبهذا تكون عملية الربط اكتملت ويمكن تشغيل ملف المشروع للتأكد بأن الربط صحيح ولا زال المشروع يعمل كما هو: node index.js نحصل على خرج مشابه للتالي: OutputYou should use sunkissedyellow on your website. It's HTML code is #F4D03F نلاحظ عدم تأثر المشروع ولا زال يعمل كما هو، والآن لنختبر ما إذا كانت التعديلات على الوحدة التي طورناها ستنعكس مباشرة ضمن المشروع الذي يستخدمها، لذلك نفتح الملف index.js الخاص بالوحدة colors ضمن محرر النصوص: cd ../colors nano index.js ونضيف مثلًا دالةً جديدةً مهمتها جلب درجة من درجات اللون الأزرق من الألوان المتوفرة، ولا تحتاج لتمرير معاملات وستعيد العنصر الثالث من مصفوفة الألوان المحلية allColors مباشرةً والذي هو من درجات اللون الأزرق، لذا نضيف الأسطر الأخيرة إلى الملف كالتالي: class Color { constructor(name, code) { this.name = name; this.code = code; } } const allColors = [ new Color('brightred', '#E74C3C'), new Color('soothingpurple', '#9B59B6'), new Color('skyblue', '#5DADE2'), new Color('leafygreen', '#48C9B0'), new Color('sunkissedyellow', '#F4D03F'), new Color('groovygray', '#D7DBDD'), ]; exports.getRandomColor = () => { return allColors[Math.floor(Math.random() * allColors.length)]; } exports.allColors = allColors; exports.getBlue = () => { return allColors[2]; } نحفظ الملف ونخرج منه، ونفتح ملف index.js ضمن مجلد المشروع really-large-application: cd ../really-large-application nano index.js ونستخدم داخله الدالة الجديدة ‎getBlue()‎ المضافة إلى الوحدة ونطبع إلى الطرفية جملة تحوي خصائص ذلك اللون كالتالي: const colors = require('colors'); const chosenColor = colors.getRandomColor(); console.log(`You should use ${chosenColor.name} on your website. It's HTML code is ${chosenColor.code}`); const favoriteColor = colors.getBlue(); console.log(`My favorite color is ${favoriteColor.name}/${favoriteColor.code}, btw`); نحفظ الملف ونخرج منه، وبذلك يصبح المشروع يستخدم التابع الجديد الذي أنشأناه ‎getBlue()‎، والآن ننفذ البرنامج ونرى النتيجة: node index.js سنحصل على خرج مشابه لما يلي: OutputYou should use brightred on your website. It's HTML code is #E74C3C My favorite color is skyblue/#5DADE2, btw نلاحظ كيف تمكنا من استخدام آخر التعديلات التي أجريناها ضمن الوحدة colors مباشرةً دون الحاجة لتنفيذ أمر الترقية npm update لتلك الوحدة، حيث يسهل ذلك عملية تطوير الوحدات البرمجية ويخفف من تكرار تنفيذ نفس الأوامر بكثرة. حاول التفكير دومًا عند تطوير التطبيقات الكبيرة والمعقدة نسبيًا كيف يمكن تجميع الشيفرات التي يتم تطويرها ضمن وحدات برمجية منفصلة تعتمد على بعضها، ويمكن إعادة استخدامها في عدة مشاريع، أما في حال كانت الوحدة البرمجية تستخدم فقط ضمن برنامج واحد عندها يفضل إبقاءها ضمن نفس مجلد المشروع ذاك وربطها عن طريق المسار النسبي لها، وأما في حال التخطيط لمشاركة الوحدة بشكل منفصل لاحقًا أو في استخدامها في مشروع مختلف عن المشروع الحالي فانظر إن كان الربط أنسب لحالتك أم التثبيت كما تعلمت إلى الآن، إذ الفائدة الأكبر من ربط الوحدات قيد التطوير استخدام أحدث إصدار منها دومًا دون الحالة لترقيتها كل حين، وإلا فمن الأسهل تثبيتها باستخدام الأمر npm install. ختامًا غصنا عميقًا في هذا المقال في وحدات نود والتي هي مجموعة من التوابع والكائنات في جافاسكربت خصوصًا في كيفية استخدامها من قبل البرامج الأخرى، فأنشأنا وحدة برمجية وحددنا داخلها بعض الدوال والكائنات كخصائص للكائن exports لإتاحتها للاستخدام من قبل التطبيقات الخارجية، واستوردنا تلك الوحدة إلى برنامج جديد واستخدمناها ضمنه. أصبح بإمكانك الآن استخراج بعض المكونات من البرامج التي تعمل عليها إلى وحدات برمجية منفصلة بتحديد ما تود إعادة استخدامها ضمنها، وبذلك تجمع البيانات والتوابع الخاصة بها معًا ضمن وحدة منفصلة وتعزلها عن باقي التطبيقات مما يمكنك من إعادة استخدامها وتطويرها وحتى مشاركتها مع الآخرين، وكلما كتبت وحدات أكثر وصقلت مهارتك البرمجية فيها، اكتسبت خبرة كبيرة تخولك من تطوير برامج نود عالية الجودة. ترجمة -وبتصرف- للمقال How To Create a Node.js Module. اقرأ أيضًا تعرف على وحدات Node.js الأساسية إدارة الوحدات البرمجية في Node.js باستخدام npm وملف package.json
  5. يمكن ضمن ملف العرض وعند إنشاء عناصر الخيارات option لقائمة الأسماء، إضافة القيمة التي تُقابلها من قائمة الأسماء العلمية كخاصية بيانات data كالتالي: <select name="scientific"> <option value="{{ $scientific }}">lactuca satvia</option> ... ^^^^^^^^^^^ <select name="common"> <option data-scientific="{{ $scientific }}">lettuce</option> ... ^^^^^^^^^^^ ثم يمكن إضافة شيفرة جافاسكربت إلى الصفحة نفسها للاستماع إلى حدث تغير قيمة قائمة الاسم العام واستخراج القيمة من الخاصية الموضوعة عليها، ثم اتغيير قيمة قائمة الاسماء العلمية واختيار القيمة المقابلة كالتالي: document.querySelector('select[name=common]').addEventListener('change', function(event){ // استخراج القيمة const scientific = event.target.options[event.target.selectedIndex].dataset.scientific; // تغيير القيمة document.querySelector('select[name=scientific]').value = scientific })
  6. أولًا تبدأ بالبحث على الانترنت عن المكتبات التي تريدها بحسب لغة البرمجة التي تعمل بها وبحسب وظيفة المكتبة المناسبة للمتطلبات التي تريدها، ولتقييم المكتبات والمقارنة بينها أولًا عامل هو المزايا التي توفرها وسهولة الاستخدام، ثم يأتي عامل الشهرة والاستخدام الواسع لها، ما يدل على اهتمام مطوري تلك المكتبات بتحديثها دومًا وإضافة المزايا الجديدة، أنسب مكان لمعرفة شهرة الحزم هي مستودعات الحزم والمكتبات البرمجية، مثل: مستودع npm لمكتبات لغة جافاسكربت مستودع packagist لمكتبات لغة PHP ضمن تلك المستودعات يمكنك مشاهدة إحصائيات عن المكتبات الموجودة، مثل عدد التنزيلات في الفترة السابقة ورابط مستودع الشيفرة المصدرية لها، مثل Github الذي يمكنك مشاهدة مدى تفاعل المطورين والمجتمع مع مكتبة ما ومدى تفضيلهم لها، ويمكنك الاستفادة من قراءة المقالات التالية:
  7. أنصحك بالبدء بتطوير الواجهة الأمامية، فهي ما سيتعامل معه مستخدم تطبيقك مباشرةً، وعند الانتهاء منها كاملةً أو من قسم منها ستتوضح لديك المتطلبات التي يحتاجها التطبيق من الواجهة الخلفية، عندها تبدأ ببناء ما تحتاجه فقط لخدمة التطبيق وإكمال المزايا، بذلك سيتركز جهدك على ما تحتاجه فقط ولن تطور أي مزايا إضافية لا حاجة لها أو تتوقع أنك قد تحتاج إليها عند بناء الواجهة الأمامية للتطبيق لاحقًا، يمكنك الاستفادة إذا أردت من قراءة المقالات التالية للتعرف على تطوير التطبيقات باستخدام React Native:
  8. لمنع حدوث أي مشاكل في التزامن بين رسم العناصر وإضافتها ضمن الصفحة، وبين ربط عمليات الحذف لكل عنصر من القائمة يمكنك تنفيذ العمليتين معًا ضمن تابع واحد فقط لإجبار الربط الصحيح بعد كل عملية رسم للعناصر، أيضًا حاول تعريف أي متغيرات تحتاجها داخل تلك التوابع ضمنها وليس خارجها أيضًا لمنع حدوث أي مشاكل في التزامن بين قيمتها والعمليات عليها، يمكنك حل المشكلة بكتابة الشيفرة السابقة ضمن تابع واحد فقط واستدعاءه كالتالي: function init() { let products = JSON.parse(localStorage.getItem("productsInCart")) || []; let productsInCartUI = document.querySelector(".cart-products"); productsInCartUI.innerHTML = products.map((product) => { return ` <div class="product-container"> <div class="product-img"> </div> <div class="product-info"> <h2 class="product-title">${product.title}</h2> <h3 class="product-description">${product.des}</h3> <h2 class="product-price">${product.price} DT</h2> </div> <div class="product-btns"> <button class="remove-to-cart" data-click="${product.id}">Remove Product</button> </div> </div> `; }); document.querySelectorAll(".remove-to-cart").forEach((item) => { let id = item.getAttribute("data-click"); let productsInCart = localStorage.getItem("productsInCart"); if (productsInCart) { item.addEventListener("click", () => { let productsInCart = localStorage.getItem("productsInCart"); let items = JSON.parse(productsInCart); let filtered = items.filter((data) => data.id != id); localStorage.setItem("productsInCart", JSON.stringify(filtered)); init(); }); } }); } init();
  9. ضمن ملف الإعدادات webpack.config.js اسم خاصية تعريف الإضافات هي plugins، الخطأ لديك هو كتابتها Plugin، أسماء الخواص يجب أن تُكتب كما هي مع مراعاة حالة الأحرف أيضًا، لكل المشكلة يجب تعديل اسم الخاصية لتصبح كالتالي: plugins: { ^^^^^^^ .. }
  10. الشهرة والاستخدام الواسع لبيئة نود Node.js في تطوير تطبيقات النظم أو الواجهات الخلفية للويب سببها الأساسي مزايا السرعة والأداء العالي للغة جافاسكربت عند التعامل مع الدخل والخرج I/O، واعتمدت عليها العديد من التطبيقات كبيرة الحجم ما زاد تعقيد وصعوبة إدارة اعتمادياتها dependencies، حيث يوفر نود نظام تقسيم الشيفرة والاعتماديات إلى وحدات modules لتنظيمها وحل تلك المشكلة، ومن أبسط أشكالها هي أي ملف جافاسكربت يحوي توابع وكائنات يمكن استخدامها من قبل البرامج أو الوحدات الأخرى، ويُدعى تجمع عدة وحدات معًا بالحزمة package، وتُدار مجموعة الحزم باستخدام برنامج مخصص لإدارة الحزم من أشهرها مدير حزم نود npm، والذي يأتي افتراضيًا مع نود ويستخدم لإدارة الحزم الخارجية في المشاريع المبنية ضمن نود، ويستخدم أيضًا لتثبيت العديد من أدوات سطر الأوامر ولتشغيل النصوص أو السكربتات البرمجية للمشاريع، فهو يدير تلك الحزم ويخزن معلوماتها ضمن ملف يسمى package.json داخل مجلد المشروع ويحوي على معلومات مثل: الحزم التي يعتمد عليها المشروع وأرقام الإصداراتها. معلومات تصف المشروع نفسه، كاسم المطور ورخصة الاستخدام وغيرها. السكربتات البرمجية الممكن تنفيذها، كالتي تؤتمت بعض المهام الخاصة بالمشروع. تساعد عملية إدارة البيانات الوصفية metadata والاعتماديات الخاصة بمشروع ضمن ملف واحد هو package.json على توحيد تلك المعلومات ومشاركتها خلال مرحلة تطوير أي مشروع برمجي على أي جهاز ومع أي مطور، حيث يُستخدم ذلك الملف من قبل مدير الحزم لإدارة تلك المعلومات تلقائيًا، ونادرًا ما نضطر لتعديل البيانات داخل هذا الملف يدويًا لإدارة الوحدات البرمجية المستخدمة في المشروع. سنستخدم في هذا المقال مدير حزم نود npm لإدارة الحزم وسنتعرف بالتفصيل على محتوى ملف package.json ونستخدمه لإدارة الوحدات البرمجية المُثبتة ضمن المشروع، وسنتعلم طريقة عرض الاعتماديات المستخدمة حاليًا وطريقة تحديثها أو إلغاء تثبيتها وفحصها للعثور على المشاكل الأمنية داخلها. ستحتاج للمتابعة وتطبيق الأمثلة في هذا المقال لتثبيت بيئة Node.js على جهازك، حيث استخدمنا في هذا المقال الإصدار رقم 18.3.0 وبذلك يكون قد ثُبت أيضًا مدير الحزم npm. إنشاء ملف الحزمة package.json لنبدأ بإعداد المشروع الذي سنطبق عليه كافة الخطوات اللاحقة، والذي سيكون عبارة عن حزمة لتحديد المواقع سنسميه locator، ووظيفته تحويل عناوين IP إلى اسم البلد المقابل لها، ولن نخوض في تفاصيل تضمين الشيفرة لذلك المشروع بل سيكون تركيزنا على جانب إدارة الحزم والاعتماديات للمشروع فقط، وسنستخدم في ذلك حزمًا خارجية كاعتماديات للمشروع وفي حال أردت تضمين المشروع بنفسك يمكنك استخدامها نفسها. بدايةً، نُنشئ ملفًا نسميه package.json، سيحوي على البيانات الوصفية للمشروع وتفاصيل الاعتماديات التي سيعتمد عليها، وكما تشير لاحقة ذلك الملف فمحتوياته ستكون مكتوبة بصيغة JSON وهي الصيغة المعتمدة لتخزين البيانات ومشاركتها على شكل كائنات جافاسكربت objects، وتتألف من أزواج من المفاتيح والقيم key/value المقابلة لها. وبما أن الملف package.json سيحوي العديد من البيانات يمكننا تجنب كتابتها يدويًا ونسخ ولصق قالب جاهز لتلك البيانات من مكان آخر، لهذا فإن أول ميزة سنتعرف عليها في مدير الحزم npm هو الأمر init، والذي سيسأل عند تنفيذه عدة أسئلة سيبني ملف package.json للمشروع تلقائيًا اعتمادًا على أجوبتنا لها. استخدام الأمر init أول خطوة هي إنشاء مجلد للمشروع الذي سنتدرب عليه من سطر الأوامر أو بأي طريقة أخرى ننشئ مجلدًا جديدًا بالاسم locator: mkdir locator وننتقل إليه: cd locator والآن ننفذ أمر تهيئة ملف package.json: npm init ملاحظة: إذا كنا ننوي استخدام مدير الإصدارات Git لإدارة إصدارات المشروع وحفظ ملفاته، ننشئ مستودع Git داخل مجلد المشروع أولًا قبل تنفيذ أمر التهيئة npm init، وسيعلم حينها الأمر أن عملية التهيئة لملف الحزمة تتم بداخل مجلد يحوي مستودع Git، وإذا كان عنوان المستودع البعيد متاحًا ضمنه سيتم إضافة قيم للحقول repository و bugs و homepage تلقائيًا إلى ملف package.json، أما في حال تهيئة المستودع بعد تنفيذ أمر التهيئة سنحتاج حينها لإضافة تلك الحقول وتعيين قيمها يدويًا. بعد تنفيذ الأمر السابق سيظهر الخرج التالي: This utility will walk you through creating a package.json file. It only covers the most common items, and tries to guess sensible defaults. See `npm help init` for definitive documentation on these fields and exactly what they do. Use `npm install ` afterwards to install a package and save it as a dependency in the package.json file. Press ^C at any time to quit. package name: (locator) أول سؤال سنُسأل عنه هو اسم المشروع name، فإن لم تُعط فستأخذ افتراضيًا اسم المجلد للمشروع، ونلاحظ دومًا اقتراح القيم الافتراضية بين القوسين ()، وبما أن القيمة الافتراضية هي ما نريدها يمكننا الضغط على زر الإدخال ENTER مباشرةً لقبولها. السؤال التالي هو عن رقم إصدار المشروع version، حيث أنها ضرورية مع اسم المشروع في حال مشاركة الحزمة التي سنطورها في مستودع حزم npm، فتستخدم حزم نود عادة الترقيم الدلالي Semantic Versioning لإصداراتها، وفيها يدل الرقم الأول على الإصدار الأساسي MAJOR الذي يشير أنه أجريت تغييرات جذرية على الحزمة، والرقم الثاني يدل على الإصدار الثانوي MINOR الذي يشير لإضافة مزايا على الحزمة، والرقم الثالث والأخير يدل على إصدار الترقيع PATCH الذي يشير لتصحيح أخطاء ضمن الحزمة. نضغط على زر الإدخال ENTER لقبول القيمة الافتراضية لأول إصدار من الحزمة وهو 1.0.0. الحقل التالي هو حقل الوصف للمشروع description وهو شرح مختصر عن المشروع ووظيفته يفيد عند البحث عن تلك الحزمة من قبل المستخدمين إن نُشر على الإنترنت، والحزمة locator التي سنطورها وظيفتها جلب عنوان IP للمستخدم وإعادة اسم البلد الذي ينتمي له هذا العنوان، وهنا يمكننا كتابة وصف معبر عن وظيفة هذه الحزمة باللغة الإنكليزية شبيه بالتالي: Finds the country of origin of the incoming request السؤال التالي هو عن الملف الأساسي أو المدخل للمشروع entry point فعند تثبيت أي حزمة واستخدامها ضمن مشروع آخر واستيرادها فإن أول ما سيُحمّل هو الملف الذي سنحدده في هذا الحقل، وقيمة المسار للملف المحدد في الحقل main يجب أن تكون نسبةً لمجلد المشروع الجذري الذي أول ما يحوي فيه الملف package.json، ويمكننا قبول القيمة الافتراضية المقترحة والضغط على زر الإدخال ENTER باعتبار أن الملف index.js سيكون المدخل هنا. ملاحظة: تستخدم معظم الحزم الملف index.js كمدخل لها، لهذا تعتبر هذه القيمة الافتراضية للحقل main كمدخل لوحدات npm، وحتى عند غياب ملف package.json من مجلد الوحدة ستحاول نود افتراضيًا تحميل الملف index.js من مجلد جذر الحزمة المُستخدمة. السؤال التالي هو عن أمر تنفيذ اختبارات الحزمة test command، وقيمته يمكن أن تكون إما مسار لملف تنفيذي أو أمر لتشغيل اختبارات المشروع، وتستخدم معظم وحدات نود الشهيرة أطر اختبار مثل Mocha أو Jest أو Jasmine أو غيرها لكتابة اختبارات المشروع، ويمكننا ترك قيمة هذا الحقل فارغة بالضغط على زر الإدخال. سنُسأل بعدها عن عنوان مستودع Git للمشروع، هنا نُدخل مسار المستودع للمشروع الحالي الذي قد يكون مُستضافًا على أحد الخدمات الشهيرة مثل GitHub، ويمكنك ترك قيمته فارغة أيضًا. سيُطلب منا بعدها إدخال بعض الكلمات المفتاحية كقيمة للحقل keywords، والقيمة عبارة عن مصفوفة من السلاسل النصية تحوي مصطلحات وكلمات مفتاحية ستفيد المستخدمين عند البحث عن الحزمة عند نشرها عبر الإنترنت، لذا يفضل إدخال بعض الكلمات القصيرة التي تتعلق بعمل الحزمة لتزداد فرصة العثور عليها وظهورها ضمن عمليات البحث، وندخل الكلمات المفتاحية مفصولًا بينها بفاصلة، فمثلًا لمشروعنا يمكن إدخال بعض الكلمات كالتالي ip,geo,country، ينتج عن ذلك مصفوفة تحوي ثلاث عناصر كقيمة للحقل keywords داخل الملف package.json. الحقل التالي هو اسم صاحب المشروع أو الكاتب والمطور له author، حيث يفيد إدخال تلك المعلومة المستخدمين الراغبين بالتواصل معه لأي سبب، مثل اكتشاف ثغرة أو مشكلة في عمل الحزمة، وتكون قيمة هذا الحقل سلسة نصية بالصيغة التالية: "الاسم <عنوان البريد الإلكتروني> (موقع الويب)" مثلًا: "Hassan <hassan@example.com> (https://mywebsite.com)" وإدخال عنوان البريد الإلكتروني وموقع الويب اختياريان ويمكن الاكتفاء بإدخال الاسم فقط. القيمة الأخيرة هي لحقل رخصة الاستخدام license، حيث يحدد ذلك الصلاحيات القانونية والحدود المسموح بها استخدام هذه الحزمة أو المشروع، وبما أن أغلب حزم نود مفتوحة المصدر لذا القيمة الافتراضية المقترحة هي رخصة ISC، لذا يجب قبل تعيين تلك القيمة مراجعة الرخص المتاحة واختيار المناسبة منها للمشروع، ويمكنك الاطلاع على معلومات أكثر على رخص المشاريع المفتوحة المصدر وفي حال كانت الحزمة مطورة للاستخدام الخاص وليست للمشاركة يمكن إدخال القيمة UNLICENSED لتحديد الحزمة كغير مرخصة للاستخدام العام أبدًا، ولمشروعنا الحالي يمكن استخدام القيمة الافتراضية بالضغط على زر الإدخال وإنهاء تهيئة وإنشاء الملف. سيعرض بعد ذلك الأمر init محتوى ملف package.json الذي سيُنشئه لنراجعه ونتأكد من جميع القيم وسيظهر خرج كالتالي: About to write to /home/hassan/locator/package.json: { "name": "locator", "version": "1.0.0", "description": "Finds the country of origin of the incoming request", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [ "ip", "geo", "country" ], "author": "Hassan <hassan@your_domain> (https://your_domain)", "license": "ISC" } Is this OK? (yes)</hassan@your_domain> في حال كانت كل البيانات صحيحة نضغط زر الإدخال للإنهاء وإنشاء ملف package.json وبعدها يمكننا تثبيت الوحدات البرمجية الخارجية ليعتمد عليها مشروعنا وتضاف تفاصيلها في ذلك الملف. تثبيت الوحدات البرمجية عند تطوير المشاريع البرمجية عادة ما نفوض المهام التي لا تتعلق بصلب عمل المشروع إلى مكتبات برمجية خارجية متخصصة في ذلك، ما يتيح للمطور التركيز على عمل المشروع الحالي فقط وتطوير التطبيق بسرعة وكفاءة أكبر عبر استخدام الأدوات والشيفرات البرمجية التي طورها الآخرون على مبدأ لا تخترع العجلة من جديد، فمثلًا إذا احتاج مشروعنا locator لإرسال طلب خارجي إلى الواجهة البرمجية API لخدمة تقدم البيانات الجغرافية اللازمة لنا وهنا يمكننا استخدام مكتبة خاصة بإرسال طلبات HTTP مباشرةً بدلًا من كتابة ذلك بأنفسنا، حيث وظيفة المشروع هي تقديم تلك البيانات الجغرافية إلى مستخدم الحزمة فقط، وأما تفاصيل إرسال طلبات HTTP لا تتعلق بوظيفة الحزمة لذا يمكن تفويضها لمكتبة خارجية جاهزة مختصة بذلك، يمكننا مثلًا استخدام مكتبة axios والتي تساعد في إرسال طلبات HTTP بشكل عملي وسهل، ولتثبيتها ننفذ الأمر التالي: npm install axios --save الجزء الأول من هذا الأمر npm install هو أمر تثبيت الحزم، ويمكن اختصارًا تنفيذه كالتالي npm i، حيث نمرر له أسماء الحزم التي نرغب بتثبيتها مفصولة بفراغات بينها وفي حالتنا نريد فقط تثبيت حزمة مكتبة axios، بعدها واختياريًا يمكن تمرير الخيار ‎--save لحفظ المكتبات المُثبتة كاعتماديات للمشروع ضمن ملف package.json وهو السلوك الافتراضي حتى دون ذكر ذلك الخيار، وبعد تثبيت المكتبة سنلاحظ ظهور خرج مشابه للتالي: ... + axios@0.27.2 added 5 packages from 8 contributors and audited 5 packages in 0.764s found 0 vulnerabilities والآن باستخدام أي محرر نصوص نعاين محتوى الملف package.json لنلاحظ التغييرات، سنستخدم مثلًا محرر nano كالتالي: nano package.json نلاحظ ظهور خاصية جديدة بالاسم dependencies أو الاعتماديات، والتي تحوي على اعتماديات المشروع الحالي: { "name": "locator", "version": "1.0.0", "description": "Finds the country of origin of the incoming request", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [ "ip", "geo", "country" ], "author": "Hassan hassan@your_domain (https://your_domain)", "license": "ISC", "dependencies": { "axios": "^0.27.2" } } وإضافة الوحدة البرمجية التي ثبتناها مع رقم إصدارها يحدد للمطورين الآخرين العاملين على نفس المشروع الاعتماديات الخارجية التي يتطلبها تشغيله. ملاحظة: قد انتبهت إلى وجود الرمز ^ قبل رقم الإصدار لاعتمادية axios، وبما أن الترقيم الدلالي يحوي ثلاثة أرقام وهي الأساسي الجذري MAJOR والثانوي البسيط MINOR والترقيع PATCH فيشير ذلك الرمز إلى تثبيت الإصدار الأساسي للاعتمادية ولا مانع من تغير الإصدار الثانوي البسيط أو إصدار الترقيع أي يمكن تنزيل الإصدار 0.28.0 أو 0.28.1 مثلًا واستخدامه ضمن المشروع، ويمكن استخدام الرمز ~ أيضًا لتثبيت الإصدار الأساسي والثانوي وسماحية تغير إصدار الترقيع فقط أي يُقبَل إصدار 0.27.3 أو 0.27.4 مثلًا. ويمكننا إغلاق الملف package.json الآن بعد الانتهاء من الاطلاع عليه، وفي حال استخدام محرر nano يمكن الخروج بالضغط على CTRL + X ثم ENTER. اعتماديات لازمة أثناء تطوير المشروع اعتماديات التطوير development dependencies هي الاعتماديات التي ستُستخدم فقط خلال مرحلة تطوير المشروع وليس خلال مراحل بناء المشروع ونشره ولا يعتمد عليها خلال مرحلة الإنتاج production وتشبه تلك الدعامات والسلالم والسقالات التي توضع أثناء بناء عمارة ثم تُزال عند الانتهاء، فمثلًا يستخدم المطورون عادة مكتبات لفحص الشيفرات البرمجية وكشف الأخطاء المحتملة وتوحيد تنسيق كتابة الشيفرات أو ما يدعى Linter. لنجرب تثبيت اعتمادية تطوير لتنقيح صياغة الشيفرات تدعى eslint ضمن المشروع بتنفيذ الأمر التالي: npm i eslint@8.0.0 --save-dev نلاحظ إضافة الخيار ‎--save-dev، والذي يخبر npm بحفظ الاعتماديات التي نثبتها كاعتمادية تطوير فقط، لاحظ أيضًا إضافة اللاحقة ‎@8.0.0 بعد اسم الاعتمادية حيث يتم وسم إصدارات المكتبات عند تحديثها، ويدل الرمز @ مدير الحزم npm أن يثبت إصدار معين من تلك الاعتمادية وفي حال تجاهلنا إضافة ذلك الوسم سيتم تثبيت آخر نسخة موسومة متاحة من تلك الاعتمادية، والآن لنعاين ملف package.json مجددًا: nano package.json ونلاحظ تغير محتواه وإضافة اعتمادية التطوير: { "name": "locator", "version": "1.0.0", "description": "Finds the country of origin of the incoming request", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [ "ip", "geo", "country" ], "author": "Hassan hassan@your_domain (https://your_domain)", "license": "ISC", "dependencies": { "axios": "^0.19.0" }, "devDependencies": { "eslint": "^8.0.0" } } نلاحظ إضافة الاعتمادية eslint ضمن الحقل devDependencies مع رقم الإصدار الذي حددناه لها. المجلد node_modules والملف package-lock.json المولدان تلقائيا عند أول تثبيت لأي حزمة ضمن مشروع نود سيُنشئ npm تلقائيًا المجلد node_modules ليُخزن ضمنه كل الوحدات البرمجية التي يحتاج إليها المشروع الحالي، وأيضًا سيُنشئ الملف package-lock.json والذي يحوي معلومات عن تفاصيل إصدارات المكتبات المُثبتة في المشروع، ولنتأكد من وجود تلك الملفات ضمن مجلد المشروع يمكننا ذلك بتنفيذ الأمر ls في سطر الأوامر لعرض الملفات الموجودة وسيظهر لنا التالي: node_modules package.json package-lock.json يحوي المجلد node_modules كافة الاعتماديات المُثبتة في المشروع، وعادة لا نضيف هذا المجلد إلى مستودع المشروع لأن حجم هذا المجلد سيكبر بعد تثبيتنا لعدة اعتماديات، ولأن ملف package-lock.json يحوي داخله أساسًا تفاصيل إصدارات المكتبات المثبتة ضمن مجلد node_modules تمامًا كما هي، ما يجعل وجود ذلك المجلد ضمن مستودع المشروع غير ضروري. ويحوي الملف package.json على قائمة بالاعتماديات المقبولة لاستخدامها ضمن المشروع، بينما يحوي الملف package-lock.json على كل التغييرات التي تحدث على ملف package.json أو مجلد node_modules ويحوي أيضًا على أرقام إصدارات الحزم المثبتة بدقة، ويمكن إضافة هذا الملف إلى مستودع المشروع عادة بدلًا من مجلد node_modules لأن محتواه يعبر عن جميع اعتماديات المشروع بكافة تفاصيلها. تثبيت الاعتماديات باستخدام package.json يمكن باستخدام الملفين package.json و package-lock.json إعداد الاعتماديات المحددة فيهما لبدء أو استئناف العمل على تطوير مشروع مع فريق، ولنفهم ذلك أكثر يمكننا إنشاء مجلد جديد فارغ بجوار مجلد المشروع الحالي بالاسم cloned_locator بتنفيذ الأوامر: cd .. mkdir cloned_locator ثم ننتقل إلى ذلك المجلد: cd cloned_locator ننسخ الآن ملفي package.json و package-lock.json من مجلد المشروع الأصلي locator إلى المجلد الجديد cloned_locator بتنفيذ الأمر: cp ../locator/package.json ../locator/package-lock.json . والآن يمكننا تثبيت نفس اعتماديات المشروع الأصلي بتنفيذ الأمر التالي: npm i سيتحقق بعدها npm من وجود ملف package-lock.json داخل المجلد الحالي، وفي حال عدم وجوده سيقرأ محتويات ملف package.json لمعرفة الاعتماديات المطلوب تثبيتها، وعادة تكون عملية التثبيت أسرع عند وجود ملف package-lock.json لأنه يحوي الأرقام الدقيقة لإصدارات الاعتماديات المطلوبة، ولن يحتاج حينها npm للبحث عن أرقام إصدارات تناسب المشروع. وكما ذكرنا، يمكن تجاهل تثبيت اعتماديات التطوير عند نشر التطبيق في مرحلة الإنتاج، وهي الاعتماديات المذكورة في ملف package.json ضمن الحقل devDependencies ولا تؤثر أبدًا على عمل التطبيق، لذا عند تثبيت المشروع خلال عملية نشر التطبيق يمكن تجاهل تثبيت تلك الاعتماديات بتنفيذ أمر التثبيت كالتالي: npm i --production حيث يشير الخيار ‎--production إلى تجاهل اعتماديات التطوير خلال عملية تثبيت اعتماديات المشروع، ولن نستعمل هذا الخيار إلا في حالات محدَّدة فقط تتعمل بمرحلة بناء المشروع وتجهيزه للنشر على الإنترنت. ولا ننسَ أيضًا العودة إلى مجلد المشروع الأساسي قبل لمتابعة تطبيق باقي الأمثلة: cd ../locator تثبيت الحزم على مستوى النظام ثبتنا حتى الآن الاعتماديات الخاصة بمشروعنا locator، ولكن يمكن استخدام npm أيضًا للتثبيت اعتماديات وحزم على مستوى نظام التشغيل، ما يعني أن الحزمة المثبتة بتلك الطريقة ستكون متاحة للمستخدم في أي مكان ضمن النظام بشكل مشابه للأوامر المتوفرة في سطر الأوامر، حيث تفيد هذه الميزة باستخدام الوحدات البرمجية كأدوات سطر الأوامر لتنفيذ مهام محددة في المشروع، فمثلًا يمكن استخدام مكتبة Hexo من سطر الأوامر من أي مكان بعد تثبيتها لإنشاء موقع لمدونة بمحتوى ثابت، وذلك بتنفيذ أمر التثبيت العام كالتالي: npm i hexo-cli -g كما نلاحظ إذا أردنا تثبيت أي حزمة عامة سنضيف الخيار ‎-g -اختصارًا إلى الكلمة Global عام- لنهاية أمر التثبيت فقط. ملاحظة: قد يظهر خطأ عند محاولة تثبيت حزمة عامة والسبب قد يكون في صلاحيات المستخدم الحالي، لذا قد تحتاج لصلاحيات مستخدم مسؤول وحاول آنذاك فتح الطرفية بصلاحية مسؤول super user أو إذا كنت تستخدم نظام شبيه بيونكس يمكن تنفيذ الأمر كالتالي: sudo npm i hexo-cli -g. ويمكن التأكد من نجاح عملية التثبيت للمكتبة بتنفيذ الأمر التالي: hexo --version سيظهر خرج مشابه للتالي: hexo-cli: 4.3.0 os: linux 5.15.0-35-generic Ubuntu 22.04 LTS 22.04 LTS (Jammy Jellyfish) node: 18.3.0 v8: 10.2.154.4-node.8 uv: 1.43.0 zlib: 1.2.11 brotli: 1.0.9 ares: 1.18.1 modules: 108 nghttp2: 1.47.0 napi: 8 llhttp: 6.0.6 openssl: 3.0.3+quic cldr: 41.0 icu: 71.1 tz: 2022a unicode: 14.0 ngtcp2: 0.1.0-DEV nghttp3: 0.1.0-DEV تعلمنا كيف يمكن تثبيت الوحدات البرمجية الخارجية باستخدام npm، وكيف أنه يمكن تثبيت الحزم محليًا إما كاعتمادية إنتاج أو تطوير، وشاهدنا كيف يمكن تثبيت الحزم باستخدام ملف package.json بمفرده أو مع ملف package-lock.json مجهزة مسبقًا لتوحيد تثبيت إصدارات الاعتماديات للمشروع بين أفراد فريق المطورين، وتعلمنا كيف يمكن تثبيت الحزم عمومًا على النظام باستخدام الخيار ‎-g لنتمكن من استخدامها من أي مكان سواء داخل مشروع نود أو خارجه. والآن بعد ما تعلمناه من طرق لتثبيت الوحدات البرمجية، سنتعلم في الفقرة التالية طرق إدارة تلك الاعتماديات. إدارة الوحدات البرمجية لا يقتصر دور مدير الحزم على تثبيت الوحدات البرمجية بل يتوسع إلى تنفيذ العديد من المهام الأخرى التي تتعلق بإدارة الحزم بعد تثبيتها فمثلًا يحوي npm على أكثر من 20 أمرًا يتعلق بذلك، حيث سنتعرف في هذه الفقرة على بعضها والتي تقوم بما يلي: عرض الوحدات البرمجية المثبتة. ترقية الوحدات البرمجية إلى إصداراتها الأحدث. إلغاء تثبيت الوحدات البرمجية التي لا نحتاج إليها. فحص الوحدات البرمجية لتحديد الثغرات الأمنية وإصلاحها. سنطبق الأوامر المتعلقة بتلك المهام على مجلد مشروعنا locator، ويمكن تنفيذ نفس تلك المهام بشكل عام عبر إضافة الخيار ‎-g في نهاية الأوامر، كما فعلنا عند تثبيت حزمة عامة على مستوى النظام سابقًا. عرض قائمة بالوحدات المثبتة يمكن معرفة الوحدات البرمجية المُثبتة ضمن مشروع ما بتنفيذ الأمر list أو ls الخاص بمدير الحزم npm بدلًا من معاينة الملف package.json يدويًا، وذلك بتنفيذ الأمر كالتالي: npm ls ليظهر لنا خرج مشابه للتالي: ├── axios@0.27.2 └── eslint@8.0.0 يمكن إضافة الخيار ‎--depth لتحديد مستوى عرض شجرة الاعتماديات السابقة، فمثلًا عندما نمرر له القيمة 0 سيظهر لنا الاعتماديات في أول مستوى فقط وهي اعتماديات المشروع الحالي فقط كما لو نفذنا الأمر npm ls دون خيارات إضافية، ويمكن إضافة الخيار ‎--all لعرض شجرة الاعتماديات كاملة كالتالي: npm ls --all ليظهر خرج مشابه للتالي: ├─┬ axios@0.27.2 │ ├── follow-redirects@1.15.1 │ └─┬ form-data@4.0.0 │ ├── asynckit@0.4.0 │ ├─┬ combined-stream@1.0.8 │ │ └── delayed-stream@1.0.0 │ └─┬ mime-types@2.1.35 │ └── mime-db@1.52.0 └─┬ eslint@8.0.0 ├─┬ @eslint/eslintrc@1.3.0 │ ├── ajv@6.12.6 deduped │ ├── debug@4.3.4 deduped │ ├── espree@9.3.2 deduped │ ├── globals@13.15.0 deduped │ ├── ignore@5.2.0 │ ├── import-fresh@3.3.0 deduped │ ├── js-yaml@4.1.0 deduped │ ├── minimatch@3.1.2 deduped │ └── strip-json-comments@3.1.1 deduped . . . ترقية الوحدات البرمجية التحديث الدوري للوحدات البرمجية المستخدمة ضمن المشروع مهم جدًا للحصول على آخر الإصلاحات والتحسينات الأمنية عليها، لذلك يمكن استخدام الأمر outdated لعرض الوحدات البرمجية التي يتوفر لها تحديثات توافق متطلبات المشروع كالتالي: npm outdated سيظهر خرج كالتالي: Package Current Wanted Latest Location Depended by eslint 8.0.0 8.17.0 8.17.0 node_modules/eslint locator يحوي العمود الأول Package من الجدول السابق على أسماء الحزم الممكن ترقيتها، والعمود الثاني Current يُظهر رقم الإصدار الحالي للحزمة المثبتة ضمن المشروع، والعمود Wanted يُظهر رقم آخر إصدار يوافق متطلبات المشروع من الحزمة المطلوب ترقيتها والعمود Latest يُظهر آخر إصدار منشور من تلك الحزمة وقد لا يوافق متطلبات المشروع، والعمود Location يُظهر مسار مجلد الحزمة الحالي، حيث يمكن تمرير الخيار ‎--depth أيضًا للأمر outdated تمامًا كما فعلنا مع الأمر ls، وتكون قيمته الافتراضية هي الصفر. ونجد من الخرج السابق أن الحزمة eslint يمكن ترقيتها إلى إصدار أحدث، لهذا يمكن استخدام أمر الترقية update أو اختصاره up مع ذكر أسماء الحزم التي نرغب بترقيتها كالتالي: npm up eslint سيُظهر لنا خرج هذا الأمر رقم إصدار النسخة الجديدة المثبتة: removed 7 packages, changed 4 packages, and audited 91 packages in 1s 14 packages are looking for funding run `npm fund` for details found 0 vulnerabilities وللتأكد من ذلك يمكننا الاستفادة من الأمر npm ls وتمرير اسم الحزمة eslint ليظهر لنا تفاصيل الحزمة المثبتة ضمن المشروع كالتالي: npm ls eslint نلاحظ عند تمرير اسم حزمة معينة للأمر npm ls ستظهر لنا شجرة الاعتماديات المثبتة ضمن المشروع لكن ستحوي فقط على ما يخص الحزمة المحددة eslint: └─┬ eslint@8.17.0 └─┬ eslint-utils@3.0.0 └── eslint@8.17.0 deduped ويمكن ترقية كل الاعتماديات في المشروع باستخدام أمر الترقية دون تحديد اسم أي حزمة كالتالي: npm up إلغاء تثبيت الوحدات البرمجية يمكن استخدام الأمر uninstall الخاص بمدير الحزم npm لإلغاء تثبيت وحدات من المشروع بإزالة الحزمة أو الوحدة تلك من مجلد node_modules ويُحذف اسم تلك الحزمة من قائمة الاعتماديات ضمن الملف package.json وملف package-lock.json. نضطر في الكثير من الأحيان لإزالة حزم معينة من مشروع نعمل عليه، مثلًا لإزالة حزمة ما بعد تجربتها وتبين أنها لا تحقق المطلوب أو أنها صعبة الاستخدام، فمثلًا لو أن حزمة axios التي نستخدمها لم تفي بالغرض المطلوب منها وهو إرسال طلبات HTTP أو أنها صعبة الاستخدام بالنسبة لهذا المشروع يمكن إلغاء تثبيتها بتنفيذ الأمر uninstall أو اختصاره un وتمرير اسم الحزمة كالتالي: npm un axios نحصل على الخرج: removed 8 packages, and audited 83 packages in 542ms 13 packages are looking for funding run `npm fund` for details found 0 vulnerabilities نلاحظ عدم ظهور اسم الحزمة التي ألغي تثبيتها، لذا نتأكد من ذلك بعرض الحزم المثبتة حاليًا كالتالي: npm ls سنلاحظ من الخرج التالي أن الحزمة eslint أصبحت الوحيدة المثبتة ضمن المشروع، ما يدل على إلغاء تثبيت حزمة axios بنجاح: locator@1.0.0 /home/ubuntu/locator └── eslint@8.17.0 فحص الوحدات وتدقيقها يُستعمل الأمر audit من مدير الحزم npm في تدقيق الحزم وفحصها لعرض المخاطر الأمنية المحتملة ضمن شجرة اعتماديات المشروع المثبتة، ولنختبر ذلك مثلًا بتثبيت إصدار قديم من حزمة request كالتالي: npm i request@2.60.0 وسنلاحظ فورًا عند تثبيت حزم قديمة منتهية الصلاحية ظهور خرج مشابه للتالي: npm WARN deprecated cryptiles@2.0.5: This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial). npm WARN deprecated sntp@1.0.9: This module moved to @hapi/sntp. Please make sure to switch over as this distribution is no longer supported and may contain bugs and critical security issues. npm WARN deprecated boom@2.10.1: This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial). npm WARN deprecated node-uuid@1.4.8: Use uuid module instead npm WARN deprecated har-validator@1.8.0: this library is no longer supported npm WARN deprecated hoek@2.16.3: This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial). npm WARN deprecated request@2.60.0: request has been deprecated, see https://github.com/request/request/issues/3142 npm WARN deprecated hawk@3.1.3: This module moved to @hapi/hawk. Please make sure to switch over as this distribution is no longer supported and may contain bugs and critical security issues. added 56 packages, and audited 139 packages in 4s 13 packages are looking for funding run `npm fund` for details 9 vulnerabilities (5 moderate, 2 high, 2 critical) To address all issues, run: npm audit fix --force Run `npm audit` for details. يخبرنا npm بوجود حزم قديمة يُفضل عدم استخدامها ووجود ثغرات ضمن الاعتماديات الحالية للمشروع، ولعرض تفاصيل أكثر عن ذلك يمكننا تنفيذ الأمر: npm audit سيظهر لنا جدولًا يعرض المخاطر الأمنية الموجودة: # npm audit report bl <1.2.3 Severity: moderate Remote Memory Exposure in bl - https://github.com/advisories/GHSA-pp7h-53gx-mx7r fix available via `npm audit fix` node_modules/bl request 2.16.0 - 2.86.0 Depends on vulnerable versions of bl Depends on vulnerable versions of hawk Depends on vulnerable versions of qs Depends on vulnerable versions of tunnel-agent node_modules/request cryptiles <=4.1.1 Severity: critical Insufficient Entropy in cryptiles - https://github.com/advisories/GHSA-rq8g-5pc5-wrhr Depends on vulnerable versions of boom fix available via `npm audit fix` node_modules/cryptiles hawk <=9.0.0 Depends on vulnerable versions of boom Depends on vulnerable versions of cryptiles Depends on vulnerable versions of hoek Depends on vulnerable versions of sntp node_modules/hawk . . . 9 vulnerabilities (5 moderate, 2 high, 2 critical) To address all issues, run: npm audit fix نلاحظ ظهور مسارات لتلك الثغرات واقتراح npm طرقًا لسدها إما بتحديث تلك الاعتماديات أو تنفيذ الأمر الفرعي fix للأمر audit لإصلاح المشاكل تلقائيًا كما هو مقترح، ولنجرب ذلك الأمر ونرى ما يحصل: npm audit fix يظهر لنا: npm WARN deprecated har-validator@5.1.5: this library is no longer supported npm WARN deprecated uuid@3.4.0: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142 added 19 packages, removed 34 packages, changed 13 packages, and audited 124 packages in 3s 14 packages are looking for funding run `npm fund` for details found 0 vulnerabilities نفذ npm ترقية لحزمتين موجودتين ما أدى لحل المشاكل الأمنية الموجودة، مع ذلك لا زال هناك ثلاث حزم ضمن المشروع قديمة ويفضل عدم استخدامها، وبهذا نرى أن الأمر audit fix لا يُصلح كافة المشاكل الموجودة دومًا، وذلك لأن حل تلك المشاكل يتطلب ترقية الحزم إلى إصدارات أعلى والتي قد تؤدي بدورها إلى حصول تعارض في شجرة الاعتماديات مما يتسبب بمشاكل توقف عمل المشروع كله، ولكن يمكن إجبار npm على ترقية تلك الحزم بتمرير الخيار ‎--force وحل جميع تلك المشاكل كالتالي: npm audit fix --force ولا ينصح بتنفيذ ذلك لما يسببه من مشاكل في التوافقية بين الاعتماديات كما ذكرنا. ختامًا تعلمنا في هذا القصل طريقة ترتيب نود للوحدات البرمجية ضمن حزم، وكيف يدير مدير حزم نود npm تلك الحزم، وكيف أن المشاريع في نود تستخدم الملف package.json لتعريف اعتماديات المشروع وإدارتها بالإضافة إلى تخزين بيانات تصف المشروع نفسه. واستخدمنا أمر npm من سطر الأوامر لتثبيت وترقية وإزالة الوحدات البرمجية وعرض شجرة الاعتماديات للمشروع وللتحقق من إمكانية ترقية الوحدات البرمجية القديمة، وهدف كل ذلك إعادة استخدام الوحدات البرمجية بين المشاريع بدلًا من إعادة كتابتها لتسريع عملية تطوير، حيث يمكنك الآن كتابة الوحدات البرمجية الخاصة بك ومشاركتها مع الآخرين لاستخدامها في مشاريعهم الخاصة، ويمكنك التدرب على ما تعلمته في هذا المقال بالبحث عن بعض الحزم التي تخدم مشكلة ما تحاول حلها وتثبيتها واختبارها، فمثلًا يمكنك تجربة استخدام TypeScript لإضافة مزايا على لغة جافاسكربت، أو تحويل موقع ويب تعمل عليه إلى تطبيق جوال باستخدام Cordova. ترجمة -وبتصرف- للمقال How To Use Node.js Modules with npm and package.json لصاحبه Stack Abuse. اقرأ أيضًا مقدمة إلى Node.js دليلك الشامل إلى مدير الحزم npm في Node.js تعرف على وحدات Node.js الأساسية
  11. الجهة الوحيدة المخولة بمساعدتك في أي أمر يتعلق بالدورات والاشتراك هم فريق مركز المساعدة، ابقَ على تواصل معهم، وأحيانًا قد يستغرق الأمر وقتًا ريثما يتم الرد ومساعدتك تأكد أنك تتواصل معهم من نفس الحساب، اشرح مشكلتك بالكامل وانتظر الرد سيساعدونك في النهاية مهما كانت مشكلتك لا تقلق
  12. يمكن حل المشكلة بزيادة حجم الذاكرة المسموح استخدامها أو إلغاء ذلك الحد، عبر تعيين قيمة الخاصية memory_limit إما بقيمة حجم حد معين، مثلًا 256M أي 256 ميجابايت، أو بتعيينها بالقيمة 1- والتي تعني إلغاء ذلك الحد، يمكنك تعيين تلك القيمة لتُطبق بشكل عام ضمن ملف php.ini لنسخة PHP التي تستخدمها بتعديل القيمة لقيمة مناسبة كالتالي: memory_limit = 256M أو يمكنك إلغاء الحد من الذاكرة ضمن تطبيقك فقط باستخدام التابع ini_set وتمرير قيمة مناسبة بإضافة السطر التالي لبداية الملف: ini_set('memory_limit', '-1');
  13. يوجد العديد من أنواع الموصلات في الشبكات، بدءًا بالأسلاك ومنها النحاسية وحديثًا الأسلاك الضوئية Fiber obtics، لكل منها مزايا مثل عرض الحزمة الكبير للأسلاك الضوئية ما يجعلها مناسبة لمد خطوط الشبكات بين الدول، مرورًا بالتجهيزات المستخدمة على تلك الشبكات، كالمبدلات Switches المستخدمة لربط عدة أجهزة ضمن شبكة محلية LAN، مرورًا بالموجهات Routers والتي تشبك عدة شبكات محلية مع بعضها أو مع شبكة الانترنت، وتختلف أنواع الشبكات بحسب طريقة تصنيفها، مثلًا بتصنيف شكل توصيلها يوجد الشبكات التسلسلية والنجمية والحلقية وغيرها، أما من الناحية الجغرافية يمكن تقسيمها لشبكة محلية LAN أو شبكة واسعة WAN، لمعرفة المزيد عن هذه الأمور يمكنك الاستفادة من قراءة المقالات التالية:
  14. يعتمد هذا المجال على الرياضيات والإحصاء في بناء نماذج تعلم الآلة والذكاء الاصطناعي، لذا يجب معرفة الأساسيات في هذا المجال من الجبر الخطي والاحتمالات والإحصاء، بعدها يمكن الدخول إلى المجال وتعلم النماذج البسيطة والتي تتطلب معرفة في عدد من مجالات الرياضيات وعلوم الحاسوب من خوارزميات وبرمجة لتنفيذ تلك النماذج، يمكنك الاستفادة من قراءة كتاب "مدخل إلى الذكاء الاصطناعي وتعلم الآلة" والذي يعتبر أفضل بداية للمبتدئين للتعرف على هذا المجال وتفرعاته والاختصاصات الموجودة وغاية كل منها، حيث سيوضح الصورة العامة ومنها يمكنك التخصص وقراءة المزيد عم المجال الذي ترغبين التوسع فيه: كما يوجد العديد من المقالات المنشورة التي تغطي عدة أفكار ومواضيع عن الذكاء الاصطناعي، يمكنك الاستفادة منها أيضًا:
  15. شيفرة جافاسكربت للحل المذكور كاملة: let task_title = document.getElementById("txt"); let submit_btn = document.getElementById("submit_todo"); let availabel = document.querySelector(".availabel"); submit_btn.onclick = function (e) { e.preventDefault(); if (task_title.value != "") { let ul = document.getElementById("ul"); let li = document.createElement("li"); li.classList.add("singil_task"); let p = document.createElement("p"); p.textContent = task_title.value; let div_icons = document.createElement("div"); div_icons.classList.add("icons"); div_icons.innerHTML = ` <i class="fa-circle-check fa-sharp fa-solid "></i> <i class="fa-circle-xmark fa-solid" style="padding-left: 15px;"></i> `; li.appendChild(p); li.appendChild(div_icons); ul.appendChild(li); task_title.value = ""; let cheak = li.querySelector(".fa-circle-check"); cheak.addEventListener("click", function () { li.classList.add("done"); }); let deleteBtn = li.querySelector(".fa-circle-xmark"); deleteBtn.addEventListener("click", function () { li.remove(); }); } else { let h5 = document.createElement("h5"); h5.textContent = "please enter a task"; h5.style.color = "red"; h5.style.display = "block"; h5.style.padding = "10px"; availabel.appendChild(h5); setTimeout((_) => { h5.remove(); }, 1000); } };
  16. كما ذكرت لك بنفس طريقة برمجة وظيفة زر انهاء المهمة، يجب تحديد زر الحذف وإضافة معالج للحدث click له، بداخله يمكنك حذف عنصر li للمهمة كالتالي: submit_btn.onclick = function (e) { let li = document.createElement("li"); // انشاء عنصر المهمة // .. let deleteBtn = li.querySelector(".fa-circle-xmark"); // تحديد زر الحذف deleteBtn.addEventListener("click", function () { li.remove(); // حذف عنصر المهمة }); //.. }
  17. المشكلة لديك أنك تحاول الاستعلام عن زر انتهاء المهمة عند تحميل الصفحة مباشرة، وذلك غير ممكن لأن المهمة لم تٌضاف بعد، الحل يكون بإنشاء عنصر li داخل حدث الاضافة وليس خارجه، والاستعلام عن زر انهاء المهمة بعد ذلك مباشرة، ثم ربط الحدث click وليس onclick به لتنفيذ عملية الانهاء كالتالي: submit_btn.onclick = function (e) { let li = document.createElement("li"); // انشاء عنصر المهمة ... let cheak = li.querySelector(".fa-circle-check"); // الاستعلام عن الأزرار cheak.addEventListener("click", ..); // ربط حدث ضغط الزر // بنفس الطريقة لزر الحذف // .. }
  18. حلقة اقرأ-قيِّم-اطبع أو REPL -اختصارًا للعبارة Read Evaluate Print Loop- هي صدفة تفاعلية interactive shell تعالج تعابير جافاسكربت البرمجية ضمن بيئة نود، حيث تقرأ تلك الصدفة الشيفرات التي يدخلها المستخدم وتُصرّفها ثم تُقيّم نتيجتها وتطبع تلك النتيجة للمستخدم على الشاشة آنيًا، وتكرر ذلك لحين خروج المستخدم من تلك الصدفة، وتأتي REPL مثبتة مسبقًا مع نود، وتسمح لنا باختبار واستكشاف شيفرات جافاسكربت داخل بيئة نود بسرعة ودون الحاجة لحفظها أولًا داخل ملف ثم تنفيذها، وسيلزمك في هذا الفصل للمتابعة معرفة بأساسيات لغة جافاسكريبت، ولبيئة نود مُثبتة على الجهاز. الدخول والخروج من الوضع REPL بعد تثبيت نود على جهازك، سيكون وضع حلقة REPL متاحًا للاستخدام مباشرةً، وللدخول إليه ننفذ الأمر node فقط ضمن سطر الأوامر كالتالي: node سيدخلنا ذلك في وضع التفاعلي: > حيث يشير الرمز ‎>‎ في بداية السطر لإمكانية إدخالنا شيفرات جافاسكربت لتُعالج، ويمكننا تجربة ذلك بجمع عددين كالتالي: > 2 + 2 نضغط زر الإدخال ENTER لتُقيّم صدفة نود ذلك التعبير البرمجي وتطبع نتيجته مباشرةً: 4 للخروج من ذلك الوضع يمكننا إما كتابة الأمر ‎.exit أو الضغط من لوحة المفاتيح على الاختصار CTRL+D، أو الضغط مرتين على الاختصار CTRL+C، للخروج والعودة إلى سطر الأوامر. والآن بعد أن علمنا طريقة الدخول والخروج من الوضع REPL، سنتعلم طريقة تنفيذ بعض شيفرات جافاسكربت البسيطة ضمنه. تنفيذ شيفرة جافاسكربت ضمن REPL يمنحنا الوضع REPL التفاعلي طريقة سريعة لاختبار شيفرات جافاسكربت فورًا، ودون الحاجة لإنشاء ملف لها أولًا، حيث يمكننا تنفيذ أي تعبير برمجي سليم يمكن تنفيذه عادةً ضمن بيئة نود، إذ اختبرنا في المثال السابق جمع عددين، ولنختبر الآن تنفيذ قسمة عددين. ندخل أولًا إلى الوضع REPL كما تعلمنا: node ونُدخل التعبير البرمجي ونضغط زر الإدخال لتنفيذه: > 10 / 5 نحصل على الخرج التالي وهو ناتج العملية السابقة: 2 يمكن أيضًا مثلًا تنفيذ العمليات على السلاسل النصية، ولنختبر ذلك بتنفيذ ضم سلسلتين نصيتين كالتالي: > "Hello " + "World" وسيظهر لنا نتيجة ضم السلسلتين: 'Hello World' نلاحظ ظهور النص في النتيجة محاطًا بعلامات اقتباس مفردة بدلًا من علامات الاقتباس المزدوجة، ففي جافاسكربت لا يؤثر نوع علامات الاقتباس على قيمة السلسلة النصية، لذا يستخدم الوضع REPL عند إظهار نتيجة فيها سلسلة نصية علامات الاقتباس المفردة دومًا. استدعاء التوابع يستخدم التابع العام console.log أو توابع طباعة الرسائل المشابهة له كثيرًا في بيئة نود، حيث يمكننا داخل REPL استدعاء التوابع أيضًا، فلنجرب مثلًا أمر طباعة رسالة كالتالي: > console.log("Hi") سيُستدعى التابع وتظهر نتيجة التنفيذ التالية: Hi undefined يمثل السطر الأول نتيجة استدعاء التابع console.log، والذي يطبع الرسالة إلى المجرى stdout والذي يمثل الشاشة، ولأن الوظيفة طباعة وليس إعادة عبر التعبير return كنتيجة لتنفيذ التابع، نلاحظ عدم وجود علامات الاقتباس حولها، بينما السطر الثاني يعرض القيمة undefined وهي النتيجة التي أعادها التابع بعد انتهاء تنفيذه. تعريف متغيرات تُستخدم المتغيرات variables أيضًا بكثرة خلال كتابتنا للشيفرات البرمجية ولا نكتفي بالتعامل مع القيم مباشرة، لذا يتيح لنا REPL إمكانية تعريف المتغيرات تمامًا كما لو كنا نكتبها ضمن ملفات جافاسكريبت، ويمكننا اختبار ذلك كالتالي: > let age = 30 تظهر لنا النتيجة التالية بعد ضغط زر الإدخال: undefined كما لاحظنا سابقًا عند استدعاء التابع console.log كانت القيمة التي يعيدها هي undefined، وهنا أيضًا جرى تعريف المتغير age ولم نُعد أي قيمة، وسيكون ذلك المتغير متاحًا حتى الانتهاء والخروج من جلسة REPL الحالية، ولاختبار ذلك نستخدم المتغير age ضمن عملية ما ولتكن ضربه بعدد كالتالي: > age * 2 تظهر لنا نتيجة العملية بعد الضغط على زر الإدخال: 60 نلاحظ أن REPL يعيد ويطبع لنا نتيجة التعبير البرمجي فورًا، لذا لا نحتاج لاستخدام التابع console.log في كل مرة نريد طباعة قيمة على الشاشة، حيث سيطبع تلقائيًا أي قيمة يعيدها الأمر المُدخل. إدخال الشيفرات متعددة الأسطر يدعم REPL أيضًا إدخال الشيفرات متعددة السطر، ولنختبر ذلك ننشئ تابعًا يضيف القيمة 3 إلى العدد المُمرر له، ونبدأ تعريفه بإدخال أول سطر منه كالتالي: const add3 = (num) => { وبعد الضغط على زر الإدخال ستلاحظ تغير الرمز < في أول السطر إلى رمز النُقط الثلاث: ... يلاحظ REPL وجود قوس معقوص } في نهاية الأمر المدخل، ما يشير إلى وجود بقية له، فيتم إضافة هامش من النقط وانتظار إدخالنا لباقي الأمر، وذلك لتسهيل القراءة حيث يضيف REPL ثلاث نقط ومسافة في السطر التالي، ليبدو أن الشيفرة يسبقها مسافة بادئة، ونكمل إدخال سطر جسم الدالة، ثم سطر قوس الإغلاق لإنهاء تعريف التابع، ونضغط زر الإدخال بعد كل سطر منها: return num + 3; } وبعد إدخال آخر سطر الحاوي على قوس الإغلاق للتابع، ستظهر لنا القيمة undefined، والتي تدل على القيمة المُرجعة من أمر إسناد الدالة إلى الثابت، ونلاحظ عودة الرمز في بداية السطر إلى رمز إدخال الأوامر ‎>‎ بدلًا من النقط ...، وتظهر لنا قيمة الأمر المُدخل: undefined > يمكننا الآن استخدام الدالة التي عرفناها add3()‎ بتمرير قيمة لها كالتالي: > add3(10) ويظهر لنا نتيجة الإضافة التي تعيدها الدالة كالتالي: 13 يمكن الاستفادة من REPL في تجربة شيفرات جافاسكربت واللعب بها قبل إضافتها إلى النظام أو المشروع الذي نعمل عليه، حيث يوفر REPL اختصارات تساعدنا خلال تلك العملية سنتعرف عليها في الفقرة التالية. التعرف على الاختصارات في REPL يوفر REPL عدة اختصارات تسهل عملية ادخال الشيفرات وتوفر الوقت، فمثلًا يحفظ REPL -كما معظم الصدفات- سجلًا بالأوامر المدخلة السابقًا لنتمكن من الرجوع إليها بدلًا من إعادة إدخالها يدويًا مرة أخرى. جرب مثلًا كتابة القيمة النصية الطويلة التالية: > "The answer to life the universe and everything is 32" يظهر لنا النص نفسه كنتيجة لذلك الأمر: 'The answer to life the universe and everything is 32' الآن إذا أردنا إدخال النص السابق نفسه، لكن مع اختلاف وهو تبديل العدد 32 إلى 42، فيمكننا ذلك عبر الضغط على مفتاح السهم العلوي UP من لوحة المفاتيح للوصول إلى آخر قيمة أدخلناها: > "The answer to life the universe and everything is 32" بعدها يمكننا تحريك المؤشر داخل النص وإزالة العدد 3 وتبديله إلى العدد 4 ونضغط زر الإدخال ENTER مجددًا: 'The answer to life the universe and everything is 42' يمكن بالضغط المستمر على السهم العلوي UP الرجوع في سجل تاريخ الأوامر المدخلة سابقًا واحد تلو الآخر، وبالمقابل يمكن الضغط على مفتاح السهم السفلي DOWN للتقدم إلى الأمام في سجل تاريخ الأوامر، ويمكن بعد الانتهاء من تفحص سجلات الأوامر المُخزنة الضغط مرارًا على مفتاح السهم السفلي DOWN إلى حين العودة إلى سطر الإدخال الفارغ لكتابة أمر جديد، ويمكن الوصول إلى قيمة آخر نتيجة عبر محرف الشرطة سفلية _، ولاختبار ذلك نكتب الرمز _ ثم نضغط على زر الإدخال: > _ سيظهر لنا السلسلة النصية التي أدخلناها مؤخرًا: 'The answer to life the universe and everything is 42' يتيح REPL أيضًا ميزة الإكمال التلقائي للتوابع والمتغيرات والكلمات المفتاحية أثناء كتابة الشيفرة، فمثلًا إذا أردنا استخدام التابع العام Math.sqrt لحساب الجذر التربيعي لعدد يمكننا فقط كتابة الأحرف الأولى لذلك الاستدعاء كالتالي مثلًا: > Math.sq ثم الضغط على زر الجدولة تاب TAB ليكمل لنا REPL كتابة باقي اسم التابع بشكل صحيح كالتالي: > Math.sqrt وعندما يكون هناك أكثر من طريقة لإكمال الأمر، سيظهر لنا جميع الاحتمالات الممكنة، فمثلًا إذا حاولنا استدعاء تابع ما من الصنف Math كالتالي: > Math. بالضغط على زر الجدولة مرتين سيظهر لنا جميع الاحتمالات الممكنة للإكمال التلقائي لذلك الأمر: > Math. Math.__defineGetter__ Math.__defineSetter__ Math.__lookupGetter__ Math.__lookupSetter__ Math.__proto__ Math.constructor Math.hasOwnProperty Math.isPrototypeOf Math.propertyIsEnumerable Math.toLocaleString Math.toString Math.valueOf Math.E Math.LN10 Math.LN2 Math.LOG10E Math.LOG2E Math.PI Math.SQRT1_2 Math.SQRT2 Math.abs Math.acos Math.acosh Math.asin Math.asinh Math.atan Math.atan2 Math.atanh Math.cbrt Math.ceil Math.clz32 Math.cos Math.cosh Math.exp Math.expm1 Math.floor Math.fround Math.hypot Math.imul Math.log Math.log10 Math.log1p Math.log2 Math.max Math.min Math.pow Math.random Math.round Math.sign Math.sin Math.sinh Math.sqrt Math.tan Math.tanh Math.trunc بحيث تظهر النتيجة السابقة بتنسيق يناسب حجم نافذة سطر الأوامر من ناحية عدد الأعمدة والأسطر لتلك الاحتمالات، وتمثل تلك الاحتمالات جميع التوابع أو الخاصيات المتاحة ضمن الوحدة Math. يمكننا في أي وقت الحصول على سطر فارغ جديد لإدخال الأوامر بالضغط على الاختصار CTRL+C، وذلك دون تنفيذ الأمر الجاري كتابته في السطر الحالي. إن معرفة الاختصارات السابقة يزيد من كفاءة وسرعة كتابة الشيفرات داخل REPL، كما يحتوي أيضًا على أوامر تزيد الإنتاجية سنتعرف عليها في الفقرة التالية. أوامر REPL يوفر REPL كلمات مفتاحية خاصة تساعدنا في التحكم به، ويبدأ كل من تلك الأوامر برمز النقطة . سنتعرف عليها. الأمر ‎.help لعرض كل الأوامر المتاحة ضمن REPL يمكننا استخدام الأمر ‎.help كالتالي: > .help سيظهر لنا جميع الأوامر المتاحة الخاصة بالوضع REPL وهي قليلة لكن مفيدة: .break Sometimes you get stuck, this gets you out .clear Alias for .break .editor Enter editor mode .exit Exit the repl .help Print this help message .load Load JS from a file into the REPL session .save Save all evaluated commands in this REPL session to a file Press ^C to abort current expression, ^D to exit the repl يفيد تنفيذ هذا الأمر في حال نسيان الأوامر المتاحة أو وظيفة كل منها. الأمران ‎.break و ‎.clear تظهر فائدة الأمران ‎.break و ‎.clear خلال كتابتنا الشيفرة متعددة الأسطر إذ تساعد على الخروج من ذلك الوضع، ولنختبر ذلك بكتابة أول سطر من حلقة التكرار for كالتالي: for (let i = 0; i < 100000000; i++) { بدلًا من إكمال كتابة أسطر ذلك الأمر يمكننا تنفيذ الأمر ‎.break أو الأمر ‎.clear للخروج: .break سيظهر لنا الرمز > من جديد، ونلاحظ أن REPL استجاب لهذا الأمر وانتقل إلى سطر جديد فارغ دون تنفيذ الشيفرة التي كنا نحاول إدخالها تمامًا كما لو أننا ضغطنا على الاختصار CTRL+C. الأمران ‎.save و ‎.load يُمكّننا الأمر ‎.save من حفظ كافة الشيفرات التي أدخلناها منذ بداية جلسة REPL الحالية إلى ملف جافاسكربت، بالمقابل يُمكّننا الأمر ‎.load من تنفيذ شيفرات جافاسكربت من ملف خارجي داخل REPL، وذلك بدلًا من كتابة تلك الشيفرات يدويًا، و لاختبار ذلك نخرج أولًا من الجلسة الحالية إما بتنفيذ الأمر ‎.exit أو باستخدام الاختصار CTRL+D، ونبدأ جلسة REPL جديدة بتنفيذ الأمر node، حيث ستحفظ كل الشيفرات التي سنقوم بكتابتها منذ الآن داخل الملف عند استخدامنا لأمر الحفظ ‎.save لاحقًا. نُعرّف مصفوفة من الفواكه: > fruits = ['banana', 'apple', 'mango'] في سطر النتيجة سيظهر: [ 'banana', 'apple', 'mango' ] نحفظ الآن المتغير السابق إلى ملف جديد بالاسم fruits.js كالتالي: > .save fruits.js ستظهر رسالة تؤكد حفظ الملف بنجاح: Session saved to: fruits.js مكان حفظ ذلك الملف هو نفس مسار المجلد الذي بدأنا منه جلسة REPL من سطر الأوامر، فمثلًا لو كان مسار سطر الأوامر عندها هو مجلد المنزل home للمستخدم، فسيُحفَظ الملف داخل ذلك المجلد. والآن نخرج من الجلسة الحالية ونبدأ جلسة جديدة بتنفيذ الأمر node مرة أخرى، ونُحمِّل ملف fruits.js الذي حفظناه سابقًا بتنفيذ الأمر ‎.load كالتالي: > .load fruits.js ليظهر لنا: fruits = ['banana', 'apple', 'mango'] [ 'banana', 'apple', 'mango' ] قرأ الأمر ‎.load كل سطر داخل ذلك الملف ونفذه تمامًا كطريقة عمل مفسر جافاسكربت، حيث أصبح بإمكاننا الآن استخدام المتغير fruits كما لو أننا أدخلناه سابقًا يدويًا ضمن الجلسة الحالية، ولنختبر ذلك ونحاول الوصول لأول عنصر من تلك المصفوفة: > fruits[1] نحصل على الخرج المتوقع: 'apple' ويمكن تحميل أي ملف جافاسكربت باستخدام الأمر ‎.load مهما كان، وليس فقط الملفات التي نحفظها، لنختبر ذلك بكتابة ملف جافاسكربت بسيط نُنشئ ملفًا جديدًا ونفتحه باستخدام محرر النصوص: nano peanuts.js ثم ندخل ضمنه الشيفرة التالية ونحفظ التغييرات: console.log('I love peanuts!'); نبدأ جلسة REPL جديدة من نفس مسار المجلد الحاوي على ملف جافاسكربت peanuts.js الجديد بتنفيذ الأمر node، ونُحمِّل الملف إلى الجلسة الحالية بتنفيذ التالي: > .load peanuts.js سيُنفذ الأمر ‎.load التعبير البرمجي console ضمن ذلك الملف ويُظهر الخرج: console.log('I love peanuts!'); I love peanuts! undefined > تظهر فائدة كلا الأمرين ‎.save و ‎.load عند كتابة الكثير من الشيفرات داخل REPL أو عندما نريد حفظ ما أدخلناه خلال الجلسة الحالية ومشاركته ضمن ملف جافاسكربت. ختامًا تتيح لنا بيئة REPL التفاعلية تنفيذ شيفرات جافاسكربت دون الحاجة لإنشاء ملف لها أولًا، كتنفيذ التعابير البرمجية واستدعاء التوابع وتعريف المتغيرات، وتوفر العديد من الاختصارات والأوامر والمزايا الداعمة لتلك العملية، كتنسيق النص تلقائيًا للأوامر متعددة الأسطر، وسجل بتاريخ الأوامر المُدخلة، إلى أوامر المسح أو الحفظ والتحميل، يضيف لك تعلم REPL مهارة قد تحتاج إليها خلال عملك في وقت ما. ترجمة -وبتصرف- للمقال How To Use the Node.js REPL لصاحبه Stack Abuse. اقرأ أيضًا المقال السابق: كتابة أول برنامج في بيئة Node.js وتنفيذه مقدمة إلى Node.js بيئة Node.js: استخدام جافاسكربت خارج المتصفح البرمجة غير المتزامنة في Node.js
  19. Node.js -تُلفظ نود جي إس- هو بيئة تشغيل جافاسكربت مفتوحة المصدر تتيح تنفيذ شيفرات جافاسكربت خارج المتصفح، وذلك باستخدام محرك جافاسكربت V8 الشهير المُستخدم ضمن متصفح جوجل كروم، ومن أشهر استخدامات هذه البيئة هو تطوير تطبيقات وخوادم الويب وحتى أدوات سطر الأوامر، وتوفر لنا هذه البيئة كتابة شيفرات الواجهات الأمامية Front-end والواجهات الخلفية Back-end بلغة برمجة واحدة وهي جافاسكربت، كما تتيح لنا ذلك توحيد لغة البرمجة ضمن طبقات المشروع كافة ما يزيد التركيز ويوفر إمكانية لاستخدام نفس المكتبات ومشاركة الشيفرة بين الواجهات الأمامية بطرف العميل والواجهة الخلفية على الخادم. تتميز بيئة نود بطريقة التنفيذ الغير متزامنة asynchronous execution ما يمنحها قوة وأفضلية بالأداء في تنفيذ المهام التي تتطلب غزارة في الدخل والخرج ضمن في تطبيقات الويب أو تطبيقات الزمن الحقيقي، كتطبيقات بث الفيديو أو التطبيقات التي تحتاج لإرسال واستقبال مستمر للبيانات. سنكتب في هذا الفصل معًا برنامجنا الأول في بيئة تشغيل نود، وسنتعرف على بعض المفاهيم في تلك البيئة التي ستساعدنا في تطوير برنامج يتيح للمستخدم معاينة متغيرات البيئة على النظام لديه، ولتنفيذ ذلك سنتعلم طباعة السلاسل النصية إلى الطرفية console، واستقبال الدخل من المستخدم، ثم الوصول لمتغيرات البيئة environment variables على النظام. المستلزمات معرفة أساسيات لغة جافاسكربت، حيث يمكنك الاطلاع على سلسلة دليل تعلم جافاسكربت وفيها 90 مقالًا ستساعدك على تعلم أساسيات اللغة كاملةً. بيئة نود مثبتة على جهازك، حيث استخدمنا في هذا الفصل الإصدار رقم 16.15.1، ويمكنك الاطلاع على مقال تثبيت Node.js على نظام أبونتو للتعرف على طريقة تثبيتها. ملاحظة: قد يختلف الإصدار الحالي لديك عن الإصدار الذي استعملناه، ولن تكون هنالك اختلافات أو مشاكل تذكر أثناء تطبيق الأمثلة والشيفرات ولكن إن حصلت إلى خطأ متعلق بتنفيذ شيفرة مطابقة تمامًا لشيفرة شرحناها فتأكد من اختلاف الإصدارات آنذاك وإن كانت المشكلة مرتبطة بها. الخطوة الأولى - الطباعة إلى الطرفية المهمة الأولى للمبرمج عند تعلمه للغة برمجة أو تجربة بيئة جديدة هي كتابة برنامج لطباعة عبارة "أهلًا بالعالم!" أو "‎Hello, World!‎"، لذا نبدأ بإنشاء ملف جديد نسميه"hello.js" ونفتحه ضمن أي برنامج محرر نصوص تريد كبرنامج المُفكرة Notepad مثلًا، سنستخدم في هذا المقال المحرر nano من سطر الأوامر كالتالي: nano hello.js نكتب الشيفرة التالية داخله ونحفظ الملف: console.log("Hello World"); يوفر الكائن console في بيئة نود في السطر السابق توابعًا تمكننا من الكتابة إلى مجاري الخرج مثل مجرى الخرج القياسي stdout أو إلى مجرى الخطأ القياسي stderr وغيرهما والتي عادةً تمثل سطر الأوامر، ويطبع التابع log القيم المُمررة له إلى المجرى stdout لتظهر لنا في الطرفية، حيث أن المجاري في نود هي إما كائنات تستقبل بيانات مثل المجرى stdout، أو تُخرج بيانات كمقبس شبكة أو ملف، وأي بيانات تُرسل إلى المجرى stdout أو stderr ستظهر مباشرًة في الطرفية، ومن أهم مزايا المجاري سهولة إمكانية إعادة توجيهها، كتوجيه خرج تنفيذ برنامج ما إلى ملف أو إلى برنامج آخر، والآن وبعد التأكد من حفظ الملف والخروج من محرر النصوص، حيث إذا كنت تستخدم nano اضغط على CTRL+X للخروج واضغط Y عند سؤالك عن حفظ الملف، وبهذا يكون البرنامج الذي كتبناه جاهزًا للتنفيذ. الخطوة الثانية - تشغيل البرنامج نستخدم الأمر node لتشغيل البرنامج السابق كالتالي: node hello.js سيتم تنفيذ شيفرات البرنامج داخل ملف hello.js ويظهر الناتج ضمن الطرفية: Hello World ما حدث هو أن مفسر نود قرأ الملف ونفذ التعليمة ‎console.log("Hello World");‎ عبر استدعاء التابع log من الكائن العام console، الذي مررنا له السلسلة النصية "Hello World" كوسيط، ونلاحظ عدم طباعة علامات الاقتباس التي مررناها على الشاشة، لأنها ضرورية ضمن الشيفرة فقط لتحديد النص كسلسلة نصية، والآن بعد أن نفذنا برنامجنا البسيط السابق بنجاح، سنطوره ليصبح أكثر تفاعلية. الخطوة الثالثة - استقبال الدخل من المستخدم عبر وسائط سطر الأوامر يُظهر البرنامج السابق نفس الخرج كل مرة عند تنفيذه، لذا ولجعل الخرج متغيرًا يمكننا جلب المدخلات من المستخدم وعرضها على الشاشة كما هي، وهذا هو مبدأ عمل أدوات سطر الأوامر، حيث أنها تقبل من المستخدم عددًا من الوسائط التي تحدد طريقة عمل البرنامج مثال على ذلك الأمر node نفسه، حيث أنه يقبل الوسيط ‎--version ليطبع عندها رقم إصدار بيئة نود المثبتة على الجهاز بدلًا من تشغيل مفسر البرامج. سنقوم بالتعديل على برنامجنا ليستقبل الدخل من المستخدم عن طريق وسائط سطر الأوامر، لهذا نُنشئ ملفًا جديدًا بالاسم arguments.js: nano arguments.js ونكتب داخله الشيفرة التالية ونحفظ الملف: console.log(process.argv); يحوي الكائن العام process في نود على توابع وبيانات تتعلق بالإجرائية الحالية، والخاصية argv ضمنه هي مصفوفة سلاسل نصية تُمثل عناصرها وسائط سطر الأوامر المٌمررة للبرنامج عند تنفيذه، وأصبح بإمكاننا الآن تمرير عدة وسائط إلى البرنامج أثناء تنفيذه كالتالي: node arguments.js hello world لنحصل على الخرج: [ '/usr/bin/node', '/home/hassan/first-program/arguments.js', 'hello', 'world' ] يمثل أول وسيط ضمن المصفوفة process.argv مسار الملف التنفيذي لنود الذي نفَّذ البرنامج، بينما الوسيط الثاني هو مسار ذلك البرنامج، والوسائط البقية تمثل الوسائط التي أدخلها المستخدم في حالتنا هي كلمة hello وكلمة world، وهي عادةً ما يهمنا عند التعامل مع الوسائط المُمررة للبرنامج وليس الوسائط التي يمررها نود افتراضيًا. الآن نفتح ملف البرنامج arguments.js مجددًا لنعدل عليه: nano arguments.js ونحذف التعليمة السابقة ونضع بدلًا منها التعليمة التالية: console.log(process.argv.slice(2)); بما أن الخاصية argv هي مصفوفة Array، يمكننا الاستفادة من التوابع المتاحة ضمن المصفوفات في جافاسكربت، مثل التابع slice لنختار العناصر التي نريدها فقط من المصفوفة، فنمرر له العدد 2 كوسيط لنحصل على كافة عناصر المصفوفة argv بعد العنصر الثاني والتي تمثل الوسائط التي مررها المستخدم بالضبط. نعيد تنفيذ البرنامج كما نفذناه آخر مرة ونلاحظ الفرق: node arguments.js hello world سيظهر لنا الخرج التالي: [ 'hello', 'world' ] بعد أن أصبح البرنامج يستقبل الدخل من المستخدم، سنطوره الآن ليعرض لنا متغيرات البيئة المتاحة للبرنامج. الخطوة الرابعة - الوصول لمتغيرات البيئة سنعرض في هذه الخطوة متغيرات البيئة environment variables المتاحة في النظام وقيمها باستخدام الكائن العام process.env ونطبعها في الطرفية، فمتغيرات البيئة هي بيانات على شكل مفتاح وقيمة key/value مُخزَّنة خارج البرنامج يوفرها نظام التشغيل، حيث يتم تعيين قيمها إما من قبل النظام أو المستخدم، وتكون متاحة لجميع الإجرائيات لاستخدامها كطريقة لضبط إعدادات البرامج أو حالتها أو طريقة عملها، ويمكننا الوصول إليها عن طريق الكائن العام process. نُنشئ ملفًا جديدًا بالاسم environment.js: nano environment.js ونكتب داخله الشيفرة التالية ونحفظ الملف: console.log(process.env); يحوي الكائن env على متغيرات البيئة المتاحة لحظة تشغيل نود للبرنامج. ننفذ الآن البرنامج الجديد: node environment.js نلاحظ ظهور خرج مشابه للتالي: { SHELL: '/bin/bash', SESSION_MANAGER: 'local/hassan-laptop:@/tmp/.ICE-unix/1638,unix/hassan-laptop:/tmp/.ICE-unix/1638', WINDOWID: '0', QT_ACCESSIBILITY: '1', COLORTERM: 'truecolor', XDG_CONFIG_DIRS: '/home/hassan/.config/kdedefaults:/etc/xdg/xdg-plasma:/etc/xdg:/usr/share/kubuntu-default-settings/kf5-settings', GTK_IM_MODULE: 'ibus', LANGUAGE: 'en_US:ar', SSH_AGENT_PID: '1427', PWD: '/home/hassan/first-program', LOGNAME: hassan, HOME: '/home/hassan', IM_CONFIG_PHASE: '1', LANG: 'en_US.UTF-8', LESSCLOSE: '/usr/bin/lesspipe %s %s', TERM: 'xterm-256color', USER: 'hassan', PATH: '/home/hassan/.nvm/versions/node/v16.15.1/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin', DBUS_SESSION_BUS_ADDRESS: 'unix:path=/run/user/1000/bus', OLDPWD: '/', _: '/home/hassan/.nvm/versions/node/v16.15.1/bin/node' } القيم الظاهرة في متغيرات البيئة في الخرج السابق تعتمد بغالبها على إعدادات نظام التشغيل، لذا ستلاحظ وجود فرق في الخرج عند محاولتك لتنفيذ البرنامج، والآن بدلًا من عرض قائمة بكافة متغيرات البيئة المتاحة سنطور البرنامج للحصول على متغير معين منها فقط. الخطوة الخامسة - الوصول لمتغير بيئة محدد تُمثل خصائص الكائن process.env رابطة بين أسماء متغيرات البيئة وقيمها مُخزَّنة كسلاسل نصية، حيث يمكننا الوصول لأي خاصية ضمن الكائن في جافاسكربت بذكر اسمها بين قوسين مربعين. نفتح الملف environment.js ضمن محرر النصوص ونعدل محتواه: nano environment.js نعدل التعليمة الموجودة فيه لتصبح ما يلي ثم نحفظ الملف: console.log(process.env["HOME"]); ننفذ البرنامج: node environment.js نحصل على خرج كالتالي: /home/hassan بدلًا من طباعة الكائن process.env بكل قيمه اخترنا الخاصية HOME فقط منه، والتي تمثل مسار مجلد المستخدم الحالي، وهي نفس القيمة التي يمثلها متغير البيئة ‎$HOME المتوفر في بيئات يونكس، وستلاحظ اختلافًا في خرج هذا البرنامج أيضًا عند تنفيذه لنفس السبب السابق، حيث سيُعرَض مسار مجلد المستخدم الخاص بك. والآن بعد أن تعلمنا طريقة الوصول لقيمة متغير بيئة محدد، سنطور البرنامج ليسأل المستخدم عن متغير البيئة الذي يريد عرضه. الخطوة السادسة - جلب متغير بيئة يحدده المستخدم سنستفيد من إمكانية وصولنا لوسائط سطر الأوامر التي يُمرِّرها المستخدم، مع إمكانية وصولنا لمتغيرات البيئة لإنشاء أداة سطر أوامر بسيطة مهمتها طباعة قيمة متغير بيئة محدد على الشاشة. نُنشئ ملفًا جديدًا بالاسم echo.js: nano echo.js ونكتب داخله الشيفرة التالية ونحفظ الملف: const args = process.argv.slice(2); console.log(process.env[args[0]]); يُخزن السطر الأول من هذا البرنامج جميع الوسائط التي مَرَّرها المستخدم ضمن ثابت يدعى args، ثم يطبع السطر الثاني عند تنفيذه متغير بيئة محدد بحسب قيمة أول عنصر من عناصر الثابت args، أي بحسب أول وسيط مرَّره المستخدم عند تنفيذ البرنامج. ننفِّذ البرنامج ونمرر له اسم متغير بيئة ما كالتالي: node echo.js HOME سيظهر لنا الخرج التالي: /home/hassan حُفِظ الوسيط HOME الذي مرَّرناه للبرنامج السابق ضمن المصفوفة args، ثم استخدمناه للعثور على قيمة متغير البيئة المقابل له باستخدام الكائن process.env، وبذلك يصبح بإمكاننا الوصول لقيمة أي متغير بيئة متوفر في النظام، وجرِّب الآن بنفسك وحاول عرض قيم متغيرات البيئة التالية: PWD, USER, PATH. والآن سنطور البرنامج لعرض عدة متغيرات بيئة معًا يطلبها المستخدم بدلًا من واحد فقط. الخطوة السابعة - عرض عدة متغيرات بيئة يمكن للبرنامج الآن في كل مرة عرض متغير بيئة واحد فقط، لذا في هذه الخطوة سنطوره ليستقبل عدد من الوسائط من سطر الأوامر ويعرض متغيرات البيئة المقابلة لها. نفتح ملف البرنامج echo.js ضمن محرر النصوص: nano echo.js ونبدل بمحتواه الشيفرة التالية ثم نحفظ الملف: const args = process.argv.slice(2); args.forEach(arg => { console.log(process.env[arg]); }); توفر لنا جافاسكربت افتراضيًا التابع forEach ضمن المصفوفات، والذي يقبل تابع رد نداء callback كمعامل له يتم استدعاءه خلال المرور على كل عنصر من عناصر المصفوفة، حيث نلاحظ أننا مررنا للتابع forEach من الكائن args رد نداء يمثل وظيفة طبع قيمة متغير البيئة المقابل للوسيط الحالي. ننفذ البرنامج السابق ونمرر له عدة أسماء لمتغيرات بيئة كالتالي: node echo.js HOME PWD لنحصل على الخرج: /home/hassan /home/hassan/first-program نتأكد باستخدامنا للتابع forEach من معالجة كافة الوسائط التي مررها المستخدم للبرنامج، والمُخزنة ضمن الثابت args وطباعة متغير البيئة المقابل لها، وبعد أن أصبح البرنامج الآن يعرض قيم جميع متغيرات البيئة التي يطلبها المستخدم، يجب معالجة الحالة التي يمرر فيها المستخدم متغير بيئة غير موجود. الخطوة الثامنة - معالجة طلب المستخدم لمتغير بيئة غير موجود لنحاول طلب عرض قيمة متغير بيئة ما غير موجود من البرنامج ونلاحظ ماذا سيحدث: node echo.js HOME PWD NOT_DEFINED نحصل على خرج كالتالي: /home/hassan /home/hassan/first-program undefined نلاحظ عرض قيمة أول متغيري بيئة في أول سطرين كما هو متوقع، أما في السطر الأخير ظهرت لنا القيمة undefined، وكما نعلم في جافاسكربت القيمة undefined تعني أن الخاصية أو المتغير غير مُعرَّف ولم تُحدد قيمته بعد، وذلك لأن متغير البيئة الذي طلبناه NOT_DEFINED غير موجود لذا طُبعت تلك القيمة عوضًا، وبدلًا من ذلك يمكننا عرض رسالة خطأ للمستخدم تُعلمه أن متغير البيئة الذي يطلبه غير موجود. نفتح الملف مرة أخرى للتعديل عليه: nano echo.js ونضيف الشيفرة التالية: const args = process.argv.slice(2); args.forEach(arg => { let envVar = process.env[arg]; if (envVar === undefined) { console.error(`Could not find "${arg}" in environment`); } else { console.log(envVar); } }); ما قمنا به هو تعديل تابع رد النداء المُمرر للتابع forEach ليقوم بالخطوات التالية: استخراج متغير البيئة للوسيط الحالي وتخزين قيمته في المتغير envVar. التحقق ما إذا كانت قيمة envVar غير مُعرّفة undefined. في حال كانت قيمة envVar غير مُعرّفة undefined نطبع رسالة تُعلم المستخدم بعدم وجود متغير بيئة لهذا الوسيط. في حال عُثر على متغير البيئة نطبع قيمته. يطبع التابع console.error رسالة على الشاشة من خلال مجرى الخطأ القياسي stderr، بينما يطبع التابع console.log القيم المُمررة له عبر مجرى الخرج القياسي stdout، ولن نلاحظ أي فرق بين استخدام المجريين stdout و stderr عند تنفيذ البرنامج من خلال سطر الأوامر، ويعتبر استخدام كل تابع منهما في حالته الخاصة وتحديدًا طباعة رسائل الخطأ عبر المجرى stderr من الممارسات الجيدة في تطوير البرمجيات، لأنه يُمَكّن البرامج الأخرى من تحديد تلك الأخطاء والتعامل معها إن لزم ذلك. والآن نعيد تنفيذ البرنامج كالتالي: node echo.js HOME PWD NOT_DEFINED لنحصل على الخرج: /home/hassan /home/hassan/first-program Could not find "NOT_DEFINED" in environment نلاحظ ظهور رسالة للمستخدم تفيد بأن المتغير NOT_DEFINED لم يُعثر عليه. ختامًا بدأنا في هذا الفصل بكتابة برنامج بسيط لطباعة عبارة بسيطة على الشاشة، وانتهينا بكتابة أداة لسطر الأوامر في نود تعرض للمستخدم متغيرات البيئة التي يطلبها، ويمكنك الآن التطوير على تلك الأداة بنفسك بمحاولة التحقق مثلًا من مدخلات المستخدم قبل طباعة أي قيمة، وإعادة خطأ مباشرةً في حال أن أحد متغيرات البيئة المطلوبة غير موجود، وبذلك سيحصل المستخدم من البرنامج على قيم متغيرات البيئة فقط في حال كانت جميع المتغيرات المطلوبة موجودة. ترجمة -وبتصرف- للمقال How To Write and Run Your First Program in Node.js لصاحبه Stack Abuse. اقرأ أيضًا مقدمة إلى Node.js بيئة Node.js: استخدام جافاسكربت خارج المتصفح البرمجة غير المتزامنة في Node.js
  20. كتاب البرمجة بلغة جافاسكربت المنشور يشرح مبادئ لغة جافاسكربت بكافة جوانبها ويتعمق في شرح بعضها، في حال كما ذكرت لديك بعض الممارسة والمعرفة بلغة جافاسكربت سيضيف الكتاب لك كثيرًا، حيث ستفهم كل جانب من جوانب اللغة وتوظفها فيما تحتاج، يمكنك إما الاطلاع وقراءة الكتاب بالكامل أو التوجه فقط إلى الفقرات التي تود التعلم عنها وتقوية مهارتك، فالكتاب مناسب للمبتدئين
  21. يمكن حل المشكلة بإضافة الخاصية dir بالقيمة auto إلى العنصر الحاول على الرسالة بعد جلبها من قاعدة البيانات، مثلًا لو كانت الرسالة تُعرض ضمن عنصر p نضيف الخاصية كالتالي: <p dir="auto"> ^^^^^^^^^^ <!-- الرسالة --> </p>
  22. حاول التطبيق بنفسك بما أنك انتهيت من حضورها، ويمكنك مراجعة درس متعلق بفكرة ما إذا أردت مراجعتها، الأمر يعود لك وللطريقةالتي تراها تحقق أكبر فائدة لك من ناحية تطور مهاراتك
  23. لتحقق أكبر استفادة وتطور من مهاراتك ركز على التطبيق العملي فهو سيعرضك لمشاكل جديدة ما يدفعك للبحث عن حلول لها وفهمها وبذلك تغطي العديد من الجوانب التي تخدم التطوير العملي، ويوجد الكثير من المقالات المنشورة عن ووردبرس يمكنك الاطلاع عليها فهي متنوعة وتشرح الكثير من النواحي المهمة في تطوير مواقع ووردبرس وصيانتها وتحسينها منها المقالات التالية: وكذلك بخصوص لغة HTML و PHP يوجد العديد من المقالات المنشورة حولها التي تغطي العديد من الجوانب منها:
  24. المشكلة في تحويل الرد القادم من الخادم وقراءته بصيغة JSON ضمن الاستدعاء التالي: .then(response => response.json()) حاول قراءة الخطأ الذي يرسله الخادم لمعرفة سبب المشكلة، يمكنك ذلك بالتحقق من رمز الحالة للرد الوارد قبل تحويل الرد إلى json، ثم قراءة جسم الطلب وعرضه في حال أردت ذلك أو معالجة الخطأ وعرض رسالة توضيحية للمستخدم إذا كان الخطأ معلومًا كالتالي: .then(async response => { if(!response.ok){ // فحص ومعالجة الخطأ console.error(await response.text()); // .. } return response; }) .then(response => response.json())
  25. هل اختبرت الخيار force مع تحديد أسماء الأفرع المحلي والبعيد كالتالي: git push origin master:main --force
×
×
  • أضف...