البحث في الموقع
المحتوى عن 'جافاسكريبت'.
-
إذا أردنا وصف البرنامج المثالي، فسيكون ذلك الذي له بنية واضحة مثل الكريستال، ويسهل وصف طريقة عمله، كما يملك كل جزء فيه دورًا محدَّدًا ومعروفًا. لكن هذا لا يكون في الواقع، بل تكون لدينا برامج تنمو نموًا عضويًا، بحيث تضاف إليها المزايا كلما دعت الحاجة إلى ذلك؛ أما الهيكلة البنائية لها والحفاظ على تلك الهيكلية فهي أمر آخر، إذ لا تُرى ثمرتها إلا في المستقبل حين يأتي شخص آخر ليعمل على البرنامج، وعلى ذلك فمن المغري للمبرمج إهمالها الآن لتصبح أجزاء البرنامج متشعبةً ومتداخلةً إلى حد فظيع. يتسبب هذا في مشكلتين حقيقيتَين، أولاهما أنّ فهم مثل هذا النظام صعب جدًا، إذ سيكون من العسير النظر لأيّ جزء فيه نظرة مستقِلة إذا كان كل شيء فيه يتصل بشيء آخر، حيث سنُجبَر على دراسة البرنامج كله من أجل فهم جزء واحد فقط؛ أما الثانية فهي إذا أردنا استخدام أيّ وظيفة من مثل هذا البرنامج في حالة أخرى، فستكون إعادة كتابتها من الصفر أسهل من استخراجها مستقِلةً من شيفرتها لتعمل في مكان آخر. يستخدم مصطلح "كرة الوحل" لمثل تلك البرامج التي لا هيكل لها، والتي يلتصق كل شيء فيها ببعضه، فإذا حاولت استخراج جزء، فستنهار الكرة كلها، ولا ينالك إلا تلطيخ يدك. الوحدات Modules تُعَدّ الوحدات محاولةً لتفادي مثل تلك المشاكل التي وصفناها، فالوحدة هي جزء من برنامج ما يحدِّد بوضوح ما هي الأجزاء الأخرى التي يعتمد عليها، وما هي الوظيفة -أي الواجهة الخاصة به- التي سيوفرها للوحدات الأخرى لتستخدمها. تشترك واجهات الوحدات مع واجهات الكائنات في أمور كثيرة كما رأينا في مقال الحياة السرية للكائنات في جافاسكريبت، إذ تجعل جزءًا من الوحدة متوفرًا للعالم الخارجي وتحافظ على خصوصية الباقي، كما يصبح النظام أشبه بقِطع الليجو LEGO في تقييد الطرق التي تتفاعل الوحدات بها مع بعضها البعض، فتتفاعل الأجزاء المختلفة من خلال وصلات connectors معرَّفة جيدًا، على عكس الوحل الذي يختلط فيه كل شيء. تسمى العلاقات التي بين الوحدات بالاعتماديات dependencies، فإذا احتاجت وحدة إلى جزء من وحدة أخرى، فسيقال أنها تعتمد على تلك الوحدة، وحين توصف تلك الحقيقة بوضوح داخل الوحدة نفسها، فيمكن استخدامها لمعرفة الوحدات الأخرى التي يجب أن تكون موجودةً من أجل استخدام وحدة ما، ولتحميل الاعتماديات تلقائيًا، كما ستحتاج كل واحدة منها إلى مجال خاص private scope إذا أردنا فصل الوحدات بهذه الطريقة. لا يكفي وضع شيفرة جافاسكربت في ملفات مختلفة لتلبية تلك المتطلبات، فلا زالت الملفات تتشارك فضاء الاسم العام global namespace نفسه، كما قد تتداخل عن قصد أو غير قصد مع رابطات بعضها bindings، وهكذا تظل هيكلية الاعتماديات غير واضحة، وسنرى بعد قليل كيف نعالج ذلك. قد يكون التفكير في تصميم وحدة ذات هيكل مناسب لبرنامج ما صعبًا بما أننا في مرحلة البحث عن المشكلة واستكشاف حلول لها، وذلك لنرى أيها يعمل، كما لا نريد شغل بالنا بتنظيم أحد الحلول إلا حين يثبت نجاحه. الحزم نستطيع استخدام نفس الجزء في برامج أخرى مختلفة، وهي إحدى المزايا التي نحصل عليها عند بناء برنامج من أجزاء منفصلة تستطيع العمل مستقِلة عن بعضها البعض. لكن كيف نُعِدّ هذا؟ لنقل أننا نريد استخدام الدالة parseINI من المقال السابق في برنامج آخر، فإذا كان ما تعتمد عليه الدالة واضحًا -لا تعتمد الدالة على شيء في هذه الحالة-، فلن نفعل أكثر من نسخ شيفرتها إلى المشروع الجديد واستخدامها؛ أما إذا رأينا خطأً في تلك الشيفرة، فسنصلحه في ذلك المشروع الجديد وننسى إصلاحه في البرنامج الأول الذي أخذنا منه الشيفرة، وبهذا نهدر الوقت والجهد في نسخ الشيفرات من هنا إلى هناك والحفاظ عليها محدَّثة. وهنا يأتي دور الحِزم packages، فهي قطعة من شيفرة يمكن نشرها وتوزيعها -أي نسخها وتثبيتها-، وقد تحتوي على وحدة واحدة أو أكثر، كما فيها معلومات عن الحِزم الأخرى التي تعتمد عليها، وتأتي مع توثيق يشرح وظيفتها كي يستطيع الناس استخدامها، فلا تكون مقتصرةً على من كتبها فقط. تُحدَّث حزمة ما إذا وُجدت مشكلة فيها أو أضيفت إليها ميزة جديدة، وعليه تكون البرامج التي تعتمد عليها -والتي قد تكون حِزمًا أيضًا- يمكنها الترقية إلى تلك النسخة الجديدة. يتطلب العمل بهذه الطريقة بنيةً تحتيةً، وذلك لحاجتنا إلى مكان لتخزين تلك الحِزم والبحث فيها، وإلى طريقة سهلة لتثبيتها وترقيتها كذلك، كما تُوفَّر هذه البنية التحتية في عالم جافاسكربت من قِبَل NPM وهي اختصار لـ Node Package Manager، التي تعني مدير الحِزم. يتكون مدير الحِزم NPM من شيئين، أولهما تقدِّم خدمة تنزيل ورفع الحزم عبر الإنترنت -أي أونلاين online-، وبرنامج -مضمَّن مع Node.js- يساعدك على تثبيت تلك الحزم وإدارتها، كما توجد أكثر من نصف مليون حزمة متاحة على NPM وقت كتابة هذه الكلمات (بنسختها الأجنبية)، وأكثرها لا فائدة منه، لكن الحزم المفيدة المتاحة للعامة موجودة هناك أيضًا. سنجد محلل ملفات INI مثلًا الذي يشبه ما بنيناه في المقال السابق في هيئة حزمة اسمها ini، كما سنرى في مقال لاحق كيف نُثبِّت مثل تلك الحزم محليًا باستخدام أمر npm في الطرفية. إتاحية مثل تلك الحزم عالية الجودة للتحميل مفيد جدًا، فهو يعني استطاعتنا تجنب إعادة اختراع برنامج كتبه مائة إنسان قبلنا، كما نتجاوز ذلك إلى استخدام حزمة مجرَّبة ومختبَرة جيدًا ببضع ضغطات على لوحة المفاتيح، ومن بداهة القول أنّ البرامج لا تكلف شيئًا في نَسخها، لكن كتابة البرامج أول مرة هي العمل الذي يكلف الجهد والوقت والمهارة، كما يماثلها الاستجابة لمن يجد مشاكل في الشيفرة، أو الاستجابة لمن يريد إدخال ميزات جديدة في البرنامج. مَن يكتب البرنامج يمتلك حقوقه افتراضيًا، ولا يستطيع أحد استخدامه إلا برخصة من المبرمج، لكن بما أنّ بعض البشر ذوو قلوب طيبة، ولأنّ نشر البرامج الجيدة سيبني لك سُمعة وسط المبرمجين، فستسمح صراحةً كثير من الحِزم تحت رخصة ما باستخدامها من أيّ كان. ترخَّص أغلب الشيفرات الموجودة في NPM بتلك الطريقة، في حين قد تشترط بعض الرخص نشر الشيفرة التي تبنيها على قمة الحزمة التي استخدمتها تحت رخصة الحزمة نفسها، وأخرى لا تريد أكثر من استخدام رخصة الحزمة نفسها حين تنشر برنامجك، وهي الرخصة التي يستخدمها أغلب مجتمع جافاسكربت، وبالتالي تأكد حين تستخدِم حِزمًا كتبها غيرك من قراءة الرخصة التي تأتي بها الحزمة. الوحدات المرتجلة Improvised modules لم يكن في جافاسكربت نظام وحدات مضمَّن حتى عام 2015، إذ لم يمنع ذلك الناس من بناء نظمًا كبيرة في جافاسكربت طيلة أكثر من عشر سنين، كما كانوا في أمسّ الحاجة إلى وحدات لهذا، وعليه فقد صمموا نظم وحداتهم الخاصة بهم فوق اللغة نفسها، حيث نستطيع استخدام دوال جافاسكربت لإنشاء نطاقات محلية local scopes وكائنات لتمثل واجهات الوحدات. لدينا فيما يلي وحدةً للانتقال بين أسماء الأيام وأرقامها -وذلك من إعادة التابع getDay الخاص بـ Date-، إذ تتكون واجهتها من weekDay.name وweekDay.number، كما تخفي رابطتها names المحلية داخل نطاق تعبير الدالة الذي يُستدعى فورًا. const weekDay = function() { const names = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; return { name(number) { return names[number]; }, number(name) { return names.indexOf(name); } }; }(); console.log(weekDay.name(weekDay.number("Sunday"))); // → Sunday يوفِّر هذا النسق من الوحدات عزلًا إلى حد ما، لكنه لا يصرح عن الاعتماديات، كما يضع واجهته في النطاق العام global scope، ويَتوقع اعتمادياتها إذا وُجدت أن تحذو حذوه، في حين كان ذلك هو الأسلوب المتَّبع في برمجة الويب لمدة طويلة، لكنه صار مهجورًا الآن وقلّما يُستخدَم. إذا أردنا جعل علاقات الاعتماديات جزءًا من الشيفرة، فيجب التحكم في تحميل الاعتماديات، ويتطلب ذلك أن نستطيع تنفيذ السلاسل النصية على أساس شيفرات، إذ تستطيع جافاسكربت فعل ذلك لحسن الحظ. تقييم البيانات على أساس شيفرات هناك عدة طرق لأخذ البيانات -أي سلسلة نصية من شيفرات برمجية- وتشغيلها على أساس جزء من البرنامج الحالي، وأبسط طريقة هي العامل الخاص eval الذي سينفِّذ السلسلة النصية في النطاق الحالي current scope، لكن هذه فكرة سيئة لا يُنصح بها، إذ تعطِّل بعض الخصائص التي تكون عادةً للمجالات مثل توقعها للرابطة التي يشير إليها اسم ما بسهولة. const x = 1; function evalAndReturnX(code) { eval(code); return x; } console.log(evalAndReturnX("var x = 2")); // → 2 console.log(x); // → 1 يمكن تفسير البيانات على أساس شيفرة بطريقة أبسط باستخدام الباني Function الذي يأخذ وسيطين هما سلسلة نصية تحتوي قائمة من أسماء الوسائط مفصول بينها بفاصلة أجنبية، وسلسلة نصية تحتوي على الدالة نفسها. يغلف الباني الشيفرة داخل قيمة دالة كي تحصل على نطاقها الخاص، ولا تفعل أمورًا غريبة مع النطاقات الأخرى. let plusOne = Function("n", "return n + 1;"); console.log(plusOne(4)); // → 5 هذا هو المطلوب تحديدًا وما نحتاج إليه في نظام الوحدات، إذ نستطيع تغليف شيفرة الوحدة في دالة، ونستخدم نطاق تلك الدالة على أساس نطاق الوحدة. CommonJS لعل أكثر منظور لوحدات جافاسكربت المضافة إليها هو نظام وحدات جافاسكربت المشتركة common Javascript والذي يدعى بالاسم CommonJS، إذ تستخدِمه Node.js الذي هو النظام المستخدَم في أغلب الحِزم على NPM. تدور الفكرة العامة لوحدات CommonJS حول دالة اسمها require، فحين نستدعيها مع اسم وحدة الاعتمادية، فستضمن أن تحميل الوحدة وستعيد واجهتها. تحصل الوحدات على نطاقها المحلي الخاص بها تلقائيًا لأنّ المحمّل يغلف شيفرة الوحدة في دالة، وما عليها إلا استدعاء require كي تصل إلى اعتمادياتها، وتضع واجهاتها في الكائن المقيَّد بـ exports. توفِّر الوحدة المثال التالية دالةً لتنسيق التاريخ، إذ تستخدِم حزمتين من NPM هما ordinal لتحويل الأعداد إلى سلاسل نصية مثل "1st" و"2nd"، وdate-names للحصول على الأسماء الإنجليزية للشهور وأيام الأسبوع، ثم تصدِّر دالةً وحيدةً هي formatDate تأخذ كائن Date وسلسلة قالب template string. قد تحتوي سلسلة القالب على شيفرات توجه التنسيق مثل YYYY للسنة كاملة وDo لترتيب اليوم في الشهر، ومن الممكن إعطاؤها سلسلةً نصيةً مثل "MMMM Do YYYY" للحصول على خرج مثل "November 22nd 2017". const ordinal = require("ordinal"); const {days, months} = require("date-names"); exports.formatDate = function(date, format) { return format.replace(/YYYY|M(MMM)?|Do?|dddd/g, tag => { if (tag == "YYYY") return date.getFullYear(); if (tag == "M") return date.getMonth(); if (tag == "MMMM") return months[date.getMonth()]; if (tag == "D") return date.getDate(); if (tag == "Do") return ordinal(date.getDate()); if (tag == "dddd") return days[date.getDay()]; }); }; تتكون واجهة ordinal من دالة واحدة، بينما تصدِّر date-names كائنًا يحتوي على عدة أشياء، إذ تُعَدّ days وmonths مصفوفات من الأسماء، كما تُعَدّ عملية فك البنية الهيكلية سهلةً جدًا عند إنشاء رابطات للواجهات المستوردة. تضيف الوحدة كذلك دالة واجهتها إلى exports كي تصل إليها الوحدات التي تعتمد عليها، إذ نستطيع استخدام الوحدة كما يلي: const {formatDate} = require("./format-date"); console.log(formatDate(new Date(2017, 9, 13), "dddd the Do")); // → Friday the 13th نستطيع تعريف require في أبسط صورها كما يلي: require.cache = Object.create(null); function require(name) { if (!(name in require.cache)) { let code = readFile(name); let module = {exports: {}}; require.cache[name] = module; let wrapper = Function("require, exports, module", code); wrapper(require, module.exports, module); } return require.cache[name].exports; } تَقرأ الدالة المختلَقة readFile في المثال أعلاه ملفًا وتعيد محتوياته في سلسلة نصية؛ أما جافاسكربت فلا توفِّر مثل تلك الخاصية، لكن توفِّر بيئات جافاسكربت المختلفة مثل المتصفحات وNode.js طرقها الخاصة للوصول إلى الملفات، وقد كان المثال يفترض وجود دالة اسمها readFile. تحتفظ دالة require بذاكرة مؤقتة cache من الوحدات المحمَّلة بالفعل، وذلك لتجنب تحميل الوحدة نفسها عدة مرات، فإذا استُدعيت، فستنظر أولًا إن كانت الوحدة المطلوبة محمَّلة من قبل أم لا، ثم تُحمِّلها إن لم تكن محمَّلة، حيث يتطلب ذلك قراءة شيفرة الوحدة وتغليفها في دالة واستدعاءها. لم تكن واجهة حِزمة ordinal التي رأيناها من قبل كائنًا وإنما دالةً، ومن مزايا CommonJS أنه رغم إنشاء نظام الوحدات لكائن واجهة فارغ مقيَّد بـ exports، يمكننا استبدال أيّ قيمة بذلك الكائن عبر إعادة كتابة module.exports، حيث يتم ذلك بعدة وحدات لتصدير قيمة واحدة بدلًا من كائن واجهة؛ وإذا عرَّفنا require وexports وmodule على أساس معامِلات لدالة التغليف المولَّدة وتمرير القيم المناسبة عند استدعائها، فسيضمن المحمِّل إتاحية تلك الرابطات في نطاق الوحدة. تختلف الطريقة التي تُرجِمت بها السلسلة النصية التي أُعطيت إلى دالة require إلى اسم ملف حقيقي أو عنوان ويب باختلاف الأنظمة، فإذا بدأت بـ "./" أو "../"، فستُفسَّر تفسيرًا مرتبطًا باسم ملف الوحدة، وبالتالي سيكون "./format-date" هو الملف المسمى format-date.js في المجلد نفسه؛ أما إذا لم يكن الاسم مرتبطًا بالوحدة، فستبحث Node.js عن حِزمة مثبَّتة تحمل الاسم نفسه، وسنفسر مثل تلك الأسماء التي في شيفرات أمثلة هذا المقال على أنها تشير إلى حِزم NPM، كما سننظر بالتفصيل في كيفية تثبيت واستخدام وحدات NPM لاحقًا في هذه السلسلة. نستطيع استخدام واحد من NPM الآن بدلًا من كتابة محلل ملف INI الخاص بنا. const {parse} = require("ini"); console.log(parse("x = 10\ny = 20")); // → {x: "10", y: "20"} وحدات ESCMAScript تظل وحدات CommonJS حلًا غير دائم وغير احترافي رغم عملها بكفاءة وسماحها لمجتمع جافاسكربت -مع NPM- بتشارك الشيفرات على نطاق واسع، وتُعَدّ صيغتها غريبةً قليلًا، إذ لا تكون الأشياء التي نضيفها إلى exports متاحةً في النطاق المحلي مثلًا، وبما أنّ require هي استدعاء دالة عادية تأخذ أيّ نوع من الوسطاء وليس القيم مصنَّفة النوع فقط، فمن الصعب تحديد اعتماديات الوحدة دون تشغيل شيفرتها. لهذا يُصدِر معيار جافاسكربت نظام وحداته الخاص منذ 2015، كما يطلق عليه غالبًا ES modules، حيث تشير ES إلى ECMAScript، ورغم أنّ المفاهيم الأساسية للاعتماديات والواجهات لا زالت كما هي، إلا أنه يختلف في التفاصيل، فصارت الصيغة الآن مدمجةً في اللغة، كما نستطيع استخدام الكلمة المفتاحية الخاصة import بدلًا من استدعاء دالة للوصول إلى اعتمادية ما. import ordinal from "ordinal"; import {days, months} from "date-names"; export function formatDate(date, format) { /* ... */ } تُستخدم بالمثل كلمة export المفتاحية لتصدير الأشياء، وقد تأتي قبل تعريف دالة أو صنف أو رابطة (let أو const أو var). لا تكون واجهة وحدة ES قيمةً واحدةً، بل مجموعة من الرابطات المسماة، كما تربط الوحدة السابقة formatDate بدالة، فإذا استوردنا من وحدة أخرى، فسنستورد الرابطة وليس القيمة، مما يعني أن الوحدة المصدِّرة قد تغير قيمة الرابطة في أي وقت، وسترى الوحدات المستوردة القيمة الجديدة. إذا وُجدت رابطة اسمها default فستُعامَل على أنها القيمة المصدَّرة الأساسية للوحدة، فإذا استوردنا وحدةً مثل ordinal التي في المثال دون الأقواس التي حول اسم الرابطة، فسنحصل على رابطة default، كما تستطيع مثل تلك الوحدات تصدير رابطات أخرى تحت أسماء مختلفة مع تصدير default الخاص بها. إذا أردنا إنشاء تصدير افتراضي، فسنكتب export default قبل التعبير أو تصريح الدالة أو تصريح الصنف. export default ["Winter", "Spring", "Summer", "Autumn"]; من الممكن إعادة تسمية الرابطات المستورَدة باستخدام الكلمة as. import {days as dayNames} from "date-names"; console.log(dayNames.length); // → 7 من الفروقات المهمة كذلك هو حدوث تصديرات وحدات ES قبل بدء تشغيل سكربت الوحدة، وبالتالي قد لا تظهر تصريحات import داخل الدوال أو الكتل، ويجب أن تكون أسماء الاعتماديات سلاسل نصية مقتَبسة وليست تعبيرات عشوائية. يعكف مجتمع جافاسكربت أثناء كتابة هذه الكلمات على اعتماد وتبني أسلوب الوحدات هذا، لكن يبدو أنها عملية طويلة، فقد استغرقنا بضع سنين بعد الاستقرار على الصياغة كي تدعمها المتصفحات وNode.js، ورغم أنها صارت مدعومةً إلى حد كبير إلا أنّ ذلك الدعم به مشاكل، ولا زال الجدل قائمًا حول الكيفية التي يجب توزيع تلك الوحدات بها في NPM. تُكتب مشاريع عديدة باستخدام وحدات ES، ثم تُحوَّل تلقائيًا إلى صيغة أخرى عند نشرها، فنحن في مرحلة انتقالية يُستخدم فيها نظامَي وحدات جنبًا إلى جنب، ومن المفيد أننا نستطيع قراءة وكتابة شيفرات بكليهما. البناء والتجميع إذا نظرنا إلى مشاريع جافاسكربت فسنجد العديد منها لا يُكتب بجافاسكربت من الأساس -تقنيًا-، فثَمة امتدادات مستَخدَمة على نطاق واسع مثل ذلك الذي تعرضنا له في مقال الزلات البرمجية والأخطاء في جافاسكريبت الذي يتحقق من اللهجة، وقد بدأت الناس باستخدام الامتدادات الجاهزة في اللغة قبل إضافتها إلى المنصات التي تشغِّل جافاسكربت بزمن طويل أصلًا، ولكي يستطيع المبرمج فعل ذلك فإنه يصرِّف compile شيفرته، ثم يترجمها من لهجة جافاسكربت التي استخدمها إلى جافاسكربت عادية، أو إلى نسخة أقدم من جافاسكربت كي تستطيع المتصفحات الأقدم تشغيلها. لكن إدخال برنامج يتكون من وحدات ومن 200 ملف مختلف إلى صفحة ويب له مشاكله، فإذا كان جلب الملف عبر الشبكة يستغرق 50 ميلي ثانية، فسيستغرق تحميل البرنامج كله 10 ثواني، أو نصف تلك المدة إن كان يحمِّل عدة ملفات في الوقت نفسه، وهذا وقت ضائع. بدأ مبرمجو الويب باستخدام أدوات تجمع برامجهم التي قضوا الساعات المضنية في تقسيمها إلى وحدات، ليكون البرنامج ملفًا واحدًا كبيرًا، ثم ينشرونه على الويب، ذلك أن جلب ملف واحد وإن كان كبيرًا عبر الويب سيكون أسرع من جلب الكثير من الملفات الصغيرة، ويطلق على مثل تلك الأدوات اسم المجمِّعات bundlers. يتحكم حجم الملفات في سرعة نقلها عبر الشبكة كذلك، فليس العدد وحده هو المعيار، وعليه فقد اخترع مجتمع جافاسكربت مصغِّرات minifiers، وهي أدوات تأخذ برنامج جافاسكربت وتجعله أصغر عبر حذف التعليقات والمسافات تلقائيًا، كما تعيد تسمية الرابطات bindings، وتستبدل شيفرات أصغر بالشيفرات التي تأخذ حجمًا كبيرًا؛ وهكذا فليس من الغريب إيجاد شيفرة في حزمة NPM أو شيفرة تعمل في صفحة ويب، وتكون قد مرت بعدة مراحل من التحويل من جافاسكربت الحديثة إلى نسخة أقدم، ومن صيغة وحدات ES إلى CommonJS، ثم جُمِّعت وصغِّرت كذلك، كما لن نخوض في تفاصيل تلك الأدوات في هذه السلسلة لأنها مملة وتتغير بسرعة، لكن من المهم معرفة أنّ شيفرة جافاسكربت التي تشغِّلها لا تكون بهيئتها التي كُتبت بها أول مرة. تصميم الوحدة تُعَدّ هيكلة البرامج واحدةً من أدق الأمور في البرمجة، إذ يمكن نمذجة أيّ وظيفة غريبة بعدة طرق، كما يُعَدّ القول بجودة تصميم برنامج ما أمرًا نسبيًا، فهناك حلول وسط دومًا، كما تتدخل التفضيلات الشخصية في الحكم على التصميم، وأفضل ما يمكن فعله لتعلُّم قيمة التصميم الجيد هو القراءة والعمل على برامج كثيرة، وملاحظة ما ينجح وما يفشل، وإذا رأيت شيفرات فوضوية فلا تركن إلى قولك أن هذا هو الواقع ولا مجال لتغييره، بل هناك مساحة لتطوير هيكل أيّ شيء تقريبًا بإعمال الفكر فيه. تُعَدّ سهولة الاستخدام أحد أركان تصميم الوحدات، فإذا كنت تصمم شيئًا ليستخدمه أشخاص عدة -أو حتى نفسك فقط-، فيُستحسن أن تكون واجهتك بسيطةً وسهلة الفهم والتوقع حين تنظر إليها بعد ثلاثة أشهر حين تنسى تفاصيل ما كتبته وما عملته، وقد يعني ذلك اتباع سلوكيات موجودة سلفًا، كما تُعَدّ حزمة ini مثالًا على ذلك، إذ تحاكي كائن JSON القياسي من خلال توفير دوال parse وstringify -لكتابة ملف INI-، كما تحوِّل بين السلاسل النصية والكائنات المجردة مثل JSON، وهكذا فإنّ الواجهة صغيرة ومألوفة، وستذكر كيف استخدمتها لمجرد عملك معها مرةً واحدةً. حتى لو لم تكن ثمة دالة قياسية أو حِزمة مستخدَمة على نطاق كبير لمحاكاتها، فستستطيع إبقاء وحداتك مألوفةً وسهلة التوقع باستخدام هياكل بيانات بسيطة تفعل أمرًا واحدًا فقط، كما توجد على NPM مثلًا وحدات عديدة لتحليل ملف-INI، إذ تحتوي على دالة تقرأ مثل هذا الملف من القرص الصلب مباشرة وتُحلله، ويجعل ذلك من المستحيل استخدام مثل تلك الوحدات في المتصفح، حيث لا يكون لنا وصولًا مباشرًا إلى نظام الملفات، كما يضيف تعقيدًا كان يمكن معالجته بأسلوب أفضل عبر تزويد composing الوحدة بدالة قارئة للملفات file-reading. يشير ذلك إلى منظور آخر مفيد في تصميم الوحدات، وهو سهولة تطعيم شيء ما بشيفرة خارجية، إذ تكون الوحدات المركزة التي تحسب قيمًا قابلةً للتطبيق في طيف واسع من البرامج، على عكس الوحدات الأكبر التي تنفِّذ إجراءات معقدة لها آثار جانبية، وقارئ ملف INI الذي يركز على قراءة الملف من القرص الصلب ليس له فائدة في سيناريو يكون فيه محتوى الملف من مصدر خارجي، وبالمثل فإنّ الكائنات الحالة stateful objects مفيدة أحيانًا، بل قد تكون ضرورية؛ لكن إذا كان يمكن تنفيذ أمر ما بدالة فمن الأفضل استخدام الدالة. توفِّر العديد من قارئات ملفات INI على NPM نمط واجهة interface style، إذ يحتاج منك إنشاء كائن أولً، ثم تحمِّل الملف إلى الكائن، وبعد ذلك تستخدِم توابع مخصصة للحصول على النتائج، وذلك الأمر شائع في البرمجة كائنية التوجه، كما هو أمر مرهق وخاطئ. علينا تنفيذ طقوس نقل الكائن خلال عدة مراحل بدلًا من تنفيذ استدعاء واحد لدالة، وبما أن البيانات مغلفة الآن في نوع كائن مخصص، فيجب على كل الشيفرات التي تتفاعل معها معرفة ذلك النوع، مما يجعل لدينا اعتماديات غير ضرورية. قد تكون لدينا حالات لا يمكن تجنب تعريف هياكل بيانات جديدة فيها، حيث لا توفر اللغة إلا بعض الهياكل البسيطة، كما ستكون العديد من أنواع البيانات معقدةً أكثر من مجرد مصفوفة أو خارطة map، لكن إذا كانت المصفوفة تكفي، فسنستخدِم المصفوفة. يمكن النظر إلى مثال المخطط الذي تعرضنا له في مقال مشروع تطبيقي لبناء رجل آلي (روبوت) عبر جافاسكريبت على أساس مثال على هيكل بيانات معقد، إذ لا توجد طريقة واضحة لتمثيل المخطط في جافاسكربت، وقد استخدمنا في ذلك المقال كائنًا تحمل خصائصه مصفوفات من السلاسل النصية، وهي العقد الأخرى التي يمكن الوصول إليها من تلك العقدة. توجد العديد من حِزم إيجاد المسار في NPM، لكن لا تستخدِم أيّ منها صيغة المخطط تلك، فهي تسمح عادةً لحدود المخطط أن يكون لها وزن يكون التكلفة أو المسافة المرتبطة به، ولم يكن ذلك ممكنًا في تمثيلنا. هناك حِزمة dijkstrajs مثلًا التي تستخدِم منظورًا شائعًا جدًا لإيجاد المسارات والذي يُسمى بخوارزمية ديكسترا Dijkstra's algorithm، إذ سُمي باسم إدزجر ديكسترا Edsger Dijkstra الذي كتبه، وهو مشابه لدالة findRoute الخاصة بنا، ومن الشائع أن تضاف اللاحقة js إلى اسم الحِزمة لتوضيح أنها مكتوبة بجافاسكربت. تستخدِم حزمة dijkstrajs صيغة مخطط تشبه صيغتنا، لكنها تستخدم كائنات تكون قيم خصائصها أعدادًا -أي أوزان الحدود- بدلًا من المصفوفات التي استخدمناها، فإذا أردنا استخدام تلك الحزمة، فسيكون علينا التأكد من تخزين المخطط بالصيغة التي تتوقعها الحزمة، كما يجب أن تحصل جميع الحدود على الوزن نفسه بما أنّ نموذجنا المبسط يعامِل كل طريق على أساس امتلاكه التكلفة نفسها -أي منعطف واحد-. const {find_path} = require("dijkstrajs"); let graph = {}; for (let node of Object.keys(roadGraph)) { let edges = graph[node] = {}; for (let dest of roadGraph[node]) { edges[dest] = 1; } } console.log(find_path(graph, "Post Office", "Cabin")); // → ["Post Office", "Salma’s House", "Cabin"] قد يكون ذلك حاجزًا معيقًا عن التركيب compositing، حيث تستخدِم حِزم عدة هياكل بيانات مختلفة لوصف أشياء متشابهة، فيكون جمعها معًا صعبًا، وبالتالي إذا أردنا تصميم شيء ليكون قابلًا للتركيب، فيجب علينا معرفة هياكل البيانات التي يستخدِمها الأشخاص أولًا، ثم نحذو حذوهم كلما تيسر. خاتمة توفر الوحدات هيكلًا لبرامج أكبر من خلال فصل الشيفرة إلى أجزاء صغيرة لها واجهات واضحة واعتماديات، كما تُعَدّ الواجهة الجزء المرئي من الوحدة للوحدات الأخرى، والاعتماديات هي الوحدات الأخرى التي تستخدِمها. ولأنّ جافاسكربت لم توفر نظامًا للوحدات في البداية، فقد بُني نظام CommonJS عليها، ثم حصلت جافاسكربت بعد مدة على نظام وحدات خاص بها، وهو الآن موجود إلى جانب CommonJS. تُعَدّ الحزمة مجموعة شيفرات يمكن توزيعها مستقِلة بذاتها، كما يُعَدّ NPM مستودعًا لحِزم جافاسكربت، ونستطيع تحميل شتى الحِزم منه سواء مفيدة أو غير مفيدة. تدريبات الروبوت التركيبي فيما يلي الرابطات التي ينشئها المشروع الذي في مقال مشروع تطبيقي لبناء رجل آلي (روبوت) عبر جافاسكريبت الذي أشرنا إليه بالأعلى. roads buildGraph roadGraph VillageState runRobot randomPick randomRobot mailRoute routeRobot findRoute goalOrientedRobot إذا أردت كتابة هذا المشروع على أساس برنامج تركيبي modular، فما الوحدات التي ستنشئها؟ وما الوحدات التي ستعتمد عليها كل وحدة؟ وكيف ستبدو واجهاتها؟ وأيّ الأجزاء ستكون متاحةً ومكتوبةً مسبقًا على NPM؟ وهل ستفضِّل استخدام حِزمة NPM أم تكتبها بنفسك؟ إرشادات للحل إليك ما كنا لنفعله بأنفسنا، لكن مرةً أخرى ليست هناك طريقة صحيحة وحيدة لتصميم وحدة معطاة: توجد الشيفرة المستخدَمة لبناء مخطط الطريق في وحدة graph، ولأننا نُفضِّل استخدام dijkstrajs من NPM بدلًا من شيفرة إيجاد المسار التي كتبناها، فسنجعل هذا المخطط قريبًا من الذي تتوقعه dijkstrajs. تصدِّر هذه الوحدة دالةً واحدةً هي buildGraph، كما سنجعل هذه الدالة تقبل مصفوفةً من مصفوفات ثنائية العناصر بدلًا من سلاسل نصية تحتوي على شخطات -، وذلك من أجل تقليل اعتماد الوحدة على صيغة الإدخال. تحتوي وحدة roads على بيانات الطريق الخام -أي مصفوفة roads- ورابطة roadGraph، كما تعتمد تلك الوحدة على ./graph وتصدر مخطط الطريق. يوجد صنف VillageState في وحدة state، حيث يعتمد على وحدة ./roads لأنه يحتاج إلى أن يكون قادرًا على التحقق من وجود طريق موجود فعليًا، كما يحتاج إلى randomPick، وبما أنّ هذه دالة من ثلاثة أسطر، فسنضعها في وحدة state على أساس دالة مساعدة داخلية، لكن randomRobot يحتاج إليها كذلك، لذا يجب تكرارها أو وضعها في وحدتها الخاصة، وبما أنّ تلك الدالة موجودة في NPM في حِزمة random-item، فمن الأفضل جعل كلا الوحدتين تعتمدان عليها، كما نستطيع إضافة دالة runRobot إلى تلك الوحدة أيضًا، بما أنها صغيرة ومرتبطة ارتباطًا وثيقًا بإدارة الحالة، في حين تصدِّر الوحدة كلا من الصنف VillageState ودالة runRobot. أخيرًا، يمكن دخول الروبوتات والقيم التي تعتمد عليها مثل mailRoute في وحدة example-robots التي تعتمد على ./roads وتصدِّر دوال الروبوت، ولكي يستطيع goalOrientedRobot البحث عن المسار، فستعتمد الوحدة على dijkstrajs أيضًا. صارت الشيفرة أصغر قليلًا من خلال نقل بعض المهام إلى وحدات NPM، حيث تنفِّذ كل وحدة مستقِلة شيئًا بسيطًا ويمكن قراءتها بنفسها، ويقترح تقسيم الشيفرات إلى وحدات على أساس تحسينات أكثر لتصميم البرنامج. يُعد اعتماد الروبوتات وVillageState على مخطط طريق بعينه غريبًا بعض الشيء، فربما يكون من الأفضل جعل المخطط وسيطًا لباني الحالة، ونجعل الروبوتات تقرؤه من كائن الحالة، فنقلل الاعتماديات -وهو أمر جيد-، ونجعل من الممكن إجراء عمليات محاكاة على خرائط مختلفة، وذلك خير وأفضل. هل من الجيد استخدام وحدات NPM لأمور كان يمكن كتابتها بأنفسنا؟ نظريًا، نعم، فمن المرجح أنك ستقع في أخطاء في الأمور المعقَّدة مثل دالة إيجاد المسار وتضيع وقتك في كتابتها بنفسك؛ أما بالنسبة للدوال الصغيرة مثل random-item فتكون كتابتها أمرًا يسيرًا، لكن إضافتها في أيّ مكان تحتاج إليها فيه سيعكر وحداتك. لكن بأي حال، لا تقلل من شأن الجهد المطلوب لإيجاد حِزمة NPM مناسبة، فحتى لو وجدتها فقد لا تعمل جيدًا أو تكون مفتقدةً إلى بعض المزايا التي تحتاجها، وفوق هذا يعني الاعتماد على حِزم NPM التأكد من أنها مثبَّتة، كما ستنشرها مع برنامجك، بل ربما يكون عليك ترقيتها دوريًا، فهذه قد تكون إحدى التبعات التي عليك تحملها، وعلى أيّ حال تستطيع اتخاذ القرار الذي يناسبك وفقًا لمقدار العون الذي تقدمه تلك الحزم لك. وحدة الطرق اكتب وحدة CommonJS بناءً على المثال الذي في مقال مشروع تطبيقي لبناء رجل آلي (روبوت) عبر جافاسكريبت، بحيث تحتوي على مصفوفة من الطرق وتصدِّر هيكل بيانات المخطط الذي يمثلها على أساس roadgraph، كما يجب أن تعتمد على وحدة ./graph التي تصدِّر الدالة buildGraph المستخدَمة لبناء المخطط، وتتوقع هذه الدالة مصفوفةً من مصفوفات ثنائية العناصر -أي نقاط البداية والنهاية للطرق-. تستطيع تعديل شيفرة التدريب لكتابة الحل وتشغيلها في طرفية المتصفح إن كنت تقرأ من متصفح، أو بنسخها إلى codepen. // أضف اعتماديات وتصديرات. const roads = [ "Salma's House-Omar's House", "Salma's House-Cabin", "Salma's House-Post Office", "Omar's House-Town Hall", "Sara's House-Mostafa's House", "Sara's House-Town Hall", "Mostafa's House-Sama's House", "Sama's House-Farm", "Sama's House-Shop", "Marketplace-Farm", "Marketplace-Post Office", "Marketplace-Shop", "Marketplace-Town Hall", "Shop-Town Hall" ]; إرشادات للحل بما أنّ هذه وحدة CommonJS، فعليك استخدام require لاستيراد وحدة المخطط، وقد وُصف ذلك على أساس تصدير دالة buildGraph، إذ تستطيع استخراجه من كائن واجهته مع تصريح فك الهيكلة const. أضف خاصيةً إلى كائن exports من أجل تصدير roadGraph، كما يجب أن يكون فصل سلاسل الطريق النصية داخل وحدتك لأن buildGraph تأخذ هيكل بيانات لا يطابق roads بالضبط. الاعتماد المتبادل بين الوحدات تُعَدّ حالة الاعتماد المتبادل بين وحدتين ويدعى circular dependency حالةً تعتمد فيها الوحدة A على الوحدة B -أي يكون الاعتماد على شكل حلقة تبادلية بين وحدتين ولذلك سمي باللغة الإنجليزية circular dependency-، والعكس صحيح مباشرةً أو غير مباشرة، كما تمنع العديد من أنظمة الوحدات ذلك بسبب استطاعتك التأكد من تحميل اعتماديات وحدة قبل تشغيلها، بعض النظر عن الترتيب الذي تختاره لتحميل مثل تلك الوحدات. تسمح وحدات CommonJS بصورة محدودة من الاعتماديات الدورية cyclic dependencies تلك، طالما أنّ الوحدات لا تستبدل كائن exports الافتراضي الخاص بها، ولا يكون لها وصول إلى واجهة غيرها حتى تنتهي من التحميل. تدعم دالة require التي تعرضنا لها سابقًا في هذا المقال مثل ذلك النوع من تبادلية الاعتماديات dependency cycle، فهل يمكنك رؤية كيف تعالج هذه الحالة؟ وما الذي قد يحدث إذا استبدلت وحدة في تعتمد وحدة أخرى عليها (أي داخلة في دورة الاعتمادية)دورة، كائن exports الافتراضي الخاص بها؟ إرشادات للحل تدور الفكرة هنا حول إضافة require وحدات إلى ذاكرتها المؤقتة قبل بدء تحميل الوحدة، وهكذا فإذا حاول أيّ استدعاء require تحميلها أثناء تشغيلها، فسيكون معروفًا وتُعاد الواجهة الحالية بدلًا من تحميل الوحدة مرةً أخرى، وذلك يتسبب في طفحان المكدس؛ أما إذا أعادت وحدة كتابة قيمة module.exports الخاصة بها، فستحصل أيّ وحدة أخرى كانت قد استقبلت قيمة واجهتها قبل أن تنهي التحميل، على كائن الواجهة الافتراضي -الذي سيكون فارغًا على الأرجح- بدلًا من قيمة الواجهة المقصودة. ترجمة -بتصرف- للفصل العاشر من كتاب Elequent Javascript لصاحبه Marijn Haverbeke. اقرأ أيضًا مقدمة إلى الوحدات Modules في جافاسكربت الوحدات Modules والحزم Packages في بايثون الوحدات Modules في AngularJS
-
- 1
-
- جافاسكريبت
- modules
-
(و 2 أكثر)
موسوم في:
-
التخزين الكائني Object storage هي وسيلة شائعة لتخزين ومعالجة الأصول الثابتة مثل الصوت والصور والنصوص وملفات PDF، وأنواع أخرى من البيانات غير المُهيكلة. مقدمو الخدمات السحابية يعرضون خدمات التخزين الكائني بالإضافة إلى التخزين المحلي أو التخزين الكُتلي Block storage، والذي يُستخدم لتخزين ملفات التطبيقات الديناميكية وقواعد البيانات. يمكنك مطالعة المقال مقارنة بين خدمات التخزين الكائني والتخزين الكتلي لمعرفة حالات الاستخدام والاختلافات بين المقاربتين. Spaces هي خدمة تخزين كائني تقدمها DigitalOcean. فبالإضافة إلى إمكانية تسجيل الدخول ورفع وإدارة وحذف الملفات المخزنة بواسطة لوحة التحكم، يمكنك أيضًا الوصول إلى مساحة تخزين DigitalOcean عبر سطر الأوامر وعبر الواجهة البرمجية Spaces API. في هذا الدرس، سوف نُنشئ تطبيق Node.js يسمح للمستخدمين برفع الملفات إلى مساحة التخزين في DigitalOcean عن طريق ملء استمارة في الواجهة الأمامية من الموقع. المتطلبات الأساسية لمتابعة هذا الدرس، سوف تحتاج: مساحة تخزين في DigitalOcean، إضافة إلى مفتاح وصول access keyومفتاح وصول سرّي لحسابك. يمكنك مطالعة الدليل How To Create a DigitalOcean Space and API Keyلإعداد حساب على DigitalOcean، وإنشاء مساحة التخزين خاصتك وإنشاء مفتاح API ورمز سري. تحتاج كذلك إلى تثبيت Node.js و npmعلى جهازك. يمكنك الذهاب إلى الموقع الرسمي لـ Node.js لتثبيت الإصدار المناسب لنظام التشغيل الخاص بك. يجب أن يكون لديك الآن حساب DigitalOcean ومساحة تخزين مع مفتاح الوصول access key إضافة إلى Node.js وnpm مُثبتين على جهازك. إضافة مفاتيح الوصول إلى ملف الاعتماد Credentials File مساحات التخزين في DigitalOcean متوافقة مع الواجهة البرمجية API لـ Amazon Simple Storage Service (S3) ، سنستخدم الخدمة SDK AWS الخاصة بجافاسكريبت في Node.js لربط الاتصال بمساحة التخزين التي أنشأناها. الخطوة الأولى هي إنشاء ملف الاعتماد credentials file، لوضع مفتاح الوصول access key ومفتاح الوصول السري secret access keyالذي حصلت عليه عند إنشاء مساحة التخزين الخاصة بك في DigitalOcean. سيتم وضع الملف في aws/credentials./~ على الماك و اللينكس، أو في C:\Users\USERNAME\.aws\credentials على ويندوز. افتح مُوجه الأوامر command prompt، تأكد من أنك في مجلد المستخدمين Users directory الخاص بك، وأنك تملك صلاحيات استخدام sudo، ثم قم بإنشاء مجلد .aws مع تضمينه ملف الاعتماد. sudo mkdir .aws && touch .aws/credentials افتح الملف، وألصق الكود البرمجي التالي داخله، مع استبدال your_access_key و your_secret_key بمفاتيحك الخاصة. credentials [default] aws_access_key_id=your_access_key aws_secret_access_key=your_secret_key الآن سوف تتم المصادقة على دخولك إلى مساحة التخزين Spaces عبر SDK AWS، ويمكننا الآن الانتقال إلى إنشاء التطبيق. تثبيت ارتباطات Node.js للبدء، قم بإنشاء مجلد لوضع تطبيق Node.js فيه ثم اذهب إليه. في هذا المثال التوضيحي، سوف نقوم بإنشاء مشروعنا في spaces-node-app في المجلد sites. mkdir sites/spaces-node-app && cd sites/spaces-node-app قم بإنشاء ملف جديد package.json لأجل مشروعك. وألصق داخله الكود أدناه. package.json { "name": "spaces-node-app", "version": "1.0.0", "main": "server.js", "scripts": { "start": "node server.js" }, "license": "MIT" } الملف package.json سيتضمّن الاسم، رقم الإصدار وترخيص استعمال التطبيق. أما الحقلscripts فسيسمح لنا بتشغيل خادم Node.js بكتابة npm start بدلًا من node server.js. سوف نقوم بتثبيت كل ارتباطاتنا dependencies بالتعليمة npm install، تليها أسماء الارتباطات الأربع في مشروعنا. npm install aws-sdk express multer multer-s3 بعد تشغيل هذا الأمر، سيتم تحديث الملف package.json. هذه الارتباطات ستساعدنا على ربط الاتصال بالواجهة البرمجية لمساحة التخزين فيDigitalOcean ، وإنشاء خادم الويب، والتعامل مع رفع الملفات. ● aws-sdk - ستسمح لنا AWS SDK لجافاسكريبت بالوصول إلى S3 من خلال الواجهة البرمجية لجافاسكريبت (JavaScript API). ● express - Express هي بيئة تطوير للشبكة تسمح لنا بإعداد الخادم بسرعة وكفاءة. ● multer - Multer هو برنامج وسيط لمعالجة رفع الملفات. ● multer-s3 - Multer S3 يمدد رفع الملفات لـ S3 object storage، والتي هي في حالتنا، مساحة تخزين DigitalOcean. الآن وبعد أن أعددنا مكان وارتباطات مشروعنا، يمكن إعداد الخادم والمنظر الأمامي front-end views. إنشاء الواجهة الأمامية للتطبيق أولًا، سننشئ عدة ملفات لأجل المناظر العامّة public views لتطبيقنا. وهي التي سيراها المستخدم على الواجهة الأمامية. قم بإنشاء مجلد اسمهpublic يتضمن index.html, success.html و error.html. حيث ستحتوي كل هذه الملفات الثّلاث على هيكل HTML أدناه، لكن مع محتويات مختلفة في body. قم بكتابة الكود البرمجي التالي في كل ملف. <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>DigitalOcean Spaces Tutorial</title> <link rel="stylesheet" href="./style.css"> </head> <body> <!-- contents will go here --> </body> </html> أكتب رسالة خطأ في body داخل error.html. error.html ... <h1>Something went wrong!</h1> <p>File was not uploaded successfully.</p> ... أكتب رسالة نجاح فيbody داخل success.html. success.html ... <h1>Success!</h1> <p>File uploaded successfully.</p> ... في index.html، سنقوم بإنشاء استمارة HTML بـ multipart/form-data. وستتكون من مُدخل input بسيط وزر الإرسال submit. index.html ... <h1>DigitalOcean Spaces Tutorial</h1> <p>Please select a file and submit the form to upload an asset to your Dig-italOcean Space.</p> <form method="post" enctype="multipart/form-data" action="/upload"> <label for="file">Upload a file</label> <input type="file" name="upload"> <input type="submit" class="button"> </form> ... وأخيرا، سنقوم بإنشاء ملف style.css وإضافة تنسيقات CSS لجعل التطبيق سهل القراءة. style.css html { font-family: sans-serif; line-height: 1.5; color: #333; } body { margin: 0 auto; max-width: 500px; } label, input { display: block; margin: 5px 0; } بهذه الملفات الثلاث، لدينا الآن استمارة لرفع الملفات والتي ستشكل ملامح الصفحة الرئيسية لتطبيقنا الصغير هذا، كما لدينا أيضًا صفحة للنجاح والخطأ لأجل المستخدم. إعداد Express Server Environment لقد أنشأنا كل الملفات الخاصة بالواجهة الأمامية لتطبيقنا، لكننا حاليا لم نقم بعدُ بإعداد أي خادم أو أي طريقة لعرضها. سنقوم بإعداد خادمNode بواسطة Express web framework. في المجلد الجذري للمشروع، قم بإنشاء ملف server.js. وفي الجزء العلوي، قم برفع أربعة ارتباطات بواسطة الدالة ()require. كما سنُوجه تطبيقنا عبر عيّنة app من express. server.js // Load dependencies const aws = require('aws-sdk'); const express = require('express'); const multer = require('multer'); const multerS3 = require('multer-s3'); const app = express(); الواجهة الأمامية موجودة في المجلدpublic ، لذلك ضع الإعدادات تحت الارتباطات مباشرة. server.js ... // Views in public directory app.use(express.static('public')); سوف نوجّه index.html, success.html و error.html نسبة إلى جذر الخادم root of the server. server.js ... // Main, error and success views app.get('/', function (request, response) { response.sendFile(__dirname + '/public/index.html'); }); app.get("/success", function (request, response) { response.sendFile(__dirname + '/public/success.html'); }); app.get("/error", function (request, response) { response.sendFile(__dirname + '/public/error.html'); }); وأخيرا، سوف نخبر الخادمَ أيّ منفذport يجب أن يُصغي إليه. في هذا المثال، سنستخدم 3001, ولكن يمكنك تعيين أي منفذ متاح. server.js ... app.listen(3001, function () { console.log('Server listening on port 3001.'); }); احفظ server.js وشغّل الخادم. يمكنك القيام بذلك عن طريق تشغيل node server.js، أو بـ npm start، الاختصار الذي وضعناه في package.json. npm start Output > node server.js Server listening on port 3001. تصفّح http://localhost:3001، وسوف ترى استمارة الرفع، لأننا جعلنا index.html جذرَ الخادم. يمكنك أيضًا تصفّح http://localhost:3001/success و http://localhost:3001/error للتحقق من أن تلك الصفحات تُوجّه بشكل صحيح. رفع ملف إلى مساحة التخزين بواسطة Multer الآن بعد أن أعددنا بيئة الخادم بشكل صحيح، الخطوة الأخيرة هي دمج الاستمارة مع Multer وMulter S3 لأجل رفع الملف إلى مساحة التخزين. يمكنك استخدام ()new aws.S3 لربط الاتصال بـعميل أمازون( S3 (Amazon S3 client. ولاستخدامه مع Spaces DigitalOcean، سنحتاج إلى تحديد نقطة مرجعيةendpoint جديدة للتأكد من أنه يرفع الملفات إلى المكان الصحيح. في وقت كتابة هذا الدرس، nyc3 هي المنطقة الوحيدة المتاحة لـمساحة التخزين Spaces. في الملف server.js، انتقل مرة أخرى إلى أعلى وقم بإلصاق الأكواد التالية مباشرة تحت التصريح بالثوابت. server.js ... const app = express(); // Set S3 endpoint to DigitalOcean Spaces const spacesEndpoint = new aws.Endpoint('nyc3.digitaloceanspaces.com'); const s3 = new aws.S3({ endpoint: spacesEndpoint }); باستخدام المثال الموجود في وثائق multer-S3، سوف نقوم بإنشاء دالة upload، وإحالة اسم مساحة التخزين خاصتك إلى الخاصية bucket. قم بإعطاء القيمة acl للخاصية public-read لضمان أن يكون الملف في متناول الجميع. لكن إن تركت تلك الخاصية فارغة فستأخذ القيمية الافتراضية private، مما يجعل الملفات غير مُتاحة على شبكة الإنترنت. server.js ... // Change bucket property to your Space name const upload = multer({ storage: multerS3({ s3: s3, bucket: 'your-space-here', acl: 'public-read', key: function (request, file, cb) { console.log(file); cb(null, file.originalname); } }) }).array('upload', 1); بعد اكتمال الدالة upload، الخطوة الأخيرة هي وَصل استمارة الرفع بالكود البرمجي لإرسال الملف وتوجيه المستخدم وفقا لذلك. انتقل إلى أسفل الملف server.js، وألصق هذا الكود مباشرة فوق الوظيفة ()app.listen في نهاية الملف. server.js ... app.post('/upload', function (request, response, next) { upload(request, response, function (error) { if (error) { console.log(error); return response.redirect("/error"); } console.log('File uploaded successfully.'); response.redirect("/success"); }); }); عندما ينقر المستخدم على submit سيتم إرسال استعلامPOST إلى upload/, سيكون Node في حالة إصغاء/انتظار لـ POST. وسيستدعي الدالة()upload. وفي حال العثور على خطأ، فستُعيد العبارة الشرطية توجيه المستخدم إلى الصفحة error/. أما إذا مرّت الأمور بشكل جيد، فستتم إعادة توجيه المستخدم إلى الصفحة success/، وسيتم رفع الملف إلى مساحة التخزين خاصتك. هذا هو الكود الكامل لـ server.js. server.js // Load dependencies const aws = require('aws-sdk'); const express = require('express'); const multer = require('multer'); const multerS3 = require('multer-s3'); const app = express(); // Set S3 endpoint to DigitalOcean Spaces const spacesEndpoint = new aws.Endpoint('nyc3.digitaloceanspaces.com'); const s3 = new aws.S3({ endpoint: spacesEndpoint }); // Change bucket property to your Space name const upload = multer({ storage: multerS3({ s3: s3, bucket: 'your-space-here', acl: 'public-read', key: function (request, file, cb) { console.log(file); cb(null, file.originalname); } }) }).array('upload', 1); // Views in public directory app.use(express.static('public')); // Main, error and success views app.get('/', function (request, response) { response.sendFile(__dirname + '/public/index.html'); }); app.get("/success", function (request, response) { response.sendFile(__dirname + '/public/success.html'); }); app.get("/error", function (request, response) { response.sendFile(__dirname + '/public/error.html'); }); app.post('/upload', function (request, response, next) { upload(request, response, function (error) { if (error) { console.log(error); return response.redirect("/error"); } console.log('File uploaded successfully.'); response.redirect("/success"); }); }); app.listen(3001, function () { console.log('Server listening on port 3001.'); }); أوقف خادم Node عبر الضغط على CONTROL + C في موجه الأوامر، وأعد تشغيله لضمان تطبيق التغييرات الجديدة. npm start انتقل إلى جذر المشروع، اختر ملفَا، وقم بتفعيل الاستمارة. إذا تم تعيين كل شيء بشكل صحيح، سيتم توجيهك إلى صفحة النجاح success page، وسوف يكون الملف متاحًا للعموم في مساحة التخزين خاصتك في DigitalOcean. بافتراض أن الملف الذي قمت برفعه هو test.txt، فإن عنوان الملف سيكون: https://your-space-here.nyc3.digitaloceanspaces.com/test.txt من الأسباب الشائعة لفشل العمليات هي الاعتمادات credentials الخاطئة، وضع ملفات الاعتماد في أماكن غير صحيحة أو استخدام اسم bucket غير صحيح. الخلاصة تهانينا، لقد أنشأت تطبيق Node.js وExpress لرفع الأصول الثابتة للتخزين الكائني object storage. يمكن أن تقوم ببعض التعديلات والتجارب على التطبيق من هنا. لا بد من أخذ احتياطات إضافية مثل المصادقة authentication لضمان سير جيد لهذا النوع من التطبيقات، وتبقى هذه نقطة انطلاق جيدة لجعل تطبيق الويب خاصتك يعمل بسلاسة معDigitalOcean Spaces . ترجمة -وبتصرّف- للمقال How To Upload a File to Object Storage with Node لصاحبته Tania Rascia
- 1 تعليق
-
- جافاسكريبت
- node.js
- (و 3 أكثر)
-
مقدمة Node.js هي منصة لجافا سكريبت للبرمجة متعددة الأغراض والتي تسمح للمستخدمين ببناء تطبيقات الشبكة بسرعة. من خلال استخدام جافا سكريبت على كل من الواجهة الأمامية والخلفية، سيكون التطوير أكثر اتساقا وسيُصمم داخل النظام نفسه. في هذا الدليل، ستتعلم كيفية تثبيت Node.js على خادم Debian 8. يحتوي Debian 8 على إصدار لـ Node.js في مجلداته الافتراضية، ولكن ذلك الإصدار قديم في الأغلب، سنقوم باستكشاف طريقتين لتثبيت أحدث إصدارات Node.js على نظامك. المتطلبات الأساسية لمتابعة هذا الدرس، ستحتاج إلى خادم Debian 8 مع مستخدم غير كامل الصلاحيات non-root user ويملك امتيازات sudo. كيفية التثبيت باستخدام PPA أسرع وأسهل وسيلة للحصول على أحدث إصدار من Node.js على خادمك هي بإضافة PPA (personal package archive) الخاص بـ NodeSource. سوف يشتمل على عدد أكبر من تحديثات Node.js مقارنة بمستودعات Debian الرسمية. كما أنه يتيح لك الاختيار بين Node.js v4.x (النسخة القديمة المدعومة على المدى الطويل, مدعومة حتى أبريل 2017)، v6.x (نسخة أحدث من LTS، والتي ستُدعم حتى أبريل 2018)، و Node.js v7.x (النسخة الحالية قيد التطوير). أولا، قم بتثبيت PPA للحصول على محتوياته. تأكد من أنك في المجلد الرئيسي home directory، استخدم curl لاستخراج النص البرمجي لتثبيت الإصدار المفضل لديك، مع استبدال 6.x برقم الإصدار الصحيح: cd ~ curl -sL https://deb.nodesource.com/setup_6.x -o nodesource_setup.sh يمكنك فحص محتويات هذا النص البرمجي بواسطة nano (أو محرر النصوص المفضل لديك): nano nodesource_setup.sh وقم بتشغيل البرنامج النصي عقِب الأمر sudo: sudo bash nodesource_setup.sh سيتم إضافة PPA إلى إعداداتك وسوف يتم تحديث حزمتك المحلية المُخزنة تلقائيًا. بعد تشغيل برنامج التنصيب من nodesource، يمكنك تثبيت حزمة Node.js بنفس الطريقة التي اتبعتها أعلاه: sudo apt-get install nodejs الحزمة nodejs تحتوي رُقامة nodejs (nodejs binary ) إضافة إلى npm، لذلك لا تحتاج إلى تثبيت npm بشكل منفصل. ولكن لكي تعمل بعض حُزم npm (مثل تلك التي تتطلب ترجمة التعليمات البرمجية من المصدر)، فستحتاج إلى تثبيت الحزمة build-essential: sudo apt-get install build-essential كيفية التثبيت بواسطة nvm بدل تثبيت Node.js بواسطة apt، يمكنك استعمال أداة خاصة تسمّى nvm (Node.js version manager). فباستخدام nvm، يمكنك تثبيت عدة إصدارات متكاملة من Node.js ما سيسمح لك بضبط بيئة العمل بشكل أسهل. كما ستعطيك إمكانية الوصول إلى أحدث إصدارات Node.js، ولكن ستسمح لك أيضا باستهداف الإصدارات السابقة التي قد يعتمد عليها تطبيقك. بدايةً سوف نحتاج إلى الحصول على حزم البرمجيات من مستودعات Debian والتي ستسمح لنا ببناء الحُزم المصدرية source packages. التعليمة nvm ستستخدم تلك الأدوات لبناء المكونات الضرورية: sudo apt-get update sudo apt-get install build-essential libssl-dev حالما يتم تثبيت الحُزم الضرورية، يمكنك حذف سكريبت التثبيتnvm من صفحة المشروع علىGitHub , رقم الإصدار قد يكون مختلفًا، ولكن بشكل عام، يمكنك تحميله بـ curl: curl -sL https://raw.githubusercontent.com/creationix/nvm/v0.32.0/install.sh -o install_nvm.sh طالع سكريبت التثبيت بواسطة nano: nano install_nvm.sh قم بتشغيل النص البرمجي بواسطة bash: bash install_nvm.sh النص البرمجي سيقوم بتثبيت البرنامج في مجلد في ~/.nvm. كما سيقوم بإضافة الأسطر اللازمة للملف ~/.profile لجعل التعليمة nvm متاحًا. لكسب إمكانية الوصول إلى التعليمةnvm وكذلك وظائفها، ستحتاج إلى تسجيل الخروج ثم تسجيل الدخول مرة أخرى، أو يمكنك إضافة المصدر ~/.profile حتى يتم اعتبار التغييرات في الجلسةsession الحالية: source ~/.profile الآن وبعد تثبيت nvm، يمكنك تثبيت أحد إصدارات Node.js بشكل منفصل. لمعرفة إصدارات Node.js المتوفرة للتثبيت، قم بكتابة: nvm ls-remote Output ... v6.8.0 v6.8.1 v6.9.0 (LTS: Boron) v6.9.1 (LTS: Boron) v6.9.2 (Latest LTS: Boron) v7.0.0 v7.1.0 v7.2.0 كما ترون، فأحدث إصدار في وقت كتابة هذه السطور هو v7.2.0، ولكن v6.9.2 هو آخر الإصدارات المدعومة على المدى الطويل. يمكنك تثبيته بكتابة: nvm install 6.9.2 سوف ترى المخرجات التالية: Computing checksum with sha256sum Checksums matched! Now using node v6.9.2 (npm v3.10.9) Creating default alias: default -> 6.9.2 (-> v6.9.2) عادة، سيتحولnvm إلى استخدام الإصدار المثبت حديثًا. لكن يمكنك أن تخبرnvm صراحة باستخدام النسخة التي حمّلناها للتو عن طريق كتابة: nvm use 6.9.2 يمكنك مشاهدة النسخة المستخدمة حاليا عن طريق كتابة في واجهة الأوامر: node -v Output v6.9.2 إن كان لديك عدة إصدارات من Node.js، يمكنك معرفة أيّ منها تم تثبيته بكتابة: nvm ls إذا كنت ترغب في جعل أحد هذه الإصدارات نسختك الافتراضية، يمكنك كتابة: nvm alias default 6.9.2 هذا الإصدار سيتم اختياره تلقائيًا عند فتح جلسة عمل جديدة على المِطراف terminal. يمكنك أيضًا الإحالة إليه باستخدام الاسم default هكذا: nvm use default كل نسخة من Node.js ستُتابع حُزمها الخاصة بها، وسيكونnpm متاحًا لإدارتها. يمكنك وضع حُزم تثبيت npm في المجلد ./node_modules الخاص بمشروعNode.js باستخدام الصيغة الطبيعية normal format. على سبيل المثال، بالنسبة للوحدة express: npm install express إذا كنت ترغب بتثبيته بشكل كلّي (جعْله متاحا للمشاريع الأخرى التي تستخدم نفس إصدار Node.js)، يمكنك إضافة العلم –g كما يلي: npm install -g express هذا سيثبّت الحزمة في: ~/.nvm/node_version/lib/node_modules/package_name التثبيت الكلي سيتيح لك تشغيل التعليمات من سطر الأوامر، ولكن سيكون عليك ربط الحزمة من مجالك المحلي local sphere للوصول إليه من داخل برنامج ما: npm link express يمكنك معرفة المزيد حول الخيارات المتاحة لك مع nvm بكتابة: nvm help خلاصة كما رأيت، هناك عدة طرق لتثبيت وتشغيل Node.js على خادم Debian 8. ظروفك ستملي عليك أيّ الطرق المذكورة أعلاه ستكون أفضل لك. وفي حين أن النسخة المحزومة packaged في مستودع Ubuntu هي الأسهل، إلّا أن طريقةnvm أكثر مرونة بالتأكيد. ترجمة -وبتصرّف- للمقال How To Install Node.js on Debian 8 لصاحبه Brian Hogan
-
أعترف بأنّي لا أمتلك خبرة كبيرة في مكتبة Webpacker الجديدة في إطار العمل Ruby on Rails، ولكنّي قرّرت الاعتماد على هذه المكتبة تمامًا والاستغناء كلّيًا عن مكتبة Sprockets للتعامل مع الأصول assets. وباعتباري أحد متّبعي مبدأ Convention Over Configuration فقد حاولت جاهدًا إيجاد الطريقة التي يمكن الاصطلاح عليها في تشييد تطبيق Webpacker. هذه المكتبة في أيامها الأولى لذا أظنّ أنّ فريق مطوري Rails لم يقوموا بهذا الأمر أيضًا، وأعتقد بأنّ مجتمع المطوّرين سيجد حلًّا لهذه المسألة قريبًا. على أي حال، إليك الطريقة التي اتبعتها في استبدال asset pipeline بـ Webpacker. إن كنت ترغب في العمل على مشروع جديد، فاستخدم الأمر: rails new blank --skip-sprockets --webpack ليتم إنشاء تطبيق Rails جديد مع الاستغناء عن مكتبة Sprockets وإضافة المكتبة Webpacker. أما لو كنت ترغب في إضافة Webpacker إلى مشروع قائم فعليك بمراجعة التوثيقات. بعد ذلك احذف بعض الجواهر gems والتي لم نعد بحاجة إليها من ملف Gemfile، وهي sass-rails، uglifier و coffee-rails. كذلك يمكنك التخلص من المجلد app/assets لأنّنا لم نعد بحاجة إليه بعد الآن. لنلق نظرة في البداية على محتويات ملف application.js الذي يتم إنشاؤه افتراضيًّا بواسطة Webpacker. /* eslint no-console:0 */ // This file is automatically compiled by Webpack, along with any other files // present in this directory. You're encouraged to place your actual application logic in // a relevant structure within app/javascript and only use these pack files to reference // that code so it'll be compiled. // // To reference this file, add <%= javascript_pack_tag 'application' %> to the appropriate // layout file, like app/views/layouts/application.html.erb console.log('Hello World from Webpacker') تشير التعليقات الواردة في هذا الملف إلى أن المجلد app/javascript/packs هو نقطة الولوج entry point إلى الحزم المستخدمة في التطبيق، وأنّ عليك وضع التطبيق الحقيقي في المجلد app/javascript. ترتيب الملفّات والمجلّدات لقد نظّمت تطبيق Webpacker الخاصّ بي كما هو موضح أدناه، والتطبيق يحمل اسم blog. سترى أنّي قد أدرجت تطبيق JavaScript الحقيقي في المجلد app/javascript/blog بدلًا من app/javascript ولم أقم بذلك اعتباطًا. أولًا: يعني هذا أن بمقدوري إضافة العديد من التطبيقات إلى المشروع الواحد وحسب الحاجة، بدلًا من تكديس جميع الشيفرات جنبًا إلى جنب. ثانيًا: ستتيح لي هذه الطريقة امتلاك نقطة ولوج حقيقية للحزم وهذا ما سأوضّحه الآن. blog +-- app | +-- javascript | | +-- blog | | | +-- fonts | | | +-- images | | | +-- styles | | | +-- index.js | | +-- packs | | | +-- application.js لنلق نظرة الآن إلى ملف app/javascript/packs/application.js وهو نقطة الولوج إلى حزمتي، وهو ملفّ بسيط للغاية: import 'blog'; سيتم استيراد التطبيق وتشغيل الملف app/javascript/blog/index.js والذي سيصبح نقطة الولوج إلى تطبيق JavaScript الخاصّ بي. بهذه الطريقة أحافظ على نقطة الولوج بسيطة قدر الإمكان أما ما تبقى من الشيفرة فيكون ضمن التطبيق. جدير بالذكر كذلك أنّك لست ملزمًا بتسمية المجلد - والملفّ - باسم blog، بل يمكنك استخدام أي اسم تشاء، ولكنّني توخيت تبسيط الأمور بجعل اسم المجلد مطابقًا لاسم تطبيق Rails. والآن سنستخدم javascript_pack_tag للإشارة إلى تطبيقنا. javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' مرحلة التطوير عند العمل في بيئة التطوير Development استخدم الأمر bin/webpack-dev-server وستتمّ مراقبة التطبيق وإعادة بنائه عند الحاجة، وإرسال التعديلات إلى المتصفّح. وبعد أن يصبح التطبيق جاهزًا للتجميع Compile يمكن استدعاء الأمر bin/webpack أو rails assets:precompile، ولكن سيتولى الخادوم هذه المهمّة على الأرجح. مكتبتا Turbolinks وRails UJS إن كنت ستستخدم Turbolinks و Rails UJS في تطبيقك فعليك إعداد هاتين المكتبتين وتشغيلهما. من السهل استدعاء المكتبتين بواسطة الأمر //= require في حال كنت تستخدم asset pipline ولكن عند استخدام هاتين المكتبتين كوحدات فالأمر مختلف قليلًا. في البداية علينا تثبيت المكتبتين: yarn add rails-ujs turbolinks بعد ذلك علينا استيراد المكتبتين وتشغيلهما في ملف app/javascript/blog/index.js: import Rails from 'rails-ujs'; import Turbolinks from 'turbolinks'; Rails.start(); Turbolinks.start(); كما تلاحظ فقد اتبعنا نفس الأسلوب في استدعاء كلتا المكتبتين وتشغيلهما. متغيرات البيئة Environment Variables يمكن الوصول إلى متغيرات البيئة عبر الشيفرات الخاصة بنا بعد تجميعها. عادة ما أضيف اللاحقة .erb إلى اسم الملف ثم أنفّذ شيئًا مماثلًا لهذا: <%= ENV['X_ENV_VAR'] %> ولكن هناك طريقة أفضل، إذ يمكن تهجير المتغيرات إلى process.env وكما يلي: export const STRIPE_API_KEY = process.env.STRIPE_API_KEY; أوراق الأنماط Stylesheets يمكن وبكل بساطة استيراد ملفات CSS أو Sass التي ترغب باستخدامها في التطبيق. لقد حدّدت الملف app/javascript/blog/styles/app.scss كنقطة ولوج Sass وبهذا أبقي جميع الملفات في مجلد styles، وتصبح عملية استيرادها إلى التطبيق أمرًا سهلًا للغاية: import './styles/app.scss'; يمكن استخدام المحرّف ~ مع import وستبدأ عملية البحث عن الملف من المجلد node_modules، فلو أردت مثلًا استيراد مكتبة Bootstrap إلى التطبيق يمكنك استخدام الشيفرة التالية: @import '~bootstrap/scss/bootstrap'; الصور لا تختلف الصور عن أوراق الأنماط في شيء، إذ يجب استيرادها في البداية لتتمكن من استخدامها في التطبيق. عادة ما أضع الصور في مجلد app/javascript/blog/images ثم أنشئ ملفًّا باسم index.js في نفس المجلد وظيفته استيراد جميع الصور في المجلد. فعلى سبيل المثال: import './logo.svg'; import './menu-open.svg'; import './menu-close.svg'; عليك الانتباه إلى أنّ هذه الطريقة لن تُضمّن الصور في أوراق الأنماط، وإنما تدفعها إلى Webpacker لتكون متاحة للاستخدام في التطبيق. ويمكنك حينئذٍ استخدام الدالة المساعدة asset_pack_path في العرض للإشارة إلى هذه الملفات. فلو أردت مثلًا استخدام إحدى هذه الصور: = image_tag asset_pack_path('logo.svg') إضافة إلى ذلك يمكنك الإشارة إلى الصور في CSS أو Sass وسيتلقّفها Webpacker بصورة تلقائية. وبصورة عامة يكون مسار الجذر نقطة الولوج الخاصّة بأوراق الأنماط لذا لن تكون بحاجة إلى استخدام المسارات المطلقة. ختامًا كما شاهدت فإن الأمر يتطلب الكثير من العمل، وهذا هو الأسلوب الذي أتبعه في استخدام Webpacker الآن. حاولت البحث عن مقالات تعنى بتفصيل طريقة استخدام Webpacker ولكنّي لم أجد شيئًا يذكر في الوقت الحاضر. أنا متحمّس جدًّا لمعرفة طريقة الاستخدام القياسية لهذه المكتبة هذا في حال تمّ تحديدها في المستقبل. ترجمة - وبتصرّف - للمقال Replacing Rails Asset Pipeline with Webpacker لصاحبه Dwight Conrad Watson.
-
- ruby on rails
- javascript
-
(و 5 أكثر)
موسوم في: