-
المساهمات
64 -
تاريخ الانضمام
-
تاريخ آخر زيارة
نوع المحتوى
ريادة الأعمال
البرمجة
التصميم
DevOps
التسويق والمبيعات
العمل الحر
البرامج والتطبيقات
آخر التحديثات
قصص نجاح
أسئلة وأجوبة
كتب
دورات
كل منشورات العضو Mohamed Lahlah
-
يغلّف كائن الوكيل Proxy كائنًا آخر ويعترض عملياته مثل: خاصيات القراءة أو الكتابة وغيرهما. ويعالجها اختياريًا بمفرده، أو يسمح بشفافية للكائن التعامل معها بنفسه. تستخدم العديد من المكتبات وأُطر عمل المتصفح الوسطاء. وسنرى في هذا الفصل العديد من تطبيقاته العملية. الوسيط Proxy صياغته: let proxy = new Proxy(target, handler) target -- وهو الكائن الّذي سنغلِّفه يمكن أي يكون أي شيء بما في ذلك التوابع. handler -- لإعداد الوسيط: وهو كائن يحتوي على "الاعتراضات"، أي دوالّ اعتراض العمليات. مثل: الاعتراض get لاعتراض خاصية القراءة في الهدف target، الاعتراض set لاعتراض خاصية الكتابة في الهدف target، وهكذا. سيراقب الوسيط العمليات فإن كان لديه اعتراض مطابق في المعالج handler للعملية المنفذة عند الهدف، فعندها سينفذ الوسيط هذه العملية ويعالجها وإلا ستُنفذ هذه العملية من قِبل الهدف نفسه. لنأخذ مثالًا بسيطًا يوضح الأمر، لننشئ وسيطًا بدون أي اعتراضات: let target = {}; let proxy = new Proxy(target, {}); // المعالج فارغ proxy.test = 5; // الكتابة على الوسيط (1) alert(target.test); // النتيجة: 5، ظهرت الخاصية في كائن الهدف ! alert(proxy.test); // النتيجة: 5، يمكننا قرائتها من الوسيط أيضًا (2) for(let key in proxy) alert(key); // النتيجة: test, عملية التكرار تعمل (3) انطلاقًا من عدم وجود اعتراضات سيُعاد توجيه جميع العمليات في الوسيط proxy إلى كائن الهدف target. تحدد عملية الكتابة proxy.test= القيمة عند الهدف target، لاحظ السطر (1). تعيد عملية القراءة proxy.test القيمة من عند الهدف target. لاحظ السطر (2). تعيد عملية التكرار على الوسيط proxy القيم من الهدف target. لاحظ السطر (3). كما نرى، الوسيط proxy في هذه الحالة عبارة عن غِلاف شفاف حول الكائن الهدف target. لنُضف بعض الاعتراضات من أجل تفعيل المزيد من الإمكانيات. ما الذي يمكننا اعتراضه؟ بالنسبة لمعظم العمليات على الكائنات، هناك ما يسمى "الدوالّ الداخلية" في مواصفات القياسية في اللغة، والّتي تصف كيفية عمل الكائن عند أدنى مستوى. فمثلًا الدالة الداخلية [[Get]] لقراءة خاصية ما، والدالة الداخلية [[Set]] لكتابة خاصية ما، وهكذا. وتستخدم هذه الدوال من قبل المواصفات فقط، ولا يمكننا استدعاؤها مباشرة من خلال أسمائها. اعتراضات الوسيط تتدخلّ عند استدعاء هذه الدوالّ. وهي مدرجة في المواصفات القياسية للوسيط وفي الجدول أدناه. يوجد اعتراض مخصص (دالّة معالجة) لكل دالّة داخلية في هذا الجدول: يمكننا إضافة اسم الدالّة إلى المعالج handler من خلال تمريرها كوسيط new Proxy لاعتراض العملية. الدالة الداخلية الدالة المعالجة ستعمل عند [[Get]] get قراءة خاصية [[Set]] set الكتابة على خاصية [[HasProperty]] has التأكد من وجود خاصية ما in [[Delete]] deleteProperty عملية الحذف delete [[Call]] apply استدعاء تابِع [[Construct]] construct عملية الإنشاء -الباني- new [[GetPrototypeOf]] getPrototypeOf Object.getPrototypeOf [[SetPrototypeOf]] setPrototypeOf Object.setPrototypeOf [[IsExtensible]] isExtensible Object.isExtensible [[PreventExtensions]] preventExtensions Object.preventExtensions [[DefineOwnProperty]] defineProperty Object.defineProperty Object.defineProperties [[GetOwnProperty]] getOwnPropertyDescriptor Object.getOwnPropertyDescriptor for..in Object.keys/values/entries [[OwnPropertyKeys]] ownKeys Object.getOwnPropertyNames Object.getOwnPropertySymbols for..in Object/keys/values/entries table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; } تحذير: التعامل مع الثوابت تفرض علينا لغة جافاسكربت بعض الثوابت - الشروط الواجب تحقيقها من قِبل الدوالّ الداخلية والاعتراضات. معظمها للقيم المُعادة من الدوالّ: يجب أن تعيد الدالّة [[Set]] النتيجة true إذا عُدلت القيمة بنجاح، وإلا ستُعيد false. يجب أن تعيد الدالّة [[Delete]] النتيجة true إذا حُذفت القيمة بنجاح، وإلا ستُعيد false. …وهكذا، سنرى المزيد من الأمثلة لاحقًا. هنالك بعض الثوابت الأخرى، مثل: يجب أن تُعيد الدالة [[GetPrototypeOf]] المطبقة على كائن الوسيط نفس القيمة الّتي ستُعيدها الدلّة [[GetPrototypeOf]] المطبقة على كائن الهدف لكائن الوسيط، بتعبير آخر، يجب أن تعرض دائمًا قراءة النموذج الأولي لكائن الوسيط نفس قراءة النموذج الأولي لكائن الهدف. يمكن للاعتراضات التدخل في هذه العمليات ولكن لابد لها من اتباع هذه القواعد. تضمن هذه الثوابت السلوك الصحيح والمُتسق لمميّزات اللغة. وجميع هذه الثوابت موجودة في المواصفات القياسية للغة. وغالبًا لن تكسرَهم إن لم تنفذّ شيئًا غريبًا. لنرى كيف تعمل في مثال عملي. إضافة اعتراض للقيم المبدئية تعدُّ خاصيات القراءة / الكتابة من أكثر الاعتراضات شيوعًا. لاعتراض القراءة، يجب أن يكون لدى المعالج handler دالّة get(target, property, receiver). وتُشغّل عند قراءة خاصية ما، وتكون الوسطاء: target - هو كائن الهدف، والذي سيمرر كوسيط أول لِـ new Proxy، property - اسم الخاصية، receiver - إذا كانت الخاصية المستهدفة هي الجالِب (getter)، فإن receiver هو الكائن الذي سيُستخدم على أنه بديل للكلمة المفتاحية this في الاستدعاء. عادةً ما يكون هذا هو كائن proxy نفسه (أو كائن يرث منه، في حال ورِثنا من الوسيط). لا نحتاج الآن هذا الوسيط، لذلك سنشرحها بمزيد من التفصيل لاحقًا. لنستخدم الجالِب get لجلب القيم الافتراضية لكائن ما. سننشئ مصفوفة رقمية تُعيد القيمة 0 للقيم غير الموجودة. عادةً عندما نحاول الحصول على عنصر من مصفوفة، وكان هذا العنصر غير موجود، سنحصل على النتيجة غير معرّف undefined، لكننا هنا سنُغلف المصفوفة العادية داخل الوسيط والّذي سيعترضُ خاصية القراءة ويعيد 0 إذا لم تكُ الخاصية المطلوبة موجودة في المصفوفة: let numbers = [0, 1, 2]; numbers = new Proxy(numbers, { get(target, prop) { if (prop in target) { return target[prop]; } else { return 0; // القيمة الافتراضية } } }); alert( numbers[1] ); // 1 alert( numbers[123] ); // 0 (هذا العنصر غير موجود) كما رأينا، من السهل جدًا تنفيذ ذلك باعتراض الجالِب get. يمكننا استخدام الوسيط proxy لتنفيذ أي منطق للقيم "الافتراضية". تخيل أن لدينا قاموسًا (يربط القاموس مفاتيح مع قيم على هيئة أزواج) يربط العبارات مع ترجمتها: let dictionary = { 'Hello': 'Hola', 'Bye': 'Adiós' }; alert( dictionary['Hello'] ); // Hola alert( dictionary['Welcome'] ); // undefined مبدئيًا إن حاولنا الوصول إلى عبارة غير موجودة في القاموس فسيُعيد غير معرّف undefined، ولكن عمليًا إن ترك العبارة بدون مترجمة أفضل من undefined، لذا لنجعلها تُعيد العبارة بدون مترجمة بدلاً من undefined. لتحقيق ذلك، سنغلّف القاموس dictionary بالوسيط ليعترض عمليات القراءة: let dictionary = { 'Hello': 'Hola', 'Bye': 'Adiós' }; dictionary = new Proxy(dictionary, { get(target, phrase) { // اعترض خاصية القراءة من القاموس if (phrase in target) { // إن كانت موجودة في القاموس return target[phrase]; // أعد الترجمة } else { // وإلا أعدها بدون ترجمة return phrase; } } }); // ابحث عن عبارة عشوائية في القاموس! // بأسوء حالة سيُعيد العبارة غير مترجمة alert( dictionary['Hello'] ); // Hola alert( dictionary['Welcome to Proxy']); // Welcome to Proxy (يعيد العبارة بدون ترجمة) لاحظ كيف أن الوسيط يعد الكتابة على المتغير الافتراضي: dictionary = new Proxy(dictionary, ...); يجب على الوسيط استبدال كائن الهدف بالكامل في كل مكان. ولا يجب أن يشير أي شيء للكائن الهدف بعد أن يستبدلهُ الوسيط. وإلا فذلك سيُحدثُ فوضى عارمة. تدقيق المدخلات باعتراض الضابِط "set" لنفترض أننا نريد إنشاء مصفوفة مخصصة للأرقام فقط. وإن أضيفت قيمة من نوع آخر، يجب أن يظهر خطأ. يُشغّل اعتراض الضابِط set عند الكتابة على الخاصية. set(target, property, value, receiver) target - الكائن الهدف، الذي سنمرره كوسيط أول new Proxy، property - اسم الخاصية، value - قيمة الخاصية، receiver - مشابه للدالّة get، ولكن هنا لضبط الخاصيات فقط. يجب أن يُعيد الاعتراض set القيمة true إذا نجح ضبط القيمة في الخاصية، وإلا يعيد القيمة false (يُشغّل خطأ من نوع TypeError). لنأخذ مثالًا عن كيفية استخدامها للتحقق من القيم الجديدة: let numbers = []; numbers = new Proxy(numbers, { // (*) set(target, prop, val) { // لاعتراض خاصية ضبط القيمة if (typeof val == 'number') { target[prop] = val; return true; } else { return false; } } }); numbers.push(1); // أضيفت بنجاح numbers.push(2); // أضيفت بنجاح alert("Length is: " + numbers.length); // 2 numbers.push("test"); // TypeError (الدالّة 'set' في الوسيط أعادت القيمة false) alert("This line is never reached (error in the line above)"); لاحظ أن الدوال المدمجة للمصفوفات ما تزال تعمل! إذ تضاف القيم عن طريق push. فتزداد خاصية length تلقائيًا عند إضافة هذه القيم. لذا فإن الوسيط لن يكسر أي شيء. يجب علينا ألا نعيد كتابة الدوالّ المُدمجة للمصفوفات الّتي تضيف القيم مثل push وunshift، وما إلى ذلك، إن كان هدفنا إضافة عمليات تحقق، لأنهم يستخدمون داخليًا دالّة [[Set]] والّتي ستُعترضُ من قِبل الوسيط. إذن الشيفرة نظيفة ومختصرة. لا تنسَ أن تعيد القيمة true عند نجاح عملية الكتابة. كما ذكر أعلاه، هناك ثوابت ستعقّد. بالنسبة للضابط set، يجب أن يُرجع true عند نجاح عملية الكتابة. إذا نسينا القيام بذلك أو إعادة أي قيمة زائفة، فإن العملية تؤدي إلى خطأ TypeError. التكرار باستخدام تابع "ownKeys" و"getOwnPropertyDescriptor" إن الدالة Object.keys وحلقة التكرار for..in ومعظم الطرق الأخرى الّتي تستعرض خاصيات الكائن، وتستخدم الدالّة الداخلية [[OwnPropertyKeys]] للحصول على قائمة بالخاصيات يمكننا اعتراضها من خلال ownKeys. تختلف هذه الدوال وحلقات التكرار على الخاصيات تحديدًا في: Object.getOwnPropertyNames (obj): تُعيد الخاصيات غير الرمزية. Object.getOwnPropertySymbols (obj): تُعيد الخاصيات الرمزية. Object.keys/values(): تعيد الخاصيات/القيم غير الرمزية والتي تحمل راية قابلية الاحصاء enumerable (شرحنا في مقال سابق ما هي رايات الخواص وواصفاتها يمكنك الاطلاع عليه لمزيد من التفاصيل). حلقات for..in: تمرّ على الخاصيات غير الرمزية التي تحمل راية قابلية الاحصاء enumerable، وكذلك تمرّ على خاصيات النموذج الأولي (prototype). … لكن كلهم يبدأون بهذه القائمة. في المثال أدناه ، نستخدم اعتراض ownKeys لجعل حلقةfor..in تمر على user، وكذلكObject.keys وObject.values، وتتخطى الخاصيات التي تبدأ بشرطة سفلية _: let user = { name: "John", age: 30, _password: "***" }; user = new Proxy(user, { ownKeys(target) { return Object.keys(target).filter(key => !key.startsWith('_')); } }); // الاعتراض "ownKeys" سيزيل الخاصّية _password for(let key in user) alert(key); // name, then: age // نفس التأثير سيحدث على هذه التوابع: alert( Object.keys(user) ); // name,age alert( Object.values(user) ); // John,30 حتى الآن، لا يزال مثالنا يعمل وفق المطلوب. على الرغم من ذلك، إذا أضفنا خاصية ما غير موجودة في الكائن الأصلي وذلك بإرجاعها من خلال الوسيط، فلن يعيدها التابع Object.keys: let user = { }; user = new Proxy(user, { ownKeys(target) { return ['a', 'b', 'c']; } }); alert( Object.keys(user) ); // <empty> هل تسأل نفسك لماذا؟ السبب بسيط: تُرجع الدالّة Object.keys فقط الخاصيات الّتي تحمل راية قابلية الإحصاء enumerable. وهي تحقق من رايات الخاصيات لديها باستدعاء الدالّة الداخلية [[GetOwnProperty]] لكل خاصية للحصول على واصِفها. وهنا، نظرًا لعدم وجود الخاصّية، فإن واصفها فارغ، ولا يوجد راية قابلية الإحصاء enumerable، وبناءً عليه تخطت الدالّة الخاصّية. من أجل أن ترجع الدالّة Object.keys الخاصية، فيجب أن تكون إما موجودة في الكائن الأصلي، وتحمل راية قابلية الإحصاء enumerable، أو يمكننا وضع اعتراض عند استدعاء الدالة الداخلية [[GetOwnProperty]] (اعتراض getOwnPropertyDescriptor سيحقق المطلوب)، وإرجاع واصف لخاصية قابلية الإحصاء بالإيجاب هكذا enumerable: true. إليك المثال ليتوضح الأمر: let user = { }; user = new Proxy(user, { ownKeys(target) { // يستدعى مرة واحدة عند طلب قائمة الخاصيات return ['a', 'b', 'c']; }, getOwnPropertyDescriptor(target, prop) { // تستدعى من أجل كلّ خاصّية return { enumerable: true, configurable: true /* ...يمكننا إضافة رايات أخرى مع القيم المناسبة لها..." */ }; } }); alert( Object.keys(user) ); // a, b, c نعيد مرة أخرى: نضيف اعتراض [[GetOwnProperty]] إذا كانت الخاصّية غير موجودة في الكائن الأصلي. الخاصيات المحمية والاعتراض "deleteProperty" وغيره هناك إجماع كبير في مجتمع المطورين بأن الخاصيات والدوالّ المسبوقة بشرطة سفلية _ هي للاستخدام الداخلي. ولا ينبغي الوصول إليها من خارج الكائن. هذا ممكن تقنيًا: let user = { name: "John", _password: "secret" }; // لاحظ أن في الوضع الطبيعي يمكننا الوصول لها alert(user._password); // secret لنستخدم الوسيط لمنع الوصول إلى الخاصيات الّتي تبدأ بشرطة سفلية _. سنحتاج استخدام الاعتراضات التالية: get لإلقاء خطأ عند قراءة الخاصية، set لإلقاء خطأ عند الكتابة على الخاصية، deleteProperty لإلقاء خطأ عند حذف الخاصية، ownKeys لاستبعاد الخصائص التي تبدأ بشرطة سفلية _ من حلقة for..in، والدوالّ التي تستعرض الخاصيات مثل: Object.keys. هكذا ستكون الشيفرة: let user = { name: "John", _password: "***" }; user = new Proxy(user, { get(target, prop) { if (prop.startsWith('_')) { throw new Error("Access denied"); } let value = target[prop]; return (typeof value === 'function') ? value.bind(target) : value; // (*) }, set(target, prop, val) { // لاعتراض عملية الكتابة على الخاصّية if (prop.startsWith('_')) { throw new Error("Access denied"); } else { target[prop] = val; return true; } }, deleteProperty(target, prop) { // لاعتراض عملية حذف الخاصّية if (prop.startsWith('_')) { throw new Error("Access denied"); } else { delete target[prop]; return true; } }, ownKeys(target) { // لاعتراض رؤية الخاصية من خلال الحلقات أو الدوالّ return Object.keys(target).filter(key => !key.startsWith('_')); } }); // الاعتراض "get" سيمنعُ قراءة الخاصّية _password try { alert(user._password); // Error: Access denied } catch(e) { alert(e.message); } // الاعتراض "set" سيمنع الكتابة على الخاصّية _password try { user._password = "test"; // Error: Access denied } catch(e) { alert(e.message); } // الاعتراض "deleteProperty" سيمنعُ حذف الخاصّية _password try { delete user._password; // Error: Access denied } catch(e) { alert(e.message); } // الاعتراض "ownKeys" سيمنعُ إمكانية رؤية الخاصّية _password في الحلقات for(let key in user) alert(key); // name لاحظ التفاصيل المهمة في الاعتراض get، وذلك في السطر (*): get(target, prop) { // ... let value = target[prop]; return (typeof value === 'function') ? value.bind(target) : value; // (*) } لماذا نحتاج لدالّة لاستدعاء value.bind(target)؟ وسبب ذلك هو أن دوال الكائن مثل: user.checkPassword() يجب تحافظ على إمكانية الوصول للخاصّية _password: user = { // ... checkPassword(value) { // دوال الكائن يجب أن تحافظ على إمكانية الوصول للخاصية _password return value === this._password; } } تستدعي الدالة user.checkPassword() وسيط الكائن user ليحلّ محلّ this (الكائن قبل النقطة يصبح بدل this)، لذلك عندما نحاول الوصول إلى الخاصّية this._password، سينُشّط الاعتراض get ( والّذي يُشغلّ عند قراءة خاصية ما) ويلقي الخطأ. لذلك نربط سياق الدوال الخاصة بالكائن مع دوالّ الكائن الأصلي، أيّ الكائن الهدف target لاحظ السطر (*). بعد ذلك، الاستدعاءات المستقبلية ستستخدم target بدلّ this، دون الاعتراضات. هذا الحل ليس مثاليًا ولكنه يفي بالغرض، ولكن يمكن لدالّةٍ ما أن تُمرّر الكائن غير المغَلّف بالوسيط (أي الكائن الأصلي) إلى مكان آخر، وبذلك ستخرّب الشيفرةولن نستطع الإجابة على أسئلة مثل: أين يستخدم الكائن الأصلي؟ وأين يستخدم الكائن الوسيط؟ أضف إلى ذلك، يمكن للكائن أن يغلف بأكثر من وسيط (تضيف عدة وسطاء "تعديلات" مختلفة على الكائن الأصلي)، وإن مرّرنا كائن غير مغلّف بالوسيط إلى دالّة ما، ستكون هناك عواقب غير متوقعة. لذا، لا ينبغي استخدام هذا الوسيط في كل الحالات. ملاحظة: الخاصيات الخاصة بالصنف تدعم محركات جافاسكربت الحديثة الخاصيات الخاصّة بالأصناف، وتكون مسبوقة بـ #. تطرقنا لها سابقًا في مقال الخصائص والتوابع الخاصّة والمحمية. وبدون استخدام أيّ الوسيط. على الرغم من ذلك هذه الخاصيات لها مشاكلها الخاصة أيضًا. وتحديدًا، مشكلة عدم إمكانية توريث هذه الخاصيات. استخدام الاعتراض "In range" مع "has" لنرى مزيدًا من الأمثلة. لدينا الكائن range: let range = { start: 1, end: 10 }; نريد استخدام المعامل in للتحقق من أن الرقم موجود في range. إن الاعتراض has سيعترض استدعاءات in. has(target, property) target - هو الكائن الهدف، الذي سيمرر كوسيط أولnew Proxy`، property - اسم الخاصية إليك المثال: let range = { start: 1, end: 10 }; range = new Proxy(range, { has(target, prop) { return prop >= target.start && prop <= target.end; } }); alert(5 in range); // true alert(50 in range); // false تجميلٌ لغويٌّ رائع، أليس كذلك؟ وتنفيذه بسيط جدًا. تغليف التوابع باستخدام "apply" يمكننا أيضًا تغليف دالّة ما باستخدام كائن الوسيط. يعالج الاعتراض apply(target, thisArg, args) استدعاء كائن الوسيط كتابع: target: الكائن الهدف (التابع - أو الدالّة - هي كائن في لغة جافا سكربت)، thisArg: قيمة this. args: قائمة الوسطاء. فمثلًا، لنتذكر المُزخرف delay(f, ms)، الذي أنشأناه في مقال المزخرفات والتمرير. في تلك المقالة أنشأناه بدون استخدام الوسيط. عند استدعاء الدالة delay(f, ms) ستُعيد دالّة أخرى، والّتي بدورها ستوجه جميع الاستدعاءات إلى f بعد ms ملّي ثانية. إليك التطبيق المثال بالاعتماد على التوابع: function delay(f, ms) { // يعيد المغلف والذي بدوره سيوجه الاستدعاءات إلى f بعد انتهاء مهلة زمنية معينة return function() { // (*) setTimeout(() => f.apply(this, arguments), ms); }; } function sayHi(user) { alert(`Hello, ${user}!`); } // بعد عملية التغليف استدعي الدالّة sayHi بعد 3 ثواني sayHi = delay(sayHi, 3000); sayHi("John"); // Hello, John! (بعد 3 ثواني) كما رأينا، غالبًا ستعمل هذه الطريقة وفق المطلوب. تُنفذّ الدالّة المغلفة في السطر (*) استدعاءً بعد انتهاء المهلة. لكن دالة المغلف لا تعيد توجيه خاصيات القراءة أو الكتابة للعمليات أو أيّ شيء آخر. بعد عملية التغليف، يُفقدُ إمكانية الوصول لخاصيات التوابع الأصلية، مثل: name وlength وغيرها: function delay(f, ms) { return function() { setTimeout(() => f.apply(this, arguments), ms); }; } function sayHi(user) { alert(`Hello, ${user}!`); } alert(sayHi.length); // 1 (طول الدالة هو عدد الوسطاء في تعريفها) sayHi = delay(sayHi, 3000); alert(sayHi.length); // 0 (إن عدد وسطاء عند تعريف المغلّف هو 0) في الحقيقة إن إمكانيات الوسيط Proxy أقوى بكثير من ذلك، إذ إنه يعيد توجيه كل شيء إلى الكائن الهدف. لنستخدم الوسيط Proxy بدلاً من الدالّة المُغلِّفة: function delay(f, ms) { return new Proxy(f, { apply(target, thisArg, args) { setTimeout(() => target.apply(thisArg, args), ms); } }); } function sayHi(user) { alert(`Hello, ${user}!`); } sayHi = delay(sayHi, 3000); alert(sayHi.length); // 1 (*) سيعيد الوسيط توجيه عملية "get length" إلى الهدف sayHi("John"); // Hello, John! (بعد 3 ثواني) نلاحظ أن النتيجة نفسها، ولكن الآن ليس مجرد استدعاءات فقط، وإنما كل العمليات في الوسيط يُعاد توجيها إلى التوابع الأصلية. لذلك سيعيد الاستدعاء sayHi.length النتيجة الصحيحة بعد التغليف في السطر (*). وبذلك حصلنا على مُغلِّف أغنى بالمميزات من الطريقة السابقة. هنالك العديد من الاعتراضات الأخرى: يمكنك العودة لبداية المقال لقراءة القائمة الكاملة للاعتراضات. كما أن طريقة استخدامها مشابه كثيرًا لما سبق. الانعكاس الانعكاس Reflect هو عبارة عن كائن مضمّن في اللغة يبسط إنشاء الوسيط Proxy. ذكرنا سابقًا أن الدوالّ الداخلية، مثل: [[Get]] و[[Set]] وغيرها مخصصة للاستخدام في مواصفات اللغة فقط، ولا يمكننا استدعاؤها مباشرة. يمكن لكائن المنعكس Reflect من فعل ذلك إلى حد ما. إذ أن الدوالّ الخاصة به عبارة مُغلِّفات صغيرة حول الدوالّ الداخلية. فيما يلي أمثلة للعمليات واستدعاءات المنعكس Reflect الّتي ستُؤدي نفس المهمة: الدالة الداخلية الدالة المقابلة في المنعكس العملية [[Get]] Reflect.get(obj, prop) obj[prop] [[Set]] Reflect.set(obj, prop, value) obj[prop] = value [[Delete]] Reflect.deleteProperty(obj, prop) delete obj[prop] [[Construct]] Reflect.construct(F, value) new F(value) … … … فمثلًا: let user = {}; Reflect.set(user, 'name', 'John'); alert(user.name); // John تحديدًا، يتيح لنا المنعكس Reflect استدعاء العمليات (new, delete…) كتوابع هكذا (Reflect.construct,Reflect.deleteProperty, …). وهذه الامكانيات مثيرة للاهتمام، ولكن هنالك شيء آخر مهم. لكل دالّة داخلية، والّتي يمكننا تتبعها من خلال الوسيط Proxy، يوجد دالّة مقابلة لها في المنعكس Reflect، بنفس الاسم والوسطاء أي مشابه تمامًا للاعتراض في الوسيط Proxy. لذا يمكننا استخدام المنعكس Reflect لإعادة توجيه عملية ما إلى الكائن الأصلي. في هذا المثال، سيكون كلًا من الاعتراضين get وset شفافين (كما لو أنهما غير موجودين) وسيُوجهان عمليات القراءة والكتابة إلى الكائن، مع إظهار رسالة: let user = { name: "John", }; user = new Proxy(user, { get(target, prop, receiver) { alert(`GET ${prop}`); return Reflect.get(target, prop, receiver); // (1) }, set(target, prop, val, receiver) { alert(`SET ${prop}=${val}`); return Reflect.set(target, prop, val, receiver); // (2) } }); let name = user.name; // shows "GET name" user.name = "Pete"; // shows "SET name=Pete" في الشيفرة السابقة: Reflect.get: يقرأ خاصية الكائن. Reflect.set: يكتب خاصية الكائن، سيُعيد true إن نجحت، وإلا سيُعيد false. أي أن كل شيء بسيط: إذا كان الاعتراض يُعيد توجيه الاستدعاء إلى الكائن، فيكفي استدعاء Reflect.<method> بنفس الوسطاء. في معظم الحالات ، يمكننا فعل الشيء نفسه بدون Reflect، على سبيل المثال، يمكن استبدال Reflect.get(target, prop, receiver) بـ target[prop]. يوجد فروقٍ بينهم ولكن لا تكاد تذكر. استخدام الوسيط مع الجالِب لنرى مثالاً يوضح لماذا Reflect.get أفضل. وسنرى أيضًا سبب وجود الوسيط الرابع في دوالّ get/set، تحديدًا receiver والّذي لم نستخدمه بعد. لدينا كائن user مع خاصية _name وجالب مخصص لها. لنُغلِّفه باستخدام الوسيط: let user = { _name: "Guest", get name() { return this._name; } }; let userProxy = new Proxy(user, { get(target, prop, receiver) { return target[prop]; } }); alert(userProxy.name); // Guest إن الاعتراض get في هذا المثال "شفاف"، فهو يعيد الخاصية الأصلية ولا يفعل أي شيء آخر. هذا يكفي لمثالنا. يبدو أن كل شيء على ما يرام. لكن لنُزد تعقيد المثال قليلًا. بعد وراثة كائن admin من الكائن user، نلاحظ السلوك الخاطئ: let user = { _name: "Guest", get name() { return this._name; } }; let userProxy = new Proxy(user, { get(target, prop, receiver) { return target[prop]; // (*) target = user } }); let admin = { __proto__: userProxy, _name: "Admin" }; // نتوقع ظهور الكلمة: Admin alert(admin.name); // النتيجة: Guest (لماذا؟) إن قراءة admin.name يجب أن تُعيد كلمة "Admin" وليس "Guest"! ما الذي حدث؟ لعلنا أخطأنا بشيء ما في عملية الوراثة؟ ولكن إذا أزلنا كائن الوسيط، فسيكون كل شيء على ما يرام. إذًا المشكلة الحقيقية في الوسيط، تحديدًا في السطر (*). عندما نقرأ خاصّية الاسم admin.name من كائن admin، لا يحتوي كائن admin على هذه الخاصية، وبذلك ينتقل للبحث عنها في النموذج الأولي الخاص به. النموذج الأولي الخاص به هو userProxy. عند قراءة الخاصية name من الوسيط، سيُشغّل اعتراض get الخاص به، وسيُعيدih من الكائن الأصلي هكذا target[prop] في السطر (*). يؤدي الاستدعاء target [prop]، عندما يكون قيمة prop هي الجالب (getter)، سيؤدي ذلك لتشغيل الشيفرة بالسياقِ this = target. لذلك تكون النتيجة this._name من الكائن الأصلي للهدف target، أي: من user. لإصلاح مثل هذه المواقف، نحتاج إلى receiver، الوسيط الثالث للاعتراض get. إذ سيُحافظ على قيمة this الصحيحة لتُمرر بعد ذلك إلى الجالِب (getter). في حالتنا تكون قيمتها هي admin. كيفية يمرر سياق الاستدعاء للحصول الجالِب الصحيح؟ بالنسبة للتابع العادي، يمكننا استخدام call/apply، ولكن بالنسبة للجالِب فلن يستدعى بهذه الطريقة. لنستخدم الدالّة Reflect.get والتي يمكنها القيام بذلك. وكل شيء سيعمل مثلما نريد. وإليك الشكل الصحيح: let user = { _name: "Guest", get name() { return this._name; } }; let userProxy = new Proxy(user, { get(target, prop, receiver) { // receiver = admin return Reflect.get(target, prop, receiver); // (*) } }); let admin = { __proto__: userProxy, _name: "Admin" }; alert(admin.name); // Admin الآن يحافظ receiver بمرجع لقيمة this الصحيحة (وهي admin)، والّتي ستُمرر من خلال Reflect.get في السطر (*). يمكننا إعادة كتابة الاعتراض بطريقة أقصر: get(target, prop, receiver) { return Reflect.get(...arguments); } استدعاءات المنعكس Reflect لها نفس أسماء اعتراضات الوسيط وتقبل نفس وسطائه أيضًا. إذ صُمّمت خصيصًا لهذا الغرض. لذا ، فإن استخدام المنعكس return Reflect... يزودنا بالأمان والثقة لتوجيه العملية والتأكد تمامًا من أننا لن ننسَ أي شيء متعلقٌ بها. قيود الوسيط لدى الوسيط طريقة فريدة لتغيير أو تعديل سلوك الكائنات الموجودة عند أدنى مستوى. ومع ذلك، هذه الطريقة ليست مثالية. وإنما هناك قيود. كائنات مضمّنة: فتحات داخلية تستخدم العديد من الكائنات المضمنة، مثل الكائنات Map وSet وDate وPromise وغيرها ما يسمى بـ" الفتحات الداخلية ". وهي مشابهة للخاصيات، لكنها محفوظة للأغراض داخلية فقط، وللمواصفات القياسية للغة فقط. فمثلًا تخزن Map العناصر في الفتحة الداخلية [[MapData]]. وتستطيع الدوال المُضمّنة الوصول إليها مباشرةً، وليس عبر الدوالّ الداخلية مثل [[Get]]/[[Set]]. لذا فإن الوسيط Proxy لا يمكنه اعتراض ذلك. ولكن ما سبب اهتمامنا بذلك؟ إنها بكلّ الأحوال داخلية! حسنًا ، إليك المشكلة. بعد أن يغلف الكائن المضمن مثل: Map باستخدام الوسيط، لن يمتلك الوسيط هذه الفتحات الداخلية، لذلك ستفشل الدوالّ المضمنة. إليك مثالًا يوضح الأمر: let map = new Map(); let proxy = new Proxy(map, {}); proxy.set('test', 1); // Error داخليًا ، تخزن Map جميع البيانات في الفتحة الداخلية [[MapData]]. ولكن الوسيط ليس لديه مثل هذه الفتحة. تحاول الدالّة المضمنة Map.prototype.set الوصول إلى الخاصية الداخلية this.[[MapData]]، ولكن لأن this=proxy، لا يمكن العثور عليه في الوسيط proxy مما سيؤدي لفشل العملية. لحسن الحظ، هناك طريقة لإصلاحها: let map = new Map(); let proxy = new Proxy(map, { get(target, prop, receiver) { let value = Reflect.get(...arguments); return typeof value == 'function' ? value.bind(target) : value; } }); proxy.set('test', 1); alert(proxy.get('test')); // 1 (works!) الآن تعمل وفق المطلوب، لأن اعتراض get يربط خاصيات الدوالّ، مثل map.set، بالكائن الهدف (map) نفسه. بخلاف المثال السابق، فإن قيمة this داخلproxy.set (...) لن تكون proxy، وإنما map الأصلية. لذا عند التطبيق الداخلي لـ set سيحاول الوصول إلى الفتحة الداخلية هكذا this.[[MapData]]، ولحسن الحظ سينجح. ملاحظة: المصفوفة العادية Array ليس لها فتحات داخلية استثناء ملحوظ: لا تستخدم المصفوفات المضمنة Array الفتحات الداخلية. هذا لأسباب تاريخية، إذ إنها ظهرت منذ وقت طويل. لذلك لا توجد مشكلة عند تغليف المصفوفة إلى باستخدام الوسيط. الخاصيات الخاصة يحدث شيء مشابه للأمر مع خاصيات الصنف الخاصة. فمثلًا، يمكن للدالّة getName() الوصول إلى الخاصية الخاصًة #name بدون استخدام الوسيط، ولكن بعد تغليفنا للكائن باستخدام الوسيط ستتوقف إمكانية وصول الدالّة السابقة للخاصّية الخاصّة: class User { #name = "Guest"; getName() { return this.#name; } } let user = new User(); user = new Proxy(user, {}); alert(user.getName()); // Error وذلك بسبب أن التنفيذ الفعلي للخاصّيات الخاصّة يكون باستخدام الفتحات داخلية. ولا تستخدم لغة جافاسكربت الدوالّ [[Get]]/[[Set]] للوصول إليها. في استدعاء getName()، تكون قيمة this هي كائن user المغلّف بالوسيط، ولا يحتوي -هذا الكائن- على فتحة داخلية مع هذه الخاصّيات الخاصّة. وللمرة الثانية، يكون ربط الدالّة بالكائن من سيحل الأمر: class User { #name = "Guest"; getName() { return this.#name; } } let user = new User(); user = new Proxy(user, { get(target, prop, receiver) { let value = Reflect.get(...arguments); return typeof value == 'function' ? value.bind(target) : value; } }); alert(user.getName()); // Guest ومع ذلك، فإن لهذا الحل بعض العيوب، كما وضحنا سابقًا: سيعرض هذا الحل الكائن الأصلي لبعض الدوالّ، مما سيُسمح بتمريره لدوالٍ أكثر وبذلك كسر الدوالّ الأخرى المتعلقة بالوسيط. Proxy! = target إن كلًا من الوسيط والكائن الأصلي مختلفان. وهذا أمر طبيعي، أليس كذلك؟ لذلك إذا استخدمنا الكائن الأصلي كخاصية في المجموعة Set، ثم غلفناه بالوسيط، فعندئذ لن نتمكن من العثور على الوسيط: let allUsers = new Set(); class User { constructor(name) { this.name = name; allUsers.add(this); } } let user = new User("John"); alert(allUsers.has(user)); // true user = new Proxy(user, {}); // لاحظ alert(allUsers.has(user)); // false كما رأينا، بعد التغليف باستخدام الوسيط لن نتمكن من العثور على كائن user في المجموعة allUsers، لأن الوسيط هو كائن مختلف عن الكائن الأصلي. لا يمكن للوسطاء proxies اعتراض اختبار المساواة الصارم ===. يمكنها اعترلض العديد من العمليات الأخرى، مثل: new (مع build) وin (مع has) وdelete (مع deleteProperty) وما إلى ذلك. ولكن لا توجد طريقة لاعتراض اختبار المساواة الصارم للكائنات. الكائن يساوي نفسه تمامًا ولا يساوي أيّ كائنٍ آخر. لذا فإن جميع العمليات والأصناف المضمّنة في اللغة التي توازن بين الكائنات من أجل المساواة ستميز الفرق بين الكائن الأصلي والوسيط. ولا يوجد من يحل محله لإصلاح الأمر. الوسيط القابل للتعطيل وسيط revocable هو وسيط يمكن تعطيله. لنفترض أن لدينا موردًا ونود منع الوصول إليه في لحظةٍ ما. أحد الأشياء التي يمكننا فعلها هو تغليف هذا المورد بالوسيط القابل للتعطيل، بدون أي اعتراضات. بهذه الحالة سيُعيد الوسيط توجيه العمليات للكائن، ويمكننا تعطيله بأي لحظة نريدها. وصياغته: let {proxy, revoke} = Proxy.revocable(target, handler) الاستدعاء من خلال proxy سيُعيد الكائن والاستدعاء من خلال revoke سيعطل إمكانية الوصول إليه. إليك المثال لتوضيح الأمر: let object = { data: "Valuable data" }; let {proxy, revoke} = Proxy.revocable(object, {}); // مرر الوسيط لمكان آخر بدل الكائن alert(proxy.data); // Valuable data // لاحقا نستدعي التابع revoke(); // لن يعمل الوسيط الآن (لأنه معطلّ) alert(proxy.data); // Error يؤدي استدعاء revoke() لإزالة جميع المراجع الداخلية للكائن الهدف من الوسيط، وبذلك لم يعد متصل بأي شيء بعد الآن. كما يمكننا بعد ذلك كنس المخلفات من الذاكرة بإزالة الكائن الهدف. يمكننا أيضًا تخزين revoke في WeakMap، حتى نتمكن من العثور عليه بسهولة من خلال كائن الوسيط: let revokes = new WeakMap(); let object = { data: "Valuable data" }; let {proxy, revoke} = Proxy.revocable(object, {}); revokes.set(proxy, revoke); // ..لاحقًا في الشيفرة.. revoke = revokes.get(proxy); revoke(); alert(proxy.data); // Error (revoked) تفيدنا هذه الطريقة بأنه ليس علينا بعد الآن حمل revoke. وإنما يمكننا الحصول عليها من map من خلال الوسيط proxy عند الحاجة. نستخدم WeakMap بدلاً من Map هنا لأنه لن يمنع كنس المخلّفات في الذاكرة. إذا أصبح كائن الوسيط "غير قابل للوصول" (مثلًا، في حال لم يعد هناك متغيّر يشير إليه بعد الآن)، فإن WeakMap يسمح بمسحه من الذاكرة مع revoke خاصته والّتي لن نحتاج إليها بعد الآن. المصادر المواصفات القياسية للوسيط : Proxy. توثيق الوسيط الرسمي من مركز مطوري موزيلا MDN. خلاصة الوسيط Proxy عبارة عن غلاف حول كائن، يُعيد توجيه العمليات عليه إلى الكائن، ويحبس بعضها بشكل اختياري. يمكنه تغليف أي نوع من الكائنات، بما في ذلك الأصناف والدوالّ. صياغته: let proxy = new Proxy(target, { /* traps */ }); … ثم يجب علينا استخدام "الوسيط" في كل مكان بدلاً من كائن "الهدف". لا يمتلك الوكيل خاصيات أو توابع. يعترض عملية ما إذا زُودَ بالاعتراض المناسب، وإلا سيعيد توجيهها إلى كائن الهدف target. يمكننا اعتراض: قراءة (get) وكتابة (set) وحذف (deleteProperty) خاصية (حتى الخاصية غير موجودة). استدعاء دالّة ما (الاعتراض apply). المعامل new (الاعتراض construct). العديد من العمليات الأخرى (القائمة الكاملة في بداية المقال وفي التوثيق الرسمي). مما سيسمح لنا بإنشاء خاصيات ودوالّ "افتراضية"، وتطبيق قيم افتراضية، وكائنات المراقبة، وزخرفة الدوالّ، وأكثر من ذلك بكثير. يمكننا أيضًا تغليف كائن ما عدة مرات في وسطاء مختلفة، وزخرفته بمختلف أنواع الوظائف. صُممّت الواجهة البرمجية للمنعكس لتكمل عمل الوسيط. بالنسبة لأي اعتراض Proxy ، هناك استدعاء للمنعكس Reflect مقابل له بنفس الوسطاء. يجب علينا استخدامها لإعادة توجيه الاستدعاءات إلى الكائنات المستهدفة. لدى الوسيط بعض القيود: تحتوي الكائنات المضمّنة في اللغة على "فتحات داخلية"، ولا يمكن الوصول إلى تلك الأشياء بالوسيط. راجع الفقرة المخصصة لها أعلاه. وينطبق الشيء نفسه على خاصيات الصنف الخاصة، إذ تنفيذها داخليًا باستخدام الفتحات. لذا يجب أن تحتوي استدعاءات دوالّ الوسيط على الكائن المستهدف بدل this للوصول إليها. لا يمكن اعتراض اختبارات المساواة الصارمة للكائن ===. الأداء: تعتمد المقاييس على المحرك، ولكن عمومًا إن الوصول إلى الخاصية باستخدام وكيل بسيط سيستغرق وقتًا أطول بعض الشيء. عمليًا يهتم بها البعض لعدم حدوث اختناق في الأداء "عنق الزجاجة". تمارين خطأ في قراءة الخاصيات غير موجودة في الكائن الأصلي عادةً ، تؤدي محاولة قراءة خاصية غير موجودة إلى إعادة النتيجة undefined. أنشئ وسيطًا يعيد خطأ عند محاولة قراءة خاصية غير موجودة في الكائن الأصلي بدلًا من ذلك. يمكن أن يساعد ذلك في الكشف عن الأخطاء البرمجية مبكرًا. اكتب دالّة wrap(target) والّتي تأخذ كائنًا target وتعيد وسيطًا والّذي سيُضيف خصائص وظيفية أخرى. هكذا يجب أن تعمل: let user = { name: "John" }; function wrap(target) { return new Proxy(target, { /* your code */ }); } user = wrap(user); alert(user.name); // John alert(user.age); // ReferenceError: Property doesn't exist "age" الحل let user = { name: "John" }; function wrap(target) { return new Proxy(target, { get(target, prop, receiver) { if (prop in target) { return Reflect.get(target, prop, receiver); } else { throw new ReferenceError(`Property doesn't exist: "${prop}"`) } } }); } user = wrap(user); alert(user.name); // John alert(user.age); // ReferenceError: Property doesn't exist "age" الوصول إلى الدليل [-1] في فهرس المصفوفة يمكننا الوصول إلى عناصر المصفوفة باستخدام الفهارس السلبية في بعض لغات البرمجة، محسوبةً بذلك من نهاية المصفوفة. هكذا: let array = [1, 2, 3]; array[-1]; // 3, آخر عنصر في المصفوفة array[-2]; // 2, خطوة للوراء من نهاية المصفوفة array[-3]; // 1, خطوتين للوراء من نهاية المصفوفة بتعبيرٍ آخر ، فإن array[-N] هي نفس array[array.length - N]. أنشئ وسيطًا لتنفيذ هذا السلوك. هكذا يجب أن تعمل: let array = [1, 2, 3]; array = new Proxy(array, { /* your code */ }); alert( array[-1] ); // 3 alert( array[-2] ); // 2 // بقية الخصائص الوظيفية الأخرى يجب أن تبقى كما هي الحل let array = [1, 2, 3]; array = new Proxy(array, { get(target, prop, receiver) { if (prop < 0) { // حتى وإن وصلنا للمصفوفة هكذا arr[1] // إن المتغيّر prop عبارة عن سلسلة نصية لذا نحتاج لتحويله إلى رقم prop = +prop + target.length; } return Reflect.get(target, prop, receiver); } }); alert(array[-1]); // 3 alert(array[-2]); // 2 المراقب أنشئ تابع makeObservable(target) الّذي تجعل الكائن قابلاً للمراقبة 'من خلال إعادة وسيط. إليك كيفية العمل: function makeObservable(target) { /* your code */ } let user = {}; user = makeObservable(user); user.observe((key, value) => { alert(`SET ${key}=${value}`); }); user.name = "John"; // alerts: SET name=John وبعبارة أخرى، فإن الكائن الّذي سيعاد من خلال makeObservable يشبه تمامًا الكائن الأصلي، ولكنه يحتوي أيضًا على الطريقة observe(handler) الّتي ستضبط تابع المُعالج ليستدعى عند أي تغيير في الخاصية. عندما تتغير خاصية ما، يستدعى handler(key, value) مع اسم وقيمة الخاصية. ملاحظة. في هذا التمرين، يرجى الاهتمام بضبط الخاصيات فقط. يمكن تنفيذ عمليات أخرى بطريقة مماثلة. الحل يتكون الحل من جزئين: عندما يستدعى .observe(handler)، نحتاج إلى حفظ المعالج في مكان ما، حتى نتمكن من الاتصال به لاحقًا. يمكننا تخزين المعالجات في الكائن مباشرة، باستخدام الرمز الخاص بنا كمفتاح خاصية. سنحتاج لوسيط مع الاعتراض set لاستدعاء المعالجات عند حدوث أي تغيير. let handlers = Symbol('handlers'); function makeObservable(target) { // 1. هيئ مخزّن المعالجات target[handlers] = []; // احتفظ بتوابع المعالج في مصفوفة للاستدعاءات اللاحقة target.observe = function(handler) { this[handlers].push(handler); }; // 2. أنشئ وسيط ليعالج التغييرات return new Proxy(target, { set(target, property, value, receiver) { let success = Reflect.set(...arguments); // وجه العملية إلى الكائن if (success) { // إن حدث خطأ ما في ضبط الخاصية // استدعي جميع المعالجات target[handlers].forEach(handler => handler(property, value)); } return success; } }); } let user = {}; user = makeObservable(user); user.observe((key, value) => { alert(`SET ${key}=${value}`); }); user.name = "John"; ترجمة -وبتصرف- للفصل Proxy and Reflect من كتاب The JavaScript language
-
إن طريقة الاستيراد والتصدير التي تحدثنا عنها في الفصل السابق، تصدير الوحدات واستيرادها تدعى بالطريقة "الثابتة". إذ أنّ صياغتها بسيطة وصارمة للغاية. دعنا في البداية نوضح بعض الأمور، أولًا، لا يمكننا إنشاء أي وسطاء للتعليمة import إنشاءً ديناميكيًا. إذ يجب أن يكون مسار الوِحدة سلسلة أولية (primitive)، ولا يجب أن تكون استدعاءً لدالة معينة. فهذا لن ينجح: import ... from getModuleName(); // خطأ، مسموح استخدام السلاسل فقط ثانيًا، لا يمكننا استخدام الاستيراد المشروط (في حال حدوث شرط معين استورد مكتبة) أو الاستيراد أثناء التشغيل: if(...) { import ...; // خطأ غير مسموح بذلك! } { import ...; // خطأ، لا يمكننا وضع تعليمة import في أي كتلة } وذلك لأن تعليمتي import/export تهدفان لتوفير العمود الفقري لبنية الشيفرة. وهذا أمر جيد، إذ يمكننا تحليل بنية الشيفرة، وتجميع الوحدات وتحزيمها في ملف واحد من خلال أدوات خاصة، ويمكننا أيضًا إزالة عمليات التصدير غير المستخدمة (هزّ الشجرة -بهدف سقوط الأوراق اليابسة). هذا ممكن فقط لأن هيكلية التعليمتين import/export بسيطة وثابتة. ولكن كيف يمكننا استيراد وِحدة استيرادً ديناميكيًا بحسب الطلب؟ تعبير الاستيراد يُحمّل التعبير import (module) الوِحدة ويُرجع وعدًا، والّذي يُستبدل بكائن الوِحدة، ويحتوي هذا الأخير على كافة عمليات التصدير الخاصة بالكائن. ويُستدعى من أي مكان في الشيفرة البرمجية. ونستطيع استخدامه ديناميكيًا في أي مكان من الشيفرة البرمجية، فمثلًا: let modulePath = prompt("Which module to load?"); import(modulePath) .then(obj => <module object>) .catch(err => <loading error, e.g. if no such module>) أو يمكننا استخدام let module = await import(modulePath) إن كنا بداخل دالّة غير متزامنة. فمثلًا، ليكن لدينا الوِحدة التالية say.js: // ? say.js export function hi() { alert(`Hello`); } export function bye() { alert(`Bye`); } … ثم يكون الاستيراد الديناميكي هكذا: let {hi, bye} = await import('./say.js'); hi(); bye(); أو إذا كان say.js يحتوي على التصدير المبدئي: // ? say.js export default function() { alert("Module loaded (export default)!"); } … بعد ذلك، من أجل الوصول إليه، يمكننا استخدام الخاصية default لكائن الوِحدة: let obj = await import('./say.js'); let say = obj.default; // أو بسطرٍ واحد هكذا: // let {default: say} = await import('./say.js'); say(); إليك المثال الكامل: الملف say.js: export function hi() { alert(`Hello`); } export function bye() { alert(`Bye`); } export default function() { alert("Module loaded (export default)!"); } الملف index.html: <!doctype html> <script> async function load() { let say = await import('./say.js'); say.hi(); // Hello! say.bye(); // Bye! say.default(); // Module loaded (export default)! } </script> <button onclick="load()">Click me</button> وإليك النتائج في هذا المثال حي. لاحظ كيف تعمل عمليات الاستيراد الديناميكية في السكربتات العادية، ولا تتطلب script type ="module". لاحظ أيضًا على الرغم من أن تعليمة import() تشبه طريقة استدعاء دالّة، إلا أنها صياغة خاصة ويحدث هذا التشابه فقط لاستخدام الأقواس (على غرار super ()). لذلك لا يمكننا نسخ تعليمة import إلى متغير، أو استخدام call/apply معها. إذ هي ليست دالّة. ترجمة -وبتصرف- للفصل Dynamic imports من كتاب The JavaScript language
-
لمُوجِّهات (تعليمات) الاستيراد والتصدير أكثر من صياغة برمجية واحدة رأينا في الفصل السابق، مقدمة إلى الوحدات استعمالًا بسيطًا له، فهيًا نرى بقية الاستعمالات. التصدير قبل التصريح يمكننا أن نقول لأيّ تصريح بأنّه مُصدّر بوضع عبارة export قبله، كان التصريح عن متغيّر أو عن دالة أو عن صنف. فمثلًا، التصديرات هنا كلّها صحيحة: // تصدير مصفوفة export let months = ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; // تصدير ثابت export const MODULES_BECAME_STANDARD_YEAR = 2015; // تصدير صنف export class User { constructor(name) { this.name = name; } } ملاحظة: لا يوجد فواصل منقوطة بعد تعليمة التصدير للأصناف أو الدوالّ لاحظ أن تعليمة export قبل الصنف أو الدالة لا يجعلها تعابير الدوالّ. ولو أنه يصُدرها، لكنه لا يزال تعريفًا للدالّة أو الصنف. لا توصي معظم الأدلة التعليمية بوضع فاصلة منقوطة بعد تعريف الدوال والأصناف. لهذا السبب لا داعي للفاصلة المنقوطة في نهاية التعليمة export class والتعليمة export function: export function sayHi(user) { alert(`Hello, ${user}!`); } // لاحظ لا يوجد فاصلة منقوطة في نهاية التعريف التصدير بعيدًا عن التصريح كما يمكننا وضع عبارة export لوحدها. هنا نصرّح أولًا عن الدالتين وبعدها نُصدّرهما: // ? say.js function sayHi(user) { alert(`Hello, ${user}!`); } function sayBye(user) { alert(`Bye, ${user}!`); } export {sayHi, sayBye}; // تصدير قائمة من المتغيرات أو… يمكننا تقنيًا وضع export أعلى الدوال أيضًا. عبارة استيراد كل شيء عادةً نضع قائمة بما نريد استيراده في أقواس معقوفة import {...}، هكذا: // ? main.js import {sayHi, sayBye} from './say.js'; sayHi('John'); // Hello, John! sayBye('John'); // Bye, John! ولكن لو أردنا استيراد وحدات كثيرة، فيمكننا استيراد كلّ شيء كائنًا واحدًا باستعمال import * as <obj> هكذا: // ? main.js import * as say from './say.js'; say.sayHi('John'); say.sayBye('John'); يقول المرء من النظرة الأولى ”استيراد كلّ شيء فكرة جميلة جدًا، وكتابة الشيفرة سيكون أسرع. أساسًا لمَ نقول جهارةً ما نريد استيراده؟“ ذلك… لأسباب وجيهة. أدوات البناء الحديثة (مثل: webpack وغيرها) لنقل مثلًا بأنّا أضفنا مكتبة خارجية اسمها say.js إلى مشروعنا، وفيها دوالّ عديدة: // ? say.js export function sayHi() { ... } export function sayBye() { ... } export function becomeSilent() { ... } هكذا نستعمل واحدة فقط من دوالّ say.js في مشروعنا: // ? main.js import {sayHi} from './say.js'; …حينها تأتي أداة التحسين وترى ذلك، فتُزيل الدوال الأخرى من الشيفرة … بذلك يصغُر حجم الملف المبني. هذا ما نسميه هز الشجر (لتَسقطَ الأوراق اليابسة فقط). لو وضّحنا بالضبط ما نريد استيراده فيمكننا كتابته باسم أقصر: sayHi() بدل say.sayHi(). بكتابة قائمة الاستيراد جهارةً نستطيع أن نفهم بنية الشيفرة دون الخوض في التفاصيل (أي نعرف ما نستعمل من وحدات، وأين نستعملها). هذا يسهّل دعم الشيفرة وإعادة كتابتها لو تطلّب الأمر. استيراد كذا بالاسم كذا as يمكننا كذلك استعمال as لاستيراد ما نريد بأسماء مختلفة. فمثلًا يمكننا استيراد الدالة sayHi في المتغير المحلي hi لنختصر الكلام، واستيراد sayBye على أنّها bye: // ? main.js import {sayHi as hi, sayBye as bye} from './say.js'; hi('John'); // Hello, John! bye('John'); // Bye, John! تصدير كذا بالاسم كذا as نفس صياغة الاستيراد موجودة أيضًا للتصدير export. فلنصدّر الدوال على أنّها hi وbye: // ? say.js ... export {sayHi as hi, sayBye as bye}; الآن صارت hi وbye هي الأسماء ”الرسمية“ للشيفرات الخارجية وستُستعمل عند الاستيراد: // ? main.js import * as say from './say.js'; // لاحِظ الفرق say.hi('John'); // Hello, John! say.bye('John'); // Bye, John! التصدير المبدئي في الواقع العملي، ثمّة نوعين رئيسين من الوحدات. تلك التي تحتوي مكتبة (أي مجموعة من الدوال) مثل وحدة say.js أعلاه. وتلك التي تصرّح عن كيانٍ واحد مثل وحدة user.js التي تُصدّر class User فقط. عادةً ما يُحبّذ استعمال الطريقة الثانية كي يكون لكلّ ”شيء“ وحدةً خاصة به. ولكن هذا بطبيعة الحال يطلب ملفات كثيرة إذ يطلب كلّ شيء وحدةً تخصّه باسمه، ولكنّ هذه ليست بمشكلة، أبدًا. بل على العكس هكذا يصير التنقل في الشيفرة أسهل (لو كانت تسمية الملفات مرضية ومرتّبة في مجلدات). توفر الوِحدات طريقة لصياغة عبارة export default (التصدير المبدئي) لجعل "سطر تصدير واحد لكلّ وِحدة" تبدو أفضل. ضَع export default قبل أيّ كيان لتصديره: // ? user.js export default class User { // نُضيف ”default“ فقط constructor(name) { this.name = name; } } لكلّ ملف سطر تصدير export default واحد لا أكثر. وبعدها… نستورد الكيان بدون الأقواس المعقوفة: // ? main.js import User from './user.js'; // لا نضع {User}، بل User new User('John'); أسطر الاستيراد التي لا تحتوي الأقواس المعقوفة أجمل من تلك التي تحتويها. يشيع خطأ نسيان تلك الأقواس حين يبدأ المطورون باستعمال الوِحدات. لذا تذكّر دائمًا، يطلب سطر الاستيراد import أقواس معقوفة للكيانات المُصدّرة والتي لها أسماء، ولا يطلبها لتلك المبدئية. التصدير الذي له اسم التصدير المبدئي export class User {...} export default class User {...} import {User} from ... import User from ... table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; } يمكننا نظريًا وضع النوعين من التصدير معًا في نفس الوحدة (الذي له اسم والمبدئي)، ولكن عمليًا لا يخلط الناس عادةً بينها، بل للوِحدة إمّا تصديرات لها أسماء، أو التصدير المبدئي. ولأنّه لا يمكن أن يكون لكلّ ملف إلا تصديرًا مبدئيًا واحدًا، فيمكن للكيان الذي صُدّر ألّا يحمل أيّ اسم. فمثلًا التصديرات أسفله كلّها صحيحة مئة في المئة: export default class { // لا اسم للصنف constructor() { ... } } export default function(user) { // لا اسم للدالة alert(`Hello, ${user}!`); } // نُصدّر قيمةً واحدة دون صنع متغيّر export default ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; لا مشكلة بتاتًا بعدم كتابة الاسم إذ لا نرى export default إلّا مرّة في الملف، بهذا تعرف تمامًا أسطر import (بدون استعمال الأقواس المعقوفة) ما عليها استيراده. ولكن دون default فهذا التصدير سيُعطينا خطأً: export class { // Error! (non-default export needs a name) constructor() {} } الاسم المبدئي تُستعمل في حالات معيّنة الكلمة المفتاحية default للإشارة إلى التصدير المبدئي. فمثلًا لتصدير الدالة بنحوٍ منفصل عن تعريفها: function sayHi(user) { alert(`Hello, ${user}!`); } // كما لو أضفنا ”export default“ قبل الدالة export {sayHi as default}; أو لنقل بأنّ الوحدة user.js تُصدّر شيئًا واحدًا ”مبدئيًا“ وأخرى لها أسماء (نادرًا ما يحدث، ولكنّه يحدث): // ? user.js export default class User { constructor(name) { this.name = name; } } export function sayHi(user) { alert(`Hello, ${user}!`); } هكذا نستورد التصدير المبدئي مع ذلك الذي لديه اسم: // ? main.js import {default as User, sayHi} from './user.js'; new User('John'); وأخيرًا، حين نستورد كلّ شيء * على أنّه كائن، فستكون خاصية default هي كما التصدير المبدئي: // ? main.js import * as user from './user.js'; let User = user.default; // the default export new User('John'); كلمتين بخصوص سوء التصديرات المبدئية التصديرات التي لها أسماء تكون صريحة، أي أنّها تقول تمامًا ما الّذي يجب أن نستورده، وبذلك يكون لدينا هذه المعلومات منهم، وهذا شيء جيد. تُجبرنا التصديرات التي لها أسماء باستعمال الاسم الصحيح كما هو بالضبط لاستيراد الوحدة: import {User} from './user.js'; // ولن تعمل import {MyUser} إذ يجب أن يكون الاسم {User} بينما في حالة التصدير المبدئي نختار نحن الاسم حين نستورد الوِحدة: import User from './user.js'; // works import MyUser from './user.js'; // works too // ويمكن أيضًا أن تكون ”استورِد كل شيء“ import Anything... وستعمل بلا أدنى مشكلة هذا قد يؤدّي إلى أن يستعمل أعضاء الفريق أسماء مختلفة لاستيراد الشيء ذاته، وهذا طبعًا ليس بالجيد. عادةً ولنتجنّب ذلك ونُحافظ على اتساق الشيفرة، نستعمل القاعدة القائلة بأنّ أسماء المتغيرات المُستورَدة يجب أن تُوافق أسماء الملفات، هكذا مثلًا: import User from './user.js'; import LoginForm from './loginForm.js'; import func from '/path/to/func.js'; ... مع ذلك تنظُر بعض الفِرق لهذا الأمر على أنه عقبة للتصديرات المبدئية فتفضّل استعمال التصديرات التي لها اسم دومًا. فحتّى لو كانت نصدّر شيئًا واحدًا فقط فما زالت تُصدّره باسم دون استعمال default. كما يسهّل هذا إعادة التصدير (طالِع أسفله). إعادة التصدير تُتيح لنا صياغة ”إعادة التصدير“ export ... from ... استيراد الأشياء وتصديرها مباشرةً (ربما باسم آخر) هكذا: export {sayHi} from './say.js'; // نُعيد تصدير sayHi export {default as User} from './user.js'; // نُعيد تصدير المبدئي ولكن فيمَ نستعمل هذا أصلًا؟ لنرى مثالًا عمليًا. لنقل بأننا نكتب ”حزمة“، أي مجلدًا فيه وحدات كثيرة وأردنا تصدير بعض ميزاتها إلى الخارج (تتيح لنا الأدوات مثل NPM نشر هذه الحزم وتوزيعها)، ونعلم أيضًا أن الكثير من وحداتها ما هي إلّا وحدات مُساعِدة يمكن أن تكون بنية الملفات هكذا: auth/ index.js user.js helpers.js tests/ login.js providers/ github.js facebook.js ... ونريد عرض مزايا الحزمة باستعمال نقطة واحدة (أي الملف الأساسي auth/index.js) لتُستعمل هكذا: import {login, logout} from 'auth/index.js' الفكرة هي عدم السماح للغرباء (أي المطوّرين مستعملي الحزمة) بالتعديل على البنية الداخلية والبحث عن الملفات داخل مجلد الحزمة. نريد تصدير المطلوب فقط في auth/index.js وإخفاء الباقي عن أعين المتطفّلين. نظرًا لكون الوظيفة الفعلية المصدّرة مبعثرة بين الحزمة، يمكننا استيرادها إلى auth/index.js وتصديرها من هنالك أيضًا: // ? auth/index.js // اِستورد login/logout وصدِرهن مباشرةً import {login, logout} from './helpers.js'; export {login, logout}; // استورد الملف المبدئي كـ User وصدره من جديد import User from './user.js'; export {User}; ... والآن يمكن لمستخدمي الحزمة الخاصة بنا استيرادها هكذا import {login} from "auth/index.js". إن الصياغة export ... from ... ماهي إلا اختصار للاستيراد والتصدير: // ? auth/index.js // اِستورد login/logout وصدِرهن مباشرةً export {login, logout} from './helpers.js'; // استورد الملف المبدئي كـ User وصدره من جديد export {default as User} from './user.js'; ... إعادة تصدير التصديرات المبدئية يحتاج التصدير المبدئي لمعالجة منفصلة عند إعادة التصدير. لنفترض أن لدينا user.js، ونود إعادة تصدير الصنف User منه: // ? user.js export default class User { // ... } لن تعمل التعليمة export User from './user.js'. ما الخطأ الذي حدث؟ ولكن هذا الخطأ في صياغة! لإعادة تصدير الملفات المصدرة إفتراضيًا ، علينا كتابة export {default as User} ، كما في المثال أعلاه. تعيد التعليمة export * from './user.js' تصدير التصديرات الّتي لها أسماء فقط، ولكنها تتجاهل التصديرات المبدئية. إذا رغبنا في إعادة تصدير التصديرات المبدئية والتي لها أسماء أيضًا، فسنحتاج إلى العبارتين: export * from './user.js'; // لإعادة تصدير التصديرات الّتي لها أسماء export {default} from './user.js'; // لإعادة تصدير التصديرات المبدئية هذه الغرابة في طريقة إعادة تصدير التصديرات المبدئية هي من أحد الأسباب لجعل بعض المطورين لا يحبونها. خلاصة والآن سنراجع جميع أنواع طرق التصدير export التي تحدثنا عنها في هذا الفصل والفصول السابقة. تحقق من معلوماتك بقراءتك لهم وتذكر ما تعنيه كلُّ واحدةٍ منهم: قبل التعريف عن صنف / دالّة / ..: export [default] class/function/variable ... تصدير مستقل: export {x [as y], ...}. إعادة التصدير: export {x [as y], ...} from "module" export * from "module" (لا يُعيد التصدير المبدئي). export {default [as y]} from "module" (يعيد التصدير المبدئي). استيراد: الصادرات التي لها أسماء من الوِحدة: import {x [as y], ...} from "module" التصدير المبدئي: import x from "module" import {default as x} from "module" استيراد كل شيء: import * as obj from "module" استيراد الوحدة (وشغِّل شيفرتها البرمجية)، ولكن لا تُسندها لمتغير: import "module" لا يهم مكان وضع عبارات (تعليمات) import/export سواءً في أعلى أو أسفل السكربت فلن يغير ذلك أي شيء. لذا تقنيًا تعدُّ هذه الشيفرة البرمجية لا بأس بها: sayHi(); // ... import {sayHi} from './say.js'; // اِستورد في نهاية الملف عمليًا عادة ما تكون تعليمات الاستيراد في بداية الملف فقط لتنسيق أفضل للشيفرة. لاحظ أن تعليمتي import/export لن يعملا إن كانا في داخل جملة شرطية. لن يعمل الاستيراد الشرطي مثل هذا المثال: if (something) { import {sayHi} from "./say.js"; // Error: import must be at top level } .. ولكن ماذا لو احتجنا حقًا لاستيراد شيء ما بشروط معينة؟ أو في وقتٍ ما؟ مثل: تحميل الوِحدة عند الطلب، عندما تكون هناك حاجة إليها حقًا؟ سنرى الاستيراد الديناميكي في المقالة التالية. ترجمة -وبتصرف- للفصل Export and Import من كتاب The JavaScript language
-
سنرى سريعًا بينما تطبيقنا يكبُر حجمًا وتعقيدًا بأنّ علينا تقسيمه إلى ملفات متعدّدة، أو ”وحدات“ (module). عادةً ما تحتوي الوِحدة على صنف أو مكتبة فيها دوالّ. كانت محرّكات جافاسكربت تعمل لفترة طويلة جدًا دون أيّ صياغة وِحدات على مستوى اللغة، ولم تكن هذه بالمشكلة إذ أنّ السكربتات سابقًا كانت بسيطة وسهلة ولم يكن هناك داعٍ فعلي للوِحدات. ولكن كالعادة صارت السكربتات هذه أكثر تعقيدًا وأكبر، فكان على المجتمع اختراع طرائق مختلفة لتنظيم الشيفرات في وحدات (أو مكتبات خاصّة تُحمّل تلك الوِحدات حين الطلب). مثال: AMD: هذه إحدى نُظم المكتبات القديمة جدًا والتي كتبت تنفيذها بدايةً المكتبة require.js. CommonJS: نظام الوِحدات الذي صُنِع لخوادم Node.js. UMD: نظام وِحدات آخر (اقتُرح ليكون للعموم أجمعين) وهو متوافق مع AMD وCommonJS. أمّا الآن فهذه المكتبات صارت (أو تصير، يومًا بعد آخر) جزءًا من التاريخ، ولكن مع ذلك سنراها في السكربتات القديمة. ظهر نظام الوِحدات (على مستوى اللغة) في المعيار عام 2015، وتطوّر شيئًا فشيئًا منذئذ وصارت الآن أغلب المتصفّحات الرئيسة (كما و Node.js) تدعمه. لذا سيكون أفضل لو بدأنا دراسة عملها من الآن. ما الوحدة؟ الوِحدة هي ملف، فقط. كلّ نص برمجي يساوي وحدة واحدة. يمكن أن تُحمّل الوِحدات بعضها البعض وتستعمل توجيهات خاصة مثل التصدير export والاستيراد import لتتبادل الميزات فيما بينها وتستدعي الدوالّ الموجودة في وحدة ص، من وحدة س: تقول الكلمة المفتاحية export للمتغيرات والدوالّ بأنّ الوصول إليها من خارج الوِحدة الحالية هو أمر مُتاح. وتُتيح import استيراد تلك الوظائف من الوِحدات الأخرى. فمثلًا لو كان لدينا الملف sayHi.js وهو يُصدّر دالّةً من الدوالّ: // ? sayHi.js export function sayHi(user) { alert(`Hello, ${user}!`); } فيمكن لملف آخر استيراده واستعمالها: // ? main.js import {sayHi} from './sayHi.js'; alert(sayHi); // function... نوعها دالة sayHi('John'); // Hello, John! تتوجه تعليمة import للوِحدة ./sayHi.js عبر المسار النسبي المُمرر لها. ويسند التابع sayHi للمتغيّر الذي يحمل نفس اسم التابع. لنشغّل المثال في المتصفّح. تدعم الوِحدات كلمات مفتاحية ومزايا خاصة، لذلك علينا إخبار المتصفّح بأنّ هذا السكربت هو وِحدة ويجب أن يُعامل بهذا النحو، ذلك باستعمال الخاصية <script type="module">. هكذا: ملف index.html: <!doctype html> <script type="module"> import {sayHi} from './say.js'; document.body.innerHTML = sayHi('John'); </script> ملف say.js: export function sayHi(user) { return `Hello, ${user}!`; } يجلب المتصفّح الوِحدة تلقائيًا ويقيم الشيفرة البرمجية بداخلها (ويستورد جميع الوحدات المتعلقة بها إن لزم الأمر)، وثمّ يشغلها وتكون نتيجة ما سبق: Hello, John! ميزات الوحدات الأساسية ولكن ما الفرق بين الوِحدات والسكربتات (الشيفرات) "العادية“ تلك؟ للوِحدات ميزات أساسية تعمل على محرّكات جافاسكربت للمتصفّحات وللخوادم على حدّ سواء. الوضع الصارم الإفتراضي تستخدم الوِحدات الوضع الصارم تلقائيًا فمثلًا إسناد قيمة لمتحول غير معرّف سينتج خطأ. <script type="module"> a = 5; // خطأ </script> النطاق على مستوى الوحدات كلّ وِحدة لها نطاق عالي المستوى خاص بها. بتعبيرٍ آخر، لن يُنظر للمتغيّرات والدوالّ من الوحدات الأخرى، وإنما يكون نطاق المتغيرات محلي. نرى في المثال أدناه أنّا حمّلنا نصّين برمجيين، ويحاول الملف hello.js استعمال المتغير user المصرّح عنه في الملف user.js ولا يقدر: ملف index.html: <!doctype html> <script type="module" src="user.js"></script> <script type="module" src="hello.js"></script> ملف user.js: let user = "John"; الملف hello.js: alert(user); لاحظ أن نتيجة ما سبق هي لا شيء لأن الدالة alert لم تتعرف على المتغير user الغير موجود، فكل وحدة لها متغيرات خاصة بها ويمكنها تصدير (عبر export) ما تريد للآخرين من خارجها رؤيته، واستيراد (عبر import) ما تحتاج استعماله. لذا علينا استيراد user.js وhello.js وأخذ المزايا المطلوبة منهما بدل الاعتماد على المتغيّرات العمومية. هذه النسخة الصحيحة من الشيفرة: ملف index.html: <!doctype html> <script type="module" src="hello.js"></script> ملف user.js: export let user = "John"; الملف hello.js: import {user} from './user.js'; document.body.innerHTML = user; // John يوجد في المتصفح نطاق مستقل عالي المستوى. وهو موجود أيضًا للوحدات <script type="module">: <script type="module"> // سيكون المتغير مرئي في مجال هذه الوِحدة فقط let user = "John"; </script> <script type="module"> alert(user); // خطأ: المتغير user غير معرّف </script> ولو أردنا أن ننشئ متغيرًا عامًا على مستوى النافذة يمكننا تعيينه صراحة للمتغيّر window ويمكننا الوصول إليه هكذا window.user. ولكن لابد من وجود سبب وجيهٍ لذلك. تقييم شيفرة الوِحدة لمرة واحدة فقط لو استوردتَ نفس الوِحدة في أكثر من مكان، فلا تُنفّذ شيفرتها إلّا مرة واحدة، وبعدها تُصدّر إلى من استوردها. ولهذا توابع مهمّ معرفتها. لنرى بعض الأمثلة. أولًا، لو كان لشيفرة الوِحدة التي ستُنفّذ أيّ تأثيرات (مثل عرض رسالة أو ما شابه)، فاستيرادها أكثر من مرّة سيشغّل ذلك التأثير مرة واحدة، وهي أول مرة فقط: // ? alert.js alert("Module is evaluated!"); // نُفّذت شيفرة الوِحدة! // نستورد نفس الوِحدة من أكثر من ملف // ? 1.js import `./alert.js`; // نُفّذت شيفرة الوِحدة! // ? 2.js import `./alert.js`; // (لا نرى شيئًا هنا) في الواقع، فشيفرات الوِحدات عالية المستوى في بنية البرمجية لا تُستعمل إلّا لتمهيد بنى البيانات الداخلية وإنشائها. ولو أردنا شيئًا نُعيد استعماله، نُصدّر الوِحدة. الآن حان وقت مثال مستواه متقدّم أكثر. لنقل بأنّ هناك وحدة تُصدّر كائنًا: // ? admin.js export let admin = { name: "John" }; لو استوردنا هذه الوِحدة من أكثر من ملف، فلا تُنفّذ شيفرة الوِحدة إلّا أول مرة، حينها يُصنع كائن المدير admin ويُمرّر إلى كلّ من استورد الوِحدة. وهكذا تستلم كلّ الشيفرات كائن مدير admin واحد فقط لا أكثر ولا أقل: // ? 1.js import {admin} from './admin.js'; admin.name = "Pete"; // ? 2.js import {admin} from './admin.js'; alert(admin.name); // Pete كِلا الملفين 1.js و 2.js سيستوردان نفس الكائن والتغييرات الّتي ستحدثُ في الملف 1.js ستكون مرئية في الملف 2.js. ولنؤكد مجددًا، تُنفذّ الوِحدة لمرة واحدة فقط. وتُنشئ الوِحدات المراد تصديرها وتُشارك بين المستوردين لذا فإن تغير شيء ما في كائن admin فسترى الوِحدات الأخرى ذلك. يتيح لنا هذا السلوك ”ضبط“ الوِحدة عند أوّل استيراد لها، فنضبط خاصياتها المرة الأولى، ومتى ما استوُردت مرة أخرى تكون جاهزة. فمثلًا قد تقدّم لنا وحدة admin.js بعض المزايا ولكن تطلب أن تأتي امتيازات الإدارة من خارج كائن admin إلى داخله: // ? admin.js export let admin = { }; export function sayHi() { alert(`Ready to serve, ${admin.name}!`); } نضبط في init.js (أوّل نص برمجي لتطبيقنا) المتغير admin.name. بعدها سيراه كلّ من أراد بما في ذلك الاستدعاءات من داخل وحدة admin.js نفسها: // ? init.js import {admin} from './admin.js'; admin.name = "Pete"; ويمكن لوحدة أخرى استعمال admin.name: // ? other.js import {admin, sayHi} from './admin.js'; alert(admin.name); // Pete sayHi(); // Ready to serve, Pete! import.meta يحتوي الكائن import.meta على معلومات الوِحدة الحالية. ويعتمد محتواها على البيئة الحالية، ففي المتصفّحات يحتوي على عنوان النص البرمجي أو عنوان صفحة الوِب الحالية لو كان داخل HTML: <script type="module"> alert(import.meta.url); // عنوان URL للسكربت (عنوان URL لصفحة HTML للسكربت الضمني) </script> this في الوِحدات ليست معرّفة قد تكون هذه الميزة صغيرة، ولكنّا سنذكرها ليكتمل هذا الفصل. في الوحدات، قيمة this عالية المستوى غير معرّفة. وازن بينها وبين السكربتات غير المعتمدة على الوحدات، إذ ستكون this كائنًا عامًا: <script> alert(this); // window </script> <script type="module"> alert(this); // غير معرّف </script> الميزات الخاصة بالمتصفحات كما أن هناك عدّة فروق تخصّ المتصفحات السكربتات (المعتمدة على الوحدات) بالنوع type="module" موازنةً بتلك العادية. لو كنت تقرأ هذا الفصل لأول مرة، أو لم تكن تستعمل المحرّك في المتصفّح فيمكنك تخطّي هذا القسم. سكربتات الوِحدات مؤجلة دائمًا ما تكون سكربتات الوِحدات مؤجلة، ومشابهة لتأثير السِمة defer (الموضحة في هذا المقال)، لكل من السكربتات المضمّنة والخارجية. أي وبعبارة أخرى: تنزيل السكربتات المعتمدة على الوِحدات الخارجية <script type="module" src="..."> لا تُوقف معالجة HTML فتُحمّل بالتوازي مع الموارد الأخرى. تنتظر السكربتات المعتمدة على الوِحدات حتّى يجهز مستند HTML تمامًا (حتّى لو كانت صغيرة وحُمّلت بنحوٍ أسرع من HTML) وتُشغّل عندها. تحافظ على الترتيب النسبي للسكربتات: فالسكربت ذو الترتيب الأول ينفذّ أولًا. ويسبّب هذا بأن ”ترى“ السكربتات المعتمدة على الوِحدات صفحة HTML المحمّلة كاملة بما فيه عناصر الشجرة أسفلها. مثال: <script type="module"> alert(typeof button); // كائن (object): يستطيع السكربت رؤية العناصر أدناه // بما أن الوِحدات مؤجلة. سيُشغل السكربت بعد تحميل كامل الصفحة </script> Compare to regular script below: <script> alert(typeof button); // خطأ: الزر (button) غير معرّف. لن يستطيع السكربت رؤية العناصر أدناه // السكربت العادي سيُشغل مباشرة قبل أن يُستكمل تحميل الصفحة </script> <button id="button">Button</button> لاحِظ كيف أنّ النص البرمجي الثاني يُشغّل فعليًا قبل الأول! لذا سنرى أولًا undefined وبعدها object. وذلك بسبب كون عملية تشغيل الوِحدات مُؤجلة لذلك سننتظر لاكتمال معالجة المستند. نلاحظ أن السكربت العادي سيُشغلّ مباشرة بدون تأجيل ولذا سنرى نتائجه أولًا. علينا أن نحذر حين نستعمل الوِحدات إذ أنّ صفحة HTML تظهر بينما الوِحدات تُحمّل، وبعدها تعمل الوحدات. بهذا يمكن أن يرى المستخدم أجزاءً من الصفحة قبل أن يجهز تطبيق جافاسكربت، ويرى بأنّ بعض الوظائف في الموقع لا تعمل بعد. علينا هنا وضع ”مؤشّرات تحميل“ أو التثبّت من أنّ الزائر لن يتشتّت بهذا الأمر. خاصية Async على السكربتات المضمّنة بالنسبة للسكربتات غير المعتمدة على الوِحدات فإن خاصية async (اختصارًا لكلمة Asynchronous أي غير المتزامن) تعمل على السكربتات الخارجية فقط. وتُشغل السكربتات غير المتزامنة مباشرة عندما تكون جاهزة، بشكل مستقل عن السكربتات الأخرى أو عن مستند HTML. تعمل السكربتات المعتمدة على الوِحدات طبيعيًا في السكربتات المضمّنة. فمثلًا يحتوي السكربت المُضمن أدناه على الخاصية async، لذلك سيُشغّل مباشرة ولن ينتظر أي شيء. وهو ينفذ عملية الاستيراد (اجلب الملف ./analytics.js) وشغله عندما يصبح جاهزًا، حتى وإن لم ينتهِ مستند HTML بعد. أو السكربتات الأُخرى لا تزال معلّقة. وهذا جيد للتوابع المستقلة مثل العدادات والإعلانات ومستمع الأحداث على مستوى المستند. في المثال أدناه، جُلبت جميع التبعيات (من ضمنها analytics.js). ومن ثمّ شُغّل السكربت ولم ينتظر حتى اكتمال تحميل المستند أو السكربتات الأخرى. <script async type="module"> import {counter} from './analytics.js'; counter.count(); </script> السكربتات الخارجية تختلف السكربتات الخارجية التي تحتوي على السمة type="module" في جانبين: تنفذ السكربتات الخارجية التي لها نفس القيمة للخاصية src مرة واحدة فقط. فهنا مثلًا سيُجلب السكربت my.js وينفذ مرة واحدة فقط. <script type="module" src="my.js"></script> <script type="module" src="my.js"></script> تتطلب السكربتات الخارجية التي تجلب من مصدر مستقل (موقع مختلف عن الأساسي) ترويسات CORS والموضحة في هذا المقال. بتعبير آخر إن جُلِبَ سكربت يعتمد على الوِحدات من مصدر معين فيجب على الخادم البعيد أن يدعم ترويسات السماح بالجلب Access-Control-Allow-Origin. يجب أن يدعم المصدر المستقل Access-Control-Allow-Origin (في المثال أدناه المصدر المستقل هو another-site.com) وإلا فلن يعمل السكربت. <script type="module" src="http://another-site.com/their.js"></script> وذلك سيضمن لنا مستوى أمان أفضل إفتراضيًا. لا يُسمح بالوحدات المجردة في المتصفح، يجب أن تحصل تعليمة import على عنوان URL نسبي أو مطلق. وتسمى الوِحدات التي بدون أي مسار بالوحدات المجردة. وهي ممنوع في تعليمة import. لنأخذ مثالًا يوضح الأمر، هذا import غير صالح: import {sayHi} from 'sayHi'; // خطأ وِحدة مجردة // يجب أن تمتلك الوِحدة مسارًا مثل: './sayHi.js' أو مهما يكُ موقع هذه الوِحدة تسمح بعض البيئات، مثل Node.js أو أدوات تجميع الوِحدات باستخدام الوِحدات المجردة، دون أي مسار، حيث أن لديها طرقها الخاصة للعثور على الوِحدات والخطافات لضبطها. ولكن حتى الآن لا تدعم المتصفحات الوِحدات المجردة. التوافقية باستخدام "nomodule" لا تفهم المتصفحات القديمة طريقة استخدام الوِحدات في الصفحات type ="module".بل وإنها تتجاهل السكربت ذو النوعٍ غير المعروف. بالنسبة لهم، من الممكن تقديم نسخة مخصصة لهم باستخدام السمة nomodule: <script type="module"> alert("Runs in modern browsers"); </script> <script nomodule> alert("Modern browsers know both type=module and nomodule, so skip this"): alert("Old browsers ignore script with unknown type=module, but execute this."); </script> المتصفحات الحديثة تعرف type=module و nomodule لذا لن تنفذ الأخير بينما ستتجاهل المتصفحات القديمة الوسم ذو السِمة type=module ولكن ستنفذ وسم nomodule. أدوات البناء في الحياة الواقعية، نادرًا ما تستخدم وحدات المتصفح في شكلها "الخام". بل عادةّ نجمعها مع أداة خاصة مثل Webpack وننشرها على خادم النشر. إحدى مزايا استخدام المجمعات - فهي تمنح المزيد من التحكم في كيفية التعامل مع الوحدات، مما يسمح بالوحدات المجردة بل وأكثر من ذلك بكثير، مثل وحدات HTML/CSS. تؤدي أدوات البناء بعض الوظائف منها: جلب الوِحدة الرئيسية main، وهي الوِحدة المراد وضعها في وسم <script type ="module"> في ملف HTML. تحليل التبعيات: تحليل تعليمات الاستيراد الخاصة بالملف الرئيسي وثم للملفات المستوردة أيضًا وما إلى ذلك. إنشاء ملفًا واحدًا يحتوي على جميع الوِحدات (مع إمكانية تقسيمهُ لملفات متعددة)، مع استبدال تعليمة import الأصلية بتوابع الحزم لكي يعمل السكربت. كما تدعم أنواع وحدات "خاصة" مثل وحدات HTML/CSS. يمكننا تطبيق عمليات تحويل وتحسينات أخرى في هذه العملية مثل: إزالة الشيفرات الّتي يتعذر الوصول إليها. إزالة تعليمات التصدير غير المستخدمة (مشابهة لعملية هز الأشجار وسقوط الأوراق اليابسة). إزالة العبارات الخاصة بمرحلة التطوير مثل console وdebugger. تحويل شيفرة جافاسكربت الحديثة إلى شيفرة أقدم باستخدام وظائف مماثلة للحزمة Babel. تصغير الملف الناتج (إزالة المسافات، واستبدال المتغيرات بأسماء أقصر، وما إلى ذلك). عند استخدامنا لأدوات التجميع سيُجمع السكربت ليصبح في ملف واحد (أو ملفات قليلة) ، تُستبدل تعليمات import/export بداخل السكربتات بتوابع المُجمّع الخاصة. لذلك لا يحتوي السكربت "المُجَمّع" الناتج على أي تعليمات import/export، ولا يتطلب السِمة type="module"، ويمكننا وضعه في سكربت عادي: في المثال أدناه لنفترض أننا جمعّنا الشيفرات في ملف bundle.js باستخدام مجمع حزم مثل: Webpack. <script src="bundle.js"></script> ومع ذلك يمكننا استخدام الوِحدات الأصلية (في شكلها الخام). لذلك لن نستخدم هنا أداة Webpack: يمكنك التعرف عليها وضبطها لاحقًا. خلاصة لنلخص المفاهيم الأساسية: الوِحدة هي مجرد ملف. لجعل تعليمتي import/export تعملان، ستحتاج المتصفحات إلى وضع السِمة التالية <script type ="module">. تحتوي الوِحدات على عدة مُميزات: مؤجلة إفتراضيًا. تعمل الخاصية Async على السكربتات المضمّنة. لتحميل السكربتات الخارجية من مصدر مستقل، يجب استخدام طريقة (المَنفذ / البروتوكول / المجال)، وسنحتاج لترويسات CORS أيضًا. ستُتجاهل السكربتات الخارجية المكررة. لكل وِحدة من الوِحدات نطاق خاص بها، وتتبادلُ الوظائف فيما بينها من خلال استيراد وتصدير الوِحدات import/export. تستخدم الوِحدات الوضع الصارم دومًا use strict. تُنفذ شيفرة الوِحدة لمرة واحدة فقط. وتُصدر إلى من استوردها لمرة واحدة أيضًا، ومن ثمّ تُشارك بين المستوردين. عندما نستخدم الوحدات، تنفذ كل وِحدة وظيفة معينة وتُصدرها. ونستخدم تعليمة import لاستيرادها مباشرة عند الحاجة. إذ يُحمل المتصفح السكربت ويقيّمه تلقائيًا. وبالنسبة لوضع النشر، غالبًا ما يستخدم الناس مُحزّم الوِحدات مثل Webpack لتجميع الوِحدات معًا لرفع الأداء ولأسباب أخرى. سنرى في الفصل التالي مزيدًا من الأمثلة عن الوِحدات، وكيفية تصديرها واستيرادها. ترجمة -وبتصرف- للفصل Modules, introduction من كتاب The JavaScript language
-
إن هذا الدليل مستوحى من دليل GitHub ودليل Bozhidar Batsov. ولدينا أيضًا دليل جافاسكربت لتنسيق الشيفرة البرمجية جدول المحتويات المسافات البيضاء المسافة البادئة في الشيفرة المسافات السطرية الأسطر الجديدة طول السطر التعليقات تعليقات على مستوى الملف/الصنف تعليقات الدالة التعليقات الكتلية والمضمّنة علامات الترقيم والإملاء والنحو تعليقات TODO شيفرات برمجية بدون تعليقات الدوال تعريف الدوال دوال الاستدعاء التعابير الشرطيّة الكلمات المفتاحية للجمل الشرطية المعامل الثلاثي الشروط المتشعّبة الصياغة قواعد التسمية الأصناف الاستثناءات التجميعات السلاسل النصية التعابير النمطية محارف النسبة المئوية ريلز نطاقات انسجم مع الشيفرة المسافات البيضاء المسافة البادئة في الشيفرة استخدم الزر (tabs) بمقدار مسافتين (وتسمى أيضًا Soft-Tab نظرًا لأن زر Tab الإفتراضي يكون ثمان مسافات). سيكون عمق الكلمة المفتاحية when بقدر عمق case. case when song.name == 'Misty' puts 'Not again!' when song.duration > 120 puts 'Too long!' when Time.now.hour > 21 puts "It's too late" else song.play end kind = case year when 1850..1889 then 'Blues' when 1890..1909 then 'Ragtime' when 1910..1929 then 'New Orleans Jazz' when 1930..1939 then 'Swing' when 1940..1950 then 'Bebop' else 'Jazz' end حاذِ وسطاء الّدالة فإما أن يكونوا على نفس السطر أو سطر لكلّ واحدٍ منهم. # bad def self.create_translation(phrase_id, phrase_key, target_locale, value, user_id, do_xss_check, allow_verification) ... end # good def self.create_translation(phrase_id, phrase_key, target_locale, value, user_id, do_xss_check, allow_verification) ... end # good def self.create_translation( phrase_id, phrase_key, target_locale, value, user_id, do_xss_check, allow_verification ) ... end حاذِ الأسطر المتتابعة بمقدار مسافتين في جملة التنفيذ للتعابير المنطقية متعددة الأسطر. # bad def is_eligible?(user) Trebuchet.current.launch?(ProgramEligibilityHelper::PROGRAM_TREBUCHET_FLAG) && is_in_program?(user) && program_not_expired end # good def is_eligible?(user) Trebuchet.current.launch?(ProgramEligibilityHelper::PROGRAM_TREBUCHET_FLAG) && is_in_program?(user) && program_not_expired end المسافات السطرية لا تترك مسافة زائدة. عند تضمين التعليقات في السطر، اترك مسافة واحدة بين نهاية الشيفرة البرمجية وبداية تعليقك. # bad result = func(a, b)# we might want to change b to c # good result = func(a, b) # we might want to change b to c استخدم المسافات حول المعاملات (operators)، وبعد الفواصل، وبعد النقطتين، وبعد الفاصلة المنقوطة، وبعد فتح قوس ما { وقبل إغلاقه أيضًا }. sum = 1 + 2 a, b = 1, 2 1 > 2 ? true : false; puts 'Hi' [1, 2, 3].each { |e| puts e } لا تضع أبدًا مسافة فارغة قبل الفاصلة. result = func(a, b) لا تضع مسافة داخل كتلة وسطاء الأنبوب (pipe)، بل ضع واحدًا بين الوسطاء في الكتلة، وواحدًا آخر خارج كتلة وسطاء الأنبوب. # bad {}.each { | x, y |puts x } # good {}.each { |x, y| puts x } لا تضع مسافات بين إشارة التعجب ! ومعاملاتها. !something لا تترك فارغات بعد الأقواس من نوع ( و[ أو قبلها ) و]. some(arg).other [1, 2, 3].length تجنب وضع المسافات عند إنشاء السلاسل النصية. # bad var = "This #{ foobar } is interpolated." # good var = "This #{foobar} is interpolated." لا تستخدم مسافة إضافية في النطاق الحرفي (range literals). # bad (0 ... coll).each do |item| # good (0...coll).each do |item| الأسطر الجديدة أضف سطرًا جديدًا بعد الجملة الشرطية if ذات الشروط المتعددة لتصبح في عدة أسطر، ولتساعد في التفريق بين الشروط وجسم الشرط. if @reservation_alteration.checkin == @reservation.start_date && @reservation_alteration.checkout == (@reservation.start_date + @reservation.nights) redirect_to_alteration @reservation_alteration end أضف سطرًا جديدًا بعد نهاية الجمل الشرطية، والكتل، والبيانات …إلخ. if robot.is_awesome? send_robot_present end robot.add_trait(:human_like_intelligence) لا تضف أسطرًا جديدةً فارغةً بين المناطق ذات مسافات بادئة مختلفة (مثلًا حول أجزاء الصنف أو أجسام الوحدة). # bad class Foo def bar # body omitted end end # good class Foo def bar # body omitted end end أضف سطرًا جديدًا فارغًا بين الدوالّ. def a end def b end أضف سطرًا فارغًا لفصل البيانات المترابطة في الدوالّ لتشكيلّ فقرات منطقية داخلها. def transformorize_car car = manufacture(options) t = transformer(robot, disguise) car.after_market_mod! t.transform(car) car.assign_cool_name! fleet.add(car) car end أضف سطرًا فارغًا في نهاية كل ملف، ولا تضف أبدًا عدة أسطر. طول السطر حافظ على كلّ سطر من الشيفرة البرمجية ليكون بطول مناسب للقراءة، وابقِ طول الأسطر أقل من 100 محرف (إليك السبب) إلا إن كان لديك سبب وجيه لجعله أطول من ذلك. التعليقات كُتِبَ هذا الشرح باستعانة كبيرة من دليل Google لتنسيق الشيفرة البرمجية للغة C++ ولغة Python. تعليقات على مستوى الملف/الصنف عند التعريف عن صنف ما يجب أن يوجد تعليق مصاحب له يصف ماهيّة الصنف وكيفية استخدامه. يجب أن يملك الملف الذي لا يحتوي على أصناف، أو يحتوي على أكثر من صنف على تعليق في أعلاه يصف محتوياته. # Automatic conversion of one locale to another where it is possible, like # American to British English. module Translation # Class for converting between text between similar locales. # Right now only conversion between American English -> British, Canadian, # Australian, New Zealand variations is provided. class PrimAndProper def initialize @converters = { :en => { :"en-AU" => AmericanToAustralian.new, :"en-CA" => AmericanToCanadian.new, :"en-GB" => AmericanToBritish.new, :"en-NZ" => AmericanToKiwi.new, } } end ... # Applies transforms to American English that are common to # variants of all other English colonies. class AmericanToColonial ... end # Converts American to British English. # In addition to general Colonial English variations, changes "apartment" # to "flat". class AmericanToBritish < AmericanToColonial ... end يجب أن تملك جميع الملفات من بينهم ملفات البيانات والإعداد، على تعليقات على مستوى الملف. # List of American-to-British spelling variants. # # This list is made with # lib/tasks/list_american_to_british_spelling_variants.rake. # # It contains words with general spelling variation patterns: # [trave]led/lled, [real]ize/ise, [flav]or/our, [cent]er/re, plus # and these extras: # learned/learnt, practices/practises, airplane/aeroplane, ... sectarianizes: sectarianises neutralization: neutralisation ... تعليقات الدالة يجب أن يكون هنالك تعليقات عند تعريف كلّ دالة وتكون قبلها مباشرة لتصف ما تؤديه هذه الدالة وكيفية استخدامها، ويجب أن تكون هذه الملفات وصفية ("يُفتح الملف") بدلًا من أمرية ("افتح الملف"). التعليقات التي تصف الدالّة، لا تشرح ما تقوم به الدالّة خطوة بخطوة. عمومًا، لا تصف هذه التعليقات كيفية تنفيذ هذه الدالّة عملها حرفيًا، وإنما، يجب ترك هذه التعليقات لتتخلل الشيفرة البرمجية للدالّة. يجب أن تذكر كلُّ دالّة ما هي مدخلاتها ومخرجاتها، إلا إن كانت لا تستوف جميع المعايير التالية: غير مرئية من الخارج. قصيرة جدًا. واضحة وبديهية. يمكنك استخدام أي تنسيق تريده، في لغة Ruby، يوجد مخططين لتوثيق وشرح عمل الدالّة وهما TomDoc وYARD كما يمكنك أيضًا كتابة تعليقات موجزة: # Returns the fallback locales for the_locale. # If opts[:exclude_default] is set, the default locale, which is otherwise # always the last one in the returned list, will be excluded. # # For example: # fallbacks_for(:"pt-BR") # => [:"pt-BR", :pt, :en] # fallbacks_for(:"pt-BR", :exclude_default => true) # => [:"pt-BR", :pt] def fallbacks_for(the_locale, opts = {}) ... end التعليقات الكتلية والمضمّنة المكان الأخير للتعليقات هو في الأجزاء الصعبة من الشيفرة البرمجية، فعّلق عليها الآن إذا توجب عليك شرحها عند مراجعة القادمة للشيفرة البرمجية. يجب أن تحصل العمليات المعقدة على بضعة أسطر من التعليقات قبل بدء العمليات، بالنسبة للأجزاء غير الواضحة فإنها التعليقات ستكون في نهاية السطر. def fallbacks_for(the_locale, opts = {}) # dup() to produce an array that we can mutate. ret = @fallbacks[the_locale].dup # We make two assumptions here: # 1) There is only one default locale (that is, it has no less-specific # children). # 2) The default locale is just a language. (Like :en, and not :"en-US".) if opts[:exclude_default] && ret.last == default_locale && ret.last != language_from_locale(the_locale) ret.pop end ret end من ناحية أخرى، لا تصف طريقة عمل التعليمات البرمجية، افترض أن الشخص الذي يقرأ شيفرتك البرمجية يعرف اللغة البرمجية (وإن لم يكن ما تحاول القيام به) أفضل منك. وفي سياق متصل: لا تستخدم التعليقات الكتلية، فلا يمكن أن تسبقها بمسافة وليس من السهل رؤيتها كالتعليقات العادية. # bad =begin comment line another comment line =end # good # comment line # another comment line علامات الترقيم والإملاء والنحو اهتم بعلامات الترقيم، والإملاء، والنحو. فمن السهل قراءة التعليقات المكتوبة بعناية على عكس نظيرتها غير المكتوبة بعناية. ينبغي أن تكون التعليقات قابلة للقراءة كنص سردي، مع وجود الحروف الكبيرة وعلامات الترقيم المناسبة. في حالات كثيرة، تكون الجمل الكاملة قابلة للقراءة أكثر من الجمل القصيرة. يمكن للتعليقات القصيرة، مثل الّتي توضع في نهاية آخر سطر في شيفرة برمجية أن تكون أقل رسمية، لكن على كلّ الأحوال يجب أن تكون متسقة مع تنسيقك للتعليقات ككلّ. على الرغم من أنه من المحبط أن يشير مُراجِع لشيفرتك البرمجية لاستخدامك فاصلة عادية في مكان الفاصلة المنقوطة، لذا من المهم أن تحافظ على شيفرتك البرمجية لتكون بمستوى عالٍ من الوضوح وقابلية القراءة، وستُساعدنا علامات الترقيم والكتابة الإملائية الصحيحة واستخدام الصحيح للقواعد النحوية في تحقيق هذا الهدف. تعليقات TODO استخدم تعليقات TODO للشيفرات البرمجية المؤقتة، أو للحلّ قصير الأجل، أو الحلّ الجيد بما يكفي ولكنه ليس مثاليًا. يجب أن تتضمن تعليقات TODO على كلمة TODO بالأحرف الكبيرة متبوعة بالاسم الكامل للشخص الذي يمكنه توفير أفضل حل للمشكلة في الشيفرة المشار إليها من قِبل تعليق TODO بين قوسين. يمكنك إضافة النقطتين ومن ثمّ ضع تعليق يشرح ما يجب القيام به، فالغرض الرئيسي هو تنسيق تعليق TODO ليصبح قابلًا للبحث ليجده الشخص الّذي يمكنه توفير مزيد من التفاصيل عند الطلب. إن تعليق TODO لا يُلزم الشخص المشار إليه لإصلاح المشكلة، ولذلك عند إنشائك تعليق TODO، فغالبًا -إن لم يكن دائمًا- ستكتب اسمك. # bad # TODO(RS): Use proper namespacing for this constant. # bad # TODO(drumm3rz4lyfe): Use proper namespacing for this constant. # good # TODO(Ringo Starr): Use proper namespacing for this constant. شيفرات برمجية بدون تعليقات لا تترك أبدًا شيفراتك البرمجية بدون تعليقات. الدوال تعريف الدوال استخدم الكلمة المفتاحية def مع الأقواس عند وجود وسطاء، واحذف الأقواس عندما لا تقبل الدوالّ أي وسطاء. def some_method # body omitted end def some_method_with_parameters(arg1, arg2) # body omitted end لا تستخدم الوسطاء الموضوعة افتراضيًا، بل استخدم معاملات الكلمات الرئيسية (keyword) – الموجودة في إصدار لغة روبي Ruby 2.0 أو الأحدث - أو يمكنك استخدام أسماء hash أيضًا. # bad def obliterate(things, gently = true, except = [], at = Time.now) ... end # good def obliterate(things, gently: true, except: [], at: Time.now) ... end # good def obliterate(things, options = {}) options = { :gently => true, # obliterate with soft-delete :except => [], # skip obliterating these things :at => Time.now, # don't obliterate them until later }.merge(options) ... end تجنب الدوالّ المتكونة من سطر واحد، على الرغم من انتشارها، إلا أنه توجد بعض الغرابة حول طريقة صياغتها مما يجعل استخدامها أمرًا غير مرغوب به. # bad def too_much; something; something_else; end # good def some_method # body end دوال الاستدعاء استخدم الأقواس لدالة الاستدعاء في الحالات التالية: إذا كانت الدالة ترجع قيمة. # bad @current_user = User.find_by_id 1964192 # good @current_user = User.find_by_id(1964192) إذا كان الوسيط الأول للدالّة يستخدم الأقواس. # bad put! (x + y) % len, value # good put!((x + y) % len, value) لا تضع أبدًا مسافة بين اسم الدالّة والقوس الأول. # bad f (3 + 2) + 1 # good f(3 + 2) + 1 تجنب الأقواس عند استدعاء الدالّة إذا لم تقبل الدالّة أي وسطاء. # bad nil?() # good nil? ستكون الأقواس اختيارية ما لم تكن الدالة تُعيدّ قيمة (أو لا نهتم بما تُعيده)، أما إذا كان الوسطاء في عدة أسطر، فيمكن للأقواس أن تزيد من قابلية القراءة. # okay render(:partial => 'foo') # okay render :partial => 'foo' في كِلا الحالتين: في حالة قبول الدالة hash كوسيط أخير اختياري، لا تستخدم الأقواس { أو } أثناء الاستدعاء. # bad get '/v1/reservations', { :id => 54875 } # good get '/v1/reservations', :id => 54875 التعابير الشرطيّة الكلمات المفتاحية للجمل الشرطية لا تستخدم الكلمة المفتاحية then للجملة الشرطية if/unless المتعددة الأسطر. # bad if some_condition then ... end # good if some_condition ... end لا تستخدم الكلمة المفتاحية do مع while أو until المتعددات الأسطر. # bad while x > 5 do ... end until x > 5 do ... end # good while x > 5 ... end until x > 5 ... end إن الكلمات المفتاحية and وor وnot محظورة، فهي لا تستحق العناء، استخدم دائمًا && و|| و! بدلًا منها. يمكنك استخدام المعدِل if/unless عندما يكون الجسم بسيط والشرط بسيط وكلّ شيء في سطر واحد، وخلافًا لذلك، تجنب استخدام if/unless. # bad - this doesn't fit on one line add_trebuchet_experiments_on_page(request_opts[:trebuchet_experiments_on_page]) if request_opts[:trebuchet_experiments_on_page] && !request_opts[:trebuchet_experiments_on_page].empty? # okay if request_opts[:trebuchet_experiments_on_page] && !request_opts[:trebuchet_experiments_on_page].empty? add_trebuchet_experiments_on_page(request_opts[:trebuchet_experiments_on_page]) end # bad - this is complex and deserves multiple lines and a comment parts[i] = part.to_i(INTEGER_BASE) if !part.nil? && [0, 2, 3].include?(i) # okay return if reconciled? لا تستخدم أبدًا الكلمة المفتاحية unless مع else، أعد كتابة الشرط لتصبح الحالة الصحيحة الأولى أولًا. # bad unless success? puts 'failure' else puts 'success' end # good if success? puts 'success' else puts 'failure' end تجنب unless مع الشروط المتعددة. # bad unless foo? && bar? ... end # okay if !(foo? && bar?) ... end تجنب استخدام unless مع معاملات الموازنة فإن استطعت استخدام الشرط if مع عكس الشرط الموجود في unless ففعل ذلك. # bad unless x == 10 ... end # good if x != 10 ... end # bad unless x < 10 ... end # good if x >= 10 ... end # ok unless x === 10 ... end لا تستخدم أقواسًا حول الشروط if/unless/while. # bad if (x > 10) ... end # good if x > 10 ... end المعامل الثلاثي تجنب استخدام العامل الثلاثي (:?) إلا في الحالات الّتي تكون فيها جميع التعابير بسيطة، إلا أنه يمكنك استخدامه بدلًا من if/then/else/end في الشروط المتكونة من سطر واحد. # bad result = if some_condition then something else something_else end # good result = some_condition ? something : something_else استخدم تعبير واحد لكلّ فرع في المعامل الثلاثي، كذلك يجب ألا يكون المعامل الثلاثي متشعبًا، إذ يُفضّل استخدام بنية if/else في هذه الحالات. # bad some_condition ? (nested_condition ? nested_something : nested_something_else) : something_else # good if some_condition nested_condition ? nested_something : nested_something_else else something_else end تجنب الشروط المتعدّدة في المعامل الثلاثي إذ يُفضّل استخدام الأخير مع الشروط الفردية. تجنب الأسطر المتعدّدة :? في المعامل الثلاثي، واستخدم if/then/else/end بدلا منه. # bad some_really_long_condition_that_might_make_you_want_to_split_lines ? something : something_else # good if some_really_long_condition_that_might_make_you_want_to_split_lines something else something_else end الشروط المتشعّبة تجنب استخدام الشروط المتشعّبة لزيادة التحكم بالشيفرة (يمكنك الاطلاع على هذا المقال لمزيد من المعلومات). يُفضّل استخدام تعبير الحراسة (guard clause) عندما تريد التحقق بيانات غير صالحة، فشرط الحراسة هو عبارة شرطية في أعلى الدالة التي تعيد النتيجة بأسرع ما يمكن. المبادئ العامة هي: أعدّ القيمة (النتيجة) مباشرة بمجرد معرفتك بأن الدالّة لا طائل منها، أو أنها لا تضف أي زيادة حقيقة للشيفرة. قلل التشعّب والمسافات البادئة في الشيفرة البرمجة من خلال إعادة النتيجة بأسرع وقت ممكن. وهذا يجعل الشيفرات البرمجية أسهل للقراءة، وتتطلّب جهدً عقليًا أقل أثناء قراءة الفرع الثاني من الشرط else. ينبغي أن تكون الشروط الجوهرية أو الأكثر أهمية ذات مسافات بادئة أقل. # bad def compute server = find_server if server client = server.client if client request = client.make_request if request process_request(request) end end end end # good def compute server = find_server return unless server client = server.client return unless client request = client.make_request return unless request process_request(request) end يُفضّل استخدام next في الحلقات بدلًا من الكتل الشرطيّة. # bad [0, 1, 2, 3].each do |item| if item > 1 puts item end end # good [0, 1, 2, 3].each do |item| next unless item > 1 puts item end الصياغة لا تستخدم for إلّا إذا كنت تعرّف تمامًا لماذا ستستخدمها، فغالبًا، يجب استخدام المكرّرات (iterator) بدلًا منها، فتعمل for بنفس شروط each (أي أنك ستضيف مستوى جديد من المراوغة للبرنامج) لكن مع عيب صغير، فلا تُعرّف for نطاق جديد (على عكس each) وستظهر المتغيرات المعرّفة في كتلتها خارجيًا. arr = [1, 2, 3] # bad for elem in arr do puts elem end # good arr.each { |elem| puts elem } يفضل استخدام {...} بدلًا من do...end للكتل المكوّنة من سطر واحد، وتجنّب استخدام {...} للكتل المكونة من عدة أسطر (فسَلّسَلةُ عدة أسطر هو شيء قبيح)، واستخدم do...end دائمًا للتحكم في التدفّق ولتعريف الدوالّ، وتجنب استخدام do...end عند السَلّسَلة. names = ["Bozhidar", "Steve", "Sarah"] # good names.each { |name| puts name } # bad names.each do |name| puts name end # good names.each do |name| puts name puts 'yay!' end # bad names.each { |name| puts name puts 'yay!' } # good names.select { |name| name.start_with?("S") }.map { |name| name.upcase } # bad names.select do |name| name.start_with?("S") end.map { |name| name.upcase } سيُجادلنا البعض ويقول أن سَلّسَلَة الأسطر المتعددة تبدو جيّدة عند استخدام {...}، لكن هل سألوا أنفسهم هل هذه الشيفرة البرمجية قابلة للقراءة حقًا. وهل يمكن استخلاص محتوى الكتلة لوضعها في دوالّ فعّالة. استخدم معاملات الإسناد المختصرة كلما كان ذلك ممكنًا. # bad x = x + y x = x * y x = x**y x = x / y x = x || y x = x && y # good x += y x *= y x **= y x /= y x ||= y x &&= y تجنب الفواصل المنقوطة باستثناء حالة تعريفك لصنف ذو سطر واحد، وعندما يكون من الملائم استخدام فاصلة منقوطة، ينبغي أن تكون متاخمة مباشرةً للعبارة التي ستنهيها. لا يجب ترك مسافة قبل الفاصلة المنقوطة. # bad puts 'foobar'; # superfluous semicolon puts 'foo'; puts 'bar' # two expressions on the same line # good puts 'foobar' puts 'foo' puts 'bar' puts 'foo', 'bar' # this applies to puts in particular استخدم :: للثوابت المرجعيّة فقط (ويشمل هذا الأصناف والوحدات) والبواني (مثل: Array() أو Nokogiri::HTML()) ولا تستخدم :: لاستدعاء الدالّة العادية. # bad SomeClass::some_method some_object::some_method # good SomeClass.some_method some_object.some_method SomeModule::SomeClass::SOME_CONST SomeModule::SomeClass() تجنب استخدام return عندما تكون غير مطلوبة. # bad def some_method(some_arr) return some_arr.size end # good def some_method(some_arr) some_arr.size end لا تستخدم القيمة المُعادة من المساواة = في الجمل الشرطيّة. # bad - shows intended use of assignment if (v = array.grep(/foo/)) ... end # bad if v = array.grep(/foo/) ... end # good v = array.grep(/foo/) if v ... end استخدم ||= كما تريد لتهيئة المتغيّرات. # set name to Bozhidar, only if it's nil or false name ||= 'Bozhidar' لا تستخدم ||= لتهيئة المتغيرات المنطقيّة، (فكر في ما سيحدث إذا كانت القيمة الحالية تساوي false). # bad - would set enabled to true even if it was false enabled ||= true # good enabled = true if enabled.nil? استخدم .call الصريحة عند استدعاء lambda. # bad lambda.(x, y) # good lambda.call(x, y) تجنب استخدام متغيرات الخاصة على نمط بيرل Perl (مثل: $ أو $0-9 …إلخ)، فهي مبهمة فعلًا. واستخدامها سيُعيقنا بكلّ شيئ عدا في السكربتات ذات السطر الواحد. يُفضّل اصدارات ذات الشكل الطويل مثل: $PROGRAM_NAME. استخدم الاختصار &: عندما تأخذ كتلة الدالّة وسيط واحد فقط، ويعمل الجسم على قراءة سِمة أو استدعاء دالّة بدون وسطاء . # bad bluths.map { |bluth| bluth.occupation } bluths.select { |bluth| bluth.blue_self? } # good bluths.map(&:occupation) bluths.select(&:blue_self?) يفضلُ استخدام some_method على self.some_method عند استدعاء الدالّة داخليًا. # bad def end_date self.start_date + self.nights end # good def end_date start_date + nights end يجب عليك استخدام self. في الحالات الثلاثة التالية: عند تعريف دالّة صنف: def self.some_method. عندما تكون self هي نموذج سجل فعالّ ActiveRecord ويكون الجانب الأيسر هو استدعاء دالّة اِسناد بما في ذلك اِسناد سِمة: self.guest = user. الإشارة للصنف داخليًا: self.class. عند تعريف كائن من أي نوع قابل للتحويل وتريده أن يكون ثابتًا، تأكد من استدعاء freeze عليه، ومن الأمثلة الشائعة على ذلك هي السلاسل النصية، والمصفوفات، ومتغيرات من نوع hash (انظر لهذا المقال لمزيد من المعلومات). والسبب في ذلك أن الثوابت في لفة روبي (Ruby) هي أنواع قابلة للتحويل، وبعضها الآخر ليس كذلك. ستتأكد من عدم قابليتها للتحويل عند استدعاء freeze عليها لأنه سيصدر استثناء عند محاولة تعديلهم إن كانت غير قابلة للتعديل، وبالنسبة للسلاسل النصية، سيُسمح بالتعامل معهم لنسخ روبي ذات الاصدار الأقدم من 2.2. # bad class Color RED = 'red' BLUE = 'blue' GREEN = 'green' ALL_COLORS = [ RED, BLUE, GREEN, ] COLOR_TO_RGB = { RED => 0xFF0000, BLUE => 0x0000FF, GREEN => 0x00FF00, } end # good class Color RED = 'red'.freeze BLUE = 'blue'.freeze GREEN = 'green'.freeze ALL_COLORS = [ RED, BLUE, GREEN, ].freeze COLOR_TO_RGB = { RED => 0xFF0000, BLUE => 0x0000FF, GREEN => 0x00FF00, }.freeze end قواعد التسمية استخدم أسلوب التسمية snake_case (جميع حروف اسم المتغير صغيرة) للدوالّ والمتغيرات. استخدم أسلوب التسمية camelCase (تكبير أول حرف من كلّ كلمة في اسم المتغير عدا أول كلمة) للأصناف والوحدات. (احتفظ بالاختصارات مثل" HTTP وRFC وXML بحالة حروف كبيرة). استخدم أسلوب التسمية SCREAMING_SNAKE_CASE (جميع حروف اسم المتغير كبيرة) للثوابت الأخرى. يجب أن تنتهي الدوالّ الإسنادية (الّتي ترجع قيمة منطقية) بعلامة استفهام. (هكذا: Array#empty?). يجب أن تنتهي أسماء الدوالّ التي يُحتمل أن تكون خطرة (مثل الدوالّ الّتي تُعدلّ self أو الوسطاء، أو exit! …إلخ) بعلامة تعجب، ويجب أن تتواجد الدوالّ الخطرة (bang) إذا تتواجدت الدوالّ غير الخطرة (non-bang) معها (يمكنك الاطلاع على المقال للمزيد من المعلومات). سمِّ متغيّرات رمي الأخطاء _. version = '3.2.1' major_version, minor_version, _ = version.split('.') الأصناف تجنب استخدام متغيرات (@@) بسبب سلوكهم السيئ في الوراثة. class Parent @@class_var = 'parent' def self.print_class_var puts @@class_var end end class Child < Parent @@class_var = 'child' end Parent.print_class_var # => will print "child" كما ترى أن جميع الأصناف في التسلسل الهرمي للصنف تتشارك في متغير صنف واحد، ويجب أن تُفضّل متغيّرات مثيل الصنف على متغيّرات الصنف. استخدم def self.method لتعريف الدوالّ المتفرّدة (Singleton Methods)، وسيجعل هذا الدوالّ أكثر مقاومة لتغييرات إعادة الهيكلة. class TestClass # bad def TestClass.some_method ... end # good def self.some_other_method ... end تجنب استخدام class << self إلا عند الضرورة، فمثلًا، الجالبات المفردة (single accessors) والسمات مستعارة (aliased attributes). class TestClass # bad class << self def first_method ... end def second_method_etc ... end end # good class << self attr_accessor :per_page alias_method :nwo, :find_by_name_with_owner end def self.first_method ... end def self.second_method_etc ... end end ضع مسافة بادئة لدوالّ public وprotected وprivate بقدر تعريف الدالّة الّتي ينتمون إليها، واترك سطرًا فارغًا أعلاها وأسفلها. class SomeClass def public_method # ... end private def private_method # ... end end الاستثناءات لا تستخدم الاستثناءات للتحكم بسير البرنامج. # bad begin n / d rescue ZeroDivisionError puts "Cannot divide by 0!" end # good if d.zero? puts "Cannot divide by 0!" else n / d end تجنب إنقاذ (rescue) صنف الاستثناء Exception. # bad begin # an exception occurs here rescue Exception # exception handling end # good begin # an exception occurs here rescue StandardError # exception handling end # acceptable begin # an exception occurs here rescue # exception handling end لا تحدّد في استثناء RuntimeError وسيطين فقط في الكلمة المفتاحية raise، ومن الأفضل استخدام خطأ الأصناف الفرعية لتوضيح الخطأ وشرحه بطريقة أفضل. # bad raise RuntimeError, 'message' # better - RuntimeError is implicit here raise 'message' # best class MyExplicitError < RuntimeError; end raise MyExplicitError يُفضّل توفير صنف استثناء والرسالة كوسيطين في الكلمة المفتاحية raise بدلًا من نسخة استثناء عادي. # bad raise SomeException.new('message') # Note that there is no way to do `raise SomeException.new('message'), backtrace`. # good raise SomeException, 'message' # Consistent with `raise SomeException, 'message', backtrace`. تجنب استخدام rescue في شكل مُعدّل (Modifier). # bad read_file rescue handle_error($!) # good begin read_file rescue Errno:ENOENT => ex handle_error(ex) end التجميعات (Collections) يفضّل استخدام الخارطة (أو الخريطة) map بدلًا من تجميعة collect. يفضّل استخدام detect على find، فاستخدام find غامض بسبب دالّة find الخاصة بكائن السجل الغعال (ActiveRecord) وسيُظهر detect على أنك تعمل مع تجميعة في لغة روبي وليس كائن سجل فعال. يُفضّل استخدام reduce على inject. يُفضّل استخدام size على length وcount لتحسين أداء الشيفرة. يفّضل استخدام تدوين الإنشاء (creation notation) ومصفوفة مجزئة مصنّفة النوع (Literal)، إلّا إذا كنت بحاجة لتمرير الوسطاء لمنشئيها. # bad arr = Array.new hash = Hash.new # good arr = [] hash = {} # good because constructor requires parameters x = Hash.new { |h, k| h[k] = {} } يُفضّل استخدام Array#join بدلًا من Array#* لوضوح الشيفرة. # bad %w(one two three) * ', ' # => 'one, two, three' # good %w(one two three).join(', ') # => 'one, two, three' استخدم الرموز (symbols) بدلًا من السلاسل النصية كمفاتيح لجدول Hash. # bad hash = { 'one' => 1, 'two' => 2, 'three' => 3 } # good hash = { :one => 1, :two => 2, :three => 3 } على نحو مماثل، استخدم رموز واضحة بدلًا من رموز السلاسل النصية عندما يكون ذلك ممكنًا. # bad :"symbol" # good :symbol استخدم Hash#key? بدلًا من Hash#has_key? وHash#value? بدلًا من Hash#has_value?. فوفقًا لماتز (Matz)، تعدّ الأشكال الطويلة مُهملة. # bad hash.has_key?(:test) hash.has_value?(value) # good hash.key?(:test) hash.value?(value) استخدم جدول hash متعدّد الأسطر لأنه يجعل الشيفرة أكثر قابليّة للقراءة، واستخدم الفواصل التذييلية للتأكد من أن أي تغيّر للوسطاء لن يتسبب بتغيّرات غريبة لشكل هذه الأسطر، وذلك عندما لا يتغيّر منطق الحلّ. hash = { :protocol => 'https', :only_path => false, :controller => :users, :action => :set_password, :redirect => @redirect_url, :secret => @secret, } استخدم الفاصلة التذيليّة في المصفوفة الّتي تمتد لأكثر من سطر واحد. # good array = [1, 2, 3] # good array = [ "car", "bear", "plane", "zoo", ] السلاسل النصية يُفضّل توليد السلسلة النصية (string interpolation) بدلًا من دمج السِلاسل النصية: # bad email_with_name = user.name + ' <' + user.email + '>' # good email_with_name = "#{user.name} <#{user.email}>" وعلاوة على ذلك، لا تنس توليد نمط روبي 1.9، لنفترض أنك تُنشئ مفاتيح ذاكرة التخزين المؤقت كالتالي: CACHE_KEY = '_store' cache.write(@user.id + CACHE_KEY) مرةً أخرى يُفضّل توليد السلسلة النصية (string interpolation) بدلًا من دمج السِلاسل النصية: CACHE_KEY = '%d_store' cache.write(CACHE_KEY % @user.id) تجنب استخدام String#+ عندما تحتاج لإنشاء قطع بيانات كبيرة، واستخدم بدلًا من ذلك String#<<. تحور عملية دمج نسخ السلاسل النصية في مكانها، وهي أسرع دومًا من String#+، والّذي ينشئ مجموعة جديدة من كائنات السلسلة النصية. # good and also fast story = '' story << 'The Ugly Duckling' paragraphs.each do |paragraph| story << paragraph end استخدم \ في نهاية السطر بدلًا من + أو << لدمج السلاسل النصية متعددة الأسطر. # bad "Some string is really long and " + "spans multiple lines." "Some string is really long and " << "spans multiple lines." # good "Some string is really long and " \ "spans multiple lines." التعابير النمطية تجنب استخدام $1-9 لأنه من الصعب متابعة ما يحتويه، واستخدم بدلًا منه المجموعات المسماة (Named groups). # bad /(regexp)/ =~ string ... process $1 # good /(?<meaningful_var>regexp)/ =~ string ... process meaningful_var كن حذرًا عند استخدام ^ و$ لأنها تطابق بداية/نهاية السطر، وليس نهايات السلسلة النصية، فإذا أردت مطابقة كامل السلسلة النصية فاستخدم: \A و\z. string = "some injection\nusername" string[/^username$/] # matches string[/\Ausername\z/] # don't match استخدم المُعدّل x للتعابير النمطية المعقدة، فهذا سيجعلها أكثر قابلية للقراءة، ويمكنك إضافة بعض التعليقات المفيدة، فقط كن حذرًا لأنه سيتجاهل المسافات. regexp = %r{ start # some text \s # white space char (group) # first group (?:alt1|alt2) # some alternation end }x محارف النسبة المئوية يفضل استخدام الأقواس الهلالية على الأقواس المعقوصة، أو الأقواس المعقوفة، أو حتى الأنابيب (pipes) عند استخدام محددات مجردة % للتناسق ولأن سلوك محارف % أقرب إلى استدعاء الدالّة منه إلى البدائل. # bad %w[date locale] %w{date locale} %w|date locale| # good %w(date locale) استخدم %w كما يحلو لك. STATES = %w(draft open closed) استخدم ()% للسلاسل النصية المتكونة من سطر واحد، والّتي تتطلّب عملية توليد سلسلة وعلامات الاقتباس المزدوجة المضمّنة، وبالنسبة للسلاسل النصية المتكونة من عدة أسطر، يُفضل استخدام heredocs. # bad - no interpolation needed %(Welcome, Jane!) # should be 'Welcome, Jane!' # bad - no double-quotes %(This is #{quality} style) # should be "This is #{quality} style" # bad - multiple lines %(Welcome, Jane!\nPlease enjoy your stay at #{location}\nCheers!) # should be a heredoc. # good - requires interpolation, has quotes, single line %(Welcome, #{name}!) استخدم ٪r للتعابير النمطية فقط والّتي تتطابق مع أكثر من محرف /. # bad %r(\s+) # still bad %r(^/(.*)$) # should be /^\/(.*)$/ # good %r(^/blog/2011/(.*)$) تجنب استخدام %x إلا إذا أردت استدعاء أمر مع علامة الاقتباس الخلفية (`) - وهو أمر مستبعد إلى حد ما -. # bad date = %x(date) # good date = `date` echo = %x(echo `date`) ريلز عند العودة فورًا بعد استدعاء render أو redirect_to، ضع الكلمة المفتاحية return في السطر التالي وليس في نفس السطر. # bad render :text => 'Howdy' and return # good render :text => 'Howdy' return # still bad render :text => 'Howdy' and return if foo.present? # good if foo.present? render :text => 'Howdy' return end نطاقات عند تعريف نطاقات نموذج السجل الفعّال (ActiveRecord)، أحِط العلاقة بـ lambda، وخلافًا لذلك سيفرض الاتصال المجردة لقاعدة البيانات تنفيذه في وقت تحميل الصنف (مشابهة لطريقة بدء التشغيل). # bad scope :foo, where(:bar => 1) # good scope :foo, -> { where(:bar => 1) } انسجم مع الشيفرة إذا كنت تعدل شيفرة برمجية ما، فألقِ نظرة عليها لعدة دقائق، وحدّد أسلوب تنسيقها، فإن كانوا يستخدمون مسافات حول جميع المعاملات الرياضية، فانسجم مع هذه الطريقة، وإن كان للتعليقات صناديق صغيرة من المربعات hash حولها، فانسجم معها أيضًا. الهدف من امتلاك مبادئ توجيهية لتنسيق الشيفرة هي الحصول على مفردات مشتركة من الشيفرات ليتمكن الناس من فهم ما تقوله بدلًا من كيف تقوله، واستعرضنا هنا قواعد التنسيق العالمية حتى يعرف الناس المفردات، لكن التنسيقات شيفراتك المحلية مهمة أيضًا، فإذا كانت الشيفرة البرمجية الّتي ستضيفها تختلف اختلافًا كبيرًا عن الشيفرات البرمجية الّتي حولها فعندها ستجعل قراءة الشيفرة صعبة ولذلك تجنب هذا الأمر - دليل Google لتنسيق الشيفرة البرمجية للغة C++ ترجمة لدليل Ruby Style Guide من شركة Airbnb على موقع GitHub
-
يشير مصطلح مفتوح المصدر (open source) لأي شيء يمكن لأي شخص تعديله ومشاركته لأن تصميمه متاح للجميع. نشأ هذا المصطلح في سياق تطوير البرمجيات للدلالة على نهج خاص لإنشاء برامج للحاسوب. أما اليوم، فإن مصطلح "مفتوح المصدر" يشير لمجموعة أوسع من القيم - والّتي نسميها "الثقافة مفتوحة المصدر. تتبنى المشاريع، أو المنتجات، أو المبادرات مفتوحة المصدر مبادئ التبادل المفتوح والمشاركة التعاونية والنماذج الأولية السريعة والشفافية العالية والجدارة والتنمية الموجهة للمجتمع. ما هي البرمجية مفتوحة المصدر؟ البرمجيات مفتوحة المصدر: هي برمجيات يمكننا رؤية شيفرتها البرمجية وفحصها وتعديلها وتحسينها. "الشيفرة البرمجية" هي جزء من برمجية لا يراه معظم مستخدمي الحاسوب على الإطلاق، ويتلاعب مبرمجو الحاسوب بالشيفرة لتغيير طريقة عمل البرمجية - سواءً أكانت "برنامج" أو "تطبيق". يمكن للمبرمجين الّذين يستطيعون الوصول للشيفرة البرمجية لبرنامج الحاسوب تحسين هذا البرنامج من خلال إضافة ميزات إليه أو إصلاح الأجزاء الّتي لا تعمل دائمًا عملًا صحيحًا. ما الفرق بين البرمجيات مفتوحة المصدر والأنواع أخرى؟ هناك بعض البرمجيات الّتي لا تُملكُ شيفرتها أو يمكن التعديل عليها سوى الجهة الّتي أنشأتها سواءً أكانت ممثلة بشخص أو فريق أو مؤسسة وتحتفظ هذه الجهة بالسيطرة الحصرية عليها. ويطلق الناس على هذا النوع من البرمجيات "بالبرمجيات المحتكرة" (proprietary) أو "مغلقة المصدر" (closed source). يمكن لأصحاب البرمجيات المحتكرة وحدهم نسخ هذه البرمجيات، وفحصها وتغييرها بطريقة قانونية. ولاستخدام البرمجيات المحتكرة لابد أن يوافق مستخدمي الحاسوب (للتوقيع على الترخيص المعروض والّذي يعرض عادةً عند التشغيل للمرة الأولى للبرمجيات) والّتي تنص على أنهم لن يفعلوا أي شيء للبرمجية مخالف صراحةً لما يسمح به منشئي هذه البرمجية. ومن هذه البرمجيات نذكر Microsoft Office وAdobe Photoshop. تختلف البرمجيات مفتوحة المصدر عن نظيرتها المحتكرة. إذ يتيح منشئي البرمجيات المفتوحة المصدر عرض الشيفرة البرمجية، ونسخها، والتعلم منها، وتغييرها، ومشاركتها أيضًا. ومن هذه البرمجيات نذكر محرر النصوص ليبر أوفيس LibreOffice وبرنامج التلاعب بالصور Gimp. وكما هو الحال مع البرمجيات المحتكرة يجب على المستخدمين عند استخدامهم البرمجيات مفتوحة المصدر الموافقة على شروط الترخيص بيدَ أن هذه الشروط تختلف اختلافًا كبيرًا عن شروط البرمجيات المحتكرة. تحدّد تراخيص البرمجيات مفتوحة المصدر طريقة استخدام البرمجيات، وتعديلها، وتوزيعها. وعمومًا تَمنحُ هذه التراخيص إذنًا باستخدام البرمجيات لأي غرض يرغب به المستخدم. إلا أن البعض الأخر من التراخيص والّتي يسميها البعض "الحقوق المتروكة" تنص على وجوب عرض الشيفرة البرمجية الخاصة لكلّ نُسخة مُعدلة من هذه البرمجية علنيًا. علاوة على ذلك بعض التراخيص تُوجبُ على أي شخص يعدلّ البرمجية ويشاركها مع الآخرين أن يشارك أيضًا المصدر الأصلي للشيفرة بدون فرض رسوم الترخيص عليها. تعمدُ تراخيص البرمجيات مفتوحة المصدر على تعزيز روح التعاون والمشاركة لأنها تسمح للجميع بإجراء تعديلات على الشيفرات البرمجية، ودمج هذه التغييرات في مشاريعهم الخاصة. وهم بذلك يشجعون مبرمجي الحاسوب بالوصول إلى البرمجيات مفتوحة المصدر وعرضها وتعديلها بأي وقت يحلو لهم، طالما أنهم سمحوا للآخرين بفعل نفس الشيئ عندما يشاركون برمجياتهم. هل البرمجيات مفتوحة المصدر مهمة لمبرمجي الحاسوب فقط؟ بالتأكيد لا. بل إن التكنولوجيا مفتوحة المصدر والعقلية المنفتحة للمصادر المفتوحة عمومًا تفيد كلًا من المبرمجين وغير المبرمجين. لأن المخترعين الأوائل للإنترنت شيدوا جزءًا كبيرًا منه على تقنياتٍ مفتوحة المصدر - مثل: نظام التشغيل Linux وتطبيق خادم الوِب المحلي Apache - وبذلك أي شخص يستخدم الانترنت فهو في الحقيقة يستفيد من البرمجيات مفتوحة المصدر. عند كلّ عملية استعراض لصفحة وِب أو التحقق من البريد الإلكتروني، أو الدردشة مع الأصدقاء، أو استخدام منصات البث الموسيقي عبر الإنترنت، أو اللعب بألعاب فيديو متعددة اللاعبين فإن حاسوب المستخدم، أو هاتفه المحمول، أو طرفيات الألعاب ستتصل بشبكة عالمية من الحواسيب من خلال برمجيات مفتوحة المصدر من أجل توجيه ونقل البيانات إلى أجهزتهم "المحلية" الموجودة أمام أعينهم. عادةً ما تتواجد هذه الحواسيب الّتي تؤدي كلّ هذا العمل الهام في أماكن بعيدة عن أنظار المستخدمين أو لا يستطيعون الوصول إليها فعليًا - وهذا هو السبب في أن بعض الأشخاص يطلقون على هذه الحواسيب "بالحواسيب البعيدة". يعتمد الناس أكثر فأكثر على الحواسيب البعيدة عند أداء مهامهم بدلًا من أجهزتهم المحلية. فمثلًا، قد يستخدمون برمجيات معالجة الكلمات وإدارة البريد الإلكتروني وتحرير الصور وهي غير مثبتة على حواسيبهم الشخصية. وإنما، ببساطة يصلون لها عبر الحواسيب البعيدة من خلال متصفح الوِب أو تطبيق على الهاتف محمول. وعندما يفعلون ذلك، فهم ينخرطون في "الحوسبة عن بُعد". يطلق بعض الأشخاص على الحوسبة عن بُعد "بالحوسبة السحابية" وذلك لأنها تتضمن أنشطة (مثل: تخزين الملفات أو مشاركة الصور أو مشاهدة مقاطع الفيديو) والّتي لا تتضمن حواسيب محلية فقط وإنما شبكة عالمية من الحواسيب البعيدة أيضًا الّتي تشكل سحابة. تتوالى أهمية الحوسبة السحابية يومًا بعد يوم وخصيصًا في الحياة اليومية للأجهزة المتصلة بالإنترنت. ومن بعض تطبيقات الحوسبة السحابية المحتكرة Google Apps. أما بعض التطبيقات الأخرى مفتوحة المصدر نذكر: ownCloud وNextcloud. تعمل تطبيقات الحوسبة السحابية "على قمة" من البرمجيات الإضافية المساعدة لها لتعمل بسلاسة وكفاءة، لذلك غالبًا ما سيقول الناس أن البرمجيات الّتي تعمل "تحت" تطبيقات الحوسبة السحابية تعمل بمثابة "منصة" لتطبيقات الحوسبة السحابية، وتكون هذه المنصات إما مفتوحة المصدر أو مغلقة المصدر. فمثلًا المنصة OpenStack هي منصة مفتوحة المصدر. لماذا يفضل الناس استخدام برمجيات مفتوحة المصدر؟ يفضل الناس البرمجيات مفتوحة المصدر على حساب البرمجيات المحتكرة لعدد من الأسباب: زيادة السيطرة على البرمجية يفضل كثير من الناس البرمجيات مفتوحة المصدر لأنها تعطيهم مزيدًا من السيطرة والتحكم. إذ يمكنهم فحص الشيفرة البرمجية للتأكد من أنها ستؤدي نفس المهمة الّتي يريدونها، بل ويمكنهم حتى تغيير أي جزء منها لا يحبونه. كما يستفيد غير المبرمجين من هذه البرمجيات أيضًا، لأنهم يمكنهم استخدام هذه البرمجية لأي غرض يرغبون فيه - وبذلك لا تُفرض عليهم الطريقة الّتي يعتقد شخص ما -صاحب البرمجية مثلًا- بأنه يجب عليهم استخدام البرمجية وفقًا لها (كما يحدث في البرمجيات المحتكرة). التعلم والتدرب من هذه البرمجيات يحبُ البعض الآخر من الناس هذه البرمجيات لأنها تساعدهم ليصبحوا مبرمجين أفضل. نظرًا لأن الشيفرة البرمجية متاحة للجميع، وبذلك يمكن للطلاب دراستها بسهولة أثناء تعلمهم لإنشاء برمجية أفضل. يمكن للطلاب مشاركة عملهم مع الآخرين أيضًا، ودعوتهم للتعليق والنقد البناء، وبذلك يصقل الطلاب مهاراتهم. عندما يكتشف الأشخاص أخطاءً في الشيفرات البرمجية لبرامجهم، يمكنهم مشاركة هذه الأخطاء مع الآخرين لمساعدتهم على تجنب ارتكاب نفس هذه الأخطاء. الحماية والأمان يُفضل بعض الأشخاص هذه البرمجيات لأنهم يرونها أكثر أمانًا واستقرارًا من البرمجيات المحتكرة. نظرًا من كون الجميع يستطيع عرض وتعديل البرمجيات مفتوحة المصدر، فيمكن لأي شخص أن يكتشف خطأ غفِلَ عنه أصحاب البرمجية أنفسهم بل ويمكن أن يصحح أو يحذف هذا الخطأ. ولأن العديد من المبرمجين يمكنهم العمل على جزء معين من البرمجية بدون طلب إذن من أصحابها، فسيُسرّع ذلك من وتيرة إصلاح البرمجية وتحديثها وترقيتها أكثر من البرمجيات المحتكرة. الاستقرار والثبات يفضل العديد من المستخدمين البرمجيات مفتوحة المصدر على نظيرتها المحتكرة للمشاريع المهمة وطويلة الأمد. نظرًا لتوزيع المبرمجين الشيفرة البرمجية علنًا للبرمجيات مفتوحة المصدر، فيمكن للمستخدمين الّذين يعتمدون على هذه البرمجيات في المهام الحرجة التأكد من أن أدواتهم لن تختفي أو تتعطل إذا توقف أصحابها عن تطويرها -وذلك لأن لديها عدة مبرمجين آخرين مهتمين بها- علاوة عن ذلك، تميل البرمجيات مفتوحة المصدر للاندماج والعمل وفقًا للمعايير المفتوحة. المجتمع الداعم للبرمجية غالبًا ما تستقطب البرمجيات مفتوحة المصدر جمهورًا من المستخدمين والمطورين المحبين لها. وهذا ليس حكرًا على هذه البرمجيات بل العديد من التطبيقات الشعبية لها مجتمعات كبيرة ومواضيع يناقشونها في لقاءاتهم واجتماعاتهم. غير أن في البرمجيات مفتوحة المصدر يكون المجتمع ليس مجرد قاعدة جماهيرية تشتري (سواء عاطفيًا بالدعم أو ماليًا بسعر البرمجية) وتكوّن بذلك مجموعة مميزة من المستخدمين وحسب، وإنما مجموعة من الأشخاص الّذين ينتجون ويختبرون ويستخدمون ويروجون بل ويؤثرون تأثيرًا جوهريًا على البرمجية الّتي يحبونها. هل يعني مصطلح "مفتوح المصدر" بأنه مجاني؟ لا. هذا مفهوم خاطئ ومنتشر حول ما يعنيه مصطلح "مفتوح المصدر"، وممكن ألا يتعلق هذا المصطلح بالمال. يمكن لمبرمجي هذه البرمجيات أن يتقاضوا المال مقابل البرمجيات مفتوحة المصدر الّتي يصنعونها أو يساهمون فيها. ولكن في بعض الحالات، نظرًا لأن ترخيص المصدر المفتوح يتطلب منهم إصدار الشيفرة البرمجية علنًا عندما يبيعون هذه البرمجيات للآخرين، ولذلك يجدُ بعض المبرمجين أن فرض رسوم على المستخدمين مقابل الخدمات والدعم الفني للبرمجية (بدلًا من البرمجية بحد ذاتها) أكثر ربحًا. وبهذا تظل برمجياتهم مجانية، ويكسبون المال من مساعدة الآخرين في تثبيتها واستخدامها واستكشاف أخطائها وإصلاحها. على الرغم من كون بعض البرمجيات مفتوحة المصدر مجانية، إلا أن مهارة البرمجة واستكشاف الأخطاء وإصلاحها في البرمجيات مفتوحة المصدر يمكن أن تكون ذات قيمة كبيرة. يسعى الكثير من أرباب العمل لتوظيف مبرمجين لديهم خبرة سابقة بالعمل على برمجيات مفتوحة المصدر تحديدًا. ما هي الثقافة مفتوحة المصدر "أي أبعد من الشيفرة البرمجية"؟ إن التعامل مع جميع جوانب الحياة بثقافة مفتوحة المصدر يعني التعبير عن الرغبة في المشاركة والتعاون مع الآخرين بشفافية (ليتمكن الآخرين من المشاهدة أو حتى الانضمام أيضًا)، واحتضان الفشل كوسيلة للتطوّر، وتوقع -أو حتى تشجيع- الجميع ليشاركوا وينضموا إلى هذه الثقافة. ويعني الالتزام أيضًا بدور فعّال في تحسين العالم، وهو أمر ممكن في حال تمكن الجميع من الوصول للطريقة الّتي صُمّم بها هذا العالم. إن العالم مليء "بالشيفرات البرمجية" - المخططات والوصفات والقواعد - الّتي توجه طريقة تفكيرنا وتشكلها من أجل أن نتصرف على أساسها. نؤمن تمامًا بأن هذه الشيفرة الضمنية (مهما كان شكلها) يجب أن تكون مفتوحةً وقابلة للوصول والمشاركة من قِبل الجميع — ليتمكنوا من تغييرها للأفضل. ترجمة -وبتصرف- للمقال What is open source software?
- 2 تعليقات
-
- 3
-
- مفتوح المصدر
- open source
-
(و 2 أكثر)
موسوم في:
-
تعلم كيفية حلّ المشاكل الشائعة في أنظمة المعالجة المتزامنة وتفادى صداع المرافق لحلّها في المستقبل. عند التعامل مع أنظمة موزعة ومعقدة، فمن الوارد جدًا أن تحتاج للمعالجة المتزامنة. ولكن لن يكون بناء شبكة عالمية خاصة توجه الحزم ديناميكيًا بدقة تصلّ للملّي ثانية أمرًا ممكنًا بدون نظام شديد التزامن. يعتمد هذا التوجيه الديناميكي على حالة الشبكة، وبينما توجد العديد من المعاملات الّتي يجب مراعاتها في هذا الأمر، سينصبُ تركيزنا على مقاييس الارتباط. في سياق الحالة الّتي سنناقشها، يمكن أن تكون مقاييس الارتباط أي شيء متعلق بالحالة رابط الشبكة و خصائصه (مثل: زمن تأخير الارتباط). الفحص المتزامن لمقاييس الارتباط تعتمد خوارزمية HALO (وهي اختصارًا Hop-by-Hop Adaptive Link-State Optimal Routing) على التوجيه الديناميكي جزئيًا على مقاييس الارتباط لحساب جدول التوجيه الخاص بها. تُجمعُ هذه المقاييس من خلال مكون مستقل متواجد بكلّ PoP (نقطة مشاركة). أما نقاط المشاركة (PoPs) وهي آلات تمثل كيانًا موجهًا واحدًا في شبكاتنا، ومتصلة بارتباطات متعددة وتنتشر في مواقع كثيرة مشكلةً بذلك شبكتنا. يستكشف هذا المكون الأجهزة المجاورة باستخدام حزم الشبكة، وسيُردُ هؤلاء الجيران بجواب أولي. يمكن استخلاص قيم زمن تأخير الارتباط من الأجوبة المتلقاة من هذه النقاط. نظرًا لأن لكل نقطة اتصال أكثر من جار واحد، فإن طبيعة هذه المهمة متزامنة جوهريًا: نحتاج إلى قياس وقت الاستجابة لكل رابط مجاور في الوقت الفعلي. لا يمكننا تحمل المعالجة التسلسلية ويجب معالجة كلّ جواب بأقرب وقت ممكن لحساب مقياس الارتباط. أرقام التسلسل وإعادة الضبط: حالة إعادة الترتيب تتبادل نقطة المشاركة الخاصة بنا الحزم وتعتمد على الأرقام المتسلسلة لمعالجة الحزم. وذلك بهدف تجنب معالجة الحزم المكررة أو الحزم غير المرتبة. اعتمد تطبيقنا الأول للخوارزمية على الرقم التسلسلي 0 لإعادة تعيين الأرقام المتسلسلة. ويستخدم هذا الرقم أثناء تهيئة المكونات فقط. المشكلة الرئيسية الّتي واجهتنا هي أننا كنا نفكر في زيادة قيمة الرقم التسلسلي الّذي يبدأ دائمًا عند 0. ولكن نلاحظ أن بعد إعادة تشغيل نقطة المشاركة، يمكن أن يحدث إعادة ترتيب للحزمة، ويمكن لحزمة ما استبدال رقمها التسلسلي برقم حزمة مستخدمة قبل عملية إعادة الترتيب. هذا يعني أن هذه الحزمة ستُتجاهلُ حتى يتغير رقمها التسلسلي ليصبح الرقم الّذي كانت تستخدمه قبل إعادة الترتيب. عملية تأسيس اتصال UDP وحالات الآلة المحدودة كانت المشكلة السابقة بانسجام الرقم التسلسلي بعد إعادة تشغيل نقطة المشاركة (ليستخدم الرقم الصحيح). هناك عدة طرق للتعامل مع هذا الأمر، وبعد مناقشة خياراتنا، اخترنا تطبيق بروتوكول مصافحة ثلاثي الاتجاهات مع تعريف واضح للحالات. تحدد عمليات تأسيس الاتصال الجلسات عبر الروابط أثناء التهيئة. وهذا يضمن أن العقد تتواصل خلال نفس الجلسة وتستخدم رقم تسلسلي مناسب لها. لتطبيق هذا الأمر تطبيقًا صحيحًا، يجب علينا تحديد حالات الآلة المحدودة وكما يجب أن تكون الانتقالات واضحة بينهم. وهذا بدوره سيسمح لنا بإدارة جميع حالات الحرجة لتأسيس الاتصال تأسيسًا صحيحًا. تُنشأ مُعرّفات الجلسة من خلال مؤسس الاتصالات. ويكون التسلسل الكامل لعملية الإنشاء على الشكل التالي: يرسل المرسل حزمة SYN(ID). يخزن المُستقبل المُعرّف الواصل له (وهو ID) ويرسل SYN-ACK (ID). يتلقى المرسل SYN-ACK (ID) ويرسل ACK (ID). كما يبدأ أيضًا في إرسال الحزم الّتي تبدأ بالتسلسل رقم 0. يتحقق المرسل من آخر معرّف ID استلمه، ويقبل ACK (ID) إذا كان معرّفه مطابقًا للمعرّف (ID) السابق. كما يبدأ بقبول الحزم الّتي تبدأ برقم تسلسلي 0. معالجة المُهلة الزمنية للحالة ستحتاج كلّ حالة افتراضيًا لمعالجة ثلاثة أنواع من الأحداث على الأكثر: أحداث عمليات الارتباط، وأحداث تراسل الحزم، وأحداث انتهاء المُهلة. وتظهر هذه الأحداث في وقت واحد، لذلك عليك التعامل معها بطريقة متزامنة. أحداث الارتباط: وهي تحديث لحالة الارتباط فإما عملية ارتباط أو عملية فصل الارتباط. يمكن لهذا إما بدء جلسة اتصال أو إنهاء جلسة موجودة. أحداث تراسل الحزم: وهي لحزم التحكم مثل: (SYN/SYN-ACK/ACK) أو استقصاء الاستجابات فقط. أحداث انتهاء المُهلة: هي الأحداث الّتي تُشغّل بعد انتهاء المهلة المُجدولة لحالة الجلسة الحالية. يكمن التحدي الرئيسي الّذي نواجهه هنا هو كيفية التعامل مع انتهاء زمن المُهلة المتزامنة والأحداث الأخرى بنفس الوقت. وهنا يمكن للمرء أن يسقط بسهولة في مشاكلة القفل المستعصي (Deadlocks) ومشكلة السباق (Race conditions). النهج الأول سنستخدم في هذا المشروع لغة Golang لإنها توفر آليات مزامنة أصيلة مثل: القنوات والأقفال الأصيلة وهي قادرة أيضًا على تدوير الخيوط (Threads) الخفيفة لمعالجتهم بطريقة متزامنة (لمزيد من المعلومات يمكنك الاطلاع على مقال سابق أخذنا فيه نظرة سريعة على لغة Golang). سنبدأ أولًا بتصميم هيكلًا (Struct) لتمثيل مُعالجات جلسات العمل والمُهلة. type Session struct { State SessionState Id SessionId RemoteIp string } type TimeoutHandler struct { callback func(Session) session Session duration int timer *timer.Timer } تُحددُ (Session) جلسة الاتصال بمعرّف الجلسة (ID)، وعنوان IP للارتباط المجاور (RemoteIp)، وحالة الجلسة الحالية (State). كما يحدد معالج المُهلة (TimeoutHandler) بدالّة رد النداء (callback)، والجلسة الّتي يجب أن يعمل من أجلها (session)، والمدة (duration)، ومؤشر للوقت المُجدول (timer). ستُخزن خارطة (أو خريطة) عامة (Global Map) -الخرائط Maps في لغة Go هي مصفوفات ترابطية يمكن التعديل عليها ديناميكيًا وهي تشبه نوع القاموس او الهاش في اللغات البرمجية الأخرى- مُعالج المُهلة المُجدولة لكل جلسة ارتباط مجاورة. SessionTimeout map[Session]*TimeoutHandler تُسجّل مُهلةٌ ما وتُلغى بالدوالّ التالية: // الدالّة المسؤولة عن جدولة المهلة لرد النداء func (timeout* TimeoutHandler) Register() { timeout.timer = time.AfterFunc(time.Duration(timeout.duration) * time.Second, func() { timeout.callback(timeout.session) }) } func (timeout* TimeoutHandler) Cancel() { if timeout.timer == nil { return } timeout.timer.Stop() } لإنشاء المُهل وتخزينها، يمكنك استخدام دالّة مشابه لهذه: func CreateTimeoutHandler(callback func(Session), session Session, duration int) *TimeoutHandler { if sessionTimeout[session] == nil { sessionTimeout[session] := new(TimeoutHandler) } timeout = sessionTimeout[session] timeout.session = session timeout.callback = callback timeout.duration = duration return timeout } بمجرد إنشاء معالج المُهلة وتسجيله بنجاح، سيُشغل عندها رد النداء بعد انقضاء المدة الزمنية (duration) المقدرة بالثانية. بينما بعض الأحداث الأخرى ستتطلبُ منك إعادة جدولة معالج المُهلة (كما يحدث في حالة SYN - كلّ 3 ثوان). لذلك، يمكنك إعادة جدولة المُهلة لرد نداء معين. هكذا: func synCallback(session Session) { sendSynPacket(session) // reschedules the same callback. newTimeout := NewTimeoutHandler(synCallback, session, SYN_TIMEOUT_DURATION) newTimeout.Register() sessionTimeout[state] = newTimeout } سيعيد رد النداء جدولة نفسه في معالج مهلة جديد ويحدّث الخارطة العامة بقيمة sessionTimeout الجديدة. سباق البيانات والمراجع الحل أصبح جاهزًا تقريبًا. لنطبق اختبار واحد بسيط للتحقق من تنفيذ رد النداء بعد انقضاء زمن المؤقت للمُهلة. للقيام بذلك، نسجلّ المُهلة، وندخلها بمرحلة النوم أو السُبات (sleep) بحسب مدتها، ثم نتحقق فيما إذا كانت إجراءات رد النداء نُفذت بالفعل. بعد تنفيذ الاختبار، من الأفضل إلغاء المُهلة المُجدولة (إذ يمكننا أيضًا إعادة جدولتها لاحقًا)، لذلك لن يكون له آثار جانبية بين الاختبارات. والمثير للدهشة أن هذا الاختبار رغم بساطته أوجد خطأ في حلنا السابق. إذ أن إلغاء المُهل المُجدولة باستخدام دوالّ الإلغاء لن يُؤدي الوظيفة الّتي نريدها. فمثلًا يمكن أن يؤدي الترتيب التالي للأحداث للدخول بحالة سباق على البيانات: لديك معالج (handler) مجدول بمُهلة معينة. الخيط 1 (Thread 1): أ) تتلقى حزمة تحكم، وتريد الآن إلغاء المُهلة المسجلة والانتقال لحالة الجلسة التالية. (مثلًا، أن تتلقى SYN-ACK بعد إرسال ** SYN **). ب) يمكنك استدعاء الدالّة timeout.Cancel()، والّتي بدورها ستستدعي الدالّة timer.Stop(). (لاحظ أن توقف مؤقت لغة Golang لن يمنع تشغيل المؤقت الّذي انتهت مدته الزمنية بالفعل). الخيط 2: أ) قبل استدعاء دالة الإلغاء مباشرةً، انقضت المدة الزمنية للمؤقت، وكان رد النداء على وشك التنفيذ. ب) نفذَّ رد النداء، وسجُلّت المُهلة الجديدة وحُدّثت أيضًا الخارطة العامة. الخيط 1: أ) الانتقال لحالة الجلسة الجديدة وتسجيل مُهلة جديدة، وتحديث الخارطة العامة. كلا الخيطين حدثا الخارطة العامة بالمُهلة الجديدة تحديثًا متزامنًا. والنتيجة النهائية هي أنك فشلتَ في إلغاء المُهلة المسجلة، وفقدتَ أيضًا الإشارة للمُهلة المُعادُ جدولتها والّتي نفذّت بواسطة الخيط 2. وبذلك سيستمر المعالج في التنفيذ وإعادة الجدولة لفترى من الزمن، مما سيؤدي لسلوك غير مرغوب به في البرنامج. هل يعد استخدام القفل كافيًا لحل المشكلة؟ في بعض الأحيان لا تَحلُّ الأقفال المشكلة تمامًا. فإن أضفنا أقفالًا قبل معالجة أي حدث وقبل تنفيذ رد النداء، فلا يزال هذا الحل لا يمنع تشغيل رد النداء المنتهية مدته الزمنية: func (timeout* TimeoutHandler) Register() { timeout.timer = time.AfterFunc(time.Duration(timeout.duration) * time._Second_, func() { stateLock.Lock() defer stateLock.Unlock() timeout.callback(timeout.session) }) } إن الفرق الآن هو أن التحديثات على الخارطة العامة متزامنة، ولكن هذا لا يمنع رد النداء من العمل بعد استدعاء الدالّة timeout.Cancel() - وتحدث هذه الحالة إذا انتهت المدة الزمنية للمؤقت المُجدول ولكنه لم يجلب القفل بعد. عندها يجب أن تُفقدُ مرة أخرى الإشارة لأحد المُهل المسجلة. استخدام قنوات الإلغاء بدلًا من الاعتماد على الدالّة timer.Stop() في لغة Golang، الّذي لن يمنع رد النداء المنتهية مُهلته الزمنية من التنفيذ، سنعتمد على قنوات الإلغاء. هذا النهج مختلف قليلًا عن سابقه. فالآن لن نعيد الجدولة بطريقة تعاودية من خلال ردود النداء وإنما سننشئ حلقة لا نهائية تنتظر إشارات الإلغاء أو أحداث انتهاء المُهل. إن الدالّة Register() ستُولّد خيوطًا جديدة والّتي ستُشغل رد النداء بعد انتهاء المُهلة، وكما أنها ستُجدولّ المهلة الجديدة بعد تنفيذ المُهلة السابقة. ستُعاد قناة إلغاء إلى المستدعي ليتحكم بالوقت المناسب لإيقاف الحلقة. func (timeout *TimeoutHandler) Register() chan struct{} { cancelChan := make(chan struct{}) go func () { select { case _ = <- cancelChan: return case _ = <- time.AfterFunc(time.Duration(timeout.duration) * time.Second): func () { stateLock.Lock() defer stateLock.Unlock() timeout.callback(timeout.session) } () } } () return cancelChan } func (timeout* TimeoutHandler) Cancel() { if timeout.cancelChan == nil { return } timeout.cancelChan <- struct{}{} } يمنحك هذا النهج قناة إلغاء مخصّصة لكل مُهلة تسجلها. يُرسلُ استدعاء الإلغاء كهيكل (Struct) فارغ إلى القناة، ويؤدي ذلك إلى الإلغاء. ومع ذلك، هذا النهج لن يحلّ المشكلة السابقة الّتي عانينا منها؛ إذ يمكن أن تنتهي المدة الزمنية للمهلة فورًا قبل استدعاء الإلغاء عبر القناة، وقبل أن يُجلبُ القفل بواسطة مُهلة الخيط. الحل في هذه الحالة هو التحقق من قناة الإلغاء بداخل نطاق المُهلة بعد جلب القفل. case _ = <- time.AfterFunc(time.Duration(timeout.duration) * time.Second): func () { stateLock.Lock() defer stateLock.Unlock() select { case _ = <- handler.cancelChan: return default: timeout.callback(timeout.session) } } () } أخيرًا، هذا سيضمنُ أن يُنفذّ رد النداء فقط بعد جلب القفل وقبل تفعيل الإلغاء. احذر من مشكلة القفل المستعصي يبدو أن هذا الحل يعمل عملًا جيدًا. إلا أنه بالرغم من ذلك نلاحظ وجود مشكلة القفل المستعصي المخفية. حاول أن تعيد قراءة الشيفرة البرمجية أعلاه وأن تحاول العثور عليها بنفسك. فكر في الاستدعاءات المتزامنة للدوالّ الموضحة. المشكلة الأخيرة هنا هي قناة الإلغاء نفسها. لقد أنشأنا قناةً بدون تفعيل ميزة التخزين المؤقت فيها، مما يعني أن عملية الإرسال هي عملية استدعاء الحجب. بمجرد استدعاء الإلغاء في مُعالج المُهلة، يمكنك المتابعة عند حدوث عملية الإلغاء. تكمن المشكلة هنا عندما يكون لديك عدة استدعاءات لنفس قناة الإلغاء، ولكن يُستهلكُ طلَبُ الإلغاء لمرة واحدة فقط. ويمكن أن يحدث هذا السيناريو ببساطة في حال كانت الأحداث المتزامنة ستلغي نفس مُعالج المُهلة، مثل حدث فصل الارتباط أو حدث تراسل حزمة التحكم. وينتج عن ذلك حالة استعصاء، والتي بدورها ستؤدي لتوقف التطبيق. الحل هنا هو تفعيل ميزة التخزين المؤقت للقناة من قبل أحد الأحداث، لذا فإن الإرسال لن يؤدي دومًا للحجب، وجعل الإرسال لن يؤدي للحجب بطريقة صريحة في حالة الاستدعاءات المتزامنة. وهذا سيضمن أن عملية الحجب ستكون بعملية إرسال واحدة فقط، ولن تؤدي الاستدعاءات المتتالية لعملية الإرسال للحجب. func (timeout* TimeoutHandler) Cancel() { if timeout.cancelChan == nil { return } select { case timeout.cancelChan <- struct{}{}: default: // can’t send on the channel, someone has already requested the cancellation. } } الخلاصة تعلمنا من خلال التطبيقات العملية كيف تظهر الأخطاء الشائعة أثناء العمل مع الشيفرات المتزامنة. نظرًا لطبيعتها غير الحتمية، يمكن اكتشاف هذه المشكلات بسهولة، بدون إجراء اختبارات مكثفة. وإليك المشاكل الرئيسية الثلاث الّتي واجهناها في تطبيقنا. تحديث البيانات المشتركة بدون مزامنة يبدو هذا الأمر جليًا، ولكن من الصعب تحديد ما إذا كانت التحديثات المتزامنة تحدث في مواقع مختلفة. والنتيجة هي حالة السباق على البيانات، حيث يمكن أن تؤدي التحديثات المتعددة لنفس البيانات لفقدان التحديث، بسبب تجاوز أحد التحديثات لتحديث آخر. في مثالنا، حدثنا مرجع المُهلة المُجدولة على نفس الخارطة المشتركة (Shared Map). (من المثير للاهتمام أنه إذا اكتشفت لغة Go عملية قراءة/كتابة متزامنة على نفس كائن الخارطة، فسترمي خطأ مُميت - يمكنك تشغيل مثال حي لكاشف سباق البيانات). يؤدي هذا في النهاية إلى فقدان مرجع المُهلة، ويجعل من المستحيل إلغاء المُهلة المحددة. تذكر دائمًا استخدام الأقفال عند الحاجة إليها. فقدان عمليات التحقق من الحالة يلزم التحقق من الحالة في المواقف الّتي لا يمكنك فيها الاعتماد فقط على حصرية القفل. موقفنا مختلف قليلًا، لكن الفكرة الأساسية مشابهة تمامًا لمتغيرات الحالة. تخيل موقفًا كلاسيكيًا حيث يكون لديك مُنتج واحد والعديد من المستهلكين يعملون في قائمة انتظار مشتركة. يمكن للمُنتج إضافة عنصر واحد لقائمة الانتظار وإيقاظ جميع المستهلكين. تعني عملية الاستيقاظ للمستهلكين بأن هنالك بعض البيانات المتاحة في قائمة الانتظار، ولأن قائمة الانتظار مشتركة، يجب مزامنة الوصول لها من خلال قفل. كلّ مستهلك لديه فرصة للاستيلاء على هذا القفل. ومع ذلك، لا يزال الحلّ غير كامل ومازلنا بحاجة للتحقق إن كان هناك عناصر في قائمة الانتظار. يلزم التحقق من الحالة لأنك لا تعرف حالة قائمة الانتظار في الوقت الّذي تمسك القفل فيه. في مثالنا، تلقى مُعالج المُهلة استدعاء الاستيقاظ (لفك حالة السُبات) من مؤقت منتهية مُهلته الزمنية، ولكنه لا يزال بحاجة للتحقق من أن إشارة الإلغاء قد أرسلت إليه بالفعل قبل أن يتابع تنفيذ رد النداء. مشكلة القفل المستعصي تحدث هذه المشكلة عندما يكون هنالك خيط ما عالقًا، ويدخل في مرحلة انتظار غير منتهية للإشارة المعنية بإيقاظه، ولكن هذه الإشارة لن تصل أبدًا. وهذا بدوره يمكن أن يقضي على تطبيقك بالكامل عن طريق إيقاف تنفيذ البرنامج بالكامل. في مثالنا، حدث ذلك بسبب استدعاءات عمليات الإرسال المتعددة لقناة غير مفعل بها ميزة التخزين المؤقت. وهذا يعني أن استدعاء الإرسال لن تعود إلا بعد نجاح العملية واستلامها التنفيذ من نفس القناة. كانت حلقة المُهل للخيوط الخاصة بنا تستقبل على الفور إشارات على قناة الإلغاء؛ ومع ذلك، بعد تلقي الإشارة الأولى، سنخرج من الحلقة ولن نستطيع الاستقبال من هذه القناة مرة أخرى. والاستدعاءات المتبقية عالقة إلى الأبد. لتجنب هذا الموقف، تحتاج للتفكير بعناية بطريقة استخدام القنوات في شيفرتك البرمجية، والتعامل مع استدعاءات الحجب بحذر، وضمان عدم حدوث مجاعة لأي خيط (انتظاره إشارة معينة للأبد). وكان الحلّ في مثالنا هو بجعل استدعاءات الإلغاء غير مؤدية للحجب - إذ لم نحتاج لاستدعاءات الحجب في حالتنا. ترجمة -وبتصرف- للمقال Lessons learned from programming in Go لكاتبه Eduardo Ferreira
-
المنهجية الأكثر منطقية لتنسيق شيفرة ملفات CSS وSASS المصطلحات التصريح عن قاعدة التصريح عن قاعدة (rule declaration): هو الاسم المختار لمحدّد معين (أو لمجموعة من المحددات) مع مجموعة من الخاصيات المصاحبة. إليك مثالًا لتوضيح الأمر: .listing { font-size: 18px; line-height: 1.2; } المحددات إن المحددات التي رأينها عند التصريح عن قاعدة، هي الجزء الّذي سيُحدد طريقة تنسيق العناصر في شجرة DOM، وذلك بحسب الخاصيات المعرّفة في المحدّد. يمكن أن تُطابقُ المحددات عناصر HTML، أو صنف العنصر (class)، أو معرّف العنصر (ID)، أو أي سِمة من سماته. وهذه بعض الأمثلة عن المحدّدات: .my-element-class { /* ... */ } [aria-hidden] { /* ... */ } الخاصيات أخيرًا، الخاصيات هي ما يمنح العناصر المحددة تنسيقها والتي صرحنا عنها في المحدد. والخاصيات هي أزواج مؤلفة من الخاصية وقيمتها، ويمكن للقاعدة المصرح عنها أن تحتوي على تعريف خاصية أو أكثر. وهكذا ستبدو طريقة التصريح عن الخاصيات: /* بعض المحددات */ { background: #f1f1f1; color: #333; } ملفات CSS التنسيق استخدم الزر (tabs) بمقدار مسافتين (وتسمى أيضًا Soft-Tab نظرًا لأن زر Tab الإفتراضي يكون ثمان مسافات). يفضل استخدام الشرطة العادية (وهي -) بدل تسمية الأصناف بأسلوب سنام الجمل (camelCasing). لا بأس باستخدام الشرطات السفلية والتسمية بأسلوب PascalCasing (وهي طريقة مشابهة لطريقة سنام الجمل إلا أنه يجب أن يكون أول حرف كبير) إن كنت تستخدم BEM ( والّتي سنتطرق إليها لاحقًا في هذا الدليل). لا تستخدم مُحدِّدات المُعرِّفات (ID). عند استخدام محددات متعددة في تصريح واحد لقاعدة ما، امنح كل مُحدّد سطر خاص به. ضع مسافة قبل قوس الافتتاح { عند التصريح عن قاعدة. ضع مسافة بعد : وليس قبله في الخاصيات. ضع أقواس الإغلاق } عند التصريح عن قاعدة في سطر جديد. ضع أسطر فارغة بين تصريحات القواعد. طريقة تنسيق سيئة .avatar{ border-radius:50%; border:2px solid white; } .no, .nope, .not_good { // ... } #lol-no { // ... } طريقة تنسيق جيدة .avatar { border-radius: 50%; border: 2px solid white; } .one, .selector, .per-line { // ... } التعليقات يفضلُ استخدام التعليقات السطرية (وهي // في Sass-land) للتعليقات الكتلية. يفضلُ أن تضع التعليق بسطر خاص به. تجنب التعليقات في نهاية السطر. اكتب تعليقات تفصيلية للشيفرة البرمجية التي لا تشرح نفسها بنفسها: استخدام الخاصية z-index. استخدام المميزات التوافقية أو بعض الخدع المخصصة للمتصفحات. OOCSS و BEM نشجع على استخدام مزيج من OOCSS و BEM وذلك للأسباب التالية: يساعدنا هذا المزيج في إنشاء علاقات واضحة وصارمة بين ملفات التنسيق CSS وملفات HTML. ينشئ مكونات قابلة لإعادة الاستخدام والتركيب. يسمح بتقليل التداخل والتخصيص. يساعد في زيادة قابلية تطوير ملفات التنسيق. OOCSS (اختصارًا "Object Oriented CSS") وهي التنسيق كائني التوجه لملفات التنسيق، هو نهج لكتابة تنسيق CSS يشجعك على التفكير بملفات التنسيق على أنها مجموعة من "الكائنات": أي أنها قابلة لإعادة استخدام أجزاءٍ منها، وقابلة للتكرار، ويمكن استخدامها بشكل مستقل عبر موقع الوِب. لمزيد من المعلومات يمكنك الاطلاع على: توثيق نيكول سوليفان (Nicole Sullivan's) OOCSS wiki. مقال مدخل إلى OOCSS (Introduction to OOCSS). BEM (اختصارًا Block-Element-Modifier) معدلّ عنصر الكتلة وهو عبارة عن اصطلاح تسمية للأصناف في HTML و CSS. طوّر في الأصل من خلال شركة Yandex ووضعوا في حسبانهم الشيفرات البرمجية الكبيرة وقابلية التطوّر والتوسع للشيفرة، ويمكن اعتبارها بمثابة مجموعة قوية من المبادئ التوجيهية لتطبيق OOCSS. لمزيد من المعلومات يمكنك الاطلاع على: خدع CSS دليلك إلى منهجية BEM - CSS مدخل إلى BEM (introduction to BEM). نوصي بالتنويع بأشكال BEM مع "كتل" مسماة بأسلوب PascalCased، والّتي تعمل بطريقة جيدة خصيصًا عند دمجها مع المكونات (مثلما يحدث في React). لا تزال تُستخدم الشُرط السفلية والشرطات للمعدلات وسلالتهم (من سيرثُ منهم). مثال // ListingCard.jsx function ListingCard() { return ( <article class="ListingCard ListingCard--featured"> <h1 class="ListingCard__title">Adorable 2BR in the sunny Mission</h1> <div class="ListingCard__content"> <p>Vestibulum id ligula porta felis euismod semper.</p> </div> </article> ); } /* ListingCard.css */ .ListingCard { } .ListingCard--featured { } .ListingCard__title { } .ListingCard__content { } .ListingCard: وهي عبارة عن "كتلة" وتمثل مكون عالي المستوى. .ListingCard__title: عبارة عن "عنصر" وتمثل سلالة .ListingCard التي تساعد في تكوين الكتلة ككلّ. .ListingCard--featured: وهو عبارة عن "معدلّ" ويمثل الحالات أو التشكيلات المتختلفة لكتلة .ListingCard. محددات المعرفات في حين أنه من الممكن تحديد العناصر حسب المعرّف في CSS، إلا أن هذه المحددات تعدّ أسلوبًا مضادًا للنمط (anti-pattern). تقدم محدّدات المعرّفات مستوى عاليًا من التخصيص غير ضروري للتصريح عن قاعدة، بل ولن يمكننا إعادة استخدام هذه المحدّدات. لمزيد من المعلومات حول هذا الموضوع، أحيلك لهذه المقالة CSS Wizardry's article والّتي تتحدث عن طريقة التعامل مع التخصيص. خطاف الجافاسكربت تجنب ربط نفس الصنف في كلّ من ملفات التنسيق CSS والجافاسكربت. لأنه غالبًا ما سيُؤدي لخلط الاثنين معًا، وبأحسن الأحوال سيؤدي ذلك لإضاعة الوقت أثناء إعادة البناء عندما يتعين على المطوّر العودة لكلّ صنف يغيره، وفي أسوأ الأحوال، سيخشى المطوّرون إجراء تعديلات عليها خوفًا من كسر الوظيفة ما المرتبطة بها. نوصي بإنشاء أصناف مخصصة للربط مع جافاسكربت مسبوقة بـ .js-: الحدود استخدم 0 بدلًا من none لتحديد أن النمط ليس له حدود. الطريقة السيئة .foo { border: none; } الطريقة الجيدة .foo { border: 0; } ملفات SASS الصياغة استخدم الصيغة .scss، ولا تستخدم أبدًا الصيغة .sass الأصلي رتب شيفرة ملفات CSS العادية وعمليات التضمين @include بطريقة منطقي (انظر أدناه) ترتيب طريقة التصريح عن الخاصيات التصريح عن خاصية أدرج جميع التصاريح القياسية للخاصيات، وأي شيء آخر ما عدا عمليات التضمين @include أو المحددات المتشعّية. .btn-green { background: green; font-weight: bold; // ... } تصريح عن عملية تضمين @include إن تجميع ووضع جميع عمليات التضمين @include في نهاية المحدّد سيسهل قراءة المحدد بالكامل. .btn-green { background: green; font-weight: bold; @include transition(background 0.5s ease); // ... } المحددات المتشعّبة يكون ترتيب المحددات المتشعّبة ( إن اضطررنا لاستخدامها) بعد عمليات التضمين ولا شيء بعدها. أضف مسافة بين عمليات التصريح عن القواعد والمحددات المتشعّبة، وكذلك بين المحددات المتشعّبة المتجاورة. طبّق نفس الإرشادات المذكورة سابقًا على المحددات المتشعّبة الخاصة بك. .btn { background: green; font-weight: bold; @include transition(background 0.5s ease); .icon { margin-right: 10px; } } المتغيرات يفضل استخدام أسماء المتغيرات التي تحتوي على شرطة عادية (مثل: $my-variable) بدلًا من أسماء متغيرات مثل أسلوب تسمية سنام الجمل (camelCased) -تكبير أول حرف من كلّ كلمة في اسم المتغير عدا أول كلمة- أو أسلوب snake_cased -جميع حروف اسم المتغير صغيرة-. لا مشكلة بإضافة شرطة سفلية لأسماء المتغيرات الّتي نود استخدامها فقط داخل نفس الملف(هكذا: $_my-variable). المخاليط يجب استخدام المخاليط (mixins) لتصغير شيفرتك البرمجية، أو لتوضيحها، أو لعزل تعقيدها - بطريقة مشابهة تمامًا للدوالّ المسماة جيدًا. يمكن للمخاليط الّتي لا تقبل أي وسطاء أن تكون مفيدة لذلك، ولكن انتبه فأذا لم تستخدم خوارزميات ضغط للملفات المشروع (مثل خوارزمية gzip)، فسيُساهم ذلك في تكرار الشيفرات البرمجية غير الضرورية في التنسيقات الناتجة. توسعة المحدّدات المُركّبة يجب تجنب التوسعة باستخدام التعليمة @extend لأنه يسلك سلوك غير معروف للبعض وغالبًا ما يكون خطير، خاصة عند استخدامه مع محددات متشعّبة. حتى إن توسيع محددات العناصر الأساسية عالية المستوى يمكن أن يسبب مشاكل إذا انتهى الأمر بتغيير ترتيب المحددات في وقت لاحق (فمثلًا إذا كانت هذه المحددات في ملفات أخرى، وتغير ترتيب تحميل الملفات فهذا سيُشكل مشكلة). يجب أن تتعامل خوارزمية الضغط Gzipping مع عمليات تصغير أحجام الملفات الّتي ستحصلُ عليها باستخدام @extend، وستمنحُك المخاليط تصغيرًا جيدًا لملفات التنسيق خاصتك. المحددات المتشعّبة ** لا تنشئ محددات متشعّبة بعمق يزيد عن ثلاثة مستويات!** .page-container { .content { .profile { // STOP! } } } عندما تصبح المحددات طويلة، من المحتمل أنك تضطر أن لتكتب تنسيق لديه المميزات التالية: مقترن بشدة بملفات HTML (وهذا يعدّ نقطة ضعف في التنسيق) -أو محدد للغاية (وهذا يعدّ نقطة قوة في التنسيق) -أو غير قابل لإعادة الاستخدام. مرة أخرى: لا تستخدم نهائيًا محددات المعرّفات المتشعبة! في حال وجب عليك استخدام محدّد المعرّف في البداية (ويجب أن تحاول بأقصى جهدك أن تتجنبه)، فلا يجب أن تكون متشعّبة أبدًا. وإن وقعت بهذه المشكلة، فعاود النظر في الشيفرة البرمجية أو أسأل نفسك لما كلّ هذا التخصيص الشديد؟ وإن أردت كتابة ملفات HTML وCSS ذات بنية جيدة، فيجب عليك ألا تستخدم محدّدات المعرّفات المتشعبة نهائيًا. ترجمة -وبتصرف- لدليل Airbnb لتنسيق ملفات CSS وSASS (Airbnb CSS / Sass Styleguide) الموجود على موقع GitHub
-
إن تزايد تعقيد تطبيقات الوِب في وقتنا الحالي جعل من ضرورة زيادة قابلية تطبيقات الوِب للتوسعة والتطوير أمرًا في غاية الأهمية. وعلى الرغم من أن الحلول القديمة المخصصة لكتابة شيفرات جافاسكربت و jQuery كانت فعَالة وكافية إلى حدٍ ما، ولكن بناء تطبيق وِبٍ في وقتنا الحاضر يتطلب درجة كبير من الانضباط والمنهجية الرسمية في تطوير البرمجيات. وهذه بعض الأمثلة على الممارسات الجيدة في تطوير البرمجيات: استخدام اختبارات الوحدة (Unit tests) وذلك للتأكد من أن تعديلات ما على الشيفرة البرمجية لن يعطّل شيفرة أُخرى. استخدام عملية كشف الأخطاء المحتملة (Linting) لضمان كتابة شيفرة برمجية خالية من الأخطاء. استخدام طرق مختلفة للبنية الهيكلية للشيفرة البرمجية لكلّ من وضع التطوير ووضع النشر. بالإضافة إلى ذلك فإن معايير الوِب بشكلها الحالي طرحت تحديات جديدة لتطوير تطبيقات الوِب. على سبيل المثال صفحات الوِب الحالية تُنشئ الكثير من الطلبات غير المتزامنة والّذي بدوره يؤدي إلى خفض أداء تطبيق الوِب انخفاضًا كبيرًا بسبب معالجة طلبات ملفات التنسيق والجافاسكربت. إذ أن لكل طلبٍ من هذه الطلبات (حتى وإن كان لملف صغير الحجم) فإنه سيحتاج إلى ترويسة اتصال (Header) وكُلفة إنشاء اتصال (handshakes). وهذه المشكلة تحديدًا تُعالجُ من خلال تجميع الملفات معًا لتصبح في ملف واحد وبذلك سنستدعي ملف جافاسكربت وملف تنسيق بدلًا من المئات الملفات. من الشائع في وقتنا الحالي استخدام لغات ذات معالجة مُسبّقة (language preprocessors) مثل: SASS و JSX والّتي تُترجم من هذه اللغات إلى ملفات جافاسكربت وملفات تنسيقات. بالإضافة إلى ذلك شاع أيضًا استخدام المحوّل (Transpilers) وهو محوّل يغيّر الشيفرة البرمجية من إصدار معين للغة جافاسكربت إلى إصدار أقدم، وذلك لضمان عمل هذه الشيفرة البرمجية على جميع المتصفحات. مثل المحوّل Babel. إن هذه المهام (والّتي لا علاقة لها بالمنطق البرمجي لتطبيق الوِب بحد ذاته) تمثل عبئًا على على المطور. ولكن لحسن الحظ يوجد حلٌ لهذا الأمر وهو منفذ المهام (Task Runner) والّذي جاء لمساعدتنا في أتمتة هذه المهام حتى نستطيع أن نحسّن من بيئة التطوير مع التركيز على بناء المنطق البرمجي لتطبيق الوِب. بمجرد ضبط الإعدادات الخاصة بمنفذ المهام كلّ ما عليك فعله هو استدعاء أمرٍ على الطرفية وستُنفذ بعدها المهام المؤتمتة. سأستخدم الأداة Gulp كمنفذ للمهام وذلك لأنه مناسب للمبرمجين وسهل التعلّم ومفهوم (تحدثنا في مقال سابق كيفية استخدام هذه الأداة بكلّ التفاصيل). مقدمة سريعة لأداة Gulp تتألف واجهة برمجة التطبيقات (API) لهذه الأداة من أربع دوالّ: gulp.src gulp.dest gulp.task gulp.watch في المثال التالي نلاحظ أن المهمة my-first-task ستستخدم ثلاثة دوال من أصل الأربعة. gulp.task('my-first-task', function() { gulp.src('/public/js/**/*.js') .pipe(concat()) .pipe(minify()) .pipe(gulp.dest('build')) }); عند تنفيذ المهمة my-first-task ستصغّر أولًا جميع الملفات المطابقة للنمط /public/js/**/*.js ومن ثمّ ستُنقل إلى المجلد build. الجميل في التعليمة pipe(). هي أنه يمكنك أخذ مجموعة من ملفات الإدخال وتمريرها عبر الأنبوب لتنفيذ بعض التحويلات المناسبة عليهم ومن ثمّ إعادة ملفات الخرج المطلوبة. لجعل الأمور أكثر توافقية. غالبًا ما تنفذ العمليات المطلوبة في الإنبوب (مثل: minify()) من خلال مكتبات مدير الحزم npm، ونتيجة ذلك من النادر جدًا في التطبيق الفعلي لهذه االعمليات أن نحتاج لكتابة تفاصيل هذه العمليات كتابةً يدوية إلا إذا أردت أن تُعيد تسمية الملفات في الأنبوب. من الجدير بالذكر أن Gulp يَستخدم المجاري (streams) الّتي توفرها node.js، وهذا يسمح بتمرير البيانات الّتي ستُعالَج عبر الأنابيب (pipes) وهذا ما تفعله الدالة .pipe()؛ لشرحٍ تفصيليٍ عن المجاري في node.js، سأحيلك إلى هذه المقالة. الخطوة التالية لفهم Gulp هي فهم مصفوفة تبعيات المهام. gulp.task('my-second-task', ['lint', 'bundle'], function() { ... }); في هذا المثال إن المهمة my-second-task تُعيد نتيجة الدالة المجهولة (anonymous function)، وذلك بعد اكتمال مهمة lint ومهمة التجميع bundle. وبذلك يُسمح لنا بفصل الاهتمامات عن بعضها بعضًا كما يمكنك أيضًا إنشاء سلسلة من المهام الصغيرة بمسؤولية واحدة مثل تحويل LESS إلى CSS. وإنشاء مهمة رئيسية (Master Task) والّتي ستستدعي ببساطة جميع المهام الأخرى (الصغيرة) عبر مصفوفة من تبعيات المهام. وأخيرًا لدينا التعليمة gulp.watch والّذي يراقب التغييرات في ملف (أو ملفات) ما والمطابق لنمط مرّر لها، وما إن يحدث تغيير ما في هذا الملف حتى تُنفذّ سلسلة من المهام المحددة. gulp.task('my-third-task', function() { gulp.watch('/public/js/**/*.js', ['lint', 'reload']) }) في المثال السابق أي تغيير في الملفات الّتي تطابق النمط /public/js/**/*.js سيؤدي إلى تشغيل مهمة كشف الأخطاء المحتملة lint وبعدها مهمة إعادة التحميل reload. إن الاستخدام الشائع للتعليمة gulp.watch هو تشغيل عمليات إعادة التحميل المباشر في المتصفح، وهي ميزة رائعة جدًا أثناء مرحلة التطوير ولن تستطيع العمل بدونها بمجرد أن تجربها. وإلى هنا نستطيع القول بأننا فهمنا كلّ ما سنحتاج لاستخدامه في الأداة Gulp. أين سنستخدم أداة Webpack؟ عندما نستخدام نمط CommonJS فإن تجميع كلّ ملفات الجافاسكربت ليصبحوا في ملف واحد ليس بهذه البساطة. إذ إن الخاصية (entry point) والّتي تُسند عادةً للقيمة index.js أو app.js مع سلسلة من التعليمات require أو import الموجودة في أعلى الملف. وسيكون شكل ملف جافاسكربت في الإصدار ES5 على الشكل التالي: var Component1 = require('./components/Component1'); var Component2 = require('./components/Component2'); وسيكون شكل ملف جافاسكربت في الإصدار ES6 على الشكل التالي: import Component1 from './components/Component1'; import Component2 from './components/Component2'; إن المثالين السابقين يجلبان التبعيات قبل تنفيذ بقية الشيفرات البرمجية في ملف app.js، وممكن أن يكون لهذه التبعيات تبعيات أخرى والّتي ستُجلب أيضًا. وبالإضافة إلى ذلك من الممكن أن تُستدعى نفس التبعية في أماكن متعددة في تطبيق الوِب خاصتك. ولكننا نريد جلب هذه التبعية مرة واحدة فقط. لهذا فأن كانت شجرة التبعيات بعمق عدة مستويات (أي التبعيات ذات هرمية كبيرة) فعندها ستزداد صعوبة تجميع هذه التبعيات في ملف واحد. ولكن لحسن الحظ يوجد حلّ رائع لهذه المشكلة وهو مُجمّع الحزم (الوحدات) مثل:Browserify أو Webpack. لماذا يفضل المطورون استخدام Webpack بدلًا من Gulp؟ بما أن أداة Webpack (تحدثنا في مقال سابق عن كيفية استخدام مجمع الحزم Webpack وأشهر طرق استعمالها في المشاريع) لتجمّيع الحزم وأداة Gulp لتنفيذ المهام من الممكن أن نتوقع أن نرى استخدام هاتين الأداتين مع بعضهما بعضًا، ولكن هنالك توجّه عام نحو استخدام Webpack بدلًا من Gulp وخصيصًا في مجتمع مطوري React. ولكن لما هذا التوجه؟ ببساطة إن قوة أداة Webpack مكنتها من تنفيذ الغالبية العظمى من المهام الملقاة على عاتق منفذ المهام (مثل: Gulp أو أي منفذ مهام عمومًا). فعلى سبيل المثال توفر Webpack خيارات تصغير وخرائط الشيفرة البرمجية المحوّلة Source Maps (لمزيد من المعلومات عنها يمكنك الإطلاع على المقال التالي) للشيفرة البرمجية المُجمعّة. بالإضافة إلى ذلك يمكن استخدامها كوسيط (Middleware) من خلال خادم مخصص يدعى webpack-dev-server والّذي يدعم كلًا من إعادة التحميل المباشر (live reloading) وإعادة التحميل النشط (hot reloading) لصفحات الوِب (والّتي سنتحدث عنها لاحقًا في هذا المقال). وكما يمكنك أيضًا تحويل الشيفرة البرمجية (transpiling) من إصدار جافاسكربت حديث مثل ES6 إلى إصدارٍ قديم مثل: ES5. ويمكنك أيضًا استخدام طريقة المعالجات المُسبقة (pre-processors) أو المُلحقة (post-processors) لملفات التنسيق. وبذلك تُترك عملية إختبار الوحدة (Unit Tests) وعملية كشف الأخطاء المحتملة (linting) كمهام رئيسية مستقلة نظرًا من كوننا قلصنا ما لا يقلّ عن ستة مهام محتملة للأداة Gulp لمهمتين فقط. يلجأ العديد من المطورين لاستخدام NPM Scripts بدلًا من استخدام Gulp، وذلك لتجنب إضافة أداة Gulp إلى المشروع في حين وجود بديل قوي ينوب عنها. في الحقيقة إن السيئة الرئيسية لاستخدام Webpack هي صعوبة ضبط إعداداته، مما يجعله خيارًا غير مرغوب به في حال أردنا إنشاء مشروع وتشغيله بأسرع وقتٍ ممكن. طرق إعداد منفذ مهام سنتعرف على ثلاث طرق لإنشاء وإعداد منفذ مهام مساعد في المشاريع البرمجية. وكلّ واحدٍ منهم سينفذ المهام التالية: إعداد خادم تطوير مع ميزة إعادة التحميل المباشر (live reloading) فور حدوث أي تعديل على صفحة الوِب المُراقبة. تجميع ملفات التنسيق والجافاسكربت (بالإضافة إلى تحويل الشيفرة البرمجية للغة جافاسكربت من الإصدار ES6 إلى ES5، وتحويل ملفات التنسيق من SASS إلى ملفات CSS وخرائط الشيفرة البرمجية المحوّلة) وذلك بطريقة قابلة للتطوير والتوسّع لهذه الملفات المحوّلة. تشغيل اختبار الوحدة (Unit Tests) سواءً كمهمة قائمة بحد ذاتها أو في وضع المراقبة. تشغيل عملية الكشف عن الأخطاء المحتملة (Linting) سواءً كمهمة قائمة بحد ذاتها أو في وضع المراقبة. توفير القدرة على جمع كلّ المهام السابقة عبر أمر واحد لكتابته عبر الطرفية. وجود أمر آخر لتجميع الملفات وضغطها أو تنفيذ تحسينات أخرى عليها من أجل عملية النشر. وستكون طرق إعداد منفذ المهام على الشكل التالي: Gulp + Browserify Gulp + Webpack Webpack + NPM Scripts سنعتمد في تطبيقنا العملي على استخدام React للواجهات الأمامية. في الحقيقة أردت في البداية أن أعمل بدون إطار عمل، ولكن استخدام React سيُبسط مسؤوليات منفذ المهام، إذ سيلزمنا فعليًا وجود ملف HTML واحد فقط، بالإضافة إلى أن React يعمل بسلاسة مع نمط CommonJS ولذلك اعتمدت أخيرًا على استخدامه. سنعمد على توضيح جميع المزايا والعيوب الخاصة بكل طريقة إعداد، وذلك لمنحك المعلومات الكاملة والصورة الشاملة لاتخاذ قرارٍ صائبٍ يناسب احتياجات المشروع الّذي تعكف على تطويره. سننشئ مشروعًا على Git وسنضيف له ثلاثة تفريعات من أجل اختبار كلّ طريقة من الطرق git checkout <branch name> npm prune (optional) npm install gulp (or npm start, depending on the setup) لنناقش الآن كلّ تفريعة (طريقة) من هذه الفروع على حدة. ستكون البنية الهرمية لمجلد المشروع على الشكل التالي: - app - components - fonts - styles - index.html - index.js - index.test.js - routes.js سنتطرق لشرح أهم الملفات الموجودة في المشروع وذلك حرصًا على كمال المعلومة. سيكون الملف index.html على الشكل التالي: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="bundle.css"> <title>Gulp Browserify Setup</title> </head> <body> <div id="app"></div> <script src="bundle.js"></script> </body> </html> نلاحظ أن هذا الملف بسيط جدًا إذ إنه يُحمّلُ تطبيق React في الوسم <div id="app"></div> ولن نستخدم إلا ملف واحد للتنسيقات وملف آخر للجافاسكربت، في الواقع في طريقة الإعداد هذه لن نستخدم حتى ملف التنسيق bundle.css. وسيكون الملف index.js على الشكل التالي: import React from 'react'; import {render} from 'react-dom'; import {Router, browserHistory} from 'react-router'; import routes from './routes'; render(<Router history={browserHistory} routes={routes} />, document.getElementById('app')); نلاحظ أن هذا الملف سيكون بمثابة نقطة الدخول (entry point) لتطبيقنا. إذ إننا بصدد تحميل React Router في الوسم div مع السِمة app الّتي أشرنا لها في الملف السابق. وسيكون الملف routes.js على الشكل التالي: import React from 'react'; import {Route, IndexRoute} from 'react-router'; import App from './components/App'; import HomePage from './components/home/HomePage'; import AboutPage from './components/about/AboutPage'; import ContactPage from './components/contact/ContactPage'; export default ( <Route path="/" component={App}> <IndexRoute component={HomePage} /> <Route path="about" component={AboutPage} /> <Route path="contact" component={ContactPage} /> </Route> ); نلاحظ أن هذا الملف يحدد مسارات المشروع. وستكون المسارات / و /about و /contact مرتبطة مع المكونات HomePage و AboutPage و ContactPage على التتالي. وسيكون الملف index.test.js على الشكل التالي: import expect from 'expect'; describe('Array', () => { it('should return -1 when the value is not present', () => { expect(-1).toBe([1,2,3].indexOf(4)); }); it('should correctly filter elements in an array', () => { const arr = [1,2,3,4,5]; const newArr = arr.filter(el => el > 3); expect(newArr.length).toBe(2); }); it('should correctly map elements in an array', () => { const arr = [1,2,3,4,5]; const newArr = arr.map(el => el * 2); expect(newArr).toEqual([2,4,6,8,10]); }); }); describe('Object', () => { it('should convert an int to a string with the toString method', () => { const val = 5; const valToString = val.toString(); expect(val).toNotBe(valToString); expect(valToString).toBe('5'); }); it('should correctly test whether an object has a certain property', () => { const obj = {a: 1, b: 2}; expect(obj.hasOwnProperty('a')).toBe(true); expect(obj.hasOwnProperty('c')).toBe(false); }); }); describe('String', () => { it('should convert a string to lowercase', () => { const str = 'HELLO'; const strLower = str.toLowerCase(); expect(strLower).toBe('hello'); }); it('should convert a string to uppercase', () => { const str = 'hello'; const strLower = str.toUpperCase(); expect(strLower).toBe('HELLO'); }); it('should remove spaces from both ends of a string', () => { const str = ' hello there '; const strTrimmed = str.trim(); expect(strTrimmed).toBe('hello there'); }); it('should split the string into an array of strings based on the provided delimiter', () => { const str = 'she-sells-seashells-down-by-the-seashore'; const strSplit = str.split('-'); expect(Array.isArray(strSplit)).toBe(true); expect(strSplit.length).toBe(7); }); }); نلاحظ أن هذا الملف يحتوي على سلسلة من اختبارات الوحدة (Unit Tests) والّتي ستختبر سلوك الشيفرة الأصلية للجافاسكربت (Native JavaScript) في عملية النشر الفعلية للتطبيق يمكنك إنشاء اختبار لكل مكون React (اختبار واحد على الأقل لكل مكوّن يعالج حالة معينة) واختبار السلوك الخاص بإطار العمل React. وعلى أية حال يكفي أن يكون لديك اختبار وحدة بسيط والّذي نستطيع تشغيله في وضع المراقبة (watch mode عند تفعيل هذا الوضع تثبت ملفات مخصصة لمراقبة أي تغييرات تحصل في الملفات الأخرى وفي حال حصول أي تغييرات في الملفات سيعاد ترجمة الملفات للحصول على الخرج الجديد). وسيكون الملف components/App.js على الشكل التالي: import React, {PropTypes} from 'react'; import Header from './common/Header'; class App extends React.Component { render() { return ( <div> <Header /> {this.props.children} </div> ); } } App.propTypes = { children: PropTypes.object.isRequired }; export default App; يمكن اعتبار الملف السابق حاوية لجميع مكونات العرض الموجودة لدينا. إذ تحتوي كلّ صفحة لدينا على الترويسة <Header/> بالإضافة إلى this.props.children والّذي يقيّم لعرض محتواه في الصفحة نفسها. فعلى سبيل المثال إذا كان في المتصفح /contact سيقيّم إلى ContactPage. وسيكون الملف components/home/HomePage.js على الشكل التالي: import React from 'react'; import {Jumbotron, Grid, Row, Col, Panel} from 'react-bootstrap'; class HomePage extends React.Component { render() { return ( <div className="HomePage"> <Jumbotron> <Grid> <h1>Home Page</h1> </Grid> </Jumbotron> <Grid> <Row> <Col sm={6}> <Panel header="Panel 1"> <p>Content A</p> <p>Content B</p> </Panel> </Col> <Col sm={6}> <Panel header="Panel 2"> <p>Content A</p> <p>Content B</p> </Panel> </Col> </Row> </Grid> </div> ); } } export default HomePage; هذا الملف سيكون الصفحة الرئيسية المعروضة. استخدمت react-bootstrap نظرًا لأن نظام الشبكة (Grid) في إطار العمل Bootstrap ممتاز لإنشاء صفحات متجاوبة. وعند استخدامه استخدامًا صحيحًا سيُقّلل عدد استعلامات الوسائط (media queries) الّتي سنكتبها للأجهزة صغيرة الحجم تقليلًا كبيرًا. المكونات الأخرى المتبقية (مثل: Header و AboutPage و ContactPage) مبنية بطريقة مشابهة (باستخدام react-bootstrap بدون التلاعب بالحالة [state manipulation]). أما الآن لنتحدث أكثر عن ملفات التنسيق. منهجية الملفات التنسيق أسلوبي المفضل في تنسيق ملفات React هو امتلاك ملف تنسيق خاص لكلّ مكوّن من المكونات. إذ سيكون هذا التنسيق ضمن نطاق هذا المكون فقط. ستلاحظ أنه في كلّ مكون من المكونات هنالك وسم div ذو مستوى أعلى وسيكون أسم الصنف التابع له مطابق لأسم المكون نفسه. لذلك المكون HomePage.js سيكون مغلف بوسم له الشكل التالي: <div className="HomePage"> ... </div> وسيكون هنالك أيضًا ملف HomePage.scss والّذي سيكون مهيكل على الشكل التالي: @import '../../styles/variables'; .HomePage { // Content here } لماذا هذه المنهجية مفيدة للغاية؟ لأنها تنتج لنا وحدات (Modular) ذات جودة عالية. مما يُلغي الكثير من المشاكل غير المرغوب بها في التنسيق. لنفترض أن لدينا مكونين React وهما Component1 و Component2. وفي كلّ واحد منهما نريد إعادة تنسيق الوسم h2 ليصبح حجم الخط كما في الملف التالي: /* Component1.scss */ .Component1 { h2 { font-size: 30px; } } /* Component2.scss */ .Component2 { h2 { font-size: 60px; } } إن حجم الخط المخصص للوسم h2 في المكونين Component1 و Component2 مكتوبان بمعزل عن بعضهما البعض سواءً أكان المكونين متجاورين أو متداخلين لن يؤثر أحدهما على الآخر. أي أن المكون سيبدو شكله مثل ما خُطط له تمامًا بغض النظر عن مكان وجوده. في الحقيقة إن الأمر ليس بهذه السهولة دائمًا، ولكنه وبكل تأكيد خطوة كبيرة باتجاه أفضل الممارسات الصحيحة للتنسيق. وبالنسبة للتنسيقات المُسبقة للمكونات (per-component styles) أحب أن أخصص مجلد تنسيقات styles يحتوي على جميع التنسيقات العامة في الملف global.scss بجانب تنسيقات SASS مخصصة والّتي تكون مسؤولة عن معالجة التنسيقات المخصّصة (في حالتنا مثل: _fonts.scss و _variables.scss من أجل الخطوط والمتغيّرات على التوالي). تتيح لنا هذه التنسيقات العامة تحديد الشكل العام للتطبيق بأكمله بينما يمكن للتنسيقات الأخرى المخصصة أن تُستورد بحسب الحاجة لها. والآن بعد أن استكشفنا بعمق الشيفرة البرمجية المشتركة بين كلِّ طرق إعداد منفذ المهام. لننتقل إلى أول طريقة منهم. 1. طريقة Gulp + Browserify يتكون ملف gulpfile.js من 22 سطرًا من تعليمات لاستيراد المكتبات والحزم و150 سطرًا من الشيفرات البرمجية الأخرى. لذا ومن أجل الإيجاز سنراجع بالتفاصيل أهم النقاط الرئيسية في هذا الملف (مثل: js و css و server و watch و default). محزم ملفات جافاسكربت // ضبط إعداد Browserify const b = browserify({ entries: [config.paths.entry], debug: true, plugin: PROD ? [] : [hmr, watchify], cache: {}, packageCache: {} }) .transform('babelify'); b.on('update', bundle); b.on('log', gutil.log); (...) gulp.task('js', bundle); (...) function bundle() { return b.bundle() .on('error', gutil.log.bind(gutil, 'Browserify Error')) .pipe(source('bundle.js')) .pipe(buffer()) .pipe(cond(PROD, minifyJS())) .pipe(cond(!PROD, sourcemaps.init({loadMaps: true}))) .pipe(cond(!PROD, sourcemaps.write())) .pipe(gulp.dest(config.paths.baseDir)); } نلاحظ في الدالّة bundle أن ملفات الجافاسكربت ستُحزّم باستخدام Browserify وستُستخدم خرائط الشيفرة البرمجية المحوّلة (Source maps) في وضع التطوير بينما ستُستخدم عملية التصغير لملفات جافاسكربت في وضع الإنتاج. في الحقيقة إن هذا النهج سيء وذلك لعدة أسباب إحداها أن المهمة ستقسّم إلى ثلاثة أجزاء منفصلة. إذ في البداية سننشئ كائن للتحزيم وليكن b وسنمرر له بعض الخيارات المطلوبة، ومن ثمّ نحدد بعض مُعالِجات الأحداث (event handlers). ثم لدينا مهمة Gulp والّتي يجب أن نمرر لها اسم الدالة بدلًا من تمريرها سطريًا (إن b.on('update') تستخدم نفس الطريقة). إن هذه الطريقة غير أنيقة مثل الطريقة الموجودة في مهام Gulp والّتي تتطلب فقط تمرير gulp.src وتوجيه بعض التغييرات. هناك مشكلة أخرى تجبرنا على اتباع طرق مختلفة لإعادة تحميل ملفات html و css و js في المتصفح. لاحظ طريقة إعداد مهمة المراقب الموضحة في الشيفرة التالية: gulp.task('watch', () => { livereload.listen({basePath: 'dist'}); gulp.watch(config.paths.html, ['html']); gulp.watch(config.paths.css, ['css']); gulp.watch(config.paths.js, () => { runSequence('lint', 'test'); }); }); عندما سيحصل أي تغيير في ملف html سيعاد تشغيل مهمة html من جديد. gulp.task('html', () => { return gulp.src(config.paths.html) .pipe(gulp.dest(config.paths.baseDir)) .pipe(cond(!PROD, livereload())); }); إن آخر تعليمة في الأنبوب هي عملية إعادة التحميل المباشرlivereload() إذا كانت قيمة الخاصية NODE_ENV ليست production، ستُحدث الصفحة في المتصفح تحديثًا تلقائيًا. يتكرر نفس السيناريو من أجل مُراقب ملفات التنسيق. إذ عند حدوث أي تغيير في أحد ملفات التنسيق تُستدعى المهمة css ويعاد تشغيلها من جديد، وأخر تعليمة في الأنبوب تكون لاستدعاء التحميل المباشر في المتصفح وذلك لعرض التغييرات مباشرة. غير أن المراقب الخاص بملفات الجافاسكربت لا يستدعي مهمة js على الإطلاق، وإنما معالج الأحداث في الأداة Browserify يتعامل مع إعادة التحميل باستخدام منهجية مختلفة تمامًا (يطلق عليها اسم مبدل الوحدات النشط [hot module replacement]). إن التضارب في هذه المنهجية مزعج للغاية. ولكنه للأسف ضروري وإذا استدعينا تعليمة إعادة التحميل المباشر في نهاية دالة bundle فعندها سيؤدي ذلك إلى إعادة بناء كلّ ملفات جافاسكربت في حال حدث أي تغيير في أي ملف منهم. من الواضح أن هذه المنهجية غير قابلة للتوسّع والتطوّر. إذ كلما زاد عدد ملفات جافاسكربت كلما استغرقت عملية إعادة التجميع وقتًا أطول. وفجأة عملية البناء الّتي كانت تستغرق 500 ميلي ثانية ستستغرق 30 ثانية مما سيُعيق منهجية التطوير الرشيق أجايل agile. محزم ملفات التنسيق gulp.task('css', () => { return gulp.src( [ 'node_modules/bootstrap/dist/css/bootstrap.css', 'node_modules/font-awesome/css/font-awesome.css', config.paths.css ] ) .pipe(cond(!PROD, sourcemaps.init())) .pipe(sass().on('error', sass.logError)) .pipe(concat('bundle.css')) .pipe(cond(PROD, minifyCSS())) .pipe(cond(!PROD, sourcemaps.write())) .pipe(gulp.dest(config.paths.baseDir)) .pipe(cond(!PROD, livereload())); }); إن المشكلة الأولى الّتي تظهر هنا هي تضمين المكتبات أو ملفات التنسيق. إذ يجب علينا أن نتذكر أن نغيّر اسم الملف في كلّ مرة يظهر بها إصدار جديد سواءً للمكتبة (أو لأي ملف تنسيق) وذلك بحذف اسم المكتبة القديمة واستبدالها بالجديدة إلى مصفوفة gulp.src؛ بدلًا من إضافة أمر التضمين في مكان مناسب أكثر في الشيفرة البرمجية. المشكلة الرئيسية الأخرى هي المنطق المعقد في كلّ أنبوب. إذ إنني اضطررت لإضافة مكتبة من مستودع مدير الحزم NPM تدعى gulp-cond فقط لإضافة الجمل الشرطية في تعليمات الأنبوب، والنتيجة النهائية لتعليمات الأنبوب ليس من السهل قراءتها (لأن الأقواس الثلاثية في كلّ مكان!). مهمة الخادم gulp.task('server', () => { nodemon({ script: 'server.js' }); }); هذه المهمة واضحة للغاية. إذ إنه في الأساس مُغلّف لأمر استدعاء nodemon server.js والّذي سيُشغّل ملف server.js في بيئة Node.js. استخدامنا nodemon بدلًا من node إذ ستؤدي أي تغييرات تحدث في الملف إلى إعادة تشغيل الخادم. افتراضيًا nodemon تُعيد تشغيل العملية عند أي تغيير في ملف الجافاسكربت ولهذا السبب من المهم لتضمين ملف nodemon.json للحدّ من مجاله. { "watch": "server.js" } لنراجع الآن الشيفرة البرمجية الخاصة بملف server.js const baseDir = process.env.NODE_ENV === 'production' ? 'build' : 'dist'; const port = process.env.NODE_ENV === 'production' ? 8080: 3000; const app = express(); يؤدي هذا الإعداد إلى تعيين الدليل الأساسي للخادم والمنفذ (port) بناءً على بيئة Node.js. وينشئ نسخة من express. app.use(require('connect-livereload')({port: 35729})); app.use(express.static(path.join(__dirname, baseDir))); ستضيف هذه التعليمات برمجية وسيطة (Middleware) وهي connect-livereload وذلك لأنها الضرورية لإعداد ميزة إعادة التحميل المباشر، كما ستضيف هذه التعليمات أيضًا برمجية وسيطة ساكنة (Static Middleware) والّتي ستتعامل مع الملحقات الثابتة. app.get('/api/sample-route', (req, res) => { res.send({ website: 'Toptal', blogPost: true }); }); إن الشيفرة البرمجية السابقة هي مجرد مسار بسيط لواجهة برمجة التطبيقات. فإذا انتقلت إلى المسار localhost:3000/api/sample-route في المتصفح سترى ما يلي: { website: "Toptal", blogPost: true } في الواجهات الخلفية للتطبيق سيكون لدينا مجلد كامل مخصص لمسارات الواجهة البرمجية للتطبيقات، وملفات منفصلة لإنشاء اتصالات مع قاعدة البيانات. هكذا أُضيف المسار وبهذه البساطة لإثبات أننا يمكننا بناء الواجهات الخلفية بعد بناء الواجهات الأمامية. app.get('*', (req, res) => { res.sendFile(path.join(__dirname, './', baseDir ,'/index.html')); }); هذا المسار الشامل يعني أنه مهما يكن الرابط التشعبي الّذي تودّ الذهاب إليه من خلال المتصفح فسيعرض لك الخادم صفحة index.html الوحيدة، وبعدها تأتي مهمة موجّه المسارات في React لعرض الصفحة المناسبة الّتي طلبتها من جانب العميل بدلًا من جانب الخادم. app.listen(port, () => { open(`http://localhost:${port}`); }); الشيفرة البرمجية السابقة تُخبر نسخة express للتنصت على المنفذ الّذي خصصناه وفتح تبويب جديد في المتصفح لعنوان الرابط الّذي طلبتَ الوصول إليه. الشيء الوحيد الّذي لا يعجبني في طريقة إعداد الخادم هو: app.use(require('connect-livereload')({port: 35729})); نظرًا لأننا نستخدم بالفعل gulp-livereload في ملفنا gulpfile، مما يدل على وجود مكانين منفصلين وجب علينا استخدام إعادة التحميل المباشر. إعداد المهام الافتراضية gulp.task('default', (cb) => { runSequence('clean', 'lint', 'test', 'html', 'css', 'js', 'fonts', 'server', 'watch', cb); }); تنفذ المهمة السابقة من خلال كتابة الأمر gulp في الطرفية. الغريب في الأمر أنك تحتاج لاستخدام runSequence من أجل تنفيذ التعليمات تسلسليًا. عادة ما تنفذ المهام بالتوازي ولكن هذه الطريقة ليست مطلوبة دومًا. فعلى سبيل المثال نحتاج لتشغيل مهمة clean قبل مهمة html للتأكد من أن مجلدات الهدف فارغة قبل نقل الملفات إليها. عندما صدرت النسخة الرابعة من gulp 4 كان إحدى مميزاتها أنها دعمت تعليمتي gulp.series لتنفيذ التعليمات بالتسلسل و gulp.parallel لتنفيذ التعليمات بالتوازي. وفضلًا عن ذلك، إن عملية إنشاء واستضافة التطبيق من خلال أمرٍ واحد هو فعلًا أمرٌ رائع، وإمكانية فهم أي جزء من أجزاء سير العمل بسهولة مثل سهولة اختبار مهمة ما في تسلسل التنفيذ. بالإضافة إلى ذلك يمكننا تفكيك المهام الكبير إلى أجزاء أصغر لنهج أكثر دقة في إنشاء واستضافة التطبيق. على سبيل المثال يمكننا إعداد مهمتين منفصلتين لإجراء عمليات إختبار المهام الأخرى وكشف الأخطاء المحتملة (linting). أو يمكن أن يكون لدينا مهمة المضيف والّتي ستُشغل الخادم والمراقب. هذه القدرة على تنظيم المهام قوية جدًا، وخاصةً عندما يتغيّر تطبيقك ويتطلب المزيد من المهام المؤتمتة. بناء التطبيق وتخصيصه لوضع التطوير مقابل وضع النشر if (argv.prod) { process.env.NODE_ENV = 'production'; } let PROD = process.env.NODE_ENV === 'production'; من خلال استخدام مكتبة yargs الموجودة في مستودعات مدير الحزم NPM. يمكننا تزويد الأداة Gulp بالرايات (Flags). هنا مثلًا سنوجّه ملف gulpfile لتعيين بيئة Node.js في وضع النشر إذا مرّرنا الراية --prod مع التعليمة في الطرفية. سيستخدم هذا المتغيّر PROD كشرط للتمييز بين وضع التطوير ووضع النشر في ملف gulpfile. على سبيل المثال أحد الخيارات الّتي نمررها لإعدادات الأداة Browserify هي: plugin: PROD ? [] : [hmr, watchify] إن التعليمة الشرطية في الشيفرة السابقة مفيدة جدًا لأنها توفر علينا كتابة ملف gulpfile منفصل لكل من وضع التطوير ووضع النشر، والّذي سيحتوي على الكثير من التعليمات المكررة. بدلًا من ذلك يمكننا تمرير gulp --prod لتشغيل وضعية النشر في تنفيذ المهام. أو تمرير gulp html --prod لتشغيل المهمة html فقط في وضع النشر. من جهة أخرى رأينا سابقًا كيف أن تمرير التعليمات الشرطية عبر الأنبوب مثل: .pipe(cond(!PROD, livereload())) ليست من السهل قراءتها. ولكن في نهاية المطاف أنها مسألة تفضيلات شخصية إذا ما كنت تريد استخدام المتغيير المنطقي (PROD) أو إنشاء ملفين منفصلين (gulpfile) للإعداد. لننتقل الآن إلى طريقة الإعداد الثانية، وهي عندما نبدل الأداة Browserify لتحل محلها الأداة Webpack. 2. طريقة Gulp + Webpack نلاحظ أن ملف الإعداد gulpfile انخفض حجمه انخفاضًا كبيرًا إذ يحتوي الآن على 12 سطرًا من تعليمات استيراد المكتبات و 99 سطرًا من الشيفرات البرمجية الأخرى. إذا فحصنا من المهمة الافتراضية: gulp.task('default', (cb) => { runSequence('lint', 'test', 'build', 'server', 'watch', cb); }); يتطلب إعداد تطبيق الوب خاصتنا الآن خمسة مهام فقط، بدلًا من تسعة. وهو في الحقيقة تحسنٌ كبير. بالإضافة إلى ذلك ألغينا الحاجة لاستخدام ميزة إعادة التحميل المباشر livereload، كما أن مهمة المراقب أصبحت كالتالي: gulp.task('watch', () => { gulp.watch(config.paths.js, () => { runSequence('lint', 'test'); }); }); هذا يعني أن المراقب لن يشغل سلوك إعادة بناء التطبيق من جديد. وكميزة إضافية لن نحتاج إلى نقل الملف index.html من app إلى dist أو build بعد الآن. وبالعودة إلى فكرة تقليل المهام، نلاحظ إن المهام المخصصة لكلٍ من ملفات html و css و js و fonts استبدلت بمهمة واحدة: gulp.task('build', () => { runSequence('clean', 'html'); return gulp.src(config.paths.entry) .pipe(webpack(require('./webpack.config'))) .pipe(gulp.dest(config.paths.baseDir)); }); بكلّ بساطة شغّل مهمتي clean و html بالتسلسل، وبمجرد ما ينتهي تنفيذهُم إجلب نقطة الدخول الخاصة بتطبيق الوب خاصتنا ومرّره إلى الأنبوب من خلال الأداة Webpack ثم مررها إلى ملف الإعداد الخاص بالأداة Webpack وهو webpack.config.js وذلك لتهيئه وأرسال الحزمة المُجمّعة الناتجة إلى baseDir (إما dist أو build اعتمادًا على الملف الإعداد ل Node.js). يمكنك إلقاء نظرة على ملف الإعداد webpack.config.js الخاص بالأداة Webpack، لكننا لن نشرحه كله، وإنّما سنشرح الخصائص المهمة المسندة للكائن module.exports. devtool: PROD ? 'source-map' : 'eval-source-map', تعيّن هذه التعليمة نوع خرائط الشيفرة البرمجية المحوّلة (source maps) والّتي سيستخدمها Webpack. تدعم Webpack مجموعة من الخياراتٍ المتنوعة من خرائط الشيفرة البرمجية المحوّلة. ويوفر كلّ وخيارٍ منهم توازنًا مختلفًا في الأداء. فمنهم خرائط ذات تفاصيل كثيرة مقابل خرائط ذات تفاصيل قليلة ولكن إعادة بنائها سريع (وهو الوقت المستغرق لإعادة تجميع الملف بعد إجراء التغييرات). وبناءً عليه يمكننا استخدام خرائط ذات تفاصيل قليلة من أجل زيادة سرعة إعادة التحميل في وضع التطوير واستخدام خرائط ذات تفاصيل كثيرة في وضع النشر. entry: PROD ? './app/index' : [ 'webpack-hot-middleware/client?reload=true', // reloads the page if hot module reloading fails. './app/index' ] هذه هي نقطة الدخول إلى مُجمّع الحزم. لاح المصفوفة المُمررة لنقطة الدخول، وهذا يدلّ على إمكانية استخدام نقاط دخول متعددة. في حالتنا نقطة الدخول هي الملف app/index.js، وكذلك نقطة الدخول الخاصة لإعداد إعادة التحميل النشط للوحدات (hot module reloading) output: { path: PROD ? __dirname + '/build' : __dirname + '/dist', publicPath: '/', filename: 'bundle.js' }, وهنا نحدد مجلد الخرج والّذي سيوضع فيه الملفات المترجمة والمجمّعة. إن أكثر خيارٍ مربكٍ هنا هو publicPath والّذي يعيّن عنوان الرابط التشعبي الّذي سيُجمّع فيه ويوضع على الخادم. لذلك على سبيل المثال إذا كان publicPath هو /public/assets عندها ستكون الحزمة المجمّعة في هذا المسار /public/assets/bundle.js على الخادم. devServer: { contentBase: PROD ? './build' : './app' } هذه التعليمة ستُعلِم الخادم بالمجلد الجذر الّذي ستستخدمه في مشروعك. إذا شعرت بالإرتباك في طريقة تعيين مجلد الخرج لوضع الملف المجمّع فما عليك سوى تذكر هذه الدلالات لكلّ مما يلي: path + filename: الموقع المحدد للحزمة المُجمّعة في الشيفرة البرمجية للمشروع. contentBase (مثل: ملف الجذر /) + publicPath: موقع الحزمة على الخادم. plugins: PROD ? [ new webpack.optimize.OccurenceOrderPlugin(), new webpack.DefinePlugin(GLOBALS), new ExtractTextPlugin('bundle.css'), new webpack.optimize.DedupePlugin(), new webpack.optimize.UglifyJsPlugin({compress: {warnings: false}}) ] : [ new webpack.HotModuleReplacementPlugin(), new webpack.NoErrorsPlugin() ], هذه بعض الملحقات الوظيفية الّتي ستعزز وظائف Webpack بطريقة ما. فعلى سبيل المثال إن الملحق webpack.optimize.UglifyJsPlugin هو المسؤول عن تصغير ملفات جافاسكربت. loaders: [ {test: /\.js$/, include: path.join(__dirname, 'app'), loaders: ['babel']}, { test: /\.css$/, loader: PROD ? ExtractTextPlugin.extract('style', 'css?sourceMap'): 'style!css?sourceMap' }, { test: /\.scss$/, loader: PROD ? ExtractTextPlugin.extract('style', 'css?sourceMap!resolve-url!sass?sourceMap') : 'style!css?sourceMap!resolve-url!sass?sourceMap' }, {test: /\.(svg|png|jpe?g|gif)(\?\S*)?$/, loader: 'url?limit=100000&name=img/[name].[ext]'}, {test: /\.(eot|woff|woff2|ttf)(\?\S*)?$/, loader: 'url?limit=100000&name=fonts/[name].[ext]'} ] هذه هي المُحمّلات والّتي تعالج بشكل أساسي الملفات الّتي تُضمّن بواسطة require()، وهي تشبه إلى حدٍ ما الأنابيب في Gulp والّتي تمكنك من ربط المُحمّلات مع بعضهم بعضًا. لنستكشف أحد المُحمّلات: {test: /\.scss$/, loader: 'style!css?sourceMap!resolve-url!sass?sourceMap'} إن الخاصية test تُخبر الأداة Webpack أن الملفات الّتي يجب على المُحمّل التعامل معها يجب أن تحقق التعبير النمطي المُمرر لهذه الخاصية. في حالتنا تكون /\.scss$/. أما الخاصية loader فهي تُحدد المُحمّل الّذي يجب أن نستخدمه. هنا نحدد المُحملات style و css و resolve-url و sass والّتي ستُنفذ بترتيب عكسي. يجب علي أن أعترف بأنني لست ماهرًا في بناء جملة تحديد المُحملات loader3!loader2!loader1. ولكن متى يجب علينا قراءة شيفرة برمجية من اليمين إلى اليسار؟ عدا هذا السطر. على الرغم من ذلك تعدّ المُحمّلات ميزة قوية جدًا لمُجمّع الحزم Webpack. في الحقيقة يسمح لنا المُحمّل الّذي استخدمته للتو باستيراد ملفات SASS بداخل الشيفرة البرمجية للجافاسكربت. فعلى سبيل المثال يمكننا استيراد ملفات التنسيق المخصّصة العامة الّتي عملنا عليها في المشروع في ملف الّذي حددنا كنقطة دخول وهو index.js وسيكون شكله كما يلي: import React from 'react'; import {render} from 'react-dom'; import {Router, browserHistory} from 'react-router'; import routes from './routes'; // CSS imports import '../node_modules/bootstrap/dist/css/bootstrap.css'; import '../node_modules/font-awesome/css/font-awesome.css'; import './styles/global.scss'; render(<Router history={browserHistory} routes={routes} />, document.getElementById('app')); وبطريقة مشابهة في مكوّن الترويسة يمكننا استيراد الملف import './Header.scss' المرتبط بهذا المكون، وهذا وبكل تأكيد ينطبق على كافة المكونات الأخرى في التطبيق. من وجهة نظري الشخصية أرى بأن هذا الأمر يعدُّ تغييرًا ثوريًا في عالم التطوير بلغة جافاسكربت. إذ لا داعي للقلق بشأن تجميع ملفات التنسيق وتصغيرها وحتى خرائط الشيفرة البرمجية المحوّلة (source maps) أيضًا، وذلك لأن مُحمّلنا يتعامل مع كلّ هذه الأمور عوضًا عنا. حتى إعادة التحميل النشط للوحدات (hot module reloading) ستعمل على ملفات التنسيق خاصتنا، ومن ثمّ فإن القدرة على التعامل مع تعليمات الاستيراد من داخل ملف الجافاسكربت سيجعل من عملية التطوير أبسط من الناحية المفاهيمية، وأكثر تناسقًا، وتقلل من تبديل السياق في التطوير (وذلك عند استخدام أدوات مساعدة مختلفة)، والمنطق البرمجي أسهل في التفكير. لإعطاء ملخص موجز حول كيفية عمل هذه الميّزة مع مُجمّع الحزم Webpack وكيف سيضع ملفات التنسيق في ملف جافاسكربت المُجمّع. في الحقيقة لا يقتصر الأمر على ملفات التنسيق وحسب بل يمكنه أيضًا فعل ذلك من أجل الصور والخطوط. {test: /\.(svg|png|jpe?g|gif)(\?\S*)?$/, loader: 'url?limit=100000&name=img/[name].[ext]'}, {test: /\.(eot|woff|woff2|ttf)(\?\S*)?$/, loader: 'url?limit=100000&name=fonts/[name].[ext]'} إن الشيفرة البرمجية السابقة توجّه المُحمّل إلى تضمين الصور والخطوط بداخل الملف المُجمّع إن كان حجمهم لا يتعدى 100 كيلوبايت، وإلا فسيعرضهم كملفات منفصلة، كما يمكننا في أي وقت تغيير حجم المسموح به، ليصبح مثلًا 10 كيلوبايت. هذا هو باختصار طريقة إعداد مُجمّع الحزم Webpack وفي الحقيقة إنه يتطلب قدرًا لا بأس به من الإعداد. ولكن فوائد استخدامه رائعة للغاية. على الرغم من أن مُجمّع الحزم Browserify لديه أيضًا ملحقاته الجميلة وتحويلاته ولكنه لا يستطيع أن ينافس محملات Webpack من ناحية الخصائص الوظيفية المُضافة. 3. طريقة Webpack + NPM Scripts في هذه الطريقة من الإعداد سنستخدم npm scripts مباشرة بدلًا من الاعتماد على Gulp لأتمتة مهامنا. سيكون ملف package.json على الشكل التالي: "scripts": { "start": "npm-run-all --parallel lint:watch test:watch build", "start:prod": "npm-run-all --parallel lint test build:prod", "clean-dist": "rimraf ./dist && mkdir dist", "clean-build": "rimraf ./build && mkdir build", "clean": "npm-run-all clean-dist clean-build", "test": "mocha ./app/**/*.test.js --compilers js:babel-core/register", "test:watch": "npm run test -- --watch", "lint": "esw ./app/**/*.js", "lint:watch": "npm run lint -- --watch", "server": "nodemon server.js", "server:prod": "cross-env NODE_ENV=production nodemon server.js", "build-html": "node tools/buildHtml.js", "build-html:prod": "cross-env NODE_ENV=production node tools/buildHtml.js", "prebuild": "npm-run-all clean-dist build-html", "build": "webpack", "postbuild": "npm run server", "prebuild:prod": "npm-run-all clean-build build-html:prod", "build:prod": "cross-env NODE_ENV=production webpack", "postbuild:prod": "npm run server:prod" } لتشغيل طريقة البناء من أجل وضع التطوير أو وضع النشر يمكنك كتابة الأوامر npm start و npm run start:prod على التتالي. نلاحظ أن هذا الملف أفضل وبكل تأكيد من ملف الإعداد gulpfile الّذي بنيناه سابقًا في هذا المقال. إذ اختصرنا الكثير من الشيفرات البرمجية لتكون عدد التعليمات بدلًا من 150 أو 99 تعليمة أصبحت 19 تعليمة (12 تعليمة إذا استثنينا التعليمات الخاصة بوضع النشر لأن معظمها مشابهة للتعليمات في وضع التطوير الّتي في بيئة Node.js لتصبح في وضع النشر). إن العيب الوحيد هو أن التعليمات مبهمة إلى حدٍ ما بالموازنة مع التعليمات الخاصة للأداة Gulp وليست معبرة مثلها. فعلى سبيل المثال لا توجد (على حسب اطلاعي على الأقل) بوجود تعليمة واحدة تشغّل npm script لينفذ تعليمات معينة تنفيذًا متسلسلًا وتعليمات أخرى ليُنفذها تنفيذًا متوازيًا. وإنما تعليمة واحدة لكل منهما. ومع ذلك هنالك الكثير من الميّزات الكبيرة لهذا المنهج. بالإضافة إلى أنه يوجد حزمة مقابلة لكل مكتبة من المكتبات الّتي كنا نستخدمها في السابق مع أداة Gulp. فبدلًا من استخدام المكتبات الموجودة في Gulp مثل: gulp-eslint gulp-mocha gulp-nodemon etc يمكننا استخدام الحزم التالية في Webpack: eslint mocha nodemon etc نقلًا عن كوري هاوس في مقالته "لماذا تركت أداة Gulp و Grunt وتوجهت إلى NPM Scripts": يحدد كوري هاوس ثلاث مشاكل رئيسية وهي: مشكلة الاعتماد على منشئي الملحقات. مشكلة تنقيح الأخطاء المرهقة. مشكلة التوثيقات الرسمية المفككة والضعيفة. ومن وجهة نظري أتفق مع جميع هذه المشاكل. 1. مشكلة الاعتماد على منشئي الملحقات مثلًا عندما تُحدّث مكتبة مثل: eslint، ستحتاج المكتبة المرتبطة بها gulp-eslint إلى تحديث مناظر لكي يتوافقوا مع بعضهم بعضًا. فإذا فقد المشرف الاهتمام بالعمل على تعديل وصيانة المكتبة، فإن الإصدار المخصص لأداة gulp من هذه المكتبة لن يتزامن مع النسخة الأصلية للمكتبة. وينطبق نفس الشيء عند إنشاء مكتبة جديدة. فعلى سبيل المثال إذا قام شخص ما بإنشاء مكتبة "xyz" وأطلقها، واحتجت فجأة إلى نسخة مخصصة من المكتبة للعمل مع أداة Gulp مثل: gulp-xyz. بمعنى آخر، هذا النهج غير قابل للتوسّع. ومن الناحية المثالية، نريد نهجًا مثل Gulp ولكن بإمكانه استخدام المكتبات الأصلية مباشرة. 2. مشكلة تنقيح الأخطاء المرهقة على الرغم من أن المكتبات مثل gulp-plumber تساعد في تخفيف هذه المشكلة تخفيفًا كبيرًا، إلا أنه من المعروف أن الإبلاغ عن الأخطاء في gulp ليس مفيدًا جدًا. فإذا كان هناك تعليمة واحدة خاطئة في الأنبوب فإنها سترمي استثناءً غير قابل للمُعالجة، وعندما تلاحق المشكلة في المكدس ستظهر مشكلة تبدو غير مرتبطة تمامًا بالسبب الحقيقي للمشكلة الّتي ظهرت في الشيفرة البرمجية. لهذا السبب يمكن أن يجعل من علمية تنقيح الأخطاء كابوسًا في بعض الحالات. ومهما بحثت عن حلول لهذه المشاكل سواءً على محركات البحث مثل: Google أو موقع Stack Overflow فلن يساعدك هذا البحث فعليًا إذا كان الخطأ مبهمً أو مضللًا. 3. مشكلة الوثائق الرسمية المفككة والضعيفة في كثير من الأحيان ألاحظ أن أحد مكتبات gulp الصغيرة يكون توثيقها الرسمي محدودًا للغاية. أظن بأن هذا الأمر يرجعُ من أن المُنشئ عادة لهذه المكتبة يكون هدفه الأساسي استخداماته الخاصة. بالإضافة إلى ذلك من الشائع أن تنّظر إلى التوثيق الرسمي لكلّ من الملحقات الإضافية للأداة Gulp والمكتبة الأصلية (الّتي تعتمد عليها تلك الملحقات)، مما يعني الكثير من تبديل السياق وزيادة كمية القراءة إلى الضعف بسبب ذلك. الخاتمة يبدو لي أنه من الواضح جدًا أن Webpack أفضل من Browserify وأن NPM scripts أفضل من Gulp، على الرغم من أن كلّ خيارٍ منهم له فوائده وعيوبه. ومن المؤكد أيضًا أن تعابير وتعليمات Gulp أكثر مقروئية وملاءمة للاستخدام من NPM scripts، ولكنك ستدفع الثمن غاليًا في جميع عمليات التعقيد المضافة. قد لا تكون كلّ طريقة من طرق الإعداد السابقة مثالية لتطبيقك، ولكن إذا رغبت في تجنب عدد هائل من تبعيات التطوير وتجربة تنقيح الأخطاء المحبطة، فإن Webpack مع NPM scripts هي الطريقة المناسبة لك. آمل أن تجد هذه المقالة مفيدة في اختيار الأدوات المناسبة لمشروعك القادم. ترجمة -وبتصرف- للمقال Webpack or Browserify & Gulp: Which Is Better? لصاحبه Eric Grosse
-
- 1
-
- أدوات بناء
- webpack
-
(و 2 أكثر)
موسوم في:
-
شهد مجال تطوير الوِب في السنوات الأخيرة تطورات هائلة على جميع المستويات والذي أدى إلى تحسنيات كبيرة على مستوى السرعة والجودة وقابلية الصيانة. ليس ذلك فحسب بل تعدى الأمر إلى إمكانية أتمتة بعض المهام الروتينية والتركيز على جودة العمل المُسلم ورفع الإنتاجية وذلك باستخدام أدوات مساعدة مثل Webpack، والتي تعدّ من أشهر الأدوات المساعدة بل إنها أصبحت عنصرًا أساسيًا في أي مشروع يُبنى بلغة جافاسكربت. حدثت بعض هذه التطورات على صعيد تطوير الواجهات الخلفية (Back-end) مثل ظهور Node.js والّتي تستخدم استخدامًا كبيرًا في هذه الأيام، وبعضها الآخر كان في تطوير الواجهات الأمامية (front-end)، ومن أبرز هذه التطورات هي النقلة النوعية للغة جافاسكربت ودعمها للعديد من المميزات والخصائص، والتي أدت في نهاية المطاف لجعلها واحدةً من أبرز الخيارات القوية في برمجة الوِب. بل وحتى إنها احتلت المرتبة الأولى في ترتيب أشهر لغات البرمجة شعبية لعام 2019 وذلك بحسب إحصائية موقع Stackoverflow. وكما أعلنت شركة Web3Techs أن لغة جافاسكربت مُستخدمة من قِبل حوالي 95% من مواقع العالم قاطبةً، وهي أيضًا على رأس قائمة "أكثر لغة مشهورة في برمجة الواجهات الأمامية" بحسب نفس الشركة. ولكن كيف جرت هذه التحولات السريعة والقوية بنفس الوقت لهذه اللغة؟ وما هي المراحل الأساسية الّتي مرت بها؟ وما هي الأدوات المختلفة الّتي ظهرت بالتزامن مع تطور هذه اللغة؟ وما هي أدوات البناء (مثل Gulp)؟ وما هو مجمع الوحدات (مثل Webpack)؟ ولماذا ظهر؟ وما هي مميزاته؟ سنحاول في هذا الدليل مرافقتكم في رحلة استكشافية للإجابة على هذه الأسئلة، ولنستعرض فيها أيضًا التاريخ العريق لهذه اللغة، ومعرفة الاسباب الرئيسية لتطورها وكيف احتلت بهذه السرعة المكانة العالية الّتي هي عليها اليوم. فهل أنت مستعد لمشاركتنا في هذه الرحلة؟ لمحة تاريخية بالعودة إلى زمن ما قبل الأدوات الحديثة كيف كان الحال آنذاك؟ في الحقيقة كانت الأمور بسيطة وسهلةً جدًا. سنبدأ رحلتنا بالتعرف على طرق استخدام لغة جافاسكربت في ملفات HTML. طرق استخدام لغة جافاسكربت يوجد طرقٌ عديدة يمكننا لتضمين الشيفرات البرمجية للغة جافاسكربت في ملفات HTML، يعتمد اختيارنا للطريقة بحسب حجم المشروع الّذي سنعمل عليه وسنذكر أهم الطرق المستخدمة من البداية وحتى يومنا الحالي. الشيفرة السطرية بدأت لغة جافاسكربت في كتابة شيفرتها البرمجية بهذه الطريقة البسيطة إذ أن الشيفرة البرمجية مكتوبة مباشرة في ملف HTML داخل وسم <script>. لا بدّ بأن معظمنا كتبَ شيفرة برمجية بهذه الطريقة في بداية تعلمه لهذه اللغة وذلك من كونها أسهل الطرق لتطبيق شيفرة برمجية معينة. في المثال التالي يحتوي الملف index.html على شيفرة جافاسكربت سطرية: <html> <head> <meta charset="UTF-8"> <title>Inline Example</title> </head> <body> <h1> The Answer is <span id="answer"></span> </h1> <script type="text/javascript"> function add(a, b) { return a + b; } function reduce(arr, iteratee) { var index = 0, length = arr.length, memo = arr[index]; for(index += 1; index < length; index += 1){ memo = iteratee(memo, arr[index]) } return memo; } function sum(arr){ return reduce(arr, add); } /* Main Function */ var values = [ 1, 2, 4, 5, 6, 7, 8, 9 ]; var answer = sum(values) document.getElementById("answer").innerHTML = answer; </script> </body> </html> هذه الطريقة جيدة جدًا للبدء إذ لا يوجد أي ملفات خارجية أو تبعية ناشئة بين الملفات، ولكن هذه هي الطريقة المثالية لبناء شيفرة برمجية غير قابلة للصيانة وذلك بسبب المشاكل التالية: عدم إمكانية إعادة استخدام الشيفرة البرمجية: فإذا احتجنا إلى إضافة صفحة أخرى وهذه الصفحة تحتاج إلى بعض الوظائف المحققة في الشيفرة البرمجية الّتي كتبناها سابقًا في الملف السابق فيجب علينا حينها نسخ الشيفرة البرمجية المطلوبة ولصقها في الصفحة المراد تحقيق الوظائف فيها. عدم وجود ثبات في التبعية بين الشيفرة البرمجية: أنت مسؤول عن تنسيق التبعية بين الدوال المستخدمة على سبيل المثال إن كان هنالك دالة معينة تعتمد في وظيفتها على نتيجة دالة أخرى محققة قبلها فيجب علينا حينها الانتباه لهذا الأمر وعدم تغيير ترتيب تواجد الدوال في الشيفرة البرمجية. التضارب في المتغيرات العامة (Global Variables): جميع الدوال والمتغيرات ستكون موجودة على نطاق عام (Global Scope) وهذا بدوره سيؤدي إلى تضارب في المتحولات، ويحدث هذا عند ازدياد ضخامة المشروع وكتابة شيفرة برمجية كبيرة مما يجعل إمكانية إعادة استخدام نفس أسماء المتحولات أمر وارد الحدوث بقوة، وخصيصًا إن كان هنالك أكثر من مبرمج يعمل على نفس المشروع. استخدام الملفات الخارجية الطريقة الأخرى المستخدمة في كتابة الشيفرة البرمجية للغة جافاسكربت هي استخدام ملفات منفصلة إذ سنجزّء الشيفرة البرمجية إلى أجزاء صغيرة ونحملها تباعًا في ملف index.html. وبهذه الطريقة يمكننا استدعاء مكتبات منفصلة والّتي تقدم لنا وظائف إضافية مثل مكتبة moment (والتي تتيح لنا التلاعب بالتواريخ وإمكانية إجراء تحويلات معينة من نمط تاريخٍ معين إلى نمطٍ آخر). وبالإضافة إلى ذلك أدى استخدام الملفات المنفصلة في الشيفرة البرمجية إلى إتاحة الفرصة لنا بإعادة استخدام الشيفرة البرمجية أكثر من مرة ومن أي مكان نريد، وبذلك نكون تخلصنا من فكرة نسخ الشيفرة البرمجية من مكان ما ولصقها في مكان آخر متى ما أردنا استخدام هذه الشيفرة. الملف index.html هو الملف الّذي سيستدعي جميع التبعيات (ملفات جافاسكربت) الّتي سيحتاجها ويكون محتواه على الشكل التالي: <html> <head> <meta charset="UTF-8"> <title>External File Example</title> <!-- الملف الرئيسي الّذي سيستدعي ملفات جافاسكربت --> </head> <body> <h1> The Answer is <span id="answer"></span> </h1> <script type="text/javascript" src="./add.js"></script> <script type="text/javascript" src="./reduce.js"></script> <script type="text/javascript" src="./sum.js"></script> <script type="text/javascript" src="./main.js"></script> </body> </html> إن هذا المثال يجمع عناصر المصفوفة الموجودة في الملف main.js ويظهرها في صفحة index.html ويكون محتوى الملفات على الشكل التالي: الملف add.js: function add(a, b) { return a + b; } الملف reduce.js function reduce(arr, iteratee) { var index = 0, length = arr.length, memo = arr[index]; index += 1; for(; index < length; index += 1){ memo = iteratee(memo, arr[index]) } return memo; } الملف sum.js function sum(arr){ return reduce(arr, add); } الملف main.js var values = [ 1, 2, 4, 5, 6, 7, 8, 9 ]; var answer = sum(values) document.getElementById("answer").innerHTML = answer; ولكن هذا الحل لم يكُ مثاليًا بل واجه المشاكل التالية: عدم وجود ثبات في التبعية بين الشيفرة البرمجية. التضارب في المتغيرات العامة مازالت موجودة. تحديث المكتبات أو الملفات المستدعاة سيكون يدويًا في حال ظهور نسخة جديدة للمكتبة (أو الملف). زيادة عدد طلبات HTTP بين المخدم والعميل: يؤدي زيادة الملفات المتضمنة في إلى زيادة الحمل على المخدم. فعلى سبيل المثال في الشيفرة السابقة سيحتاج كلّ ملف من الملفات الأربع طلب HTTP منفصل (أي أربع رحلات ذهاب وعودة من المخدم وإلى جهاز العميل) وبناءً عليه سيكون من الأفضل لو استخدمنا ملفًا واحدًا بدلًا من أربع ملفات منفصلة. كما في المثال التالي: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>External File Example</title> <!-- الملف الرئيسي الّذي سيستدعي ملفات جافاسكربت --> </head> <body> <h1> The Answer is <span id="answer"></span> </h1> <script src="/dist/bundle.js"></script> </body> </html> استخدام كائن الوحدات (Module Object) ونمط الوحدات (Module Pattern) باستخدام نمط الوحدات (والذي تحدثنا عنه في مقال مفصّل) يمكننا أن نقلل التضارب في المتغيّرات العامة. إذ إننا سنكشف عن كائن واحد للمجال العام (Global Scope) وهذا الكائن سيحتوي على كافة الدوال والقيم الّتي سنحتاج إليها في تطبيق الوِب خاصتنا. في المثال التالي سنكشف عن كائن واحد وهو myApp للمجال العام (Global Scope)، وكلّ الدوال ستكون متاحة عن طريق هذا الكائن. والمثال التالي سيُوضح الفكرة: الملف my-app.js: var myApp = {}; الملف add.js: (function(){ myApp.add = function(a, b) { return a + b; } })(); الملف reduce.js: (function () { myApp.reduce = function (arr, iteratee) { var index = 0, length = arr.length, memo = arr[index]; index += 1; for (; index < length; index += 1) { memo = iteratee(memo, arr[index]) } return memo; } })(); الملف sum.js: (function () { myApp.sum = function (arr) { return myApp.reduce(arr, myUtil.add); } })(); الملف main.js: (function (app) { var values = [1, 2, 4, 5, 6, 7, 8, 9]; var answer = app.sum(values) document.getElementById("answer").innerHTML = answer; })(myApp); الملف index.html: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>JS Modules</title> </head> <body> <h1> The Answer is <span id="answer"></span> </h1> <script type="text/javascript" src="./my-app.js"></script> <script type="text/javascript" src="./add.js"></script> <script type="text/javascript" src="./reduce.js"></script> <script type="text/javascript" src="./sum.js"></script> <script type="text/javascript" src="./main.js"></script> </body> </html> لاحظ أن كلّ ملف من الملفات غُلّف بطريقة نمط الوحدات ما عدا my-app.js إذ أن طريقة التغليف العامة المتبعة في نمط الوحدات هي على الشكل التالي: (function(){ /*... your code goes here ...*/ })(); بهذه الطريقة نجد أن كلّ المتحولات المحلية ستبقى ضمن مجال الدالة المستخدمة فيه. وبذلك لن يكون هنالك أي احتمالية لحدوث تضارب في المتغيرات العامة. وهكذا نكون حللنا مشكلة التضارب في المتغيرات العامة. سيكون الوصول إلى الدوال المعرفة في الملفات مثل (add و reduce ..إلخ) عن طريق ربط اسم الدالة مع الكائن myApp كما في المثال التالي: myApp.add(1,2); myApp.sum([1,2,3,4]); myApp.reduce(add, value); كما يمكننا أيضًا تمرير الكائن myApp كوسيط مثل ما فعلنا في ملف main.js وذلك من أجل تصغير أسم الكائن وجعل الشيفرة البرمجية أصغر حجمًا. تعدّ هذه الطريقة تحسنًا ممتازًا بالموازنة مع المثال السابق وفي الحقيقة إن أشهر مكتبات جافاسكربت مثل jQuery تستخدم هذه الطريقة؛ إذ أنها فقط تكشف عن متغير عام وهو $ وكل التوابع تُستدعى عن طريق هذا الكائن. في الحقيقة إننا إلى هذه اللحظة لم نصل إلى الحل النهائي إذ أن الحل السابق لا يزال يعاني من بعض المشاكل مثل: عدم وجود ثبات في التبعية بين الشيفرة البرمجية: نلاحظ في ملف index.html الملفات لا تزال بحاجة إلى وضعها وفق ترتيب معين يحافظ على التبعية بين الملفات إذ أن الملف myApp.js يجب أن يأتي قبل أي ملف وملف main.js يجب أن يأتي بعد كلّ الملفات. التضارب في المتغيّرات العامة: صحيح أننا قللنا عدد المتغيّرات العامة إلى الواحد ولكنها لا تزال موجودة. تحديث المكتبات: إن تحديث المكتبات (أو التبعيات أو أي ملف عمومًا) المستدعاة سيكون يدويًا في حال ظهور نسخة جديدة للمكتبة أو للتبعية. زيادة عدد طلبات HTTP بين الخادم والعميل: وذلك بعد زيادة عدد الملفات المستدعاة في ملف index.html. ظهور CommonJS جرت نقاشات عديدة في عام 2009 حول إمكانية جلب لغة جافاسكربت إلى الواجهات الخلفية (استخدامها من جانب الخادم) وفعلًا كان ذلك على يد مهندسٍ شاب يعمل لدى شركة موزيلا ويُدعى كيفن دانجور (Kevin Dangoor). قدمت CommonJS طريقة لتعريف الوحدات لحل مشكلة المجال في جافاسكربت من خلال التأكد من تنفيذ كلّ وحدة في فضاء الأسماء (namespace) الخاص بها وذلك بإجبار الوحدات على تصدير تلك المتغيّرات صراحةً (الذين نريد عرضهم واستخدامهم في المجال العام) وأيضًا تعريف تلك الوحدات المطلوبة للعمل بالشكل الصحيح مع بعضها بعضًا. بالإضافة إلى ذلك قدمت CommonJS واجهة برمجية مخصصة للجافاسكربت هذه المميزات فتحت أبصار العالم لإمكانيات هذه اللغة العظيمة، وجذبت اهتمام كبير من المطورين والذي انعكس في سرعة تطورها وتأقلمها مع جميع التغيّرات والمشاكل الّتي واجهتها. إن CommonJS ليست مكتبة جافاسكربت وإنما هي معايير تنظيمية لكتابة الشيفرة البرمجية (على سبيل المثال الطريقة الّتي خصصتها لاستيراد الوحدات استيرادً منظمًا) وهذه المعايير شبيه بالّتي تطرحها منظمة ECMA (European Computer Manufacturers Association). ولتحقيق التنظيم في طريقة استدعاء الوحدات تقدم لنا CommonJS الدوال والأغراض المساعدة لذلك وهي: دالة require(): والّتي تسمح لنا باستيراد وحدات معينة في المجال المحلي الّذي نكون فيه. كائن الوحدة module.exports: والذي يسمح لنا بتصدير دالة ما من المجال الحالي إلى الوحدة. ولنأخذ مثلًا بسيطًا عن كيفية استخدام CommonJS: الملف moduleA.js: module.exports = function( value ){ return value*2; } الملف moduleB.js: var multiplyBy2 = require('./moduleA'); var result = multiplyBy2( 4 ); لاحظ أننا لم نستخدم الاسم الكامل للملف moduleA.js وإنما حذفنا اللاحقة، كما أن /. تشير إلى أن الوحدة moduleA موجودة في نفس المجلد الموجود فيه moduleB. وبفضل مشروع CommonJS ظهر أكبر مشروع معتمدًا عليه وهو Node.js وهي بيئة تشغيل لغة جافاسكربت مفتوحة المصدر وتعمل على جميع أنظمة التشغيل، والّتي تستطيع تشغيل شيفرة جافاسكربت خارج المتصفحات. بالإضافة إلى ذلك تسمح Node.js للمطوري الواجهات الخلفية باستخدام جافاسكربت لكتابة برمجيات تعمل من جهة الخادم وذلك لتوليد صفحات الوِب توليدًا ديناميكيًا قبل إرسالها إلى المتصفح، وكما تستطيع أيضًا التعامل مع الملفات وقواعد البيانات، ومختلف أنظمة الشبكات وخدمات أنظمة التشغيل. الوحدة غير المتزامنة AMD علِمنا إن الهدف الرئيسي الّذي بنيت عليه CommonJS هو استخدامها في الواجهات الخلفية (من طرف المخدم) ولذلك مشكلة استدعاء الوحدات استدعاءً متزامنًا لم تكُ مشكلة في ذلك الحين، بل ولم تكُ لتظهر لولا إصرار مجتمع المطورين على جلب CommonJS إلى الواجهات الأمامية ولنُلخص مشكلة CommonJS هي عندما نستدعي الوحدة معينة في ملف ما ولتكن مثلًا add كما في السطر التالي: var add=require('add'); سيتوقف النظام حتى تصبح الوحدة add جاهزة؛ أي أن هذا السطر البرمجي سيجمد المتصفح أثناء تحميل هذا الملف. ماذا لو كان هنالك أكثر من وحدة مطلوبة كيف ستكون الأمور عندها؟ لذلك إنها ليست أفضل طريقة لتعريف الوحدات من جانب المتصفح. من أجل نقلِ صياغة تعريف وحدة ما من صياغة يفهمها الخادم إلى صياغة يفهمها المتصفح قدمت لنا CommonJS العديد من التنسيقات لتعريف الوحدات وكانت واحدة من أشهرها هي تعريف الوحدات بالطريقة غير المتزامنة (Asynchronous Module Definition) والّتي تُعرف إختصارًا AMD وتكون طريقة تعريفها كما في المثال التالي: define([‘add’, ‘reduce’], function(add, reduce){ return function(){...}; }); إن الدالة define تأخذ قائمة بالتبعيّات الموجودة والتي مرّرناها لها كقائمة وستُعيد النتيجة إلى الدالة المجهولة (Anonymous function) كوسطاء (الدالة المجهولة هي الدالة الّتي ليس لديها اسم ولقد فصلنا في مقالٍ سابق كيفية استخدمها وذلك في سلسلة تعلم جافاسكربت). وسيكون ترتيب التبعيات بنفس الترتيب الموجود في القائمة ومن ثم ستُعيد الدالة المجهولة النتيجة والتي سنُصدرها ونستخدمها في نهاية المطاف. هذه الطريقة تُكافئ الطريقة السابقة لاستدعاء الوحدات (التبعيات) لأنها تعطي نفس النتائج الوظيفية، إلا أن هذه الطريقة تستخدم التحميل غير المتزامن للوحدات. إن CommonJS و AMD حلّت آخر مشكلتين متبقيتين مع نمط الوحدات وهما على الشكل التالي: عدم وجود ثبات في التبعية بين الشيفرة البرمجية. التضارب في المتغيرات العامة. نحن فقط سنهتم بالتبعيات الموجودة من أجل كلّ وحدة (أو ملف) وبالتأكيد في هذه الحالة لا يوجد هناك أي تضارب في المتغيرات العامة. مُحمل الوحدات RequireJS في الحقيقة إن طريقة استخدام AMD مع CommonJS كانت مفيدة جدًا، ولكن كيف يمكننا استخدامها في المتصفح وهنا جاء محمل الوحدات RequireJS إلى الساحة والّذي سيساعدنا لتحميل الوحدات تحميلًا غير متزامن AMD. والذي يعتبر من أوائل التطبيقات الفعلية لمكتبة CommonJS مع AMD. على الرغم من إسمها، إلا أن هدفها ليس دعم بناء جملة 'require' في CommonJS. إن RequireJS تُتيح لنا كتابة وحدات من نمط AMD. ملاحظة: قبل أن تطبق المثال التالي، يجب عليك تنزيل ملف require.js من موقعه الرسمي. لاحظ أن الاستدعاء الوحيد الموجود في ملف index.html هو: <script data-main=”main” src=”require.js”> </script> هذا الوسم سيُحمِّلُ محليًا مكتبة require.js إلى الصفحة، ومن بعدها السمة data-main في الوسم <script> ستخبر المكتبة من أي ملف ستبدأ. بعد أن تستدعي require.js ملف main.js سيقودنا هذا الملف إلى التبعيات المتعلقة به، وهكذا إلى أن نحمل جميع التبعيات (الملفات) المطلوبة. ملاحظة: استخدمنا main فقط بدون الإشارة إلى اللاحقة والتي هي js وذلك لأن هذه المكتبة تفترض بأن كلّ القيم الّتي سنُسندها لهذه الخاصية ستكون ملفات جافاسكربت. وسيكون تحميل التبعيات للمثال السابق كما في الصورة التالية: نلاحظ أن المتصفح يحمّل الملف index.html والملف بدوره يحمّل المكتبة require.js والّذي سيتكفل ببقية الملفات الأخرى. وبهذا نكون حللنا جميع المشاكل الّتي واجهتنا حتى هذه اللحظة، ولكن وكما هي العادة في عالم البرمجة في كلّ محاولة لإصلاح مشكلةٍ ما تظهر مشكلةٌ أخرى، ولكن عادة ما تكون أخف من سابقتها في التأثير. ومن المشاكل الّتي ظهرت مع هذا الحل هي: صياغة AMD طويلة ومضجرة: إذ أن كلّ شيء يجب تغليفة بالدالة define ولذا يوجد مسافة بادئة (يُقصد تعريف الدالة define) للشيفرة البرمجية وهذه المشكلة يمكن ألا تظهر مع الملفات الصغيرة ولكن ما إن يكبر حجم الملف وتزداد تفاصيله ستصبح مشكلة كبيرة. قوائم الاعتماديات الموجودة في المصفوفة: إن قائمة الاعتماديات الموجودة في المصفوفة يجب أن تتشابه تمامًا مع القائمة المررة كوسيط للدالة وإذا كان لدينا العديد من التبعيات ستزداد الأمور تعقيدًا. في النسخة الحالية من HTTP للمتصفحات تحميل العديد من الملفات الصغيرة سيخفف من الأداء (وخصيصًا بدون استخدام أي تقنيات مساعدة مثل: ذاكرة التخزين المؤقت Cache). مجمع الوحدات أو مجمع الحزم (Module Bundler) انطلاقًا من الأسباب السابقة أراد بعض الأشخاص بناء التعليمات من خلال CommonJS لكن هذه الأخيرة مخصصة للعمل على الخوادم وليس المتصفح وهي تدعم التحميل المتزامن أيضًا. بقيت هذه الإشكالية عصية على الحل حتى جاء مجمع الوحدات Browserify لينقذ الموقف ويقدم لنا حلولً مناسبة. وهو يعدّ أول مجمع وحدات وجد آنذاك، وكان باستطاعته تحويل الوحدات المكتوبة بصياغة CommonJS والتي لا يستطيع المتصفح أن يفهمها بطبيعة الحال (وذلك لأنها بُنيت بالأساس للتعامل مع الخادم) إلى صياغة يفهمها المتصفح. يمكننا التعامل مع مجمع الحزم Browserify من خلال سطر الأوامر إذ يمكنك إعطاؤه أمرًا ما وتمرير بعض الخيارات الإضافية المتاحة لكل أمر من الأوامر الموجودة. وهو يعتمد في بنيته على مدير الحزم npm وبيئة Node.js. يمكن لمُجمع الوحدات Browserify تجميع شجرة التبعيات للشيفرة البرمجية الخاصة بك وتحزيمها في ملف واحد. وأيضًا يحل مشكلة عدم إمكانية الوصول إلى الملفات من قبل المتصفح إذ إنه يبحث عن جميع تعليمات require (والتي لا يمكن للمتصفح أن يستدعي الملف المطلوب لأنه لا يملك الصلاحيات المناسبة لذلك) فيستبدلها بمحتوى الملف المراد تضمينه (وذلك انطلاقًا من اعتماده على Node.js والتي تستطيع الوصول إلى الملفات) وهكذا يصبح الملف جاهزًا للتنفيذ في المتصفح ولا يواجه أي مشكلة. حلول كثيرة فأي منها سنستخدم؟ إذا لدينا الكثير من الخيارات المتاحة فأي واحدة منهم سنستخدم في مشروعنا القادم؟ إذ إننا حتى هذه اللحظة ناقشنا العديد من الخيارات المتاحة لكتابة الوحدات مثل: نمط الوحدات (Module Pattern) وطريقة CommonJS مع AMD وطريقة RequireJS فأي منهم سنعتمده لمشروعنا القادم؟ الجواب: ولا واحدة منها. كما نعلم بأن لغة جافاسكربت لم تكُ تدعم نظام الوحدات في أصل اللغة، ولهذا نلاحظ وجود العديد من الخيارات المتاحة لتعويض هذا النقص. ولكن هذا الأمر لم يستمر طويلًا إذ بعد تحديث المواصفات القياسية للغة جافاسكربت ES6 أصبح وأخيرًا نظام الوحدات جزءًا أساسيًا منها وهذا يكون جواب سؤالنا الرئيسي أي الخيارات سنستخدم؟ إنها طريقة ES6 (تحدثنا في سلسلة مقالاتٍ سابقة عن المميزات الجديدة لهذا الإصدار من لغة جافاسكربت ES6). تُستخدم التعليمة import و export لاستيراد وتصدير الوحدات في التحديث الجديد ES6. وإليك مثالًا عمليًا يوضح لك كيفية استخدامها: الملف main.js: import sum from "./sum"; var values = [ 1, 2, 4, 5, 6, 7, 8, 9 ]; var answer = sum(values); document.getElementById("answer").innerHTML = answer; الملف sum.js: import add from './add'; import reduce from './reduce'; export default function sum(arr){ return reduce(arr, add); } الملف add.js: export default function add(a,b){ return a + b; } الملف reduce.js: export default function reduce(arr, iteratee) { let index = 0, length = arr.length, memo = arr[index]; index += 1; for(; index < length; index += 1){ memo = iteratee(memo, arr[index]); } return memo; } في الحقيقة دعم جافاسكربت لنظام الوحدات بهذا الشكل المختصر قلبَ مجريات الُلعبة، بل إن نظام الوحدات هذا سيحكم عالم جافاسكربت في نهاية المطاف. إنها باختصار مستقبل لغة جافاسكربت. ولكن! (أعلم بأنك سئمت من هذه الكلمة ولكن يجب علينا إكمال بقية جوانب هذه الطريقة لتوضيح جميع تفاصيل المشهد لديك) هذا التحديث من اللغة لم تدعمه جميع المتصفحات المستخدمة في هذا اليوم، أو لنكن أكثر دقة ما تزال بعض المتصفحات لا تدعم هذه التحديث وخصوصًا المتصفحات القديمة وفي الحقيقة نسبة المستخدمين لهذه المتصفحات كبيرة وتؤخذ بعين الإعتبار. إذا ما الّذي يجب علينا فعله الآن؟ وكيف سنحوّل الشيفرة البرمجية من الإصدار ES6 إلى الإصدار ES5؟ هنا يأتي دور أهم أداة سنتعرف عليها اليوم ألا وهي مجمع الوحدات Webpack. ما هي Webpack؟ هي عبارة عن مجمع واحدات (Module Bundler) أو أداة بناء (Build Tool) مثل أداة Browserify وهي تحول شجرة تبعية الملفات وتُجمعها في ملف واحد. ليس ذلك فحسب وإنما تحوي على الكثير من المميزات الأخرى نذكر منها: تقسيم الشيفرة البرمجية: عندما يكون لدينا تطبيقات متعددة تتشارك نفس الوحدات يمكننا من خلال هذه الأداة تجميع الشيفرة البرمجية في ملفين أو أكثر. فمثلًا إذا كان لدينا التطبيقين التاليين app1 و app2 ويشترك كلاهما في العديد من الوحدات إذا استخدمنا Browserify سيكون لدينا app1.js و app2.js وكل ملف منهم مجمع فيه الشيفرات البرمجية الخاصة بكل تطبيق، بينما مع أداة Webpack يمكننا إنشاء ملف app1.js وملف app2.js وبالإضافة إلى الملف المشترك shared-lib.js. نعم ستضطر إلى تحميل ملفين في صفحة Html ولكن مع إمكانية استخدام تقنيات مساعدة مثل: التخزين في الذاكرة المؤقتة للمتصفح (Cache) واستخدام شبكة توصيل المحتوى الموزعة CDN (Content Delivery Network) كلّ هذه التقنيات ستقلل من وقت التحميل الإبتدائي للصفحات. المُحملات (Loaders): مع استخدام المُحمل المخصص يمكنك تحميل أي ملف إلى التطبيق إذ يمكنك استخدام الدالة reuiqre ليس فقط لتحميل ملفات جافاسكربت فقط (مثلما كانت أداة Browserify) وإنما ملفات التنسيقات وملفات SaSS وملفات Less بالإضافة إلى إمكانية تحميل الصور والخطوط والملفات والكثير غيرها. الملحقات (Plugins): تقدم Webpack ميزة الملحقات مجموعة واسعة من المهام مثل تحسين الحزمة وإدارة الملحقات (مثل الصور) بالإضافة إلى ذلك يمكننا حتى التلاعب بالملفات قبل تحميلها وتحزيمها في الملف الهدف (على سبيل المثال إمكانية ضغط الصور قبل رفعها على المخدم سنتطرق لهذه الميزّة لاحقًا في هذا الدليل). نظام الأوضاع (Mode): توفر لك هذه الأداة ثلاث أنواع من الأوضاع وضع التطوير ووضع الإنتاج أو بدون وضع. باختصار يستخدم كلّ وضع من الأوضاع من أجل سيناريو معين: وضع التطوير: يستخدم عند العمل في مرحلة التطوير لمشروع الوِب وتكون الشيفرة البرمجية مقروءة ومفهومة. وضع الإنتاج: يستخدم عند العمل في مرحلة عرض مشروع الوِب على الإنترنت وتكون الشيفرة البرمجية مختصرة وغير مقروءة. بدون وضع: يكون الشيفرة الّتي استخدمت في مرحلة التطوير هي ذاتها الّتي ستعرض على الإنترنت بدون أي تعديل. تسهل عملية تحويل الشيفرات البرمجية إلى إصدار أقدم (Transpiling): إمكانية تحويل شيفرات جافاسكربت البرمجية الحديثة مثل ES6 والّتي لا تدعمها بعض المتصفحات إلى شيفرات جافاسكربت البرمجية القديمة مثل ES5 من خلال Babel على سبيل المثال، وبذلك نكون ضربنا عصفورين بحجر واحد أولهما أننا كتبنا التطبيق بالإصدار الحديث من اللغة، وثانيهما أننا استطعنا تشغيل التطبيق على كافة المتصفحات. أليس هذا رائعًا؟ لابد بأنك متحمسٌ جدًا للتعرف على هذه الأداة والبدء في استخدامها على الفور في مشاريعك القادمة إذًا هيا بنا نبدأ لنتعرف عليها أكثر ونتعلم كيفية استخدامها. مفاهيم أساسية قبل الشروع في العمل مع هذه الأداة لا بدّ لنا من التعرف على بعض المفاهيم الرئيسية والتي سنستخدمها بكثرة في الأمثلة ملف الإعداد webpack.config.js :هو عبارة عن ملف يحدد كيفية تعامل الأداة Webpack مع بعض الخصائص للمحملات أو الملحقات وبعض خصائص الأداة نفسها. وعند إنشائنا لمشروع جديد لن نجده في مجلد المشروع وذلك بسبب ميزة التعديلات الصفرية (zero configuration) الجديدة الّتي جاءت مع الإصدار الرابع أو الأحدث من هذه الأداة والتي تمكننا من استخدام الأداة بدون الحاجة لأي تعديل في ملف الإعداد. بل يمكننا عدم إنشاؤه من الأساس. يمكن تخصيص هذا الملف تخصيصًا بسيطًا أو متقدمًا وذلك بحسب المشروع الّذي تعكفُ على تطويره، وهنالك بعض الخصائص الأساسية المشتركة في كلّ المشاريع، والّتي يجب التنويه إليها: الخاصية Entry: تحدد هذه الخاصية ملف الأولي من الشروع الّذي سنبنيه (نجمعه) في أول التنفيذ. الخاصية Output: تحدد مجلد المشروع الّذي سنستخدمه في مرحلة الإنتاج (أو النشر) يكون فيه الشيفرة البرمجية الّتي نريد استخدامها لنشر المشروع على الإنترنت. تحتوي هذه الخاصية على بعض الخيارات نذكر منها: filename: نحدد فيها أسم الملف الّذي نريد تجميع الحزم فيه. path: نحدد فيها مسار الملف الّذي نريد تجميع الحزم فيه. الخاصية Loaders: تحدد المحملات الّتي نريد أن نستخدمها مع Webpack. كلّ مُحمل يحتوي على بعض الخيارات نذكر منها: test: تحدد نوع الملفات الّتي سيُحملها هذا المُحمل (يمكننا استخدام التعابير النمطية في تحديد الملفات). exclude: الملفات الّتي نريد استبعادها (والتي من الممكن أن تحقق الخاصية الأولى). use: ما هو المُحمل الّذي سنستخدمه لإجراء التغييرات على الملفات المقبولة في test ولم تُستبعد في الخاصية exclude. الخاصية Plugins: تحدد هذه الخاصية الملحقات الّتي نريد استخدامها وكما تحدد بعض الخيارات لهذه الملحقات. الخاصية Mode: تحدد هذه الخاصية الوضع الّذي نريد العمل عليه أي طريقة نشر المشروع إلى مجلد الخرج (output). كانت هذه أبرز المفاهيم الأساسية الّتي سنتعامل معها في الأمثلة القادمة. إعداد بيئة التطوير بما أن مجمعات الحزم (الوحدات) تعتمد على Node.js اعتمادً أساسيًا للوصول إلى نظام الملفات من أجل تجميع الاعتماديات بشكلها النهائي لذا لابد لنا من تثبيت Node.js في البداية وإليك الخطوات الكاملة لتثبيته على نظام التشغيل ويندوز (يمكنك الاطلاع على مقال سابق تحدثنا فيه عن كيفية تثبيت Node.js على نظام لينكس): ندخل إلى الموقع الرسمي الخاص ببيئة Node.js ونحمل النسخة الموافقة لنظام التشغيل الخاص بك (يفضل تحميل الإصدارات ذات الدعم الطويل والتي تُدعى LTS). بمجرد إنتهاء التنزيل نفتح الملف المُنزّل. سيسألك معالج التثبيت عن موقع التثبيت والمكونات الّتي تريد تضمينها في عملية التثبيت يمكنك ترك تلك الخيارات بوضعها الافتراضي. انقر فوق تثبيت وانتظر حتى ينتهي معالج التثبيت. تهانينا أصبحت بيئة Node.js جاهزة للعمل. ملاحظة: يمكنك التأكد من تثبيت البيئة تثبيتًا صحيحًا من خلال تنفيذ الأمر التالي على سطر الأوامر (Command line) node –v يجب أن يظهر لك نسخة Node.js المثبتة وفي حالتنا ستظهر 12.16.1. كما يمكنك التأكد من تثبيت مدير الحزم Node Package Manager والّذي يُعرف اختصارًا npm من خلال الأمر npm –v يجب أن تظهر لك نسخة npm المثبتة وفي حالتنا ستظهر 6.13.4. نحن جاهزون الآن لإنشاء مشروع جديد وتثبيت Webpack! إنشاء مشروع جديد وتثبيت Webpack في البداية ننشئ مجلدًا جديدًا للمشروع وندخل إليه عبر التعليمات التالية: $ mkdir project; cd project ثم ننشئ مشروعًا جديدًا عبر التعليمة التالية: $ npm init -y ثم نثبت أداة Webpack عبر التعليمة التالية: $ npm install -D webpack webpack-dev-server يجب أن يظهر لك نتيجة مماثلة للصورة التالية: الآن لننشئ بنية الملفات الهرمية التالية: webpack-demo |- package.json + |- index.html + |- /src + |- index.js ملاحظة: جرى عرض الملفات في هذه المقالة بأسلوب عرض التغييرات في git فالأسطر الّتي بجانبها إشارة موجبة + يجب عليك إضافتها والّتي بجانبها إشارة سالبة - يجب عليك حذفها. الآن نفتح ملف index.js الموجود في مجلد src ونجري عليه التعديلات التالية: unction component() { const element = document.createElement('div'); // Lodash, currently included via a script, is required for this line to work element.innerHTML = _.join(['Hello', 'webpack'], ' '); return element; } document.body.appendChild(component()); ونعدل الملف index.html الموجود في مجلد webpack-demo ونجري عليه التعديلات التالية: <!doctype html> <html> <head> <title>Getting Started</title> <script src="https://unpkg.com/lodash@4.16.6"></script> </head> <body> <script src="./src/index.js"></script> </body> </html> كما يجب علينا أن نعدل ملف package.json لتفعيل الوضع الخاص كي نمنع النشر العرضي للشيفرة البرمجية الخاصة بنا ليصبح على الشكل التالي: { "name": "webpack-demo", "version": "1.0.0", "description": "", + "private": true, - "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "webpack": "^4.20.2", "webpack-cli": "^3.1.2" }, "dependencies": {} } لمزيد من المعلومات حول التفاصيل الداخلية لملف package.json ننصحك بأخذ جولة في التوثيق الرسمي الخاص به. في المثال السابق هنالك تبعيات ضمنية إذ أن الملف index.js يعتمد على loadash (وهي مكتبة تساعد المبرمجين على كتابة شيفرة برمجية أكثر إيجازًا وتزيد من قابلية الصيانة) في عمله وبناءً عليه يجب أن يكون loadash مُضمن قبل أن تعمل الشيفرة البرمجية للملف index.js. ولكن إن الملف index.js لم يُصرح بوضوح عن حاجته لهذه المكتبة وإنما افترض أنها موجودة ضمن المتحولات العامة. في الحقيقة إن إدارة المشروع بهذه الطريقة تعدّ مشكلة وذلك للأسباب التالية: ليس من الواضح أن الشيفرة البرمجية للملف index.js تعتمد على مكتبة خارجية. إذا كانت التبعية مفقودة، أو ضُمنت في الترتيب الخطأ، فلن يعمل التطبيق بالطريقة الصحيحة. إذا ضُمنت تبعيةً ما ولكن لم تُستخدم، سيضطر المتصفح إلى تنزيلها مع أنها غير ضرورية. لنستخدم Webpack لإدارة هذه المشروع بدلًا من الطريقة الحالية. إنشاء حزمة باستخدام Webpack سنعدل بنية المشروع قليلًا لفصل الشيفرة البرمجية الّتي سنعمل عليها عن الشيفرة البرمجية الّتي سننشرها. وتجدر الإشارة إلى أن الشيفرة البرمجية الّتي سننشرها هي الناتج المصغّر والمحسّن لعملية البناء الناتجة عن الشيفرة الّتي كتبناها والتي ستُحمّل في نهاية المطاف في المتصفح. إذًا سنضع الشيفرة البرمجية الّتي سنعمل عليها في المجلد src أما الّتي سننشرها ستكون في مجلد dist أي ستكون بهذا الشكل: webpack-demo |- package.json + |- /dist + |- index.html - |- index.html |- /src |- index.js لتحزيم مكتبة loadash (والّتي هي تبعية في ملف index.html) باستخدام الملف index.js سنحتاج أولًا إلى تثبيت هذه المكتبة محليًا باستخدام الأمر التالي: npm install --save lodash عند تثبيت حزمة (مكتبة) معينة والّتي سنحتاجها في عملية التحزيم النهائية قبل النشر النهائي يجب علينا إضافة install --save أما إذا أردت تثبيت حزمة (مكتبة) معينة لمرحلة التطوير فقط يجب عليك إضافة install --save-dev. لمزيد من المعلومات ننصحك بالإطلاع على التوثيق الرسمي لمدير الحزم NPM. الآن لنضيف تعليمة استيراد الحزمة في الشيفرة البرمجية للملف index.js الموجود في المجلد src كما في الشكل التالي: + import _ from 'lodash'; + function component() { const element = document.createElement('div'); - // Lodash, currently included via a script, is required for this line to work element.innerHTML = _.join(['Hello', 'webpack'], ' '); return element; } document.body.appendChild(component()); بما أننا قمنا باستيراد مكتبة loadash في الملف السابق لنعدل الملف index.html بما يتوافق مع ذلك ليكون كما في الشكل التالي: <!doctype html> <html> <head> <title>Getting Started</title> - <script src="https://unpkg.com/lodash@4.16.6"></script> </head> <body> - <script src="./src/index.js"></script> + <script src="main.js"></script> </body> </html> ملاحظة: يتطلب ملف index.js صراحة وجود المكتبة (أو أي تبعية إذا أردنا تعميم الفكرة) loadash ويربطه كمتحول _ (أي لا يوجد تضارب في المجال). من خلال تحديد التبعيات الّتي تحتاجها الوحدة، يمكن لمُجمع الوحدات (المقصود Webpack) استخدام هذه المعلومات لإنشاء مخطط بياني للاعتماديات (dependency graph). ثم يستخدم الرسم البياني لإنشاء حزمة محسنة والّتي ستُنفذ الاستدعاءات بالترتيب الصحيح. لننفذ التعليمة التالية npx webpack والتي ستأخذ الشيفرة البرمجية في الملف index.js وهو القيمة المسندة للخاصية entry وتنشر الخرج في ملف main.js والذي سيكون القيمة في المسندة للخاصية output. إن تعليمة npx والتي تأتي مع نسخة 8.2 من Node.js أو أحدث، و 5.2.0 من مدير الحزم NPM أو أحدث. إذ تعمل على أداة Webpack الثنائية الموجودة في (./node_modules/.bin/webpack) الّتي ثبتناها في البداية. ويكون ناتج التنفيذ كما يلي: npx webpack ... Built at: 13/06/2018 11:52:07 Asset Size Chunks Chunk Names main.js 70.4 KiB 0 [emitted] main ... WARNING in configuration The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment. You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/ ملاحظة: من الممكن أن يختلف الناتج الّذي سيظهر لك بعض الشيء ولكن إذا نجحت عملية البناء فلا تقلق من رسالة التحذير فإن الأمور على ما يرام. افتح الملف index.html يجب أن يظهر لك Hello webpack فإذا ظهرت لديك تهانينا هذا يدلّ على أنك أنجزت جميع الخطوات بنجاح. إذا ظهرت لديك رسالة خطأ في صياغة ملف جافاسكربت المصغّرة (minified) وذلك عند فتحك لملف index.html فعيّن وضع التطوير ومن ثم شغل الأمر npx webpack من جديد. هذا الخطأ يتعلق بتشغيل حزمة الوٍب npx على أحدث نسخة من Node.js (الإصدار 12.5 أو الأحدث) بدلًا من الإصدار ذو الدعم الطويل LTS. الوحدات في المواصفات القياسية لنسخة جافاسكربت ES6 دُعمت تعليمتي import وexport إذ أن معظم المتصفحات تدعمها في الوقت الحالي (ولكن يوجد بعض المتصفحات لا تدعمها) وWebpack ليست استثناءً بل إنها تدعمها دعمًا متميزًا. تدعم Webpack هذه الخاصية من خلال تحويل (transpiles) الشيفرة البرمجية المقابلة للتعليمتين import وexport إلى نسخة أقدم من ES6 مثل ES5 وبذلك تؤمن فهم المتصفح ما تعنيه هذه التعليمتين باللغة الّتي يفهمها. إضافةً إلى ذلك يدعم Webpack العديد من صيغ الوحدات الأخرى لمزيد من المعلومات يمكنك زيارة التوثيق الرسمي. ملاحظة: إن Webpack لن يحوّل أي تعليمة برمجية عدا تعليمتي import وexport ولذلك في حال كنت تستخدم مميزات أخرى من المواصفات القياسية ES6 فعندها يجب عليك استخدام محولات المخصصة لذلك مثل Babel أو Bublé. استخدام ملف الإعداد إن جميع الإصدارات الّتي جاءت بعد الإصدار الرابع من Webpack تدعم ميزة التعديلات الصفرية الّتي تحدثنا عنها سابقًا في هذا الدليل، ولذا إذا أردنا أن نخصص بعض الإعدادات في المشروع لا بد لنا من إنشاء ملف الإعداد إنشاءً يدويًا. إذًا سنضيف ملف الإعداد على بنية الملفات وستصبح البنية الهرمية للمشروع على الشكل التالي: webpack-demo |- package.json + |- webpack.config.js |- /dist |- index.html |- /src |- index.js وسنُضيف إليه بعض التعليمات المهمة والتي سنستخدمها كثير في المشروع وبذلك ستتحسن إنتاجيتنا أكثر من ذي قبل. وتكون الإضافة كما يلي: const path = require('path'); module.exports = { entry: './src/index.js', output: { filename: 'main.js', path: path.resolve(__dirname, 'dist'), }, }; أما الآن لنجرب تنفيذ التعليمة التالية: npx webpack --config webpack.config.js ... Asset Size Chunks Chunk Names main.js 70.4 KiB 0 [emitted] main ... WARNING in configuration The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment. You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/ ملاحظة: في حالة وجود أكثر من ملف للإعداد تلتقط التعليمة Webpack ذلك إلتقاطًا إفتراضيًا. ونستخدم خيار --config وبعده اسم الملف لتوضيح فكرة أنه بإمكانك تمرير أسم أي ملف تريده تمريرًا يدويًا، وهذا سيكون مفيدًا جدًا لملفات الإعداد الأكثر تعقيدًا والتي سنحتاج لتقسيمها إلى ملفات متعددة. يتيح ملف التكوين مرونة أكبر بكثير من استخدام سطر الأوامر CLI (Command Line Interface) البسيط. إذ يمكننا تحديد وتثبيت قواعد معينة للمُحمل (Loader) والملحقات (Plugin) وتخصيص الخيارات بالإضافة للعديد من التحسينات الأخرى. لمزيد من المعلومات يمكنك الإطلاع على التوثيق الرسمي لملف الإعداد. إنشاء إختصار لتعليمة البناء يمكننا استخدام مميزات Webpack لجعل الأمور أسهل من خلال وضع بعض اللمسات الجمالية مثل اختصار بعض الخطوات، وذلك من خلال كتابة الأوامر في ملف package.json وتحديدًا في scripts. وبذلك نختصر على أنفسنا عناء كتابة بعض التعليمات الطويلة والمُملة. إذًا لنعدل ملف package.json ليصبح على الشكل التالي: { "name": "webpack-demo", "version": "1.0.0", "description": "", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "build": "webpack" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "webpack": "^4.20.2", "webpack-cli": "^3.1.2" }, "dependencies": { "lodash": "^4.17.5" } } يمكننا الآن استخدام الأمر npm run build بدلًا من الأمر npx الّذي استخدمنا سابقًا. لاحظ بأنه يمكننا دائمًا الإشارة إلى الحزم المثبتة محليًا في مدير الحزم npm في السمة scripts في الملف package.json بنفس الطريقة الّتي استخدمناها مع التعليمة npx. هذا الطريقة هي متعارف عليها في معظم المشاريع القائمة على مدير الحزم npm لأنه يسمح لجميع المساهمين لاستخدام نفس التعليمات المشتركة (وحتى مع بعض الخيارات مثل --config). ملاحظة: يمكنك تمرير الخيارات الخاصة لتعليمة ما من خلال إضافة شرطتين -- بين الأمر npm run build والخيارات الّتي نريد تخصيصها مثل npm run build -- --colors. npm run build ... Asset Size Chunks Chunk Names main.js 70.4 KiB 0 [emitted] main ... WARNING in configuration The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment. You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/. أما الآن وبعد أن تعلمنا الأساسيات عن كيفية استخدام Webpack لننتقل إلى الاستخدامات الأهم لهذه الأداة مثل إدارة الصور والخطوط ..إلخ. إذا نفذت جميع التعليمات السابقة تنفيذًا صحيحًا يجب أن يكون البنية الهرمية للمشروع على الشكل التالي: webpack-demo |- package.json |- webpack.config.js |- /dist |- main.js |- index.html |- /src |- index.js |- /node_modules ملاحظة: إذا كنت تستخدم الإصدار الخامس من مدير الحزم npm من الممكن أن ترى ملفًا إضافيًا اسمه package-lock.json. استخدام Webpack لإدارة الملحقات بعد أن تعلمنا أساسيات Webpack وإنشأنا مشروعًا صغيرًا يعرض "Hello Webpack"، سنحاول الآن أن نركز أكثر على الأمور المهمة والتي تخدمنا بها هذه الأداة مثل إدارة ملفات التنسيق والصور وسنرى بالضبط كيف تتعامل هذه الأداة مع كلٍّ منهم. يستخدم مطورو الواجهات الأمامية أدوات بناء مثل: Grunt و Glup لمعالجة هذه الملحقات ونقلها من مجلد src إلى مجلد dist أو حتى لإنشاء مجلد جديد لهذه الملحقات، تستخدم Webpack نفس الفكرة للتعامل مع الوحدات، بالإضافة إلى أن Webpack تجمع كلّ التبعيات ديناميكيًا وتنشئ ما يعرف بمخطط التبعيات وهذا أمر رائع لأن كلّ وحدة (Module) توضح صراحة ما هي تبعياتها وبذلك نتجنب تجميع الوحدات غير المستخدمة في المشروع. واحدة من أروع مميزات Webpack هي إعطاؤك إمكانية تضمين أي نوع آخر من الملفات، إلى جانب جافاسكربت، ولهذه الفكرة تحديدًا أنشئ المحمل (Loader). أي يمكن تطبيق نفس المزايا المذكورة أعلاه (التبعيات المستخدمة من قبل الوحدات) على كلّ شيء مستخدم في إنشاء المواقع أو تطبيقات الوِب أيضًا. إدارة ملفات التنسيق CSS في البداية سنعدل قليلًا في بنية الملفات وبعض الملفات قبل أن ندير ملفات التنسيق. سنعدل الملف dist/index.html ليصبح كما يلي: <!doctype html> <html> <head> - <title>Getting Started</title> + <title>Asset Management</title> </head> <body> - <script src="main.js"></script> + <script src="bundle.js"></script> </body> </html> وكما سنعدل أيضًا ملف الإعداد webpack.config.js ليصبح: const path = require('path'); module.exports = { entry: './src/index.js', output: { - filename: 'main.js', + filename: 'bundle.js', path: path.resolve(__dirname, 'dist'), }, }; وبذلك يُصبح كلّ شيء جاهز لنبدأ. أولًا لنثبت محمل ملفات التنسيق css-loader الّذي سيساعدنا في معالجة تعليمة الاستيراد import الّتي سنستخدمها أيضًا من أجل استيراد ملفات التنسيق ومحمل التنسيق style-loader الّذي سيساعدنا في أخذ ملف التنسيق ووضعه في ملف (أو ملفات) تنسيق منفصلة. إذًا لنُنفذ الأمر التالي: npm install --save-dev style-loader css-loader ولنُعدل ملف الإعداد webpack.config.js ليحوي المعلومات اللازمة لحزم المُحمل (Loader) الّتي ثبتناها للتو وسيصبح الملف على الشكل التالي: const path = require('path'); module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist'), }, + module: { + rules: [ + { + test: /\.css$/, + use: [ + 'style-loader', + 'css-loader', + ], + }, + ], + }, }; ملاحظة: تستخدم Webpack التعابير النمطية (Regular Expression) لتحديد الملفات الّتي يجب البحث عنها وتقديمها إلى مُحمل (Loader) محدد. في الحالة السابقة نلاحظ أنه ستُقدم جميع ملفات التنسيق (ذات اللاحقة css) إلى المُحمل style-loader والمُحمل css-loader. وهذا بدوره سيمكنك من استيراد ملف التنسيق الّذي سيكون مثل هذا import './style.css' إلى الملف الّذي يعتمد على ملف التنسيق هذا. عند تشغيل مجمع الحزم Webpack سيُضاف ملف تنسيق مخصص في وسم <style> وذلك في داخل الوسم <head>. لنجرب ذلك من خلال إضافة ملف تنسيق جديد style.css إلى مشروعنا واستيراده في الملف index.js: لننشئ أولًا ملف التنسيق style.css في مشروعنا لتصبح البنية الهرمية للمشروع على الشكل التالي: webpack-demo |- package.json |- webpack.config.js |- /dist |- bundle.js |- index.html |- /src + |- style.css |- index.js |- /node_modules ومن ثم لنُضف بعض الخصائص إلى ملف style.css ليصبح على الشكل التالي: .hello { color: red; } ولنستدعي ملف التنسيق style.css في ملف index.js ليصبح على الشكل التالي: import _ from 'lodash'; + import './style.css'; function component() { const element = document.createElement('div'); // Lodash, now imported by this script element.innerHTML = _.join(['Hello', 'webpack'], ' '); + element.classList.add('hello'); return element; } document.body.appendChild(component()); ومن ثم نُنفذ أمر البناء التالي: npm run build ... Asset Size Chunks Chunk Names bundle.js 76.4 KiB 0 [emitted] main Entrypoint main = bundle.js لنفتح الآن ملف index.html ولنرى ما هي التغييرات الّتي جرت عليه. لاحظ أن كلمة "Hello Webpack" أصبحت الآن باللون الأحمر. لمعرفة ما الّذي فعلته Webpack افحص الصفحة من خلال أدوات المطوّر (المُقدمة من متصفح جوجل كروم على سبيل المثال)، ولكن لا تعرض مصدر الصفحة لأنها لن تقدم لك النتيجة الّتي نود الإشارة إليها. إذ أن الوسم <style> سيُنشئ ديناميكيًا من قِبل جافاسكربت. لذلك افحصها حصرًا من خلال أدوات المطوّر وافحص تحديدًا الوسم <head> يجب أن يحتوي على الوسم <style> والذي استوردناه في ملف index.js. ملاحظة: سابقًا كان علينا تصغير (Minimize) ملفات التنسيق تصغيرًا يدويًا قدر الإمكان وذلك بحذف المساحات الفارغة بين الخصائص الموجودة في الملف من أجل زيادة سرعة تحميل الصفحة، ولكن مع الإصدار الرابع من مجمع الحزم 4 Webpack أو الأحدث منه أصبح تصغير ملفات التنسيق خطوة افتراضية في الأداة، وبذلك أضافت لنا هذه الأداة بُعدًا آخر لتحسين الشيفرة البرمجية وسبب وجيّه لزيادة محبتنا لها. إدارة ملفات SASS تتيح لنا Webpack إمكانية التعامل مع ملفات SASS وتحويلها إلى ملفات تنسيق عادية. ولنأخذ مثالًا عمليًا نطبق فيه كيفية تحويل ملفات SASS إلى ملفات تنسيق عادية. سنركز في هذا المثال على دعم الملفات SCSS مثل: (your-styles.scss) و الملفات SCSS modules مثل: (your-component.module.scss). تجنبًا لتعقيد الأمور أكثر من اللازم سنبدأ بمشروع جديد ننفذ فيه هذه الفكرة. سنفترض أنك أنشأت مشروعًا جديدًا وثبتّ أداة Webpack، لننتقل الآن لتثبيت الإضافات والمُحملات اللازمة لهذا المشروع. سنبدأ أولًا بتثبيت الحزم التالية: npm install --save-dev node-sass sass-loader style-loader css-loader mini-css-extract-plugin ستكون مهمة كلّ حزمة من الحزم على الشكل التالي: node-sass: ستوفر هذه الحزمة ربط Node.js مع LibSass وهذا الأخير هو مترجم SASS. sass-loader: هو مُحمّل ملفات SASS إلى مشروعنا. css-loader: تستخدم هذه الحزمة لتفسير التعليمة @import و @url() بما تشير إليه في ملفات التنسيق. style-loader: تستخدم هذه الحزمة لإسناد الخصائص الموجودة في ملفات التنسيق إلى الوسوم الفعلية في شجرة DOM الافتراضية. mini-css-extract-plugin: هذه الحزمة تستخرج الخصائص الموجودة في ملفات التنسيق إلى ملفات منفصلة. إذ أنها تنشئ لكل ملف جافاسكربت ملف تنسيق الخاص به وهو لدعم ميّزة التحميل عند الطلب (On-Demand-Loading) لملفات التنسيق و خرائط الشيفرة البرمجية المحوّلة Source Maps (لمزيد من المعلومات عنها يمكنك الإطلاع على المقال التالي). سنحتاج إلى إضافة مُحملين، أحدهما للتنسيقات العامة والأخرى للتنسيقات المركبة. واللّذان يشار إليهما بملفات (SCSS modules). إذ أن هذه الأخيرة تعمل جيدًا مع المكتبات أو أطر العمل المُعتمدة على المكونات (component-based) مثل: React. سنضيف هذه الحزمة الملحقة إلى ملف الإعداد webpack.config.js على الشكل التالي: + const MiniCssExtractPlugin = require('mini-css-extract-plugin') module.exports = { plugins: [ + new MiniCssExtractPlugin({ + filename: isDevelopment ? '[name].css' : '[name].[hash].css', + chunkFilename: isDevelopment ? '[id].css' : '[id].[hash].css' + }) ] } نلاحظ أننا أضفنا خاصية أسماء الملفات المجزأة من أجل زيادة فعالية وسهولة خرق ذاكرة التخزين المؤقّت (Cache Bustring وهي تحِلُّ مشكلة التخزين المؤقت في المتصفح باستخدام إصدار معرف فريد للملف يميزه وذلك من أجل أخباره في حال وجود نسخة جديدة من الملف الذي حفظه فعلًا في هذه الذاكرة، وذلك من أجل أن يحملها تلقائيًا إلى ذاكرة التخزين المؤقت بدلًا من نسخة الملف القديمة الموجودة فيه)، والآن سنضيف الخواص المناسبة لملف الإعداد webpack.config.js من أجل عملية التحويل المناسبة للملفات: module.exports = { module: { rules: [ + { + test: /\.module\.s(a|c)ss$/, + loader: [ + isDevelopment ? 'style-loader' : MiniCssExtractPlugin.loader, + { + loader: 'css-loader', + options: { + modules: true, + sourceMap: isDevelopment + } + }, + { + loader: 'sass-loader', + options: { + sourceMap: isDevelopment + } + } + ] + }, + { + test: /\.s(a|c)ss$/, + exclude: /\.module.(s(a|c)ss)$/, + loader: [ + isDevelopment ? 'style-loader' : MiniCssExtractPlugin.loader, + 'css-loader', + { + loader: 'sass-loader', + options: { + sourceMap: isDevelopment + } + } + ] + } ] }, resolve: { - extensions: ['.js', '.jsx'] + extensions: ['.js', '.jsx', '.scss'] } } نلاحظ أن القاعدة الأولى المطبقة في الشيفرة السابقة ستُطبق على الملفات ذات الامتدادات .module.scss أو .module.sass إذ في البداية ستُحوّلُ ملفات SASS إلى CSS من خلال sass-loader ومن ثم ستُمرّر إلى الحزمة css-loader لمعالجة التعليمات @import() و url() ..إلخ. ومن ثمّ ستُسندُ الحزمة style-loader الخصائص المناسبة في DOM أو ستسند من خلال الحزمة Mini CSS Extract Plugin وذلك لإخراج ملفات التنسيق أثناء وضع النشر. القاعدة الثانية مشابهة جدًا للقاعدة الأولى باستثناء أننا لا نحوّل أسماء الأصناف. الآن أصبح ملف الإعداد جاهز نحتاج الآن لإنشاء ملف تنسيقات SASS للتأكد من أن كل شيء يعمل مثلما خطط له. أنشئ مجلد src ثم أنشئ بداخله ملفًا جديدًا باسم app.module.scss وأضف الشيفرة البرمجية التالية: .red { color: red; } أي عنصر سيأخذ الصنف red سيكون لونه أحمر. افتح الملف app.js واستدعي الملف السابق على الشكل التالي: import styles from './app.module' نلاحظ أن اسم الملف لا يحتوي على اللاحقة .scss وذلك لأننا سبق وأخبرنا Webpack بأن يأخذ بعين الاعتبار هذه اللاحقة وذلك في ملف الإعداد. ثم لنُضيف الآن الدالة التالية في نفس الملف app.js ليصبح على الشكل التالي: function App() { return <h2 className={styles.red}>This is our React application!</h2> } أليست سهلة جدًا؟ بغض النظر عن كيفية تحويل الحزمة css-loader اسم الصنف الّذي بنيناه (red) والّذي سيصبح مثل: _1S0lDPmyPNEJpMz0dtrm3F أو شيء من هذا القبيل، ولكنها ساعدتنا في مهمتنا مساعدةً كبيرة. لنُضيف الآن بعض التنسيقات العمومية. لننشئ ملف جديد وليكن global.scss في المجلد src ولِنفتحه ونُضيف بداخله الشيفرة البرمجية التالية: body { background-color: yellow; } هذا الملف سيجعل لون خلفية الصفحة الرئيسية لتطبيق الوِب خاصتنا أصفر. ولكن يجب أن نفتح الملف index.js ونستدعي ملف التنسيق السابق في بداية الملف. مثلما هو موضح في الشيفرة التالية: import './global' واخيرًا سنحصل على الخرج التالي: نلاحظ أن استخدام وحدات SCSS يتطلب منا جهدًا لا بأس به، ولكن بالموازنة مع كمية الفائدة الّتي تقدمها وحدات SCSS من سهولة في الصيانة لتطبيق الوِب في المستقبل فأعتقد أن الأمر يستحق هذا الجهد. بالإضافة إلى ذلك توفر أداة Webpack حزم أُخرى مخصصة لأي نكهة من نكهات ملفات التنسيق مثل الحزمة: postcss لتوفير دعم Postcss أو الحزمة Less لدعم ملفات التنسيق من نوع Less أيضًا. إدارة الصور تتيح Webpack لنا إمكانية إدارة الصور مثل: الخلفيات والأيقونات وذلك من خلال مُحمل الملفات. في البداية لنثبت أولًا مُحمل الملفات من خلال الأمر التالي: npm install --save-dev file-loader ومن ثم سنضيف إلى ملف الإعداد webpack.config.js المعلومات اللازمة لعمل هذا المُحمل كما في الشكل التالي: const path = require('path'); module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist'), }, module: { rules: [ { test: /\.css$/, use: [ 'style-loader', 'css-loader' ], }, + { + test: /\.(png|svg|jpg|gif)$/, + use: [ + 'file-loader', + ], + }, ], }, }; عند استيراد لصورة معينة ولتكن import MyImage from './my-image.png' ستضاف هذه الصورة إلى مجلد الخرج (output) الّذي حددناه في ملف الإعداد وسيحمل المتغير MyImage عنوان الرابط التشعبي الخاص بالصورة (URL) بعد معالجتها. عندما استخدمنا محمل ملفات التنسيق css-loader جرى نفس السيناريو السابق. أي أنه سيكون الخاصية التالية في ملفات التنسيق url('./my-image.png') والمُحمل سيلاحظ أن الملف هذا موجود محليًا وبذلك سيُحول './my-image.png' إلى المسار النهائي المُسند للخاصية (output) في ملف الإعداد webpack.config.js ومُحمل ملفات html (html-loader) سيتعامل مع <img src="./my-image.png" /> بنفس الطريقة. لنُضف الآن الصور اللازمة في مشروعنا. وهنا يمكنك استخدام أي صورة تريدها. لننسخ الصورة الّتي سنعمل عليها ولتكن icon.png إلى مشروعنا. لتصبح البنية الهرمية للمشروع على الشكل التالي: webpack-demo |- package.json |- webpack.config.js |- /dist |- bundle.js |- index.html |- /src + |- icon.png |- style.css |- index.js |- /node_modules ومن ثم لنستورد هذه الصورة في ملف src/index.js، ولنُضيفها إلى وسم <div> المتواجدة فيه، ولتصبح الشيفرة البرمجية للملف src/index.js على الشكل التالي: import _ from 'lodash'; import './style.css'; + import Icon from './icon.png'; function component() { const element = document.createElement('div'); // Lodash, now imported by this script element.innerHTML = _.join(['Hello', 'webpack'], ' '); element.classList.add('hello'); + // Add the image to our existing div. + const myIcon = new Image(); + myIcon.src = Icon; + + element.appendChild(myIcon); return element; } document.body.appendChild(component()); ومن ثم سنعدل ملف التنسيق من أجل تضمين الصور الّتي نعمل عليها ليصبح بذلك ملف التنسيق src/style.css على الشكل التالي: .hello { color: red; + background: url('./icon.png'); } ولننفذ الآن أمر البناء التالي: npm run build ... Asset Size Chunks Chunk Names da4574bb234ddc4bb47cbe1ca4b20303.png 3.01 MiB [emitted] [big] bundle.js 76.7 KiB 0 [emitted] main Entrypoint main = bundle.js ... إذا نفذت جميع الخطوات السابقة تنفيذًا صحيحًا يجب أن ترى الأيقونة (الصورة) مكررة في الخلفية، بالإضافة إلى وسم <img> بجوار النص "Hello webpack". وإذا فحصت الصورة ستجد أن اسمها الفعلي تغيّر إلى شيء شبيه بهذا الاسم 5c999da72346a995e7e2718865d019c8.png هذا يعني أن Webpack عثرت على الملف في مجلد src وعالجته. الخطوة المنطقية التالية الّتي سننفذها هي تصغير حجم الصور الّتي سنستخدمها في المشروع وتحسينها وذلك من خلال الحزم المساعدة المتوفرة مع الأداة Webpack مثل الحزمة Imagemin. ضغط الصور باستخدام Imagemin إن طريقة ضغط الصور الّتي سنستخدمها في المشروع هي باستخدام حزمة Imagemin إذ تعدّ هذه الحزمة خيارًا ممتازًا وذلك لأنها تدعم مجموعة متنوعة من أنواع الصور وسهلة التكامل مع الشيفرة البرمجية لأدوات البناء أو مجمع الحزم (الوحدات). سنعمل على مشروع جديد تجنبًا للمشاكل التي من الممكن أن تظهر لنا في حال أكملنا العمل على المشروع السابق. سنفرض أنك أنشأت مشروعًا جديدًا وثبّت الأداة Webpack تثبيتًا صحيحًا. في البداية لنثبت هذه الحزم عبر التعليمة التالية: npm install imagemin-webpack-plugin copy-webpack-plugin --save-dev ملاحظة: أن الحزمة copy-webpack-plugin ستساعدنا على نسخ الصور من مجلد images/ إلى مجلد dist/ وهو مجلد النشر وهو اختصار لكلمة distribution. لننشئ ملف الإعداد webpack.config.js ولنعدله ليتناسب مع الحزم الجديدة ليصبح على الشكل التالي: const ImageminPlugin = require('imagemin-webpack-plugin').default; const CopyWebpackPlugin = require('copy-webpack-plugin'); const path = require('path'); module.exports = { entry: './index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist') }, plugins: [ new CopyWebpackPlugin([{ from: 'img/**/**', to: path.resolve(__dirname, 'dist') }]), new ImageminPlugin() ] } تعدّ هذه الطريقة من أسهل الطرق لإعداد لهذه الحزمة. ولنضغط الصور الّتي في المجلد images/ وننسخها إلى المجلد dist/ من خلال تنفيذ الأمر التالي: webpack --config webpack.config.js --mode development نلاحظ من التعليمة السابقة أن وضع التنفيذ الحالي هو وضع التطوير، ولكن ما الّذي سيحدث إذا نفذنا التعليمة في وضع النشر؟ لنرى ما الّذي سيحدث. webpack --config webpack.config.js --mode production ستُظهر لنا في هذه المرة أداة Webpack تحذيرًا يخبرك بأن الصور ذات النوعية PNG الخاصة بك لا تزال تتجاوز الحد الأقصى للحجم الموصى به، على الرغم من بعض الضغط. ولذلك سنحتاج إلى ضبط اليدوي لعملية الضغط لهذا النوع من الصور لتصبح أفضل مما قبل. لنعدل قيمة الضغط للصور ذات النوعية PNG لتصبح 50% وسيكون ملف الإعداد على الشكل التالي: const ImageminPlugin = require('imagemin-webpack-plugin').default; const CopyWebpackPlugin = require('copy-webpack-plugin'); const path = require('path'); module.exports = { entry: './index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist') }, plugins: [ new CopyWebpackPlugin([{ from: 'img/**/**', to: path.resolve(__dirname, 'dist') }]), new ImageminPlugin({ pngquant: ({quality: [0.5, 0.5]}), }) ] } نلاحظ أننا مررنا للغرض Pngquant مجالًا لقيمة جودة الصور من 0.5 إلى 0.5 أي من الحدّ الأدنى إلى الحدّ الأعلى لجودة الصورة. إذ أن الحدّ الأعلى الأفتراضي 1 والحدّ الأدنى الافتراضي 0. ولكن ماذا لو أردنا أن نضبط الجودة (نسبة الضغط) المستخدمة لبقية الأنواع من الصور بنفس الطريقة السابقة؟ في الحقيقة يوجد بعض الملحقات الإضافية المساعدة لهذا الغرض تحديدًا والّتي سنستعرضها في هذا الجدول: نوع الصور ضغط مع خسارة بعض معلومات الصورة ضغط بدون خسارة بعض معلومات الصورة JPEG imagemin-mozjpeg imagemin-jpegtran PNG imagemin-pngquant imagemin-optipng GIF imagemin-giflossy imagemin-gifsicle SVG Imagemin-svgo WebP imagemin-webp table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; } نلاحظ أن لدينا نوعين من طرق ضغط الصور وهما كالتالي: ضغط مع خسارة بعض معلومات الصورة (Lossy): يؤدي استخدام هذا النوع إلى تصغير حجم الصورة تصغيرًا ملحوظًا، ولكن مع فقدان بعض معلومات الصورة. ضغط بدون خسارة بعض معلومات الصورة (Lossless): يؤدي استخدام هذا النوع إلى تصغير حجم الصورة تصغيرًا أقل من الطريقة السابقة، ولكن بدون فقدان بعض معلومات الصورة. إن طريقة الضغط الإفتراضية للحزمة imagemin للصور ذات النوعية JPEG هي الطريقة imagemin-jpegtran، سنستعرض كيفية ضغط الصور باستخدام الحزمة الأخرى وهي imagemin-mozjpeg. في البداية يجب علينا تثبيت هذه الحزمة من خلال الأمر التالي: npm install imagemin-mozjpeg ولنُعدل ملف الإعداد webpack.config.js لضبط طريقة ضغط الصور وفق ما نريده. ليصبح الملف على الشكل التالي: const imageminMozjpeg = require('imagemin-mozjpeg'); const ImageminPlugin = require('imagemin-webpack-plugin').default; const CopyWebpackPlugin = require('copy-webpack-plugin'); const path = require('path'); module.exports = { entry: './index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist') }, plugins: [ new CopyWebpackPlugin([{ from: 'img/**/**', to: path.resolve(__dirname, 'dist') }]), new ImageminPlugin({ pngquant: ({quality: [0.5, 0.5]}), plugins: [imageminMozjpeg({quality: 50})] }) ] } نلاحظ أن نسبة الضغط في هذه الحزمة من 100 إذ مررنا القيمة 50 وبذلك نخبره بأننا نريد ضغط الصور من النوع JPEG بنسبة 50%. ولنُنفذ الآن التعليمة التالية لنرى النتيجة: webpack --config webpack.config.js --mode production تهانينا في حال نفذّت جميع الخطوات تنفيذًا صحيحًا يجب أن تكون الصور مضغوطة وفقَ المطلوب. إن الأداة Webpack تحذرنا من الصور ذات الحجم الكبير، ولكنها لا تستطيع إخبارنا إن كانت الصور مضغوطة أم لا، ولهذا السبب سنستخدم الأداة Lighthouse للتحقق من التغييرات المُنفّذة. تسمح لنا الأداة Lighthouse من التحقق من ترميز الصور بكفاءة وإن كانت الصور الموجودة في صفحتك مضغوطة على نحوٍ أمثلي أم لا (لمزيد من المعلومات حول هذه الأدة ننصحك بالاطلاع على هذا المقال المفصّل). وستكون النتيجة مشابهة للصورة التالية: بنفس الطريقة يمكنك استخدام بقية الحزم لتخصيص قيمة الضغط المطلوب للأنواع الأخرى من الصور المستخدمة في مشروعك. إدارة الخطوط يمكننا إدارة الخطوط أيضًا مع Webpack من خلال مُحمل الملفات الّذي ثبتناه في إدارة الصور (سنعمل على نفس المشروع الّذي بنيناه في فقرة إدارة الصور)، والذي سيأخذ أي ملف تضعه له في ملف الإعداد ويضعه في المسار النهائي المُسند للخاصية (output) في ملف الإعداد webpack.config.js. أي أن مُحمل الملفات قادر على التعامل الخطوط أيضًا. لنعدل الآن ملف الإعداد webpack.config.js ليصبح على الشكل التالي: const path = require('path'); module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist'), }, module: { rules: [ { test: /\.css$/, use: [ 'style-loader', 'css-loader' ], }, { test: /\.(png|svg|jpg|gif)$/, use: [ 'file-loader', ], }, + { + test: /\.(woff|woff2|eot|ttf|otf)$/, + use: [ + 'file-loader', + ], + }, ], }, }; أضف خطًا معينًا إلى مجلد المشروع لتصبح البنية الهرمية للمشروع على الشكل التالي: webpack-demo |- package.json |- webpack.config.js |- /dist |- bundle.js |- index.html |- /src + |- my-font.woff + |- my-font.woff2 |- icon.png |- style.css |- index.js |- /node_modules من خلال التعديلات الّتي أجريناها على ملف الإعداد لمُحمل الملفات يمكننا الآن جعل التعليمة الّتي تصرح بها عن المسارات للخطوط @font-face إلى الشكل التالي url(...). سيعاد توجيه هذه المسارات إلى المسار النهائي المُسند للخاصية (output) في ملف الإعداد webpack.config.js تمامًا كما تعامل مع الصور. إذا سيصبح ملف التنسيق src/style.css على الشكل التالي: + @font-face { + font-family: 'MyFont'; + src: url('./my-font.woff2') format('woff2'), + url('./my-font.woff') format('woff'); + font-weight: 600; + font-style: normal; + } .hello { color: red; + font-family: 'MyFont'; background: url('./icon.png'); } لنرى كيف سيتعامل Webpack مع الخطوط ولننفذ أمر البناء على الشكل التالي: npm run build ... Asset Size Chunks Chunk Names 5439466351d432b73fdb518c6ae9654a.woff2 19.5 KiB [emitted] 387c65cc923ad19790469cfb5b7cb583.woff 23.4 KiB [emitted] da4574bb234ddc4bb47cbe1ca4b20303.png 3.01 MiB [emitted] [big] bundle.js 77 KiB 0 [emitted] main Entrypoint main = bundle.js ... افتح ملف index.html وانظر إلى النص "Hello webpack" كيف تغير شكل الخط ليصبح مطابق لشكل الخط الجديد. أما الآن لننتقل إلى واحدٍ من أبرز استخدامات مجمع الحزم Webpack وهو تحويل الشيفرات البرمجية الخاصة بالإصدارات الحديثة من المواصفات القياسية للغة جافاسكربت مثل ES6 إلى ES5. تحويل الشيفرة البرمجية باستخدام Babel بعد أن تعرفنا كيف ندير جميع التبعيات الموجودة في شيفرات جافاسكربت البرمجية من خلال Webpack. لا بُدّ لنا من جعل هذه الشيفرة البرمجية تعمل على جميع المتصفحات وذلك لكي نضمن أنه مهما يكن المتصفح الّذي سيستخدمه العميل (أو المستخدم) ستظهر النتيجة المطلوبة تمامًا مثل ما نريده. عملية التحويل (Transpiling): هي عملية تغيير الشيفرة البرمجية من إصدار معين إلى إصدار أقدم وذلك لضمان عمل هذه الشيفرة البرمجية على المتصفحات كلها. مقتطف من المخطط التفصيلي لدعم المتصفحات للمميزات الإصدار ES5 والإصدار ES6 من لغة جافاسكربت. سننشئ مشروعًا بسيطًا لشرح طريقة استخدام هذا المُحول: mkdir webpack-demo2 cd webpack-demo2 npm init -y npm install webpack webpack-cli --save-dev ملاحظة: لن نتطرق للتفاصيل نظرًا لأنها شُرحت في بداية هذا الدليل. ستكون البنية الهرمية للمشروع على الشكل التالي: webpack-demo2 |- package.json + |- index.html + |- /src + |- index.js في البداية سنحتاج لتثبيت ثلاث حزم للتعامل مع المحول Babel وستكون على الشكل التالي: npm install babel-core babel-loader babel-preset-env --save-dev الحزمة babel-core: تحتوي على الشيفرة البرمجية للنواة الأساسية للمُحول Babel. الحزمة babel-preset-env: تحتوي على الشيفرة البرمجية الّتي ستمكن النواة الأساسية للمحول Babel من تحويل الشيفرة البرمجية للجافاسكربت ذات الإصدار 6ES إلى الإصدار ES5. الحزمة babel-loader: أو (مُحمل المحول) وهي الّتي ستستخدمها Webpack في تحميل المحول والتعامل معه في عملية التحزيم (Bundling). أما الآن سننشئ ملف babelrc. والذي سيطلب من المحول Babel أن يستخدم الحزمة babel-preset-env أثناء عملية التحويل وسيكون الملف babelrc. على الشكل التالي: +{ "presets": ["env"] } ومن ثم سننشئ ملف الإعداد webpack.config.js ونطلب من Webpack الإستعانة بالمحمل Babel أثناء تحزيمه. وسيكون ملف الإعداد webpack.config.js على الشكل التالي: + const path = require('path'); + module.exports = { + entry: { main: './src/index.js' }, + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'main.js' + }, + module: { + rules: [ + { + test: /\.js$/, + exclude: /node_modules/, + use: { + loader: 'babel-loader' + } + } + ] + } + }; سنضع الآن في ملف src/index.js شيفرة برمجية من الإصدار ES6، وليصبح على الشكل التالي: + const fn = () => "Arrow functions're Working!"; + alert(fn()); ومن ثم ننفذ الأمر التالي: npm run dev ومن ثم لنفتح ملف dist/main.js نلاحظ أن الدالة السهمية (Arrow function) تُحوّلُ إلى دالة عادية وذلك وفق الطريقة المتبعة في الإصدار ES5 من لغة جافاسكربت. وسيكون محتوى الملف dist/main.js على الشكل التالي: 'use strict'; var fn = function fn() { return "Arrow functions're Working!"; }; alert(fn()); وبذلك استطعنا استخدام مميزات الإصدار الحديث من لغة جافاسكربت ES6 وتحويله إلى إصدار أقدم ES5 لتفهمه المتصفحات القديمة. هل من المخاطرة تعلم Webpack؟ بعد أن تعرفنا سريعًا على Webpack يمكن لسائل أن يسأل ماذا لو أنني تعلمت العمل على Webpack وبعدها ظهرت أداة جديدة أفضل منها أو تغطي مجال أوسع في العمل؟ أليس ذلك هدرًا لوقتي ولجهدي؟ صراحة، الشيء الوحيد الّذي استطيع تأكيده لك هو أن عجلة التحديث في مجال الواجهات الأمامية سريع الدوران، وما إن تظهر مشكلة ما حتى يتسارع المطورون لحلها فمن الممكن أن تظهر أداة أقوى منها (وهذا لا اتوقعه أن يحدث قريبًا وخصيصًا مع الدعم الكبير الّتي تشهده هذه الأداة من الشركات العملاقة مثل فيسبوك) هذا وارد الحدوث، ولكن هذا الأمر لا يُلغي فكرة أهمية تعلمك العمل مع هذه الأداة إذ أن معظم الأدوات الّتي تظهر لحل مشكلة معينة تكون متشابهة في البنية وطريقة العمل مع بعض الإختلافات. أي إن تعلمك العمل مع هذه الأداة سيجهزك للعمل مع الأدوات الأخرى المستقبلية. ومن يعلم ربما تكون أنت صاحب فكرة الأداة الجديدة الّتي ستتفوق على Webpack! والسؤال الأخر الّذي من الممكن يخطر في أذهاننا أيضًا هل تستحق هذه الأداة الوقت المبذول لتعلمها؟ أو بتعبير أخر هل النتائج الّتي سأجنيها من استخدام هذه الأداة ستغطي على الوقت المبذول لتعلمها؟ في الحقيقة لا أبالغ أن قُلت أنه من الواجب عليك تعلمها فورًا وبدون تردد وخصيصًا إن كُنت تتخذُ من مهنة تطوير الواجهات الأمامية (أو مطور برمجيات عمومًا) مصدرًا أساسيًا للدخل في حياتك! لأنها ستعزز كثيرًا من إنتاجيتك وسرعتك وجودة العمل المُسلّم وهذا ما ستشعر به من أول شهر من استخدامك لها. في الحقيقة إن مجيئ Webpack بكل هذه المميزات وقابلية التخصيص والإمكانيات أطاح بجميع المنافسين لديها مثل Browserify، ليس ذلك فحسب بل تخطّت إمكانياتها حدود الفكرة التي أُنشأت من أجلها لتهدد أدواتٍ مساعدةٍ أخرى كأدوات البناء مثل Gulp (والتي تحدثنا عنها في مقالٍ سابق) إذ أنه يمكننا من خلال NPM Script أن نعوّض عمل Gulp ولكن هذا سيكون على حساب الصعوبة إذ أن العمل مع Webpack ليس بسهولة العمل مع Gulp. ننصحك بالانتقال بعد الانتهاء من هذا المقال إلى مقال، أيهما أفضل كأداة مساعدة Webpack أم Browserify مع Gulp؟ الذي يشرح الفروقات بين كل هذه الأدوات وحالات استخدامها وغيرها من التفاصيل المفيدة. بالإضافة إلى ذلك وفرت لنا Webpack إمكانية التكامل مع أدوات مساعدة أُخرى مثل أداة السقالة Yeoman (والتي تحدثنا عنها في مقالٍ سابق) وبذلك نحصل على فوائد كِلتا الأداتين ونختصر وقتنا اختصارًا ملحوظًا. وبذلك يمكننا القول بأن المرونة الموجودة لدى Webpack وملحقاتها الرائعة والدعم المتميز من مجتمعها ومستخدميها جعلها من أقوى الخيارات الموجودة في الساحة حاليًا. الخاتمة بدأنا هذا الدليل ونحن لا نعرف شيئًا عن Webpack، ثم حفرنا عميقًا في تاريخها لنفهم تمامًا ما هي الأسباب الّتي أدت لظهور أداةٍ قوية كهذه الأداة، ثم بدأنا بتجهز المتطلبات اللازمة لتشغيلها، وفصلّنا في جميع المفاهيم الأساسية لها، وتعلمنا كيفية استخدامها في إدارة ملفات التنسيق والصور والخطوط ، وكيفية استخدامها لضغط الصور قبل رفعها وتعلمنا طريقة استخدامها لتحويل الشيفرة البرمجية من إصدار حديث إلى إصدار أقدم منه. وأخيرًا ناقشنا أهمية تعلمها وفوائده. وختامًا إن هذا الدليل لم يغطي كافة المميزات والجوانب الموجودة في أداة Webpack وإنما فقط أردنا من هذا الدليل أن يلفت نظرك لأهمية هذه الأداة وقدراتها المتميزة في مساعدتك في العمل على المشاريع وخصوصًا الكبيرة منها. بل وجعلها يدك اليمنى في أي مشروع جديد وبالتأكيد إن كلّ هذه المعلومات لن تُشبع فضول المطوّر المحترف ولذلك يمكنك دومًا الاطلاع على التوثيق الرسمي للأداة لمزيد من المعلومات. المصادر التوثيق الرسمي لأداة Webpack. التوثيق الرسمي لمدير الحزم npm. المقال Using Imagemin with webpack لصاحبته Katie Hempenius. المقال How to configure SCSS modules for Webpack لصاحبه Jon Preece. المقال Brief history of JavaScript Modules لصاحبه SungTheCoder.
-
- 4
-
- webpack
- أدوات بناء
-
(و 4 أكثر)
موسوم في:
-
شهد قطاع صناعة الألعاب في الأونة الأخيرة تطورًا كبيرًا وفي كلّ عام يزداد الإقبال عليها أكثر مما قبل؛ فصُمّمت الألعاب الإلكترونية في عام 1960، إذ كانت تحتاج لحواسيب ضخمة لم تكُ متاحة للعموم آنذاك. وبعد ذلك بعشر سنوات تطورت الألعاب لتصبح تجارية في عام 1970، وذلك مع مجيء أول أجهزة الألعاب الإلكترونية والحواسيب المنزلية. ونظرًا إلى القدرات المنخفضة للحواسيب آنذاك كان بإمكان مطوّر واحد أن يُنتج لعبة بكاملها. ولكن عند مطلع القرن 21 ومع تزايد قدرات الحواسيب على معالجة البيانات، وتصاعد توقعات المستهلك أصبح من المستحيل أن ينتج مطور واحد لعبة تناسب هذا العصر بل ستحتاج إلى فريق عمل متكامل من منتجين ومصممين ومبرمجين ومهندسي صوت ومؤلفين موسيقيين ومصممي المراحل والعديد من الإختصاصات الأخرى لإنتاج لعبة تناسب هذا العصر. يوجد في وقتنا الحالي العديد من منصات الألعاب، مثل أجهزة البلايستيشن (PlayStation) أو أجهزة الإكس بوكس (Xbox) إذ أن لكل منصة منصة ألعاب صُممت لتُعلب عليها فقط، والبعض الأخر من الألعاب مخصصة لأجهزة الحواسيب (مثل الويندوز أو الماك أو اللينكس)، أو بعض الألعاب الأخرى المخصصة لمتصفح الوِب (تدعى ألعاب المتصفح [Browser Game]) والّتي سنناقشها اليوم ونعلمك كيفية إنشاء لعبة بسيطة تعمل على المتصفح. ألعاب المتصفح (Browser Game) عادةً ما تُعرف ألعاب المتصفح بألعاب الوِب، وهي نمط من الألعاب الّتي تُلعب باستخدام متصفح الإنترنت (مثل غوغل كروم أو أوبرا أو أي متصفح أخر) وذلك من خلال طلب عنوان موقع اللعبة، وبعد تحميل الموقع تظهر لدينا اللعبة ويمكننا بعدها البدء باللعب. غالبًا ما تُصمم هذه الألعاب باستخدام تقنيات الوِب القياسية (مثل: HTML5، و CSS3، و JavaScript وغيرها) أو بتقنيات أخرى مثل أدوبي فلاش ولكن جميع هذه التقنيات لديها إمكانيات محدودة؛ وذلك بسبب مسائل التكاملية والجودة مع المتصفح، ولكن مع ذلك فإن هذه التقنيات هي الّتي تجعل هذه الألعاب تعمل على جميع المتصفحات. واجهت ألعاب المتصفح منذ بدايتها العديد من التحديات، بعضها من ناحية الرسوميات، وبعضها الأخر من ناحية الأداء والسرعة، وبعضها من ناحية الضغط على المخدمات الّتي تستضيف هذه الألعاب (وذلك بسبب كثرة العناصر المحملة من ملفاتٍ وصورٍ والعديد من المكونات الأخرى الموجودة في اللعبة). ولكن ما لبثت هذه التحديات بالظهور حتى أُوجدت لها حلولٌ عديدة فعّالة، ولكن مع ذلك فإنها لم تستطع أن تنافس الألعاب التقليدية وخصوصًا مع الفارق الكبير بين طريقة تعامل كلّ منهم مع موارد الحاسوب. حجزت ألعاب المتصفح لنفسها مكانًا في الألعاب التنافسية متعددة اللاعبين (مثل لعبة الشطرنج ولعبة البلياردو وعلى غرارها من الألعاب)، وهي عادةً ما تركز على الجانب الإجتماعي من اللعبة أكثر من اللعبة بحد ذاتها، إذ تمنح إمكانية الدردشة بين اللاعبين أثناء اللعب، وإمكانية إنشاء ملفاتهم الشخصية (User Profile)، وبالإضافة إلى نظام تنافسي مبني على النقاط والكؤوس مما يعزز روح المنافسة ويضيف متعةً أخرى للعبة. نشأة وتاريخ صناعة ألعاب المتصفح وتطورها ظهرت ألعاب متصفح الوِب في بداية التسعينات من القرن الماضي ولكن لم تبدأ بالانتشار إلا في عام 1996 تزامنًا مع ظهور برنامج Macromedia Flash (المستخدم لتعديل الرسوميات) ولغة البرمجة أكشن سكربت (Action Script) وسرعان ما شكّلا مع بعضهما أول أدوات صناعة ألعاب المتصفح آنذاك. وبعد عام واحد أطلقت شركة Sun Microsystems موقعها الخاص هوت جافا (HotJava) لاستضافة الشيفرات البرمجية المحمولة للغة جافا (Java applet) وتعمل هذه الألعاب على جميع المتصفحات الّتي تدعم لغة جافا، والّذي كان شرارة البدء لعصر ألعاب المتصفح. وبعد النجاح الكبير لألعاب المتصفح والّذي لوحظ من خلال الازدياد الكبير لعدد الأشخاص المنضمين لمواقع ألعاب المتصفح والإقبال الشديد عليها، مما أدى لاهتمام بعض الشركات الكبرى لهذا النمط من الألعاب من بينها شركة ياهو والّتي انضمت إلى هذا المجال لتطلق موقعها Yahoo! Games في عام 1998، والّذي يحتوي على العديد من الألعاب ولتصبح بذلك من أوائل شركات صناعة ألعاب المتصفح آنذاك. وفي عام 2016 أعلنت الشركة عن إغلاق الموقع بسبب عدم تمكن الفرق البرمجية لشركة ياهو من حل المشاكل المتزايد للتقنيات المستخدمة في بناء الألعاب وعدم تلبية هذه الألعاب لمتطلبات الوِب الحديثة كلّ هذه العوامل أدت إلى إغلاق موقع في شهر فبراير من عام 2016. الفرق بين ألعاب المتصفح والألعاب التقليدية تختلف ألعاب المتصفح عن الألعاب التقليدية بعدة نواحي أهمها: 1. طريقة اللعب إن ألعاب المتصفح تتطلب الاتصال الدائم بالإنترنت طوال زمن اللعب لأنه من دون الاتصال بالإنترنت لا يمكن تشغيل الألعاب وذلك لأنها مخزنة على مخدمات موجودة عبر الإنترنت وليس في الحاسوب الشخصي. على عكس الألعاب التقليدية إذ أن أغلبها لا يتطلب اتصالًا دائمًا بالإنترنت. وعلى صعيد أخر في دراسة أجراها باحثون من جامعة ماينتس الألمانية على 8203 شخص من لاعبي لعبة المتصفح الشهيرة ترافيان (Travian) تبين لهم أن ألعاب المتصفح يُستمتع بها أساسًا بسبب الجانب الاجتماعي منها الّذي ينطوي عليه خصائص الوقت، والمرونة العالية في العب، وسهولة الاستخدام، والتنافس بين اللاعبين، وكما أشارت الدراسة نفسها أن سهولة الوصول إلى هذا النوع من الألعاب (لعبة ترافيان-وبشكل عام جميع ألعاب المتصفح) أدت إلى زيادة عدد مرات فتح اللعبة بالموازنة مع الألعاب التقليدية، ولكن مدة جلسات اللعب المتواصلة قصيرة. 2. مصادر الدخل للألعاب يختلف اعتماد ألعاب المتصفح على مصادر الدخل فمنها من يعتمد على عرض إعلانات داخل اللعبة (مثل إعلانات غوغل أدسنس وغيرها)، أو إلى عرض عمليات الشراء داخل اللعبة للحصول على مميزات أو نقاطٍ تتفوق بها على المنافسين، ومنها من تعتمد على الاشتراك الشهري أو السنوي، والبعض الأخر يعتمد على شراء اللعبة لمرة واحدة فقط. وفي بعض الحالات النادرة تكون الألعاب مجانية بالكامل. وفي المقابل فإن غالبية الألعاب التقليدية تعتمد على شراء اللعبة لمرة واحدة بالإضافة لعمليات الشراء داخل اللعبة في حال كانت تُلعب على الإنترنت. 3. استخدام موارد الحاسوب تعدّ ألعاب المتصفح ذات رسوميات منخفضة بالموازنة مع الألعاب التقليدية وذلك من كونها تتعامل مع إمكانيات المتصفح والموارد الّتي يستخدمها، على عكس الألعاب التقليدية الّتي تتعامل مع موارد الجهاز تعاملًا مباشرًا ولكن على صعيد أخر تستهدف ألعاب المتصفح شريحة كبيرة من المستخدمين وعلى كافة الأجهزة وخصوصًا أصحاب الأجهزة الضعيفة والمتوسطة. وعلى صعيدٍ آخر إن غالبية الألعاب التقليدية تحجز مساحة كبيرة من سعة القرص الصلب على عكس ألعاب المتصفح، كما تستهلك الألعاب التقليدية قدرًا كبيرًا من ذاكرة الوصول العشوائي (RAM) بالموازنة مع ألعاب المتصفح مما يجعل ألعاب المتصفح خفيفة جدًا على موارد الحاسوب مهما كانت مواصفاته. مشاكل وحلول وكما ذكرنا سابقًا عانت ألعاب المتصفح العديد من المشاكل فما أن تُحلّ مشكلة حتى تظهر أخرى. وإن من أبرز المشاكل الّتي واجهتها هي: معالجة الرسوميات إن تصاعد توقعات المستهلكين أثقلت كاهل المطورين من ناحية رفع جودة الألعاب وتعزيز تجربة المستخدم، وفي ظل الإمكانيات المحدودة لألعاب المتصفح والّتي جعلت من عملية مواكبة تطور الألعاب عمليةً شاقةً جدًا. ومن أكثر المشاكل الّتي تواجه مطوري ألعاب المتصفح هي معالجة الرسوميات ذات الأبعاد الثنائية أو الثلاثية والّتي شكلت لفترة طويلة سدًا منيعًا في تطور هذا النمط من الألعاب. كان ذلك إلى أن جاءت تقنية WebGL والّتي سمحت لنا بكتابة رسوميات ثلاثية الأبعاد في صفحات الوِب باستخدام جافا سكربت (JavaScript) عبر العنصر canvas (وكما تحدثنا في عدة مقالات عن هذه التقنية وأهميتها). ولكن تقنية WebGL لا تُنشِئ «عناصر» على الصفحة، إذ تتعامل مباشرةً مع البكسلات؛ ولذا نقول عن تقنية WebGL أنّها تقنيةٌ منخفضة المستوى: إذ أنّها توفِّر تحكمًا دقيقًا بالفضاء ثلاثي الأبعاد. والجدير بالذكر أن هذه التقنية، WebGL، مبنية على الواجهة البرمجية OpenGL ES والّتي هي نسخة فرعية من OpenGL. ولكن ماهي OpenGL؟ (Open Graphics Library) هي عبارة عن واجهة برمجية رسومية مستقلة تعمل على مختلف أنظمة التشغيل، ومتوافقة مع عدة لغات برمجية، وصُممت لإنتاج رسوميات ثنائية وثلاثية البعد سواءً المعقدة منها أو البسيطة من خلال تركيبها من مكونات هندسية بسيطة مثل المضلعات أو المثلثات أو المستقيمات …إلخ، وكما أنها تشكّل همزة الوصل بين وحدة المعالجة المركزية CPU ووحدة معالجة الرسوميات GPU. أما OpenGL ES (والتي تعرف اختصارًا للعبارة Open Graphics Library for Embedded Systems) فهي نسخة فرعية من OpenGL الرئيسية، ومبنية خصوصًا للأنظمة المدمجة مثل: الأجهزة الذكية وأجهزة التابلت …إلخ. وهي الّتي بنيت عليها تقنية WebGL. ومع أن WebGL مبنية على OpenGL ولكن يوجد فرق بين إمكانيات كلٍّ منهما وهذه بعض النقاط الّتي تختلف فيها WebGL عن OpenGL. تملك WebGL المميزات التالية: مبنية بلغة جافا سكربت. تُستخدم خصيصًا لمعالجة رسوميات المتصفح. إمكانياتها قليلة بالموازنة مع OpenGL لأنها مبينة على نسخة فرعية من OpenGL وهي OpenGL ES. سهلة التعلم والاستخدام. أما OpenGL فلديها المميزات التالية: مبنية بلغة C. تُستخدم خصيصًا لمعالجة رسوميات تطبيقات سطح المكتب والألعاب. إمكانياتها كثيرة نظرًا لأنها تتخاطب مع العتاد تخاطبًا مباشرًا. صعبة التعلم والاستخدام. وبناءً على ذلك تعدّ WebGL خيارًا جيدًا لمعالجة الرسوميات في المتصفح وتصييرها. الضغط على الخادم تعدّ مشكلة الضغط على المخدم وتوقفه عن العمل من أبرز المشاكل المطروحة، إذ أن بنية ألعاب المتصفح المؤلفة من ملفاتٍ (HTML أو CSS أو JavaScript أو أي ملفات أخرى) وصورٍ والمقاطع الصوتية وفي بعض الأحيان مقاطع فيديو تجعل من استقرار المخدم المضيف وبقائه بالخدمة مهمةً صعبةً للغاية بل شبه مستحيلة، وخصوصًا في حال استقبال الموقع عددًا كبيرًا من الزوار في آنٍ واحد. ولقد ظهر في الأونة الأخير حلٌ عملي ومناسب جدًا لهذه المشكلة وهو استخدام الاستضافة السحابية. ولكن ما هي الاستضافة السحابية؟ هي خدمة استضافة تُمكّن العملاء من الاستفادة من خدماتها بقدر ما يحتاجون إليه، اعتمادًا على مطالب مواقعهم على الإنترنت، وأنهم سيدفعون فقط مقابل ما يستخدمونه. وتعتمد الاستضافة السحابية على مفهوم موازنة الحمل (Load Balancing) والّذي يوزع الأحمال (الزوار) على أكثر من خادوم لتحقيق أكبر سرعة استجابة لموقع الوِب. ومن أبرز المميزات الّتي تقدمها الاستضافة السحابية هي: الوثوقية: بدلًا من استضافة الموقع على خادم واحد مادي يستضاف الموقع على قسم افتراضي إذ يستمد موارده من شبكةٍ واسعةٍ من الخوادم المادية. أي أنه في حال توقف أحد الخوادم لن يؤثر ذلك على توافر الموقع وسوف يستمر بسحب الموارد من شبكة الخوادم المتبقية. المرونة: تبقى موارد الموقع متاحة حتى في حال ارتفاع حركة الزوار أو ازدياد الطلبات على الموقع، أو طلب موارد إضافية تبقى إمكانية الوصول للموارد سلسة. الدفع على قدر الاستخدام: يدفع العميل فقط على قدر ما يستخدم، أي ليس هنالك أي هدر في حال كان الطلب أقل من المعتاد. صناعة ألعاب المتصفح تستخدم العديد من اللغات البرمجية لتصميم ألعاب المتصفح ولكل لغة خواص معينة تميزها عن الأخرى. فعلى سبيل المثال يمكنك بناء لعبة على محرك Unity وذلك باستخدام برنامج Unity3D باستخدام لغة #C أو لغة ++C. والّذي يمكنك بناء ألعاب متميزة نظرًا لإمكانياته القوية والكبيرة (ولقد تناولنا في سلسلة سابقة عن أبرز مميزات هذا البرنامج ننصحك بالاطلاع عليها). وكما يمكنك استخدام تقنيات الوِب القياسية مثل HTML5/CSS أو PHP أو حتى لغة جافاسكربت (JavaScript)، والّتي تعدّ من الخيارات الجيدة لبناء هذه النوع من الألعاب نظرًا من كونها تتيح إمكانية عالية لتفاعل المستخدم مع الموقع (في حالتنا اللعبة). وكما لديها العديد من أُطر العمل المتاحة وشعبية كبيرة تجعلها من أبرز اللغات لتطور ألعاب المتصفح. وبعد أن تعرفنا عن ماهية ألعاب المتصفح، وتجولنا في تاريخها ونشأتها، وبيّنا أهم الفروقات بينها وبين الألعاب التقليدية، وناقشنا أهم المشاكل الّتي تتعرض لها والحلول المناسبة، ما رأيك بأن نُكّمل هذا المشوار ونبدأ ببناء أول لعبة لنا؟ بكل تأكيد أنت متحمس لخوض هذه المغامرة ومعرفة تفاصيلها إذًا اِربط الأحزمة واستعد للانطلاق. سنستخدم لغة جافاسكربت لإنشاء هذه اللعبة. وذلك لإنّها تتطلّب مجهودًا قليلًا بالموازنة مع بقية الخيارات المتاحة وتعمل في كلّ البيئات. سوف ننطلق من لا شيء وننشئ لعبةً ممتعة دون أن نستغرق أي وقت يُذكر. تتألف هذه السلسلة حول صناعة لعبة عبر جافاسكربت من خمس مقالات هي: حلقات اللعبة التكرارية جلب المدخلات من اللاعب كشف التصادمات الجاذبية إنشاء السلالم وختام اللعبة نرجو لك قراءة ممتعة! اقرأ أيضًا المقال التالي: حلقات اللعبة التكرارية تعرف على أشهر محركات الألعاب Game Engines
- 1 تعليق
-
- 3
-
يتطلب كلّ موقع الكتروني استضافة وذلك ليكون متاحًا للزوار عبر شبكة الإنترنت. يُدرك المصممون والمدونون أهمية ذلك تمامًا وأغلبهم يستخدم المواقع الإلكترونية لنشر أعمالهم وعلاماتهم التجارية. عندما يتعلق الأمر باختيار مزود استضافة المواقع فهنالك العديد من الخيارات المتاحة بعضها مجاني ويوفر لك ميزات محدودة جدًا بينما البعض الأخر قد يكلفك القليل من المال ولكن يقدم ميزات غير محدودة. وأنا أفضل خدمات لاستضافة المدفوعة وذلك لتوفيرها العديد من التسهيلات بالموازنة مع المزودات الرخيصة أو المجانية. 1. BlueHost وهو أحد الشركات الرائدة في تقديم خدمات استضافة المواقع والّتي توفر استضافة ووردبريس ممتازة وموصّى بها من قِبل شركة ووردبريس الرسمية، وتستطيع إدارة ازدياد عدد الزوار إلى موقعك بدون إبطائه. عروض الشركة: استضافة مشتركة - يعدّ خيارًا مناسبًا للمصممين والمدونين الّذين لا يتوقعون ملايين الزيارات شهريًا. استضافة VPS والاستضافة المخصصة - تعدّ خيارًا مناسبًا للأعمال الّتي تحتاج المزيد من موارد المخدم. ويوجد خبراء جاهزين لمساعدتك على مدار ساعة وطيلة أيام الأسبوع. استضافة الموزّعين - يعدّ هذا الخيار مناسبًا للأشخاص الّذين يعملون في مجال بيع خدمات الاستضافة حيث يمكنك شراؤها واستخدام مساحة التخزين في القرص الصلب المخصصة وعرض النطاق (Bandwidth) لاستضافة مواقع العملاء. وهذه بعض الميزات فيما يخص خيار المصممين والمدونين وتعرض الشركة العديد من الميزات منها: استضافة نطاقات (Domains) بشكل غير محدود. مساحة استضافة بشكل غير محدودة. شهادة SSL مجانية. استقبال 100 دولار أمريكي كهدية من شركة غوغل و100 من شركة مايكروسوفت عند إنفاقك 25 دولار أمريكي في حسابك الجديد على منصاتهم الإعلانية. تراسل بيانات بشكل غير محدود. حسابات بريد إلكتروني بشكل غير محدود. نطاق (Domain) مجاني مخصص. يمكنك الحصول على هذه الميزات والكثير غيرها الموجودة في الخطة الاستضافة المشتركة مقابل 2.95 دولار أمريكي شهريًا، ومن ناحية أخرى استضافة المواقع المخصصة غالية بعض الشيء ولكنها توفر المزيد من موارد المخدم مثل: وحدة معالجة مركزية 4×2.3 غيغا هرتز. ذاكرة وصول عشوائي (RAM) بسعة 4 غيغا بايت. سعة تخزين 500 غيغا بايت (منسوخة احتياطيًا في حال فشل النسخة الأصلية). 5 تيرا بايت عرض النطاق (Bandwidth). 3 عناوين IP. شهادة SSL مجانية. اسم نطاق مجاني. دعم فني على مدار الساعة وطيلة أيام الأسبوع. ضمان استرداد أموالك خلال 30 يوم. تبلغ كلفة الاستضافة المخصصة القياسية 79.99 دولار أمريكي شهريًا. يمكنك أيضًا الحصول على خطة المتاجر الإلكترونية والّتي تتيح مجموعة من المميزات الّتي يحتاجها جميع أصحاب المتاجر الإلكترونية ومن بين المميزات المتوفرة في جميع خطط المتاجر الإلكترونية هي: اسم نطاق مجاني. تثبيت تلقائي لمتجر WooCommerce. شهادة SSL مجانية. تراسل بيانات بشكل غير محدود. استقبال 100 دولار أمريكي كهدية من شركة غوغل و100 من شركة مايكروسوفت عند إنفاقك 25 دولار أمريكي في حسابك الجديد على منصاتهم الإعلانية. عنوان IP مخصص. ويمكنك الحصول على الخطة الابتدائية والّتي تأتي مع مميزات أخرى مثل: استضافة متجر واحد. سعة تخزين تبلغ 100 غيغا بايت. مجموعة من القوالب المجانية الجاهزة للتركيب. حماية خصوصية النطاق. مكالمة مجانية للمساعدة في التثبيت. شهر كامل مجانًا لبرنامج أوفيس 365 Mailbox. كل هذه المميزات مقابل 6.95 دولار أمريكي شهريًا للخطة الابتدائية ويوجد ميزات أخرى في الخطط الأخرى المتوفرة للمتاجر الإلكترونية. 2. WP Engine تعدّ هذه الشركة الخيار الإحترافي لمواقع الووردبريس حيث تمنحك الشركة خادم استضافة لا ينقطع مع القدرة على التعامل مع ازدياد عدد الزوار، ولا داعي للقلق بشأن الحماية لأنه محمي جيدًا من الاختراق. مع خدمة عملاء قوية وسريعة بنفس الوقت تجعل هذه الشركة مرشحًا قويًا جدًا لخدمات استضافة المواقع ومع فريق من الخبراء في الووردبريس ستشعر بالإطمئنان بشأن أي مشكلة قد تصيب موقعك. وتعد هذه الشركة خيارًا جيدًا للمدونين والمصممين الّذين يبحثون عن استضافة بسعر معقول. جميع الخطط المُقدمة من قبل هذه الشركة تأتي مع ضمان استرداد أموالك بالكامل خلال 60 يومًا إذا لم تعجبك الخدمة أو المنصة وتشمل بعض عروضهم ما يلي: النسخ الاحتياطي اليومي. استعادة النسخ الاحتياطي بنقرة زر واحدة. أمان عالي الجودة. تفعيل التخزين المؤقت (Caching) بشكل تلقائي. إلغاء الخدمة بشكل مجاني خلال 60 يومًا. وحاليًا تقدم الشركة خطة للشركات الناشئة بمبلغ 35.00 دولار أمريكي شهريًا وهي تشمل ما يلي: تثبيت موقع ووردبريس عدد 1. 25000 زيارة للموقع شهريًا. تبادل بيانات 50 غيغا بايت. سعة التخزين تساوي 10 غيغا بايت. أكثر من 35 قالب جاهز للتثبيت. بيئة متكاملة لتطوير معتمدة على إطار العمل Genesis. دعم فني على مدار الساعة وطيلة أيام الأسبوع. قابلية نقل الموقع على إستضافة أخرى. إصدار PHP 7.3. خوادم موزعة CDN (وهي شبكة توصيل المحتوى تتألف من مجموعة من الخوادم المتزامنة والموزعة على شبكة الانترنت في أماكن جغرافية مختلفة، تحتوي على نسخ من المواقع الّتي تضيفها شركة الإستضافة، وفي حال رغب المستخدم في الوصول لمحتوى على الانترنت مدعوم بهذه الميزة فإن الشبكة ستقوم بارساله لأقرب خادم لموقعة الجغرافي يحتوى على نسخة من المحتوى المطلوب) من أجل سرعة الوصول من كافة المناطق. أداة تقيس مدى تفاعل صفحة الويب. تشمل الخطط الأخرى ما يلي: الخطة الاحترافية - تتضمن نفس ميزات الخطة الشخصية مع مزيد من موارد الخادم وتبدأ أسعارها من 99.00 دولار أمريكي شهريًا. خطة الأعمال (Business) - تأتي بنفس الميزات مع مزيد من موارد الخادم المتاحة في الخطة الاحترافية وذلك لاستضافة مواقع الأعمال الكبيرة وتبدأ أسعارها 249.00 دولار أمريكي شهريًا. 3. Media Temple تقدم هذه الشركة تجربة استضافة من الطراز العالمي وخصوصًا للمصممين والمدونين ومهما يكن حجم موقعك فإن الشركة تستطيع التعامل معه. جميع خدمة الشركة تتناسب مع سعر الخدمة. يوجد العديد من الخيارات والخطط المتاحة والّتي تختلف من حيث السعر والمميزات والسعة وتوفر كل خطة قابلية الترقية للخطة الأعلى منها وذلك لكي تتناسب مع نمو موقعك وتشمل الخطط الّتي تقدمها هذه الشركة ما يلي: خطة الشبكة (Grid): وهي استضافة مشتركة. خطة DV Managed. خطة DV Developer. خطة DV Enterprise. خطة Managed WordPress. وعندما نتحدث عن المصممين والمدونين فإن أفضل خطة تناسبهم هي خطة Grid وهي تتكامل بشكل مثالي مع الووردبريس مع العديد من الفوائد للمبتدئين ومن بعض هذه الميزات: استضافة 100 موقع. عرض نطاق (Bandwidth) يصل إلى 1 تيرا بايت. إمكانية تخزين قواعد البيانات بعدد يصل 100 قاعدة. سعة تخزين تصل إلى 20 غيغا بايت. 1000 حساب بريد الكتروني. تقديم الدعم الفني على مدار الساعة وطيلة أيام الأسبوع وعلى مدار السنة. كلّ هذه الميزات متوفرة بسعر 20 دولار أمريكي شهريًا. وتعدّ المنصة سهلة الاستخدام ويمكنك ترقية الخطة متى تشاء. وبعض الخطط المطروحة الأخرى مثل خطة المطورين (DV Developer) والّتي تبدأ بمبلغ 30 دولار أمريكي وخطة المدراء (DV Managed) تبدأ بمبلغ 55 دولار شهريًا. 4. Arvixe هي شركة استضافة معروفة لدى أغلب المطورين والمصممين والمدونين.أفضل ميزات هذه الشركة هي الموثوقية والجودة وتوفرها بأسعار معقولة. هناك العديد من الخطط الّتي تقدمها هذه الشركة للمطورين والمصممين من ضمنها: الخطط الشخصية خطط الأعمال خطط الموزّعين توجد أيضًا خوادم VPS وخوادم مخصصة تعمل على أنظمة التشغيل Windows و Linux إذا كنت تبحث عن أداة بسيطة تستطيع تحمّل ازدياد عدد الزوار على موقعك والتأكد من أن الموقع لا يزال متوفر وفي أي وقت فاطلب خدمات هذه الشركة. من خلال الاستضافة المشتركة الممتازة، يمكن للمصممين والمدونين أن يرتاحوا حيال التعامل مع الزيارات لمواقعهم على الإنترنت وزيادة نمو هذه المواقع. توفر هذه الاستضافة ميزات مختلفة بما في ذلك: سعة التخزين غير محدودة. تناقل بيانات غير محدود. حسابات بريد إلكتروني غير محدودة. عدد قواعد بيانات غير محدود. اسم نطاق مجاني. أداة مجانية لبناء المواقع. متجر إلكتروني مجاني. لوحة تحكم سهلة. تقديم الدعم الفني على مدار الساعة وطيلة أيام الأسبوع وعلى مدار السنة. كل هذه الميزات والكثير غيرها ستكلفك 7 دولار أمريكي شهريًا فقط. مع ضمان عدم وجود رسوم مخفيّة، يمكنك أيضًا الحصول على ضمان استرداد الأموال خلال 60 يومًا في حالة عدم رضاك عن المنصة. الخطط الأخرى تشمل: استضافة الأعمال مقابل مبلغ 25 دولار أمريكي شهريًا. استضافة الموزّعين مقابل مبلغ 23 دولار أمريكي شهريًا. استضافة VPS مقابل مبلغ 43 دولار أمريكي شهريًا. 5. HostGator تعدّ هذه الشركة واحدة من أفضل مواقع الاستضافة وتشتهر بدعم العملاء على مدار الساعة وطيلة أيام الأسبوع، وهو ما يفضله العديد من المصممين والمدونين. وتقدم خدماتها بأسعار معقولة جدًا، تقدم خطط استضافة تتضمن: استضافة مشتركة. استضافة الموزّعين. استضافة VPS والخطط المخصصة. تم تصميمها لتعمل بشكل مثالي مع المبتدئين والمحترفين. وتقدم ميزة نقل الاستضافة بشكل سهل للغاية وعندما تنقل من مضيف إلى شركة HostGator فإنها توفر لك المميزات التالية: نقل نطاق مجانًا. نقل الملفات مجانًا. نقل قاعدة البيانات مجانًا. نقل الموقع مجانًا. تشمل الميزات الّتي تقدمها الشركة لمستخدميها: سعة التخزين وعرض نطاق غير محدود. لوحة تحكم سهلة. تثبيت سكربت بنقرة واحدة. دعم فني على مدار الساعة وطيلة أيام الأسبوع. يمكنك الحصول على كلّ هذه الميزات بسعر 3.96 دولار شهريًا. الخطط الأخرى تتضمن: استضافة الموزّعين بمبلغ يبدأ من 19.95 دولار أمريكي شهريًا. استضافة مخصصة لمواقع الووردبريس بمبلغ يبدأ من 5.95 دولار أمريكي شهريًا. استضافة VPS بمبلغ يبدأ من 29.95 دولارًا أمريكي للشهر الأول. خوادم مخصصة تبدأ من 119 دولار أمريكي للشهر الأول. توفر هذه الشركة ضمان وقت تشغيل المخدم بنسبة 99.9 ٪ وضمان استعادة الأموال لمدة 45 يومًا أيضًا. 6. GoDaddy هي واحدة من أكثر شركات شهرةً في مجال استضافة المواقع، كل مصمم ومطور يعرف ذلك جيدًا، وهي الخيار الأول لدى المصممين. تقدم الخدمة ميزات كثيرة للمبتدئين والخبراء. تعرف الشركة مدى أهمية موقعك الويب بالنسبة لك، لذا فإنها تعتني به جيدًا وتهتم بكلّ شيء من مدير الملفات ومدير النطاق وإدارة قواعد البيانات والّذي يعدّ رائعًا وسهل الاستخدام للغاية ويمكنك الحصول على الدعم الفني والمساعدة الّتي تحتاجها من خلال فريق الدعم الّذي يعمل على مدار الساعة وطيلة أيام الأسبوع ويمكنك التواصل معهم عبر الهاتف أو البريد الإلكتروني. ومع ذلك، فإن إدارة ملايين النطاقات تجعل الدعم أبطأ قليلًا ولكن فريق الدعم سريع الاستجابة دائمًا ويتم حل الشكاوى عادةً خلال 24 ساعة فقط. وتشمل الميزات الّتي تقدمها هذه الشركة لكل خطة: نطاق مجاني بشرط شراء الخطة السنوية. بريد الكتروني مجاني. التثبيت بنقرة واحدة لأكثر من 125 قالب مجاني. عرض نطاق غير محدود. سعة تخزين قواعد البيانات 1 غيغا. لوحة تحكم سهلة الاستخدام ومرنة. حماية ومراقبة على مدار الساعة وطيلة أيام الأسبوع لهجمات حجب الخدمة DDoS والحماية منها. على الرغم من توفر هذه الميزات بالإضافة للكثير غيرها في كل خطة، إلا أن الأسعار تختلف من حيث التخزين وعدد المواقع المستضافة. يمكنك استضافة موقع ويب فردي بمبلغ 5.99 دولار أمريكي للشهر الأول مع سعة تخزين 100 غيغا بايت لاستضافة عدد مواقع غير محدود وبسعة تخزين غير محدودة يجب عليك دفع 7.99 دولار أمريكي للشهر الأول. الخطة الأخرى تشمل استضافة مدونة أو موقع ووردبريس يبدأ من 6.99 دولار أمريكي شهريًا. 7. Eleven2 تعدّ هذه الشركة من الشركات الّتي يُنصح بها بسبب جودة الخدمة والدعم والتجهيزات والميزات ووقت التشغيل.وأغلب المصممين والمدونين يفضلون الاستضافة المشتركة بسبب أسعارها المعقولة جدًا. وتوفر أداة مجانية لمساعدتك في إنشاء موقع متميز ومتكامل في دقائق معدودة، ويمكنك إدارة موقعك بسهولة باستخدام أدوات وميزات لوحة التحكم القياسية cPanel. مع دعم فني للعملاء، فهي متوفرة على مدار الساعة وطيلة أيام الأسبوع لمساعدتك. كما أنها توفر ميزة فريدة للدردشة المباشرة الّتي تمكنك من الدردشة مع الخبراء حول مسائل استضافة الموقع الخاص بك وضمان وقت تشغيل المخدم بنسبة 99.9 ٪. إنها بالفعل شركة استضافة مُتقنة. وتقدم هذه الشركة ميزات وفقًا لخططها المختلفة الّتي تكون إما شهرية أو سنوية. تتوفر خطة S-200 القياسية مقابل 7 دولارات أمريكية شهريًا وتشمل: مساحة تخزين 250 غيغا بايت. عرض نطاق 20 تيرا بايت شهريًا. مجالات (Domains) غير محدودة. ضبط الإعدادات بشكل فوري. عدد قواعد بيانات غير محدود. امكانية النسخ الاحتياطي. ضمان استرداد الأموال خلال 60 يومًا. تقدم هذه الشركة العديد من الخطط الأخرى الّتي ستناسب احتياجاتك حتمًا. تشمل الخطط الأخرى: خطة S-300 مقابل 14 دولار شهريًا مع سعة تخزين 500 غيغا بايت وعرض النطاق 40 تيرا بايت شهريًا. خطة R-100 للبائعين مقابل 13.97 دولار شهريًا مع سعة تخزين 100 غيغا بايت وعرض النطاق 1 تيرا بايت شهريًا. خطة R-200 للبائعين مقابل 24.47 دولار شهريًا مع سعة تخزين 200 غيغا بايت وعرض النطاق 2 تيرا بايت شهريًا. خطة R-300 للبائعين مقابل 34.97 دولار شهريًا مع سعة تخزين 300 غيغا بايت وعرض النطاق 3 تيرا بايت شهريًا. تقدم أيضًا الشركة خطة استضافة سحابية والّتي تعدّ من الخطط الرخيصة نسبيًا بالموازنة مع خدمات أمازون السحابية أو خدمات مايكروسوفت. وتقدم الاستضافة السحابية الخدمات التالية: منصة سريعة جدًا باستخدام حاويات لينكس مع أقراص صلبة من نوع SSD. نسخ احتياطي يومي لضمان عدم خسارة أي جزء من بياناتك. فريق من المهندسين يعملون على صيانة الخدمة السحابية لضمان عدم توقفها. عدد من المراكز السحابية موزعة على مختلف الدول. دعم فني سريع جدًا مع سرعة رد تبلغ 10 دقائق فقط. تقدم كل هذه الخدمات مقابل 25 دولار شهريًا مع سعة تخزين 125 غيغا بايت وعرض النطاق 10 تيرا بايت شهريًا. 8. Fatcow تعدّ خيارًا جيدًا للمصممين والمدونين الّذين بحوزتهم رأس مال قليل أو الّذين يطلقون الموقع الإلكتروني لأغراض شخصية. وهي تحافظ على خدمات وميزات موقعك يعمل طول الوقت وبلا انقطاع، يجد الناس أنها موثوقة وودية للغاية. تتأكد الشركة من حصول كلّ عميل على موقع متاح دائما عبر الشبكة وغير متقطع بمساعدة النسخ الاحتياطي للخادم يوميًا. خدمة العملاء موثوقة جدًا ومتاحة على مدار الساعة وطيلة أيام الأسبوع ويمكنك أيضًا التواصل معهم عبر البريد الإلكتروني. وتقدم الشركة برامج تعليمية ومكتبة لأكثر الأسئلة شيوعًا والأجوبة عليها. وتشمل حلول الاستضافة الّتي تقدمها ما يلي: خطة Fatcow. استضافة ووردبريس. حجز دومين. استضافة VPS. استضافة مخصصة. تسجيل نطاقات. مجموعة أيقونات Farm-Fresh. تشمل الميزات الّتي تقدمها: سعة تخزين غير محدودة. عرض النطاق غير محدود. ضمان التشغيل والمتابعة بنسبة 90%. قاعدة بيانات MySQL غير محدودة. استضافة نطاقات بشكل غير محدود. صندوق بريد نوع POP غير محدود. ضمان استرداد المال خلال 30 يوم. استقبال 100 دولار أمريكي كهدية من شركة مايكروسوفت و 100 دولار أمريكي من شركة غوغل عند إنفاقك 25 دولار أمريكي في حسابك الجديد على إعلانات غوغل أو مايكروسوفت. كل هذه الميزات متوفرة بسعر 4.08 دولار أمريكي شهريًا وأما خطة استضافة مواقع الووردبريس فهي بأقل سعر على الإطلاق وهو 3.75 دولار أمريكي شهريًا. وتشمل الخطط الأخرى: استضافة خوادم VPS الأساسية وتبدأ من 19.99 دولار أمريكي شهريًا وتأتي مع معالج واحد وذاكرة وصول عشوائي بسعة 1 غيغا بايت وسعة تخزين تبلغ 40 غيغا بايت وعرض النطاق 1 تيرا بايت. استضافة خوادم VPS للأعمال وتبدأ من 47.99 دولار أمريكي شهريًا وتأتي مع معالجين وذاكرة وصول عشوائي بسعة 4 غيغا بايت وسعة تخزين تبلغ 90 غيغا بايت وعرض النطاق 3 تيرا بايت. استضافة خوادم VPS المثالية وتبدأ من 79.99 دولار أمريكي شهريًا وتأتي مع 4 معالجات وذاكرة وصول عشوائي بسعة 8 غيغا بايت وسعة تخزين تبلغ 120 غيغا بايت وعرض النطاق 4 تيرا بايت. استضافة مخصصة تبدأ من 119.99 دولار أمريكي شهريًا وتأتي مع معالجين وذاكرة وصول عشوائي بسعة 4 غيغا بايت وسعة تخزين تبلغ 500 غيغا بايت وعرض النطاق 5 تيرا بايت. 9. InMotion Hosting تعدّ هذه الشركة ذائعة الصيت بين الشركات المتوسطة والكبيرة، سواءً أكان موقع أو مدونة تقدم لك الشركة الخدمة الّتي تريدها وتأتي شهرة هذه الشركة من فريق الدعم الفني الحائز على عدة جوائز ويصل وقت تشغيل المخدم إلى 99.9٪. تتعامل الشركة مع احتياجات العمل المتنامية، من الأعمال التجارية الصغيرة إلى مستوى الشركات. تضمن الشركة سرعة جيدة وخدمة عالية مع مراكز بيانات متعددة. توفر ميزة منطقة السرعة القصوى (Max Speed Zone) لخطط VPS ورجال الأعمال والّتي تمكن المستخدمين القريبين من مراكز البيانات في الشركة الاستمتاع بالتصفح أسرع بست مرات من أي شخص أخر. تتأكد الشركة من حصولك اتاحية موقعك بشكل دائم عبر الإنترنت وبدون انقطاع ومع توفر دعم الفني العملاء على مدار الساعة وطيلة أيام الأسبوع. تم تحسين استضافة مواقع الووردبريس بشكل كبير للمدونين وتقدم لك الشركة بعض الميزات الرائعة لجعل مدونتك تنمو بسرعة. تتضمن خدمات الاستضافة: استضافة رجال الأعمال. استضافة رخيصة. استضافة VPS. خوادم مخصصة. استضافة الموزّعين. تشمل الميزات الّتي توفرها الشركة: سعة التخزين غير محدودة عرض النطاق غير محدود. باقة مواقع مجانية بأكثر من 400 مواقع مع ميّزة التثبيت بنقرة واحدة. أقراص التخزين الصلبة من نوع SSD. تقدم لوحة تحكم cPanel & WHM بشكل مجاني. إعلانات مجانية بقيمة 150 دولار أمريكي. اسم نطاق مجاني. ضمان استرداد المال خلال 90 يوم (تشمل خطة استضافة الموزّعين والاستضافة المخصصة والاستضافة المشتركة). هنالك الكثير من الميزات الأخرى المضمنة في الخطة وهي متاحة مقابل 6.39 دولار أمريكي شهريًا. وتشمل الخطط الأخرى استضافة VPS تبدأ من 29.19 دولار أمريكي شهريًا. استضافة مخصصة تبدأ من 105.69 دولار أمريكي شهريًا. استضافة الموزّعين تبدأ من 15.39 دولار أمريكي شهريًا. 10. TMDHosting عندما تقوم بتشغيل موقع أو مدونة مشهورة، فإنك ستحتاج إلى المعرفة بالعديد من الأدوات والمعلومات التسويقية. إن هذه الشركة ستسهل الأمر عليك وستقوم بتشغيل موقع الويب الخاص بك أو مدونة. وتوفر لك وقت تشغيل مضمون بنسبة 99.9٪ في جميع خطط الاستضافة، وتقدم لك أفضل تجربة استضافة وتضمن لك استرداد أموالك لمدة 30 يومًا في حالة عدم رضاك عن المنصة. توفر الشركة أيضًا استضافة للتطبيقات المفتوحة المصدر بما في ذلك استضافة Joomla، واستضافة Magneto …الخ. الميزة الّتي تتفوق بها هذه الشركة على منافسيها هي دعم العملاء السريع على مدار الساعة وطيلة أيام الأسبوع، وسيتم حل استفساراتك في غضون 15 دقيقة. كما تقدم مساعدة على أعلى مستوى مع أحدث التقنيات المتاحة. بالنسبة للمبتدئين، تقدم الشركة برامج تعليمية تساعدك خطوة بخطوة في أي مسألة تتعلق باستضافة المواقع. وتقدم العروض الترويجية بشكل يومي والّتي تكون تحديثات على خطط الاستضافة الحالية وتقدمها بأقل الأسعار للعملاء. خدمات الاستضافة الّتي تقدمها الشركة تشمل: استضافة المواقع. استضافة الموزّعين استضافة VPS استضافة الخدمات السحابية (Cloud). خوادم مخصصة تشمل الميزات المختلفة الّتي توفرها استضافة الشركة اسم نطاق مجاني للأبد. خوادم CloudLinux-powered. سعة التخزين وعدد زيارات غير محدودة. إصدارات PHP متعددة. قواعد بيانات MySQL بشكل غير محدود. ضمان استعادة المال خلال 60 يوم. وهنالك عدة ميزات أخرى مع الحزمة. تتوفر خطة استضافة الويب بمبلغ 2.95 دولار أمريكي شهريًا. والخطط أخرى تشمل: استضافة الموزّعين تبدأ من 19.95 دولار أمريكي شهريًا مع ضمان استعادة المال خلال 30 يوم. استضافة VPS تبدأ من 19.97 دولار أمريكي شهريًا. استضافة سحابية تبدأ من 5.95 دولار أمريكي شهريًا. خوادم مخصصة تبدأ من 229 دولار أمريكي شهريًا. ختامًا إن كل شركات الاستضافة السابقة موصّى بها للمصممين والمدونين، ومع ذلك أنصحك بتصفح كل خطة عن كثب لتقرر أي العروض هي الأفضل بالنسبة لك. ففي بعض الأحيان يكون الدعم الفني للعملاء هو الفارق الوحيد بين الشركات الاستضافة العادية والشركات المتميزة. ترجمة -وبتصرف- للمقال Unbiased Review of the 10 Top Web Hosting Services
-
- 1
-
- استضافة
- استضافة مواقع
-
(و 4 أكثر)
موسوم في:
-
إن معظم المؤسسات في هذه الأيام تنتقل من نموذج أعمال المُحسن للموارد والذي يستند إلى النفقات الرأسمالية (CAPEX) إلى نموذج أعمال المُحسن للسوق والذي يستند إلى النفقات التشغيلية (OPEX). ولكن ما الذي يقود هذا التحول؟ في الحقيقة أن الهدف الأساسي من هذا التحول هو تقليل الوقت اللازم للتسوق وإسعاد الزبائن باستمرار عن طريق تقديم قيمة (Value) لهم وتُعرف القيمة بأنها منتج أو خدمة معينة والتي يكون الزبون مستعدًا للدفع مقابلها. مرحبا بك في التحول الرقمي. هل أنت مستعد لتبني عقلية DevOps في شركتك؟ لقد أشار مدير DevOps السيد Donovan Brown إلى أن DevOps إن DevOps هي رحلة تعلم وتطوير بشكل مستمر، مع وجهة لن تصل إليها أبدًا. ولهذا فإن جميع الصور الواردة في هذا المقال على شكل رمز اللانهاية. ولكوني شخصًا ذو عقلية مرئية (visual-minded)، فقد أنشأت عرضًا تقديميًا يحتوي على صور من أجل مخيم تدريب DevOps العالمي GDBC (Global DevOps Bootcamp) وهذا الحدث السنوي الذي تنسقه المجتمعات المحلية في جميع أنحاء العالم وذلك لخلق بيئة استكشافية وتعاونية لاستعراض مبادئ التحول الرقمي ورؤى DevOps. دعنا نستكشف سريعًا أربعة من الصور المرجعية (والتي تدعى أيضًا بالصور المرئية والإنفوجرافيك). واذا أردت التعمق أكثر يمكنك مراجعة كتاب The DevOps Handbook، لكُتّابه Gene Kim، و Jez Humble، وPatrick Debois، وJohn Willis. الممارسات بناءً على استطلاع قامت به شركة مايكروسوفت على مستوى العالم لتحديد أهم المجالات التي يجب التركيز عليها في DevOps والمجالات التي تحتاج إلى تحسين، والذي جاء فيه أن جميع المشاركين الذين يستخدمون DevOps يركزون على نفس الممارسات في استخدام DevOps ونفس التقنيات لتحقيق DevOps و سوف نستعرضها في الصورتين التاليتين. في الصورة الأولى نلاحظ الممارسات الخمس الأولى الرئيسية: يشجع كبار المختصين في الأداء (Performers) على تعزيز عقلية استسراع النمو، والمكافأة على الابتكار، والتعاون، والتجريب، والتعلم، والتعاطف مع المستخدمين. والسعي لعملية تسليم تطبيقات سريعة الاستجابة، والجدولة المرنة، والتجارب التكرارية. ورصد المشاكل والتعرف عليها وتخفيفها، والقضاء باستمرار على الاختناقات التي تهدر الأداء، وقياس مؤشرات الأداء المهمة وذلك لتحسين المخرجات، مثل معدل فشل التغيير المنخفض (CFR Change Failure Rate)، والوقت الأدنى للاستعادة من الفشل ( Minimal Time To Recover MTTR)، ويجب دومًا أن نحرص على معالجة المشكلات من جذورها. وأخيرًا، لنأتي إلى التكنولوجيا، والتي هي أداة تمكين ستكون محور الصورة التالية. نلاحظ في هذه الصورة التقنيات التي تركز عليها DevOps: يقوم نظام التحكم في النُسخ (Version Control System) بإدارة إصدارات التطبيق واعداداته، والبنية الأساسية، وغيرها من الشيفرات البرمجية للتطبيق. وهو يتيح التعاون بين الفِرق ومراقبة الأنشطة مثل عمليات النشر (Deployments). يستخدم كبار المختصين في الأداء نسخ فرعية من التطبيق وذلك لعزل التغييرات قصيرة الأجل ومعاينتها قبل أن يتم دمجها في النسخة الأساسية (Master)، ويستمر مراجعة وتدقيق طلبات الدمج باستمرار ويتم إصدار النُسخ على أساس ذلك. يجب أن يُنظر للاختبار على أنه نشاط مستمر، وأن يُدمج في مخطط سير العمل للمطور وذلك من أجل تحقيق التكامل المستمر مع التغيّرات (Continuous Integration CI) والتسليم المستمر للتطبيق (Continuous Delivery CD). يتيح لك التخزين السحابي توفير البنية التحتية اللازمة والفعّالة وذلك للتحرك بأقصى سرعة ممكنة عند الضرورة وذلك تجنبًا للوقت المبذول في بناء البنية التحتية المناسبة للشركة. وأخيرًا، تمكنك المراقبة من تكوين فرضية، أو التحقق من صحة التجارب، أو دحضها، واكتشاف المشاكل بشكل استباقي قبل حدوثها وفهم حالة التطبيق. يسرد الشريط الأسود الموجود على يمين الصورة المنتجات التي يجب أخذها في عين الاعتبار عند البحث عن تقنية للتطوير والإنتاج والهندسة المشتركة وبيئات أخرى. حاول أن تقيّم المنتجات المدرجة وحدّث هذه القائمة بانتظام لأن هذا الجزء متغير وإلزامي بنفس الوقت من أجل اختصار الوقت. العادات بناءً على قصة تحوّل 65,000 مهندس إلى DevOps بمساعدة فريق خدمات Visual Studio، تركز هذه الصورة على العادات الخمس الرئيسية التي تعلموها أثناء تحولهم إلى DevOps. يعدّ التركيز على العملاء، والاستقلال الذاتي للفِرق، وتنسيق الشركة، والتركيز على عادات Shift Left، هي عوامل التطوير لمنهجية التصميم المرن (Agile)، كما أن عقلية الإنتاج أولًا والبنية التحتية مثل الموارد المرنة جميعهم خواص تتفرد بها عقلية DevOps. يعدّ التركيز على العملاء جزءًا من سعينا لإمتاعهم وهاجسنا بتقديم خدمة قيّمة لهم. ويجب أن تُصغ إلى المستخدمين بشكل نشط أي أن تسمع لمشاكلهم واقتراحاتهم، وتمكين الميزات وتعطيلها تدريجيًا، وإجراء التجارب المستمرة، وقياس مؤشرات الأداء الرئيسية. واستخدام المُراجعات المتوفرة لتحسين الأداء أو المنتج ومحاولة الإستفادة بأكبر قدر منها وزيادة التأثير على العملاء إلى أقصى حد. تُعد عادات Shift left طريقةً لاختبار البرمجيات والنظام، ويتم إجراء الاختبار في وقت مُبكر من دورة الحياة (أي تم نقله إلى اليسار في الجدول الزمني للمشروع). ويشجع shift left على المراجعات، والتحقق من الصحة، والموافقات على كل من الاختبار والأمان في أقرب وقت ممكن في دورة توصيل الميزات وذلك لزيادة الجودة وتمكين أسلوب الفشل السريع (أحد أهداف هذه الفلسفة هي تقليل الخسائر، وذلك عندما يكشف الاختبار وجود شيء ما لا يعمل فالأفضل دومًا تجربة شيء آخر بسرعة. أي تجاوز الفشل بسرعة). عندما يتجاوز الأعطال الفنية الحد المحدد مسبقًا (مثلا خمسة أخطاء لكل مهندس)، يجب تشجيع الفِرق التي تعمل على المميزات على تعليق العمل حتى يتم جميع الأخطاء الموجودة. إن استقلالية الفريق وتنسيق الشركة يهتمان بما تقوم الشركة بإنشائه وكيفية إنشائه وسبب إنشائه. لذا ستحتاج إلى وتيرة عمل سريعة ونشاط دائم داخل الشركة لتمكين فِرق القيادة وفِرق المميزات من التعاون بشفافية وفعالية. تمتلك أكثر فرق المميزات فعالية خاصية تحويل الفكرة إلى نتائج، مع الاستقلال الذاتي للقرارات حول كيفية تطويرها ودعمها لخصائص ومميزات هذه الفكرة. إن عقلية الإنتاج أولًا لا تُميّز كيفية التعامل مع المميزات والأخطاء خلال التطوير والاختبار والدعم الفني. وإنما كل شيء يجب أن يكون مُؤتمت، ومُحرر، ومُحسن في الإنتاج. ويتم الاعتماد على النشر الدائري والحلقات (ring-based deployment and rings) والتي تنص على عرض التغييرات لمستخدمين محددين ومراقبتها والتحقق من صحتها قبل عرضها على جميع المستخدمين وذلك للحد من الأخطاء التي تنتج أثناء إحداث تغييرات في المميزات المطروحة، ومعالجة جميع المشكلات من جذورها، وتذكر أن تكون واضحًا عند حدوث مشكلة معينة وتوضيحها وما هو السبب الرئيسي لظهورها والحل الذي استخدمته لتجاوزها (لان المستخدم سيكون أكثر تفهما في حال كان لديه معرفة بسبب حدوث المشكلة والآلية المتبعة في حلها). تصف البنية التحتية مثل الموارد المرنة كيفية تكيّف الحلول المطروحة مع الخدمات السحابية والحاويات (container) والخدمات المصغّرة (Microservices). يجب تبني تحوّل عملي يكون منطقيًا لشركتك وأهدافك ومنتجاتك وثقافتك. كما هو الحال مع العادات السابقة، من المهم أن نُفضّل البنية الاستقلالية على بنية الوصفية وألا نُغير كل شيء في وقت واحد. الشروع في العمل يجمع التصور الأخير بين كل ما سبق ويقترح خمس خطوات للبدء مع Devops. أفضّل أن أبدأ بالتقييم لقدرته في تحديد المناطق الرئيسية التي يمكن تحسينها. توفر التقييمات معيارًا لعقلية وأداء DevOps مقارنة بباقي المنهجيات المتبعة في قطاع الصناعات الأخرى. من المهم أن نفهم أي جزء من عملك يسير على ما يرام وأي جزء يحتاج لأن تستثمر فيه لنقله إلى المستوى التالي. إن كُلًا من التقييمات التابعة لشركة DORA و تقييمات شركة مايكروسوفت بالنسبة Devops هي تقييمات رائعة للبداية. بالإضافة إلى ذلك، إجمع أدوات القياس لاستخدامها كقاعدة لقياس التقدم - على سبيل المثال، معدل تكرار النشر (Deployment Frequency) والوقت اللازم لتطبيق التغييرات (lead time for changes)، ومتوسط وقت الإصلاح (mean time to repair)، ومعدل فشل التغيير (change failure rate). إن التحدي الأكبر الذي سوف يواجهك هو الناس وثقافاتهم. يحتاج كل شخص لأن يعتنق فكرة التحول الرقمي، وأن يفهم كيف سيتأثر في هذا التغيير، وأن يُشجع على رفع مستوى الشفافية, ويجب عليه المشاركة بشكل فعّال وأن يتحمل المسؤولية الكاملة للقيمة المضافة للخدمة المتوقعة منهم. ويشمل هذا التحدي أيضًا مجلس قيادة الشركة والذي يحتاج لأن يقدم الدعم والإلهام والتمكين وتوجيه الموظفين لرؤية واضحة. وبناء عليه ستقوم إما بإنجاح هذا التحول كفريق واحد أو بإفشاله. وبدون وجود أشخاص ملتزمين وثقافة تجريبية، فإن بقية رحلة التحول إلى DevOps ستفشل. إن العملية (Process) هي نظامك الهندسي، والذي يقوم بتمكين الفرق من إصلاح المشاكل بشكل مباشر، واستخدام الإدارة والتطوير الرشيق (lean management and development)، وتقديم خدمة قيّمة باستمرار. ويوفر نظام الهندسة المشترك الثبات، ويخول فِرق المميزات، ويتيح ويشجع الجميع على دعم بعضهم بعضًا والمساهمة فيه. يجب أن تتضمن أهم أهداف عملياتك التركيز على الجودة، واحتضان البنيات خفيفة الترابط (loosely coupled architectures) لتمكين التطوير، والإدارة الخفيفة، والأتمتة، والإصدارات المتعددة في اليوم، والاحتفال بالنجاح كفريق وكشركة. المنتجات هي أسهل وصلة في السلسلة. فهي تمكن الجميع من التركيز على ما هو مهم ألا وهو توفير قيمة (Value) للعملاء. تعدّ القيمة من أهم الأشياء الّتي تُرضي المستخدمين. تتضمن مؤشرات الأداء الرئيسية مثل تكرار النشر، والوقت اللازم لتطبيق التغييرات، ومعدل فشل التغيير، ووقت الاسترداد من الخطأ. سواء كنت تتعامل مع كل هذه الخطوات دفعة واحدة أو خطوة بخطوة أو حتى لو أنك ابتكرت خطة للانتقال وذلك بالاستعانة بكل الخطوات التي ناقشناها. تذكر دومًا أن تكون واقعيًا. أيّ من صور أحببتها؟ أيّ منها أحسست بأنها لا تضيف أي قيمة؟ وما الجزء الذي تعتقد بأنه مفقود؟ دعونا نتعاون لكشف الغموض عن DevOps ومساعدتك وموظفينك على التألق. يحتاج المستخدمون إلى فهم أنهم ليسوا وحدهم وأن يعرفوا بأنه يمكنهم الاعتماد على الممارسات المُجربة والمُثبت فعاليتها وعلى النهج التعليمي الواقعي. ترجمة -وبتصرف- للمقال Visualizing a DevOps mindset لصاحبه Willy-Peter Schaub
-
تعدّ المواقع المبنية على ووردبريس من أكثر الأهداف عرضةً للهجوم على الإنترنت، إذ أن قابلية تعرضها للاختراق أكثر من أي نوع آخر من المواقع. فإذا كنت تملك موقعًا مبني على ووردبريس، أو أحد أصدقائك، أو أحد معارفك ولم يتعرض أي منكم للاختراق بعد، فأنتم إما محظوظون جدًا أو أنك تملك أشخاص حذرين جدًا يحيطون بك! وبما أن هذه المواقع تعمل على الإنترنت وتُشغّل مئات الآلاف من الشيفرات البرمجية في الخلفية، فمن المهم الاهتمام بالقضايا الأمنية الخاصة بالموقع وخاصة أن ووردبريس منصة معروفة ومشهورة واستهدافها من قبل المهاجمين هو أمر وارد الحدوث. عندما كانت شركة مايكروسوفت ويندوز منصة جديدة نسبيًا ومسيطرة على مواضيع تخص قضايا الحماية والأمن، صرّح القائمون عليها أن من أسباب تعرض المنصة لعدد من الهجمات هو الأخطاء والثغرات الأمنية المرتكبة من قِبل الشركة نفسها، وهو ما تم استغلاله على الفور عند صدور نظام الويندوز. وكذلك هو الحال في المواقع المبنية على ووردبريس والّتي تحتل وبقوة حوالي 34.8% من إجمالي عدد المواقع على مستوى العالم وذلك بحسب إحصائية نشرها موقع w3techs. وهذا شيء رائع، ولكنه أيضًا يفرض علينا أن نأخذ قضايا الحماية الأمنية على محمل الجد، فعلى سبيل المثال لو وجد شخصٌ ما ثغرة أمنية رئيسيّة في ووردبريس عندها سيكون بإمكانه وبكل سهولة أن يسيطر على الآلاف من الخوادم في غضون ساعات. ولهذا السبب تعد مواقع ووردبريس الهدف الأكثر عرضةً للهجوم ويجب علينا الاهتمام بحمايتها اهتمامًا بالغًا. هل يعدّ هذا المقال دليلًا كاملًا لحماية ووردبريس بالطبع لا وإنما هو مجموعة من أفضل الممارسات الأمنية في ووردبريس. أدرك جيدًا أن عنوان هذا المقال "الدليل الكامل في أمن مواقع ووردبريس" مبالغ فيه قليلًا، فهو بحد ذاته ليس كذلك وذلك لأن المسائل الأمنية ليست عبارة عن قواعد ثابتة، أو مجرد تثبيت إضافة أمنية معينة، أو عمل أي شيء آخر مقيّد بفترة زمنية. لا إنه ليس كذلك! بل هو عبارة عن ممارسات تتطور باستمرار وذلك لأن الثغرات الأمنية متجددة باستمرار وتفرض علينا دومًا تحديث سياساتنا الأمنية الّتي نتبعها. يمكن تمثيل مفهوم الأمن بأشياء تم وضعها في صندوق ومن ثم تم وضع الصندوق بعمق 400 قدم تحت سطح الأرض في مكان لا يعرفه أحد وسط الصحراء، الواضح هنا أن الأشياء في مأمن ولكنها غير مفيدة أبدًا. من الجدير بالذكر هنا أن أي شيء يعمل على شبكة الإنترنت، على سبيل المثال المواقع المبنية على ووردبريس، لديها بيئة متغيرة باستمرار ويمكن أن يشكّل ذلك خطرًا عليها فيصبح من الواجب العمل على حمايتها من التهديدات. وحتى يتم ذلك، سوف نعمل جاهدين على تحديث كافة المعلومات الّتي تحتاجها في هذا المقال. سأغطي في هذا المقال جميع الأساسيات الّتي ستحتاجها والكثير من الممارسات الأخرى الّتي ستساعدك في مواجهة القضايا الأمنية. بالرغم من أنني أتفق بأنه لا يمكن اعتباره دليلًا كاملًا. هل مواقع ووردبريس ليست آمنة؟ من الأمور الأكثر شيوعًا عند الناس الّذين يبحثون في موضوع أمن وورد بريس أنهم يشكّون في كونه آمن ومحمي في الأصل، أو أنهم يسمعون من شخص أخر يؤكد لهم على أنه ليس كذلك، وهذه تعد شائعات وشكوكًا حول أمان ووردبريس، هناك الكثير ممن يحاولون استغلال جهلك بآلية حماية ووردبريس فيحاولون بيعك منتجاتهم على الإنترنت سواءً أكانت خطة لأمن ووردبريس، أو خطة استضافة، أو حتى برنامج يدير كلمات المرور، لذلك هدفي في هذا المقال إزالة الغموض عن أفضل ممارسات الأمنية لحماية موقعك المبني على ووردبريس. وحتى نجيب على هذا التساؤل يجب علينا معرفة أن ووردبريس عبارة عن نظام نظام إدارة محتوى إلكتروني مفتوح المصدر ويتميز بأنه واسع وله العديد من الطرق للتركيب على مواقع الاستضافة وضبط إعداداته وتخصيصه والّتي يمكن أن تكون بأكثر من طريقة. وانطلاقًا من هذا التنوع نجد في بعض الأحيان مواقع ووردبريس غير آمنة والّتي من المحتمل أن تُخترق خلال الـ 24 أو 48 ساعة القادمة. وإذا كنا نتحدث عن النظام الأساسي الموجود في الملف المضغوط والّذي يمكنك تنزيله من الموقع الرسمي للوردبريس فإنه وبرأيي الشخصي آمن تمامًا. بشكل عام، كلّ يوم هناك احتمال 0.00001٪ بأن تظهر مشكلة غير مسبوقة في ووردبريس والّتي يمكن أن يتعرض من خلالها هذا النظام للهجوم على مستوى العالم. في الوقت الحالي تأتي مواقع ووردبريس مع ميزة التحديث التلقائي المدمج، وبإمكان شركات الاستضافة تحديث إصدار موقعك في حال فشل التحديث التلقائي. فهذا أمر بالغ الأهمية، لأنه يمكن أن يكون الإصدار 5.2.4 يواجه مشكلة أمنية ما والّتي سيكتشفها شخص ما في الأشهر أو السنوات القادمة. فتعمل منظمة ووردبريس جاهدة على تحديث نظامها باستمرار، فتصدر مثلًا إصدار 5.2.5 وتصدر بعدها إصدار 5.2.50 والّذي يعمل على إصلاح تلك المشاكل أو الثغرات الأمنية المكتشفة في الإصدار الّذي قبله. يمكن عد ووردبريس آمن وذلك عندما تحافظ على تحديث موقعك بأحدث إصدارات ووردبريس المتاحة. والتأكد من أن موقعك موجود على استضافةٍ موثوقٍ بها، والقيام بتثبيت التحديثات الموثوقة للإضافات الموجودة لديك، وعندها فقط يمكنك عد ووردبريس آمنًا تمامًا. ولكن عندما لا تقوم بأي شيء مما سبق، فمن المحتمل وبشدة أن يكون موقعك غير محمي بل ومُعرض للاختراق أيضًا. شبكة الروبوتات المقرصنة (Botnets) عمومًا إن المواقع المبنية على ووردبريس ليست معرضة للخطر بسبب الهجمات الفردية من أشخاص مجهولين وإنما الخطر الحقيقي يأتي من الهجمات الآلية. بالتأكيد هناك احتمال كبير أن تجد ولو شخص واحد يحاول الوصول لموقعك. ولكنه لن يشكل خطرًا مثل الهجمات الآلية. بالطبع هذا في حال ما لم تكن أغضبت أحدًا من عباقرة الاختراق. في الحقيقة إن التهديد الحقيقي لمواقع ووردبريس والّذي يعرضها للخطر هو الهجمات الآلية والّتي تُبرمج من قِبل أشخاص يحاولون الاستيلاء على موقعك، فيتحكم هؤلاء الأشخاص بالهجمات باستخدام الحواسيب، ولذلك ليس هنالك أشخاص يحاولون بأنفسهم الدخول إلى موقعك بواسطة أسماء مستخدمين وكلمات مرور مختلفة وإنما هي عملية آلية تُدار بواسطة الحواسيب. وغالبًا ما تجد هؤلاء الأشخاص (المخترقين) يكتبون برامج للبحث عن مواقع ووردبريس على شبكة الإنترنت بالكامل ومحاولة تسجيل الدخول إلى جميع هذه المواقع الّتي يجدونها. وهم نفسهم أيضًا من يحاولون استغلال الثغرات الأمنية المعروفة في الإضافات. ويكون هدفهم في أغلب الأحيان اختراق الكثير من الحواسيب لتكوين شبكة كبيرة من الحواسيب المقرصنة والّتي تسمى شبكة الروبوتات المقرصنة وتسمية الروبوتات جاءت من كون جميع هذه الحواسيب تخدم المخترق دون اختيارها تمامًا مثل أجهزة الروبوت وبمجرد أن ينضم جهاز جديد لشبكة الروبوتات فإن المخترق يستطيع التجسس على صاحب الجهاز دون أن يشعر صاحب الحاسوب بذلك. ويكون هدف هذا المخترق إما جمع المعلومات من خلال التجسس وسرقة معلوماتك السرية مثل بطاقات الائتمان والحسابات البنكية أو شن هجمة حجب الخدمة الموزعة (DDos Attack)، أو إرسال رسائل مزعجة (Spam)، أو تنفيذ مهام أخرى يريدها المخترق. وهنا يبرز لدينا مفهوم واضح لنموذج التهديد الخاص بموقع ووردبريس الخاص بك إذ أنه يتكون في الغالب من هجمات تلقائية مُدارة من قِبل أشخاصٍ بواسطة عناصر غير بشرية، ولذلك فإن القيام ببعض الممارسات الأمنية أمر مهم للغاية. لماذا تتعرض مواقع ووردبريس للاختراق؟ يجب في معظم الأحيان أن تقلق بشأن الهجمات الآلية، بدلًا من التركيز على هجمات الأفراد، وأن تعرف السبب وراء محاولتها الهجوم والسيطرة على الموقع الخاص بك. فهذا الموقع الصغير بالنهاية موقع ويب خاص بك أنشئ لأحد هذه الأسباب: مشاركة صورك الخاصة مع الأصدقاء. بيع الحاجات الخاصة بك على الإنترنت. الكتابة والتدوين للترفيه والسعادة …إلخ. هذه الروبوتات في الحقيقة لا تستهدف موقع ووردبريس الخاص بك إلا إذا كان موقعك يحتوي على بعض الثغرات الأمنية والّتي ستكون مدخلها للسيطرة عليه. وتختلف الأهداف الّتي تدفع المخترقين للاستيلاء على موقعك ولكن من أكثر الأسباب شيوعًا للاستيلاء المواقع هي كالتالي: إعادة توجيه حركة المرور إلى مواقعهم. الاستيلاء على تفضيلات محرك البحث الخاص بك المتصدر في جوجل (SEO rankings) لسرقة عصير الروابط ووضعها في صفحاتهم. تشويه الموقع للغطرسة والتفاخر أو لإيصال رسائل سياسية. التنزيل بالتنقل - مما يتسبب بجعل زوار موقعك ينزلون برامج ضارة وفيروسات …إلخ. فتح ثغرات في الموقع لاستغلالها لاحقًا. الوصول إلى بيانات موقعك، وقوائم المستخدمين، وسجل عمليات الشراء …إلخ. إرسال رسائل عبر البريد الإلكتروني عشوائيًا - فإذا كان موقعك يمكنه إرسال بريد إلكتروني، فيمكنهم استخدام ذلك لإرسال رسائل البريد الإلكتروني الخاصة بهم أيضًا. استخدام موارد الخادم (وحدة المعالجة المركزية في الغالب) لإجراء عمليات حسابية مفيدة والّتي تقوم غالبًا بتعدين العملات الرقمية مثل البتكوين. إن هذه الهجمات الآلية هي الّتي يتوجب عليك التفكير بها والحذر منها. فمن المحتمل جدًا أن يكون المتسلل ذكيًا ولديه أسبابه الدافعة اختراق موقعك والّتي من الممكن أنني لم أذكرها. ما أحاول توضيحه هو أن موقعك غير محمي حمايةً كافية. قد تظن أنه لا يوجد لديك قائمة مستخدمين مثيرة للاهتمام، ولكن عليك أن تضع ببالك أن هناك خادمًا يمكن استخدامه لتعدين العملات الرقمية أو لإرسال رسائل البريد المزعج، وذلك باعتبار أن لكل خادم وحدة معالجة مركزية، ولذا فإن الغالبية العظمى من الخوادم تستطيع إرسال بريد إلكتروني. الحماية المتكاملة للموقع يعتمد أمان مواقع ووردبريس على تكامل مجموعة من المسائل، وليس على مسألة واحدة بحد ذاتها. وفي الحقيقة إن ووردبريس قائم على مجموعة كبيرة ومتنوعة من التقنيات، وهذا ما يجب علينا أخذه بالحسبان فمن الممكن أن تحتوي بعض الأجزاء الّتي يتكون منها ووردبريس على ثغرات. على سبيل المثال يستخدم ووردبريس لغة PHP (وهي لغة برمجة صُمّمت أساسًا لتطوير وبرمجة تطبيقات الويب) في بناء نواته الأساسية ومن الممكن إن تحتوي بعض إصداراتها أحيانًا على ثغرات أمنية. ولكن إذا كنت تُحدّث الإصدارات بانتظام، فلن تؤثر عليك. أما إذا لم تكن كذلك، فقد تؤثر المشكلات الّتي تحتوي عليها لغة PHP على الموقع الخاص بك. هناك احتمال بأن يكون هناك شيء آخر في ممارسات الأمان الرئيسية الخاصة بالموقع غير صحيحة. فمثلًا إذا كانت خبرتك قليلة في ضبط إعدادات خوادم الويب، فقد تفعل أشياءً تظن بأنها ضرورية لتشغيل شيء ما في موقعك دون أن تدرك أنها ستؤدي إلى آثار أمنية سلبية عميقة. وهذا يعد أحد الأسباب الرئيسية الّتي تجعلني أعطي أكبر قدر من الأهمية لضبط إعدادات خوادم الويب للمحترفين. هذا لا يعني أنه لا يمكنك إعداد الخادم الفعلي الخاص بك بنفسك، أو شراء خادم افتراضي خاص VPS بسعر رخيص وضبط إعداداته باستخدام سكربت إعداد كتبه شخص آخر. ولكن فعليًا عليك أن تكون على ثقة ودراية في الاعدادات الّتي تشغّل ووردبرس فوقها. لأنك إذا قمت بضبط موقعك ضبطًا صحيحًا، ولم تنتبه لوجود خطأ في ضبط إعدادات قواعد البيانات MySQL، فعندها لا تزال أشياء سيئة جدًا ممكن أن تحدث لموقعك. تجنب توصيات الأمان الشائعة غير الضرورية قبل ذكر ما يجب عليك اتباعه، لابد أن أذكر لك أمرًا أرى أنه من الواجب ذكره هنا إذ شاع استخدامه رغم قلة الفائدة المرجوة منه. معظم هذه النصائح لا تضر نسبيًا بالموازنة مع فائدتها إذا أدت عملها. ولكن سبب عدم توصيتي لك باستخدامها هو أن فوائدها وإن وجدت ستكون قليلة جدًا. ومن الممكن أن تتجاهل الممارسات الأمنية الأكثر قيمة منها عندما تنفق على هذه الممارسات وقتًا طويلًا لا تستحقه بالموازنة مع المكاسب الّتي تحققها. لديك مطلق الحرية بالقيام بها أو لا. ومن هذه التوصيات ما يلي: 1. إخفاء نسخة ووردبريس سنبدأ بأكثر نصيحة مشهورة وهي إخفاء رقم إصدار ووردبريس الخاص بك، أو إخفاء بأنك تعمل على ووردبريس من الأساس وهذا صعب جدًا القيام به أما الخيار الأول فهو في الأساس لا قيمة له. عمومًا من الصعب إخفاء أنك تقوم بتشغيل ووردبريس، ومعظم الأشخاص يحاولون القيام بذلك يغيرون وسم في مواقعهم أو التخلص منها. ولكن أي مخترق ذكي لن يعتمد فعليًا على ذلك، فذكر أحد مشرفي المواقع الّتي تعمل على أنظمة مختلفة غير ووردبريس بأنه لاحظ أشخاص يبحثون في موقعهم كما لو أن موقعهم مبني على ووردبريس بالفعل. إخفاء إصدار ووردبريس الخاص بك أقل حساسية. جوهر الموضوع هنا هو أن الهجمات الآلية ستقوم بالتحقق في كون موقع ووردبريس الخاص بك موجود على إصدار معروف ومحدّث من ووردبريس، ومن ثم يهاجمك إذا كان إصدار ووردبريس مدرجًا في قائمة الإصدارات المستهدفة. شخصيًا لم أسمع بأي أحد ذكر أن الهجمات الآلية تعمل بهذا الأسلوب، ولا أدري لماذا يفترضون مثل هكذا افتراض. اعتقد أن هذه النصيحة تفقد قيمتها لو كان موقعك محدّث بأحدث إصدار من ووردبريس، لذلك هنا تكمن أهمية تحديث الإصدار بدلًا من إخفاء رقم إصدار ووردبريس. 2. تعطيل الواجهتان البرمجيتان XML-RPC و JSON REST في هذا الصعيد أشعر أن الحديث موجه إلى الأشخاص الّذين يستخدمون موقعهم كواجهة تطبيقات برمجية (API) فقط، فليس هناك أي ضرر حقيقي في تعطيل الواجهة البرمجية XML-RPC لووردبريس (وهو بروتوكول إجراء عن بعد يستخدم صيغة الترميز XML لتمثيل البيانات ويستخدم HTTP لنقل البيانات ولتنفيذ الإستدعاءات). تُستخدَم الواجهة XML-RPC بواسطة عدة أدوات مثل أداة Marse edit وتطبيق ووردبريس للهواتف المحمولة وأدوات أخرى مشابهة من أجل الاتصال بموقعهم. وأصبح من الممكن الآن لتلك الأدوات الاعتماد على واجهة التطبيقات البرمجية Restful (وهي طريقة للتخاطب بين الأنظمة على الإنترنت والّتي تعتمد على بروتوكول HTTP وعلى الدوال المعروفة في هذا البروتوكول وتعتمد أيضًا على خاصية أن كلّ خدمة يمكن الوصول إليها عن طريق رابط فريد) أصبح من الممكن أيضًا استخدام هذه الأدوات للاعتماد على واجهة Rest بدلًا من XML-RPC، على الرغم من أن أغلب هذه الأدوات لم تُنشأ لهذا الغرض. وتعد واجهة التطبيقات البرمجية JSON REST إضافة جديدة إلى ووردبريس، ويمكن أن تلبي استخدامات مشابهة للتقنيات السابقة. بالإضافة إلى ذلك، تعمل ووردبريس داخليًا على الاستفادة من الواجهة JSON Rest للميزات الّتي توفر تفاعلًا أفضل. إن كلا الطريقتين السابقتين تعد من الطرق الّتي يمكن للمهاجم تتبعها بعد موقعك. وهناك سجلات معروفة للأشخاص الّذين يقومون بهذه الهجمات. والسبب وراء إدراجها في قائمة الأشياء الّتي لا أوصي بها، هو أن كلا النوعين من الهجمات ليسا شائعين للغاية. كما أن واجهات التطبيقات البرمجية (APIs) مفيدة للغاية، وربما تجد شيئًا ما في المستقبل يكون مفيدًا لموقعك وعندما تحاول القيام به على موقعك فتجد أنه لا يعمل أو يتسبب في تعطل موقعك وذلك لأنك حظرت واجهات التطبيقات البرمجية بشكل كامل. إذًا الحل الأفضل هو الحصول على جدار حماية على الإنترنت، ومن الناحية المثالية أن يكون مضيف موقعك يحتوي على جدارٍ ناري. وإن لم يكن الحال كذلك، فيمكنك شراء جدار ناري بنفسك من شركات مثل Sucuri (وهي شركة أمنية معروفة، ولديها شهرة كبيرة على شبكة الإنترنت. توفر جدار حماية قوي للمواقع المبنية على ووردبريس، وتعمل على حماية المواقع من هجمات الحرمان من الخدمة [DDos Attack]، وغيرها من تهديدات أمان مواقع ووردبريس) أو Cloudflare وكلاهما يوفر خدمات متشابهة. وباستعمال هذا الجدار يجب أن يتوقف احتمال حدوث حركة زيارات غير طبيعية لواجهات التطبيقات البرمجية، وعمومًا أن تدع الأشياء الجيدة فقط تمر عبرها هو أمر غير مضمون ولكنه يعتبر حلًا أفضل من إيقافها بالكامل. 3. تغيير بادئة قاعدة بيانات في مرحلة ضبط إعدادات ووردبريس يوجد حقل نصي يتطلب بادئة لقاعدة البيانات، والقيمة الافتراضية له تكون _wp. يخزن ووردبريس بياناتك، والمنشورات والتعليقات والمنتجات وغيرها في جداول قاعدة البيانات والّتي تبدأ بهذه البادئة. ما أريد توضيحه هنا هو أن القيمة الافتراضية أقل أمانًا ويجب عليك بدلًا من ذلك جعلها مثل _jadfl، والّتي من غير المحتمل أن تمتلكها مواقع ووردبريس أخرى. في هذه الحالة احتمالية حمايتك بتطبيق هذه الخطوة قليل. بدلًا من ذلك يجب عليك أن تحمي نفسك بشكل خاص من هجوم حقن قواعد البيانات (SQL-injection) لأنه غالبًا من خلاله تُخترق مواقع وورديريس، ولكن لكي يحدث ذلك، يجب أن يكون إصدار ووردبريس أو مكون إضافي يحوي على ثغرة تُمكّن المخترق من حقن قواعد البيانات من خلالهم. لذلك إذا كانت الإضافات أو القوالب في ووردبريس محدّثة، فلن تحصل فعليًا على فائدة إضافية من ذلك. وعلاوةً على ذلك أنه من السهل جدًا إذا كان لديك ثغرة أمنية تتيح هجوم حقن قواعد البيانات في موقعك، فإن المهاجم سيستعرض جداول قاعدة البيانات الخاصة بك، ولكن ليس بالسرعة العمياء على افتراض أن المنشورات موجودة في جدول باسم wp_posts، بل إن عدد المهاجمين الّذين يفترضون أن هذا هو موقع جدولك ولا يتحققون منه ربما يكونون نسبة مئوية صغيرة من الأشخاص الأذكياء بما يكفي للقيام بالهجوم في المقام الأول. أعتقد أنه إذا كنت تقوم بضبط إعدادات موقع جديد، ووجدت طريقة سهلة لتغيير بادئة قاعدة البيانات الخاصة فغيّرها مباشرةً، ولكن إذا كان موقعك يستخدم بادئة _wp لقواعد البيانات، فلا تُضع وقتك في محاولة القيام بذلك وخاصةً إن لم تكن مطور ويب. 4. إعادة تسمية عنوان URL لتسجيل الدخول إلى ووردبريس النصيحة الأخيرة الّتي اعتقد أنها لا تستحق الاهتمام فعلاً هي نقل عنوان URL لتسجيل الدخول الخاص بك من example.com/wp-login.php إلى شيء مثل example.com/my-secret-url-for-puppy-login. تم تسهيل ذلك من خلال بعض إضافات الأمان في ووردبريس وهي ليست مضيعة للوقت تمامًا. لا أستطيع أن أنكر فكرة أنه إذا تعذر على المهاجم العثور على صفحة تسجيل الدخول الخاصة بك، فلن يتمكن حتى من محاولة شن هجوم القوة الغاشمة (Brute force) على كلمات المرور الخاصة بك (سنتحدث لاحقًا في هذا المقال عن هذه النقطة بالتفصيل). المشكلة في هذه النصيحة هي أنه إذا وجد أحد المهاجمين صعوبة في العثور على صفحة تسجيل الدخول لموقعك، فربما ستجد أنت أو زملاؤك صعوبةً في ذلك أيضًا. إذا كانت لديك طريقة آمنة وموثوق بها لوضع إشارة مرجعية (Bookmark) على الصفحات الّتي تجدها سهلة الاستخدام، ففعل ذلك مباشرة فأنت بهذه الخطوة تحظى ببعض الفوائد الأمنية المتواضعة. ولكن مرة أخرى، فإن الفائدة ليست كبيرة إذا قمت ببعض الأشياء الأخرى بشكل صحيح. إذا كانت جميع الحسابات على موقعك تملك كلمات مرور جيدة، فمن غير المرجح أن ينجح هجوم تخمين كلمة المرور. أضف إلى ذلك أنه إذا كان لديك طريقة ما للحد من عدد محاولات تسجيل الدخول الفاشلة لحساب معين في موقعك ضمن فترة زمنية قصيرة، فأنت بذلك تقلص احتمال نجاح هجوم تسجيل الدخول. وهذان العاملان معًا - كلمات مرور جيدة وقيود على عمليات تسجيل الدخول الفاشلة - أكثر قيمة من عنوان URL الغامض لتسجيل الدخول إلى موقعك. أفضل الممارسات العامة لتأمين مواقع ووردبريس هناك بعض المهام الشائعة الّتي يجب عليك القيام بها إذا كنت تريد أن تحافظ على أمان موقعك. يوجد العديد منها ولكن سأسرد لك أبرزها وما يحب عليك فعله دائمًا. هذه القائمة ليست مرتبة حسب الأهمية، ولكن نسبيًا عند قيامك بالخطوات الخمس الأولى، فستكون متقدمًا على 50٪ من مواقع ووردبريس الأخرى على الإنترنت. ومن غير المحتمل أن تواجه مشكلة أمان لا يمكن إصلاحها في موقعك، وإذا كنت تطبق جميعها، فسأتفاجأ حقًا إذا تعرض موقعك للاختراق. هذا بالطبع ليس ضمانًا، بل إحتمال أرجحه من واقع خبرتي الشخصية. 1. استعمال كلمات مرور قوية الطريقة الأساسية الّتي يمكن من خلالها مهاجمة كلّ موقع من مواقع ووردبريس على الإنترنت هي طريقة بسيطة وهي تخمين الروبوتات كلمات المرور للحساب بانتظام، على كلّ موقع من مواقع ووردبريس على الإنترنت تقريبًا. فإذا كان لديك كلمة مرور سيئة فاحتمال أن تفقد حسابك كبيرة، وبالتالي موقعك. بعض كلمات المرور السيئة مثل: password p455w0Rd [اسم الموقع] charlieiscute هناك العديد من المدارس الّتي تشرح أهمية قوة كلمة المرور وطريقة جعلها كذلك. لكن جميعنا نتفق على أن هذه الكلمات سيئة إلى حد كبير، نظرًا لاحتمالية أن يخمنها المهاجم إذا علِم المنهجية المتبعة في صنع كلمة المرور. كلمة "password" وما شابهها -حتى تلك البدائل الرائعة الممزوجة بالمحارف والأرقام- تكون دائمًا على رأس قائمة البرامج المخصصة لتخمين كلمة المرور الّتي يجب تجربتها. أسماء المواقع هي أيضا مرشحة بشكل كبير للتخمين. وبعبارة "[اسم الموقع]"، أقصد مثلا مثل "academy.hsoub" أو "hsoub" ككلمة مرور لحساب المدير. تتميز "charlieiscute" عن غيرها بأنها ليست كلمة مأخوذة من القاموس، وهي أطول من سابقتها. كما أنها أيضًا فريدة من نوعها بعض الشيء، ولكن ليس من الصعب تخمينها لأنها تستخدم أحرفًا صغيرة فقط، فلا توجد فيها أحرف كبيرة أو أرقام أو رموز. رغم أنه من غير المرجح أن يعرف المهاجم ذلك، ولكن عندما تقوم بتضمين فئات أخرى من المحارف، فإنك تقلل من احتمال قيام نظام التخمين العشوائي بتخمين كلمة المرور بشكل أسرع. خطوات لتعزيز قوة كلمات المرور للووردبريس هناك مجموعة من الخطوات تحسّن أمان كلمة المرور في موقع ووردبريس. ولكن سأذكر هنا أهم ثلاثة خطوات: امتلك كلمة مرور يصعب تخمينها: كلمات المرور الجيدة تكون صعبة القراءة ومعقدة مثل "RP@yu3ohd&LtpwzM}rWhgp6#AtY6HAzjvxKnz9zh". كلمة المرور هذه طويلة وعشوائية وفريدة من نوعها. وهنا احتمالية تخمينها منخفضة. ولأنها فريدة من نوعها - وهي طبعًا ليست كلمة المرور الخاصة بي على Facebook أو ArtWorld أو RandomInternetReseller أو أي شيء آخر- فكشف كلمة المرور الخاصة بموقع ووردبريس لن يعرض بقية الحسابات للخطر. إبطاء هجمات التخمين لكلمة المرور: بشكل افتراضي، يستطيع الروبوت تخمين 1000 كلمة مرور في الدقيقة (تقريبًا) على موقع ووردبريس الخاص بك. وهذا العدد كبير أو مرتفع. إذا منعت الأشخاص من الاستمرار في محاولة تسجيل الدخول بعد عدد معين من المحاولات الفاشلة مثل ثلاث أو أربع أو خمس مرات، أياً كان - فسوف تبطئ من هجمات كثيرة محتملة. الطريقة المفضلة للقيام بذلك هي تقييد محاولات تسجيل الدخول. استعن بهذا الدليل السريع والّذي يشرح كيفية الحد من محاولات تسجيل الدخول بخطوات تفصيلية. استخدم مدير كلمات المرور: إذا نفذت الخطوة الأولى، فسوف تتعب بسرعة من كتابة كلمة المرور هذه، أو ربما تنساها. بهذه اللحظة ستحتاج برنامج يدير لكلمات المرور. هناك أنواع كثيرة جدًا من مديري كلمات المرور الّتي بإمكاني إخبارك باستخدامها ولماذا. ولكنني شخصيًا أستخدم 1Password ولكن يمكنك استخدام أي مدير كلمات مرور أخر مثل KeePass و LastPass والّتي تعدّ من البرامج المشهورة أيضًا. 2. تحديث إصدار ووردبريس وجميع الإضافات والقوالب الطريقة الأخرى الّتي من المؤكد أن كلّ موقع من مواقع ووردبريس على الإنترنت مضمون للهجوم من قبل الهجمات الآلية المنتشرة وهو استغلال الثغرات المعروفة في البرامجيات الّتي تشغّل على الموقع. سواءً أكنت تستخدم Revolution Slider أو TimThumb أم لا أو أحد إضافات ووردبريس الشهيرة، فمن المحتمل أن تحاول الروبوتات الاستفادة من المشاكل المكتشفة في الإصدار القديم من هذه الإضافات. ولذا فإن هناك فرصة جيدة للاختراق بالرغم من أن هذه الطرق تعدّ قديمة جدًا، إلا أن الروبوت تحاول مهاجمتك عن طريقها. لهذا السبب يجب عليك أن تبقي إصدار ووردبريس محدّثًا باستمرار. بالإضافة إلى كلّ القوالب الّتي تستخدمها أيضًا. من الشائع حاليًا أن تحوي إضافات أو قوالب ووردبريس على ثغرات أمنية أكثر من نظام ووردبريس بحد ذاته، مع العلم أنه يمكن أن يكون الثلاثة يحتوون على ثغرات. عندما يدرك باحث خارجي أو مطور داخلي أن هناك مشكلة أمنية في بعض الشيفرات البرمجية في مواقعهم، فتقع على مسؤوليته أن يُنشىء نسخة جديدة من الموقع لا تحتوي على هذه الثغرة الأمنية. ومعظم المستخدمين لنظام ووردبريس يجيدون القيام بذلك. لذلك السبب، يجب عليك أن تؤدي دورك عندما يقدمون تحديثات بالإصلاحات أو ترقيعات الأمان فيجب عليك تثبيتها مباشرةً. ويمكنك جعل ووردبرس يثبت هذه التحديثات نيابةً عنك، أو يمكنك القيام بذلك بنفسك، أو حتى باستخدام إضافة مخصصة لإدارة موقعك الخاص مثل ManageWP. بعض الاستضافات توفر ذلك نيابةً عنك، ويمكنك أيضًا استئجار شخص ما للقيام بذلك إن لم تكن تستطيع فعلها بنفسك. ولكن يجب عليك فعل ذلك إذا كنت جادًا بشأن أمان وحماية موقعك. 3. إنشاء نسخ احتياطية بشكل دوري وتلقائي من أفضل الأشياء الّتي يمكنك القيام بها للحفاظ على أمان موقعك هو التأكد من حماية بياناتك في حال حدث أي مكروه لموقعك أو بياناتك. أي يجب أن يكون لديك على الأقل نسخة احتياطية حديثة واحدة لموقعك يمكنك استخدامها لإعادة بناء موقعك أو استعادته في حالة حدوث اختراق أو تخريب. أعتقد أن الطريقة الوحيدة للحصول على نسخة احتياطية حديثة وموثوقة من موقعك هي القيام بذلك تلقائيًا. نظرًا لأننا بشر، وومن الممكن جدًا أن نخطئ، أو ننسى، فغالبًا إذا طلبتُ منك أن تضغط 1-10 نقرات في موقعك للحصول على نسخة احتياطية محدّثة، فغالبًا لن تقوم بذلك. لهذا السبب يجب أن تحاول استخدام الإضافات الجاهزة الّتي توفر النسخ الاحتياطي. ويجب أن يكون هذا النسخ الاحتياطي مثاليًا أيضًا بحيث يحتوي على كلّ ملفاتك من صور وملفات PDF وقاعدة البيانات وجميع المعلومات المهمة في موقعك. وستقوم الإضافات نسخة احتياطية لبياناتك ووضعها على خادم آخر غير الخادم الّذي تستخدمه، لأن ذلك سيجعل موقعك أكثر أمانًا في حال استطاع المتسلل التسلل إلى خادمك فلن يستطيع بسهولة الوصول إلى الخادم الّذي يحوي النسخ الاحتياطية الخاصة بك. نظرًا لأن هذا منشور حول أمان ووردبريس وليس عن النُسخ الاحتياطية، فلن أملأ لك قائمة كاملة بجميع الخيارات المتاحة للنسخ الاحتياطي ولكنني أظن أن Updraft Plus و Jetpack/Vaultpress و Backup Buddy الّتي تتبادر إلى ذهني حاليًا هي حلول جيدة للنسخ الاحتياطي. 4. التحقق من موقعك وفحصه بانتظام في الحقيقة أن هذه الخطوة مهمة ولكنها لن تستطيع أن تمنع حدوث شيء سيء لموقعك، والسبب في أنه من المهم أن تبقى على اطلاع على حالة موقعك كي تمنع حدوث أشياء أسوأ. فعندما ترى أشياء غريبة تحدث على موقعك، فمن السهل إصلاحها مبكرًا. ويصبح الأثر أقل سوءًا - لأن محرك البحث Google سيحذر الزائرين من موقعك في حال اكتشافه أي مشكلة أمنية تسبب الضرر للزوار - وتخفف الضرر الّذي سيلحق بموقعك. إذا لم تُسجل الدخول بانتظام إلى موقعك لتفقده، فيجب عليك على الأقل التأكد بانتظام بأن الشكل الّذي يظهر للزبائن يبدو جيدًا. وإذا لم تكن قادرًا على القيام بذلك، فيجب أن تتأكد على الأقل من متاح ويمكن الوصول إليه وذلك باستخدام خدمات خارجية (مثل Pingdom). تتأكد هذه الخدمات من إمكانية استعادة نسختك الاحتياطية عند الدخول الجديد للموقع مباشرةً في حالة حدوث شيء سيء أو غريب. هذا في أسوأ الحالات، ولكنه ضروري لصحة الموقع على المدى الطويل. 5. تجنب الشيفرات البرمجية المقرصنة أو المسروقة هذه ليست خطوة مهمة للغاية لمعظم الناس. فمعظمنا يستخدم إصدار ووردبريس من الموقع الرسمي أو من المطورين الّذين استأجرناهم أو من البائعين الآخرين ذوي السمعة الطيبة. فإذا كنت تقوم بكل هذه الأشياء فليس عليك القلق من هذه النقطة. في الغالب الّذي يجب عليه أن يقلق هم الأشخاص الّذين يحاولون توفير المال عن طريق قرصنة الإضافات، أو القوالب، وما إلى ذلك أو الأشخاص الّذين يحاولون توفير المال عن طريق الحصول على الشيفرات البرمجية من "نوادي الإضافات" أو غيرها من المصادر منخفضة الثقة. إذا قمت بذلك، فمن المحتمل أن تكون هذه الشيفرات مجانية وذلك لأن شخصًا ما وضع ثغرة بداخله تتيح له التسلل إلى موقعك في المستقبل. في بعض الأحيان، يمكن للإضافات المجانية لووردبريس المتاحة في الموقع الرسمي أن يستولى عليها من قِبل جهات شريرة وتجري عمليات سيئة لها. ولكنه ولحسن الحظ نادر الحدوث، وفريق المشرفين على حماية ووردبريس يقوم بمراقبة وإصلاح أي مشكلة. ولكن إذا كنت قلقًا بشأن هذه النقطة فإن أفضل حلّ هو أن تكون أكثر تركيزًا ومعرفةً في الإضافة الّتي ستقوم بتثبيتها. 6. تعزيز أمان تسجيل دخول للووردبرس بواسطة HTTPS و 2FA واحدة من أكثر الأشياء شيوعًا في نصائح الأمان للووردبريس هي الحصول على شهادة SSL. إنها بالتأكيد خطوة جيدة. ولكن الشيء المهم الّذي يدركه عدد قليل جدًا من الأشخاص هو أن HTTPS / SSL لا تؤمن حقًا موقع ووردبرس الخاص بك، ولكن تؤمن الاتصال بين موقعك ومن يتصفحه؛ لذلك فهو ذو قيمة عالية، ولكن هذا ليس كلّ شيء. في صميم الموضوع، يجب اعتبار HTTPS بمثابة نفق آمن بين موقعك وأي شخص يشاهده في متصفح الويب. يمكن اختراق اتصالات HTTP بواسطة أي من الأجهزة المختلفة الموجودة بين الزائر والخادم. أما مع HTTPS، فإنه من المستحيل تقريبًا أن يتطفل هؤلاء الأشخاص على الاتصال. لذا فإن الفائدة المباشرة من HTTPS هي أن بيانات تسجيل الدخول الخاصة بك من اسم المستخدم وكلمة المرور أو بيانات اعتماد بطاقة الائتمان أو أي بيانات حساسة ستكون أكثر أمانًا. ومن غير المرجح أن يرى شخص ما يتطفل على الشبكة اللاسلكية الموجودة في المقهى على كلمة المرور الخاصة بك إذا كنت تستخدمها. لذلك يجب أن تحصل على شهادة HTTPS بكل الوسائل. لا تعتقد أن ذلك يؤمن تثبيت ووردبريس الحقيقي على الخادم بأي طريقة من الطرق، لأنه ليس كذلك. وهناك خطوة أخرى جيدة ولكنها غير مريحة إلى حد ما وهي استخدام الاستيثاق الثنائي (Two-Factor Authentication) والّذي يعرف اختصارًا 2FA وتثبيته على موقعك. عند القيام بذلك، ستحتاج إلى ثلاثة أشياء لتسجيل الدخول بدلًا من شيئين. عادةً ما نقوم بتسجيل الدخول إلى موقع ووردبريس باستخدام اسم المستخدم أو عنوان البريد الإلكتروني وكلمة المرور.ولكن مع الاستيثاق الثنائي، فستحتاج أيضًا إلى "معلومة أخرى"، وبما أن الهواتف أصبحت أساسيات الحياة في عصرنا الحالي فيمكننا إرسال رسائل نصية إليه للتحقق من المستخدم. في كلتا الحالتين، فأنت أكثر أمانًا لأن معرفة كلمة المرور الخاصة بك لن تكون كافية للاستيلاء على حسابك وسيحتاجون إلى رمز مكون من 4 أو 6 أرقام أيضًا والّذي سيرسل إلى هاتفك. هناك مجموعات متغيرة باستمرار من الإضافات الّتي تعطي هذه الميزة في ووردبريس حاليًا، فمثلًا Jetpack تقدم هذه الخدمة أو بإمكانك استخدام إضافة مستقلة. يمكنك الرجوع إلى مقالة كيف تستعمل SSL وHTTPS معووردبريس ومقالة كيف تحمي حسابك على مدوّنةووردبريسباستعمال الاستيثاق الثنائي2FA لمزيد من المعلومات. 7. التحكم في إمكانية الوصول لموقعك من المهم جدًا أن تكون على دراية وفهم كامل بما يتعلق بمن توفر لهم خدمة الوصول إلى موقعك ومعرفة الممارسات الأمنية اللازمة لتأمين ذلك. ويمكن عدها مجموعة من الخطوات إذ أنها أشمل من كونها خطوة واحدة. ومن بعض الخطوات الّتي اعتقد أنها مناسبة تحت هذا العنوان هي كالتالي: إعطاء كلّ مستخدم صلاحية الوصول إلى حساب مخصص مثلًا لديك جمال وسارة كلاهما يسجلان الدخول إلى موقعك باستخدام حساب مستخدم مثل المسؤول. هنا عليك تمييز كلّ منهم بحساب خاص به وصلاحيات خاصة بهم وذلك باستخدام إدارة صلاحيات المستخدمين وأدوارهم في ووردبريس. على سبيل المثال امنح الكاتب جمال حساب "محرر"، وامنح سارة حساب "المدير" والّذي يملك كافة الصلاحيات على الموقع. يعتمد هذا على مبدأ يسمى الحد الأدنى من الأمتيازات والّذي ينص على إعطاء القدرة على الوصول إلى المعلومات والموارد الضرورية فقط من الموقع أي أقل قدر من الصلاحيات الضرورية. لا تمنح المستخدمين إمكانية إنشاء حسابات بكلمات مرور ضعيفة إذا اتبعت النصيحة السابقة، فستريد أيضًا التأكد من عدم استخدام جمال وسارة كلمات المرور ضعيفة. يمكنك القيام بذلك عن طريق سؤالهم أو إخبارهم بعدم جواز ذلك عن طريق وضع رسالة خطأ تخبرهم عدم جواز ذلك أو عبر آلية معينة تمنع كلمات المرور الضعيفة بأوامر مثل استخدام إضافة مخصصة لذلك. لا تعطي صلاحيات الوصول للكثيرين، أعني بذلك المزيد من النصائح الحساسة، والّتي أصبحت في الغالب أسهل مع إضافات الكثيرة المتوفر لأمان ووردبريس، وإذا كنت مطورًا، قم بتعيين بعض ثوابت للووردبريس مثل عدم السماح بتحرير الملفات في منطقة إدارة الووردبرس، والتأكد من أنك لا تمنح الأشخاص حق الوصول إلى مخدم الموقع الويب أو الوصول إلى SFTP (وهو بروتوكول نقل البيانات بشكل آمن باستخدام التشفير من دون نقل أي بيانات خالصة في الملفات النصية ويعدّ أكثر أمانًا من بروتوكول FTP لنقل الملفات) ولا أي صلاحية لا يحتاجون إليها أي حافظ بذلك على إطار الحماية العام. 8. تثبيت الإضافات الأمنية الخاصة بووردبريس يوجد العديد من الإضافات الأمنية الجيدة في ووردبريس، والّتي تعد عاملًا مساعدًا جيدًا في العديد من الاحتياجات الأمنية الشائعة الّتي أدرجتها أعلاه (وبعضها أيضًا يساعد في ما لم أذكره أو ما عددته في الفئة غير المفيدة). من أسماء الإضافات الّتي تتبادر إلى ذهني سريعًا هي Sucuri و WordFence و Jetpack Protect و iThemes Security. وهناك العديد من الإضافات الأخرى، فهذه القائمة لا يمكن اعتبارها قائمة نهائية، وأتمنى منك ألّا تأخذ درجة الترتيب المذكورة كمقياس للأهمية. الإضافات تساعد في العمل فهي تفعل أشياء كثيرة مثل إنشاء سجلات الوصول، وتعمل على حظر واستبعاد الجهات السيئة المعروفة، وتعمل أيضًا على الحد من محاولات تسجيل الدخول الفاشلة، وتساعدك على إصلاح المزيد من المسائل الداخلية مثل مشاكل أذونات الملفات على موقعك. وتوفر لك الإضافات WordFence و Sucuri آلية مفيدة لمنع دخول الهجمات الآلية وإدارة حركة الزيارات الخبيثة إلى الموقع ومن ثم التخلص منها بما يسمى "جدار حماية تطبيق الويب". ختامًا الحصول على الأمن هو أمر صعب للغاية، ولكن أتمنى أن أكون وفقت في توضيح بعض الأمور المهمة، صحيح أن المقال طويل نوعًا ما، فهو يشتمل على 5000 كلمة، ولكن إذا ركزت بشكل جيد فيها، أو قرأت البنود الّتي ذُكرت بعناية، فسوف تتعلم الكثير وهذا ما أتمناه. بالرغم من أنني وكما ذكرت منذ البداية لا أشعر أن هذا العنوان الّذي وضعته "الدليل الكامل في أمن مواقع ووردبريس" عنوانًا دقيقًا، إلا أنني أعتقد بأني ذكرت بعض من أفضل الممارسات الأمنية لأصحاب مواقع ووردبريس. إن المسائل الأمنية في ووردبريس بشكل عام موضوع يحتاج منك فقط أن تحدّث إصدار ووردبريس وجميع الإضافات والقوالب باستمرار ويحتاج أيضًا لاستخدام كلمات مرور قوية ومن هنا تكون بقية النصائح الأمنية مفيدة ولكن غير ضرورية لأن ووردبريس بحد ذاته آمن تمامًا ولكن النسخ الاحتياطي يجلب لك الثقة في آمان موقعك مثل مشاهدة موقعك واستباق الخطوات في حال حدوث مكروه وضبط صلاحيات الوصول للمستخدمين والحصول على إضافات مخصصة للحماية ولكن العديد من النصائح الّتي تسمعها من الناس بالرغم من أنها ليست عديمة الفائدة، إلا أنني لا أنصح بقضاء بعض الوقت في التركيز عليها. وأخيرًا إن جعل موقعك آمنًا تمامًا إلى الأبد أمر مستحيل بضغطة أو خطوة واحدة. وإنما هي مهمة متغيرة ومتطورة باستمرار. حاول أن تكون على اطلاع بأخر الأخبار عن أمن ووردبريس، وفكر في انعكاس ذلك على موقعك، وستتعلم أكثر بكثير مما تعلمته في هذا المقال أو في أي مقال أخر يتحدث حول هذا الموضوع. ترجمة -وبتصرف- للمقال The Complete Guide to WordPress Security لصاحبه David Hayes
-
لقد أحدثت الحاويات (Containers) والخدمات المصغّرة (Microservices) ثورة في تطوير التطبيقات وإدارة البنية التحتية لها، وقدمت لنا تحديات أمنية جديدة دون أن تعالج التحديات السابقة. إذن ما هي أبرز التحديات الأمنية الجديدة؟ وما الذي يجب علينا فعله تجاهها؟ تقنيات جديدة وتحديات جديدة تُعدّ الخدمات المصغّرة (Microservices) من أشهر المعماريات البرمجية في وقتنا الحالي، والتي جاءت لتحل مشاكل الطريقة التقليدية في تطوير المشاريع والتي تُعرف باسم (Monolithic) وهي معمارية تجعل كلّ خدمات المشروع عبارة عن وحدات برمجية متراصّة ومتجانسة مع بعضها بعضًا وتعمل جميعها في وقت واحد. فإذا حدث مشكلة ما في أحد خدمات المشروع سوف تتأثر جميع الأجزء المتجانسة الأخرى أمّا (Microservices) قسّمت المشروع الواحد إلى عِدّة مشاريع منفصلة، وكلّ مشروع يكون عبارة عن خدمة مصغرة، وتعمل كل خدمة منهم بشكل مستقل عن الخدمة الأخرى مع إمكانية إتصال خدمة معينة بخدمة أخرى من خلال قنوات اتصال محددة. لقد غيّرت الخدمات المصغّرة كلّ شيء. إذ نلاحظ أن أغلب الشركات في وقتنا الحالي تتجه لوضع خارطة عملٍ معتمدة على الخدمات المصغّرة باستخدام بنى معمارية مثل البنية التحتية الثابتة (Immutable infrastructure)، أو البنية المعمارية التي لا تدعم التشارك (shared-nothing architecture)، أو التطبيقات المتوضعة في الحاويات (containerized applications). لقد كانت طريقة الكشف عن فعالية المشاريع في حالات معينة مثل العمل في نطاق صغير، أو في الإستقلالية، أو في تحقيق الإستدامة الذاتية تُعرف عند بناء الواجهة البرمجية API وتشغيلها على الآلة الوهمية (virtual machine) أو بداخل حاوية، أو حتى بتشغيلها على القرص الصلب (حيث يكون مُثبت عليه نظام التشغيل)، أما الآن فيمكننا الكشف عن فعالية هذه المشاريع باستخدام الخدمات المصغّرة بحيث يتم إختبار كلّ حالة على حدة ومعرفة فعالية المشاريع بدون بناء واجهة برمجية. وعلى الرغم من المزايا المتعددة لإستخدام الحاويات إلا أنها تقدم لنا مجموعة من التحديات أيضًا من بين تلك التحديات المباشرة التي تواجه الحاويات هي الزحف على الحاويات (container crawl)، وحماية الحاويات، وتعقب الخدمات المصغّرة، ومعرفة مصدر نُسخ الحاويات. إذًا ما تأثير ذلك على الأمن في بيئة الخدمات المصغّرة؟ إن التحديات الأمنية لا تزال موجودة في بيئة التطبيقات أحادية النواة monolithic applications، ولا نزال نحتاج إلى معرفتها وتحديدها لمعالجتها. بالإضافة إلى ذلك، نحتاج إلى النظر في التحديات الأمنية الإضافية التي تفرضها الخدمات المصغّرة وتقنيات الحاويات. ومن منظور أمني، زادت إمكانية التعرض للهجوم بشكل كبير في عالم الخدمات المصغّرة بالموازنة مع التطبيقات أحادية النواة. أن زيادة قابلية النشر للوحدات وللأجهزة البعيدة (مثل: الهواتف الذكية والأجهزة المحمولة والأجهزة اللاسلكية الأخرى) أدى إلى زيادة التعرض للخطر، وإحتمالية حدوث هجوم أمني على الأجهزة البعيدة، ومشكلات التكاملية. بالإضافة إلى ذلك، قد تحتوي نُسخ Docker الأساسية على ثغرات أمنية والتي قد تضر بالخدمات المصغّرة. لذا ينبغي إدارة مصدر نُسخ Docker لضمان صحتها وخلوها من الثغرات. ويشكّل نشر تحديثات الأمنية والترقيعات مع المحافظة على أدنى مدة زمنية لتعطل الحاويات تحديًا آخر يصعب تجاوزه. يمكن أن تضيف DevOps المزيد من التركيز على الاحتياجات الأمنية في عالم الخدمات المصغّرة. حيث تركز أدوات النشر والبناء المستمرة حاليًا على تبسيط عملية التسليم، على الرغم من عدم تركيزها كثيرًا على حماية DevOps . أفضل الممارسات الأمنية لمواجهة بعض التحديات المذكورة أنفًا، دعونا نُلق نظرة على أفضل الممارسات التي يمكن إتخاذها. كجزء من استراتيجية DevOps الفعالة، يجب دمج خطوات الحماية والتدقيق المستمرة في منهجية DevOps. ويجب أن يتضمن الاختبار المستمر مُميزات إختبار الأمان المختلفة ويجب إضافة التحقق من سلامة نُسخ الحاويات إلى منهجية DevOps. وكما يمكننا أيضًا استخدام السجلات الخاصة private registries والتي تضمن لنا أن النُسخ موثوق بها وموجودة داخل الشركة. وينبغي تعزيز المراقبة من خلال آليات للكشف عن حالات الشذوذ لاكتشاف أي استخدام خاطئ للموارد أو الأحداث الغير طبيعية مع الحاويات، كما ينبغي تعزيز جميع قدرات المراقبة لتتبع العمليات المعتمدة على وكيل (agent-based) والعمليات بدون وكيل (agentless). كما يجب تحديث إدارة الترقيعات (Patch management) وتحسينها لضمان ترقيع الحاويات بأحدث التحديثات مع ضمان الحد الأدنى من وقت التوقف عن العمل. ولقد قدم Docker آلية لتحديد مصدر النُسخ تسمى (Notary)، والتي تستند إلى TUF (اختصارًا إلى The Update (Framework الذي يُستخدم في توزيع وتحديث البرامج بشكل نموذجي). ولكن العديد من أخصائيي الحاويات في وقتنا الحالي لا يستخدمونه Notary وذلك لأنه لا يزال في مرحلة التطوير. كما أن Notary يتطلب عملية إدارة رئيسية خبيرة في الشركة. تعدّ المنطقة التي تقع بين الخدمات المصغّرة من المناطق التي تُرتكب فيها الأخطاء أمنية، ويمكن استخدام Gateway API (وهي برمجية تقع أمام الواجهة البرمجية وتعمل كنقطة دخول واحدة لمجموعة محددة من الخدمات المصغّرة) بفعالية في هذا الموقف. إن تشفير الرسائل باستخدام تشفير الرسائل التقليدي PKI (البنية التحتية للمفتاح العام Public Key Infrastructure وهو إطار التشفير والأمن السيبراني الذي يحمي الاتصالات بين الخادم والعميل) ليس خيارًا قابلًا للتوسعة في هذا الموقف. لذا بدلًا من ذلك، يمكننا استخدام JSON Web Token) JWT) وهو رمز مشفّر مع مجموعة من سياسات التأكيد assertion policies حول الطالب للخدمة (requestor).حيث يوقعّ خادم الهوية الرسائل برمز مُميز والذي يُمكن التحقق منه بواسطة نظام المستلم (recipient system). كما يمكن تشفير JWT رقميًا للحفاظ على سرية وسلامة التأكيدات (assertions) والتي تسمى حينَئِذٍ JSON Web Encryption) JWE). غالبًا ما تكون الحماية صعبة وباهظة الثمن. لذا حاول أن تجعل من الحماية سمة أساسية للبنية التحتية للخدمات المصغّرة لتوفير أفضل مستوى من الأمان والأداء. ترجمة -وبتصرف- للمقال Secured DevOps for microservices لصاحبه Sreekanth Nyamars
-
على مدار الأشهر القليلة الماضية، نشرت بعض المقالات الّتي تستكشف النقاط الأساسية الثلاث في تحولك الرقمي وهي المنتج والعملية والناس، وذلك لتمكينك من الانتقال من نموذج عمل مُحسن للموارد استنادًا إلى النفقات الرأسمالية (CAPEX) إلى نموذج مُحسن للسوق استنادًا إلى النفقات التشغيلية (OPEX). ومن الأمثلة على الانتقال من CAPEX إلى OPEX هو استخدام خدمات أمازون السحابية AWS بدلًا من إنشاء بنية تحتية من الصفر والتي ستُخدّم المشروع. ومن بين الفوائد الأخرى، يساعدك إتحاد الأشخاص والعمليات والمنتجات على رفع قيم العمل الأساسية، مثل زيادة قيمة الأعمال Business Value، مثل: زيادة قيمة الموظف، وقيمة العميل وقيمة الموارد وكثير من هذه القيم التي لا تقاس مباشرة بالقيم النقدية، بالإضافة إلى تقليل أوقات دورة التسليم، وتمكين الجميع من التعلم والتكيّف وتطوير مستواهم بشكل مستمر. يركز المهندسون (مثلي) عادة على المنتجات والأدوات والتقنيات. إذا أردنا النجاح في الانتقال إلى DevOps، فمن المهم أن ندرك أنه لا يمكنك شراء أو تركيب DevOps! المنتجات هي أدوات داعمة، وليست الحل السحري للأمر، فهي تتيح لك التركيز على النتائج، مثل الأتمتة، والتطابق، والموثوقية، والقدرة على الصيانة، وتمكين الميزات أو تعطيلها بشكل تدريجي، و تصور الخطوات اللازمة للانتقال من إنشاء المنتج إلى تسليمه إلى العميل النهائي أو ما يسمى flow of value. عندما نتحدث عن العملية، فإننا غالبًا ما نتجه نحو الأتمتة لرفع قيمة الكفاءة والاستقرار والثبات. ومن المهم أيضا تضمين أهداف الاحتفال بالنجاح سواءً كفريق أو منظمة، والتركيز على الجودة انطلاقًا من تصوّرها وتخيلها وانتهاءً بتحقيقها على أرض الواقع، وإنشاء عملية إدارة التغيير بحيث تكون سريعة الاستجابة وخفيفة، واحتضان البنيات المترابطة بشكل خفيف (loosely coupled architectures) لتمكين التطوير، والسعي إلى إصدار عدة مميزات جديدة في اليوم. إن جوهر هذا التحول هو تحول الناس وثقافتهم وليس المنتجات أو العمليات أو حتى حجم المنظمة. يحتاج الناس لأن يدركوا أهمية التحول وأن يعتنقوا فكرة DevOps، وأن يفهموا كيف ستتأثر مهامهم ووظائفهم، ويجب عليهم تحمل مسؤولية أدوارهم المنوطة إليهم في هذا التحول. ويجب عليهم أيضًا أن يدركوا أن DevOps لا تقتصر على التنمية والعمليات، حتى وإن كان اسمها بحد ذاته يدل على ذلك ويستبعد أصحاب المصلحة الآخرين.إنك تحتاج إلى إزالة جميع العوائق والمفاهيم الخاطئة داخل مؤسستك والتي تمنع من الانتقال إلى DevOps، وأن تجمع بين كلّ أصحاب المصلحة ومن جميع الأقسام في الشركة، بما في ذلك أقسام التطوير وخدمات البيانات والعمليات والأمان والأعمال إلى أن يتم تحقيق التحول إلى DevOps. ترجمة -وبتصرف- للمقال Breaking down walls between people, process, and products لصاحبه Willy-Peter Schaub
-
إذا كنت مهتما بتعلم البرمجة، فعلى الأرجح أنك رأيت هذا الاقتباس من قبل: في الحقيقة لا أبالغ إن قلت أن البرمجة أصبحت من أهم المهارات في عصرنا الحالي، وذلك لأنها دخلت في جميع مجالات حياتنا سواءً كنا ندرك ذلك أم لا فانطلاقًا من الهواتف المحمولة ومرورًا بالمنازل والشاشات الذكية وانتهاءً بالسيارات ذاتية القيادة. كل شيء من حولنا لم نكن لنراه بهذا الشكل لولا البرمجة، فأنا على سبيل المثال أكتب وأعدل هذا الموضوع من خلال برنامج بُرمج عبر لغة برمجة وأنت تقرأه على الإنترنت باستخدام برنامج (المتصفح) وربما رأيته على مواقع التواصل الاجتماعي وكُلها برامج أيضًا. ومما لا شك فيه أن تعلم البرمجة أصبح ضرورة مُلحة في هذا العصر بل إن تعلمك لها سيزيد من فرصك بشكل كبير في الحصول على عمل. ولقد تحدثنا في مقالٍ سابق عن كيفية تعلم البرمجة والدخول إلى هذا المجال من أوسع أبوابه (إن كنت حديث عهدٍ في البرمجة أو تخطط للبدء بها، فننصحك بقراءته قبل إكمال هذا المقال) وسوف نتحدث في هذا المقال عن أكثر المهارات صعوبةً في احتراف البرمجة ألا وهي «حل المشاكل». حل المشكلات وارتباطها بالبرمجة لطالما وقفنا حائرين أمام مشكلةٍ ما ونسأل أنفسنا ماهي الطريقة الصحيحة للخروج من هذا المأزق؟ هل ستكون طريقة الحلّ التي أتبعها مشابهة للطريقة التي يتبعها المبرمجين المحترفين؟ كيف أستطيع أن أفكر مثلما يفكر المبرمجين المحترفين؟ في البداية وقبل الخوض في التفاصيل دعنا نُعرف البرمجة بحد ذاتها ولنذهب للمقال السابق ونلقي نظرة سريعة عليها: نلاحظ أن التعريف السابق صحيح ولكنه مُقْتضَب ولا يقدم لنا المعنى الكامل والدقيق للبرمجة لذا لابدّ لنا من تعريفٍ أكثر دقة وموضوعية يساعدنا في فهم هذا المقال. يعرف موقع HackerRank في تقريره عن مهارات المطورين بعام 2018: نستنتج مما سبق أن البرمجة تعتمد اعتمادًا أساسيًا على التفكير المنطقي والرياضي والقدرة على حلّ المشاكل وإن لغات البرمجة ما هي إلا وسيلة للتخاطب مع الحاسوب. إذن الأمر كله يتعلق بتطوير قدرتك في حلّ المشاكل وجعل هذه العملية سهلة وسلسة بنفس الوقت لذا فاسمح لي بأن آخذك معي برحلة صغيرة في هذا المقال نتعرف فيها على أهمية حلّ المشاكل وضرورتها في مشوارك البرمجي. دورة علوم الحاسوب دورة تدريبية متكاملة تضعك على بوابة الاحتراف في تعلم أساسيات البرمجة وعلوم الحاسوب اشترك الآن لماذا حلّ المشاكل مهم؟ تأتي أهمية حل المشاكل من كونها من أكثر المهارات المطلوبة عالميًا فوفقًا لتقرير أصدره موقع HackerRank جاء فيه: احتلت مهارة حلّ المشاكلالمرتبة الأولى بنسبة 94.9% لأكثر مهارة مطلوبة لأصحاب الشركات سواء الصغيرة منها أو الكبيرة. وعلى الصعيد العملي، إن عملية بناء أي شيء من الصفر سواء أكانت آلة معينة أو أي مُنتج جديد ستواجه الكثير من المشاكل في البداية، فعلى سبيل المثال بناء خدمات أمازون السحابية (AWS)، والّتي جاءت حلًا لمشكلة النفقات العالية للبنية التحتية لتجارتها الإلكترونية بالإضافة إلى الوقت الطويل الّتي تحتاجه في عملية البناء والذي شكل تحدي كبير في إمكانية توسع الشركة، إلى أن جاء آندي جاسي كبير مستشاري جيف بيزوس في ذلك الوقت والذي استطاع إيجاد حلّ لهذه المشكلة ولم يتوقف عند ذلك الحد وإنما أختار تحويل هذا الحلّ إلى خط أعمال جديد تحت أسم خدمات أمازون السحابية والّتي بلغت عائداتها السنوية لعام 2018 قيمة تناهز 25 مليار دولار. نجد من التجربة السابقة أنه حصلت مشكلة فأُوجِد لها حلًا وأثناء خلق حل للمشكلة ظهرت الكثير من المشاكل الأخرى لتقع في سلسلة من المشاكل لا تنتهي. وبعد حلّ جميع المشاكل سيصبح المنتج جاهزًا للعمل، أي أن دورة حياة أي منتج سواءً أكان برمجيًا أو ماديًا ستحتوي على المشاكل، وبناءً على ذلك تكون مهارة حل المشاكل اللَبِنَة الأساسية في بناء مشوارك البرمجي، والّتي يجب علينا الإهتمام بها عند الإقدام على تعلّم البرمجة. تكون طريقة تعاملنا مع المشاكل غير منظمة وعشوائية في أغلب الأحيان، فمعظمُ المبرمجين الجُدد يَبْدَؤُونَ بحلّ أي مشكلة تُواجهُهُم بالطريقة التالية: جرّب أي حلّ للمشكلة. إذا لم ينجح الحلّ الأول حاول أن تجرّب أي حلّ آخر. إذا لم يفلح الحلّ كرر الخطوة الثانية إلى أن تصل إلى الحلّ. لا يحصل هذا الأمر مع المبرمجين فقط، وإنما يحصل مع أي شخص يواجه مشكلة ولا يستعن على حلها بالتحليل والتفكير المنطقي. في الحقيقة، قد يحالفك الحظ أحيانًا ويتم حلّ المشكلة ولكن كن حذرًا! فهذه هي الطريقة الأسوأ لحلّ المشكلات بل وإنها ستهدر وقتك بشكل كبير لذا من الأفضل استخدام طريقة منظمة توصلنا للحلّ، واسمح لي بأن أشاركك طريقة شاملة توصلك إلى حلّ أي مشكلة قد تواجهك. بناء خطة حلّ شاملة إن خطة الحلّ الشاملة هي مجموعة من الخطوات الَّتي تتبعها لحلّ أي مشكلة تواجهك، حيث تكون هذه الخطة عامة وتخصص بحسب المشكلة، وهي ليست تعليمات ثابتة وإنما مبادئ ننطلق منها جميعًا، وكلٌ منا يطبقها بأسلوبه الخاص، وفي نهاية المطاف إذا انطلقنا جميعًا من نفس المبادئ، فسنصل حكمًا على نفس جودة الحلّ أيضًا. إن هذه الخطة مؤلفة من عدة أجزاء ويناقش كلّ جزء فيها جانبًا معينًا من المشكلة إلى أن نصل للحلّ، وهي على الشكل التالي: 1. فهم المشكلة إن فهمنا للمشكلة المطروحة هي الخطوة الأكثر صعوبة في طريقنا لحلّها، بل إن أكثر المشاكل تأتي صعوبتها من عدم تمكننا من فهم عميق لها. ولكن متى تعلم بأنك استطعت فهم المشكلة؟ إذا كنت قادرًا على شرحها بكلمات واضحة وسهلة بحيث يستطيع أي شخص فهمها عندها تكون بالفعل قد فهمت المشكلة. هل تذكر عندما كنت عالقًا في مشكلة وبدأت بشرحها فوجدت أخطاء منطقية كثيرة لم تكن قد انتبهت لها من قبل؟ أغلب المبرمجين قد مروا بهذه الحالة من قبل، لذلك من الأفضل دومًا أن تكتب المشكلة الَّتي تواجهك باستخدام مخططات الحالة (State diagram وهي نوع من المخططات الَّتي تستخدم لتوصيف سلوك نظام ما) أو بأن تخبر شخص آخر عن المشكلة سواءً أكان زميل لك في الشركة أو عضو في الفريق أو أي شخص أخر. شاع بين المبرمجين استخدام البطة المطاطية في شرح أجزاء المشكلة لها لفهمها وإيجاد حل لها. يعد أسلوب البطة المطاطية في تنقيح الأخطاء من أكثر الأساليب سهولة وبساطة، إذ يضع المبرمج بطةً بجوار حاسوبه، وعند مواجهته لأي مشكلة يشرع في شرح الشيفرة البرمجية للبطة وما هي النتيجة المرتقبة من الشيفرة وموازنتها مع النتيجة الحالية وغالبًا ما يجد الخطأ أثناء شرحه. وخلاصة لهذه الخطوة أنقل إليك مقولة ألبرت أينشتاين: 2. تحليل المشكلة إن تقسيم المشكلة يلعب دورًا مهمًا في طريقك لإيجاد الحلّ، لذا حاول أن تقسّمها إلى أجزاء صغيرة ثمّ حُلّ كلَّ جزء منها على حدة ويُنصح في البداية بحلّ أسهل جزء منها ومن ثمّ الأصعب فالأصعب وهكذا إلى أن يتمّ حلّ جميع أجزائها، وبعدها إجمع هذه الأجزاء مع بعضها للحصول على الحلّ النهائي للمشكلة الأصلية (الكبيرة). منذ فترة كنا بصدد برمجة إضافة لمتصفح غوغل كروم بهدف تسهيل مهمة لموقع ما، فقررنا تجربة هذه الطريقة وبدأنا بتقسّيم المهمة على أجزاء ثم عملنا على حلّ كل جزء منها، وبعدها جمعنا هذه الأجزاء مع بعضها بعضًا لبناء الإضافة، وبالفعل كان العمل بهذه الطريقة سهلًا جدًا ومريحًا كما لم نعهده من قبل. في بعض الأحيان عندما تواجه شيفرات برمجية كتبها مبرمجون لا يتبعون مبادئ SOLID (وهي مجموعة من العادات والمبادئ الَّتي يتبعها المبرمجون للحصول على شيفرة برمجية قابلة للصيانة وسهلة التعديل والتكيف مع متطلبات المشروع المتغيرة)، وغالبًا ما تكون شيفرات أولئك المبرمجين غيرمفهمومة ومتشابكة ويطلق عليها اسم Spaghetti code (تكون هذه الشيفرات ذات بنية معقدة ومتشابكة وصعبة القراءة وغير مرتبة أي تكون مثل المعكرونة ملتوية ومتشابكة) ولنفرض أنه طُلبَ منك تعديل هذه الشيفرة أو إضافة وظائف جديدة إليها،عندها حتمًا ستواجه العديد من المشاكل في عملية تقسيم المشكلة ومعرفة أي جزء من الشيفرة البرمجية المُسبب للخطأ لذا كان الحصول على شيفرات برمجية مرنة وقابلة للتعديل هي الأرضية المشتركة بين العديد من تقنيات تبسيط الشيفرات البرمجية مثل مبادئ SOLID أو مبدأ MVC والذي ينص على تقسيم الوحدات من حيث طبيعة مهمتها إلى ثلاثة أقسام (Model-View-Controller) والبرمجة كائنية التوجه OOP (Object-Oriented Programming)، إذ أن جميعهم يهدفون إلى فصل الأكواد إلى أقسام ليسهل تطويرها وتنقيحها وصيانتها. 3. إعداد خطة للحل بعد أن فهمت وحللت المشكلة، يأتي دور وضع الخطة المناسبة للحلّ بحيث تغطي كافة الجوانب والتفاصيل للمشكلة، ولا تشرع في الحلّ من دون خطة (على أمل أن تجد الحلّ بطريقة ما) لأن المفتاح الرئيسي للوصول للحلّ هي الخطة الواضحة والمنظمة والتي تضمن وصولنا للحلّ النهائي. أعط لنفسك وقتًا لتحلّيل المشكلة وربط المعلومات المدخلة إلى البرنامج ونوعها والمعلومات الَّتي ستظهر كخرج للبرنامج وفهم سياقها وللحصول على خطة جيدة يجب عليك الإجابة على السؤال التالي: إذا أُعطي للبرنامج الدخل س، ما هي الخطوات اللازمة للحصول على الخرج ع؟ إن إجابتك على هذا السؤال سوف يحدد ماهي الخطوات اللازمة لحلّ المشكلة ومن ثَمَّ تقوم بترتيبها في خطة واضحة ومنظمة من أجل الحصول على الخرج الذي تريده. 4. مواجهة حالة السكتة البرمجية ماذا لو فرضنا أنك لم تستطع حلّ أي جزء من المشكلة، ولا حتى الأجزاء السهلة منها (وهذا قد يحدث في بعض الأحيان)، إن كثير من المبرمجين يقعون في هذه الحالة فلا يستطيعون أن يُحرزوا أي تقدم يذكر في تطوير الشيفرة البرمجية وهذا أشبه ما يمكن بالسكتة الدماغية (حيث لا يستطيع المريض القيام بأي حركة)، في الحقيقة إن هذه الحالة طبيعية جدًا ومعظمنا قد تعرض لها في بداية مشواره والاختلاف الوحيد هو أن المبرمج المحترف لديه فضول أكثر حول المشكلة ومعرفة سبب حدوثها بدلًا من أن يكون منزعجًا أو غاضبًا منها. وفي هذه الحالة هنالك حلّين يمكنك تجربتهما للخروج من هذا المأزق: 1-4. تنقيح الأخطاء (Debug) ليس المقصود هنا الأخطاء الكتابية في صياغة اللغة (Syntax errors) مثل نسيان فاصلة منقوطة أو أي خطأ في استخدام المتغيرات أو الدوال أو ما شابه ذلك من أخطاء والَّتي تقوم باكتشافها أي بيئة تطوير متكاملة (IDE وهي عبارة عن محرر شيفرة برمجية مدمج مع نظام ذكي لإكمال الكود ومصحح أخطاء). وبالطبع ليست أيضًا الأخطاء الّتي تظهر أثناء التنفيذ (Runtime Errors) والّتي تكون عادة نتيجة لفشلٍ في فتح ملف ما أو محاولة القسمة على صفر أو مثل هذه الأخطاء، وإنما المقصود هنا هو أخطاء المنطق البرمجي (Logic errors) الّتي ينفِّذ فيها البرنامج أمرًا غير الَّذي بُرمج من أجله، لذا من الأفضل أن تحاول أن تفحص الشيفرة البرمجية سطرًا سطرًا لعلك تجد هذا الخطأ، أوإذا كنت تعمل على لغاتٍ مثل (C++, C) والّتي تدعم استخدام المُنقِّح Debugger (الذي يراقب عمل البرنامج ويتحكم في تنفيذه بطريقة تستطيع فيها إيقاف تنفيذ البرنامج أو حتى تغييره في أي موضع من الشيفرة وذلك من خلال مجموعة من الأدوات الّتي يقدمها المُنقِّح، مثل: GNU Debugger) فيمكنك استخدامه لإيجاد الخطأ ومن ثَمّ إصلاحه. ملاحظة : من المنهجيات البرمجية الجيدة هي كتابة تعليقات توضيحية قبل كلّ دالة (Function) أو صنف (Class) برمجي وخُصوصًا تلك الأجزاء المعقدة منها لأن ذلك سوف يساعدك كثيرًا في عملية مراجعة الشيفرة البرمجية وتنقيحها. 2-4. مراجعة وتقييم الحلّ في كثير من الأحيان عند مواجهتنا للمشاكل وخصوصًا للكبيرة منها قد نضيع في التفاصيل الصغيرة للمشكلة الَّتي نواجهها وننسى المشكلة الرئيسية ولذلك من الأفضل دومًا أن نسأل أنفسنا هل هذه هي الطريقة الأفضل للحلّ؟ هل هنالك حلًّا عموميًا أفضل من الموجود؟ هل الشيفرة البرمجية أمثلية؟ هل الحلّ يخرق معايير الجودة المطلوبة؟ ارجع خطوة إلى الوراء وحاول أن تراها من منظور مختلف، وحتمًا ستلاحظ العديد من المشاكل التي غفلت عنها أثناء انشغالك بالتفاصيل الصغيرة. ملاحظة : هنالك طريقة أخرى يتبعها بعض المبرمجين لإعادة تقييم الحلّ وهي حذف الشيفرة البرمجية الخاطئة بالكامل وإعادة كتابتها من جديد فكثير ما يأخذ ذلك وقتًا أقل من تتبع المشكلة ذاتها وحلها. 5. البحث عن حلول عبر الإنترنت إن أغلب المشاريع البرمجية تكون متشابهة بكثيرٍ من الوظائف والخصائص، ونادرًا ما نرى مشروع ذو أفكارٍ جديدة بالكامل لذا فإن أي مشكلة برمجية تواجهها قد واجهها عدد كبير من المبرمجين من قبلك وأوجدوا لها حلولًا وشاركوها مع غيرهم، وكل ما عليك فعله هو أن تتعلم كيف تبحث عن المشكلة. وبالطبع صديقنا stackoverflow والذي يعد من أشهر منصات مشاركة الحلول البرمجية الّذي يقدم لك الحلّ الَّذي أجمع عليه أغلب، المبرمجين، ويوجد العديد من المنصات الأخرى المشابهة مثل AskUbuntu وهو النسخة العربية من موقع stackoverflow والكثير غيرهم، وحتى لو أنك قد حللت المشكلة أنصحك بتصفح الحلول الموجودة لأنك سوف تتعلم طرقًا أُخرى لحلّها قد تكون أسهل وأفضل بكثير من الحلّ الَّذي وصلت اليه. إلى الآن نكون قد ناقشنا الخطوة الأولى من الطريقة الشاملة لحلّ المشاكل والآن لننتقل إلى الخطوة الثانية. التدرب على هذه الخطة إن أي مهارة جديدة تحتاج إلى تدريبٍ مُمنهجٍ ومستمرٍ حتى تتقنها. لذا لا بدّ لك من وضع خطة واضحة للتدريب وتخصيص زمن محدد وثابت تقضيه في صقل تلك المهارة.يمكنك البدء مثلًا بتخصيص ساعة للتدريب يوميًا لمدة شهر كامل، وبعدها توازن نفسك مع ما كنت عليه في السابق من سرعةٍ في تحديد المشكلة وجودةٍ الحل المطروح والوقت الإجمالي لحل المشكلة وبكل تأكيد ستلاحظ تحسنًا واضحًا في أدائك. وقد تساءل نفسك كيف يمكنني التدرب على حلّ المشاكل؟ في الحقيقة هنالك طريقة مفيدة جدًا أنصحك بها لتكوين قاعدة صلبة تنطلق منها، وهي على الشكل التالي. 1. التدرب على المسائل البرمجية تعد المسابقات البرمجية سواءً على مستوى الجامعة أو على مستوى القطر أو حتى على مستوى العالم مثل مسابقة ACM ICPC من أفضل الفرص للتدريب في مجال البرمجة وصقل هذه المهارة بل وتعتبر دفعة كبيرة لك في رحلتك كمبرمج ولمستقبلك المهني أيضًا، إذ تبادر العديد من الشركات العالمية لِضمّ أولئك المنافسين المتميزين إليها بعد أن أثبتوا بالفعل أنهم النخبة في مجالهم. فإن كنت تخطط للانضمام إلى هذه المسابقة فلا تتردد وبادر بالتحضير لها من الآن. ويقدم لنا الكاتب Steven Halim في كتابه Competitive Programming بعض الفوائد الَّتي نجنيها من التدرب على حلّ المشاكل البرمجية في المسابقات. السرعة في كتابة الشيفرة البرمجية كتابة الشيفرة البرمجية بسرعة في المسابقات يوفر لك الكثير من الوقت للتفكير في بقية المشاكل واختبارها لذلك نرى أغلب المبرمجين يتدربون على سرعة الكتابة على لوحة المفاتيح لينصب تركيزهم على حلّ المشكلة فقط، ولذلك من المنطقي أن تفضل الشركات الموظفين الَّذين سَبق إن شاركوا في المسابقات البرمجية لمعرفتها بسرعة إنتاجيتهم وقدرتهم على تطوير وإيصال منتج برمجي بسرعة عالية بِالْمُوازنة مع أقرانهم ممن لم يشاركوا في المسابقات. العصف الذهني وحصر الخوارزميات الممكنة لا بدّ من تحسين قدرتك على تحديد نوع المسألة بسرعة وأن تمتلك المعرفة الكافية بالخوارزميات لتتمكن من تطبيقها مباشرة على المسألة المطروحة، لذلك ينبغي على المبرمج الَّذي ينوي الانضمام إلى المسابقة أن يتمكن من جميع الخوارزميات الشائعة في المسائل البرمجية. العمل الجماعي وروح الفريق تعتمد المسابقة البرمجية على ترتيب الفرق بحسب عدد المسائل الَّتي أجابوا عليها، والوقت الَّذي استهلكوه لحلّ كُلَ مشكلة، وعدد الحلول الخاطئة الَّتي أُرسلت للحكم. وانطلاقًا من ذلك تكون فوائد العمل كفريق واحد متعددة منها إمكانية توزيع المسائل على كلّ عضو في الفريق بحيث يزداد العدد الكلي للمسائل المحلولة بتوزيعها بين بعضهم بعضًا، ومناقشة حلّ كلّ مسألة وبذلك ينقص عدد الحلول الخاطئة الَّتي قد تُرسل للحكم، وحُكمًا سينقص الوقت المستغرق لحلّ المسائل. وعلى الصعيد العملي أيضًا نجد أن طبيعة عمل المبرمجين سواءً في الشركات الصغيرة أو الكبيرة، يُطلب منهم العمل كفريق واحد، فعلى سبيل المثال منذ فترة طَلَبَ مني أحد العملاء بناء موقع ويب، وكنت عندها قد أنتهيت من بناء فريقي البرمجي. وبالفعل بدأنا العمل على الموقع و قسّمنا العمل على أجزاء فعكف كلُّ واحد منا على تطوير جزئه الخاص من الموقع، إذ استلمتُ جزء الواجهات الخلفية، وقام أحد أعضاء الفريق باستلام الواجهات الأمامية، والآخَر استلم بناء تطبيق للهواتف الّتي تعمل بنظام أندرويد وفي نهاية الأسبوع جمعنا كل الأجزاء مع بعضها بعضًا وأنهينا المشروع بوقتٍ قياسي لم نكن لنستطيع بلوغهُ لو أننا لم نعمل كفريقٍ واحد، ولذا فإن انضمامك إلى المسابقة البرمجية يجعلك تتدرب على العمل ضمن فريق قبل أن تواجهها في سوق العمل في حال أردت العمل مع أي شركة مستقبلًا. المرونة العصبية إن تدريبك المستمر على المسائل البرمجية سيؤدي إلى زيادة منطقة الحصين (المنطقة المسؤولة عن الذاكرة والتوجيهات) في الدماغ وهذا ما أثبتته الباحثة اليانور ماجواير من كلية لندن الجامعية عندما أجرت دراسة على سائقي الأجرة في مدينة لندن فقاموا بإجراء فحص بالرنين المغناطيسي الوظيفي fMRI لأدمغة السائقين الذين قضوا قرابة عامين من التدريب في سبيل تعلم كيفية التنقل في منعطفات المدينة وذلك من أجل الحصول على رخصة القيادة ومقارنتها بصور لأدمغة رجال أصحاء من نفس العمر ولا يعملون كسائقي أجرة فتبين أن منطقة الحصين أصبحت أكبر لدى السائقين، كما لاحظوا أنه كلما أمضى سائق الأجرة فترة أطول في التدريب، زاد حجم الحصين، وذلك استجابة إلى الخبرة الّتي يكتسبها السائق. والآن تخيل معي ما سيحدث في حال تدربك على حل المشاكل لمدة عام أو أكثر، سيصبح حلّك للمشاكل البرمجية عادة سهلة ومسلية خلاف ما كانت عليه من صعوبة وتعقيد. يوجد العديد من المواقع الَّتي تقدم المسائل البرمجية مثل HackerRank أو موقع TopCoder والكثير غيرهم. 2. التدرب باستخدام الألعاب نعم أنت لم تخطئ القراءة إنها الألعاب! تعد الألعاب أداة قوية جدًا لتنمية المهارات العقلية والقدرات الدماغية على التفكير والتذكر وربط المعلومات ببعضها بعضًا، حيث أن غالبية الألعاب تحتاج لتجميع المعلومات وتنظيمها بحيث تستطيع الاستفادة منها وجعلها مفتاحًا للحلّ، بل إن الركيزة الأساسية الَّتي تشترك بها كافة الألعاب هي حلّ المشاكل! بالتأكيد نحن لا نقصد هنا ألعاب الفيديو فقط وإنما ألعاب الذكاء أيضًا مثل : لعبة الشطرنج، والألغاز الرياضية، ولعبة سودوكو، ولعبة Go، والكثير من الألعاب الَّتي تندرج تحت هذا السياق تعد ألعاب مفيدة. فعند محاولتك حلّ الألغاز في Saw أنت فعليًا تقوم بحلّ المشاكل (ولكن بإطار مختلف). وتساعدنا أيضًا في انشاء سيناريوهات للحلّ وهذه المهارة مفيدة في عالم البرمجة في حال تعثرت في أسلوب حلّ معين فتغير سيناريو الحلّ وتبدأ من جديد. في الحقيقة إن الشيء المشترك بين جميع الناس الناجحين هي اكتسابهم لعادات يومية لحلّ المشاكل الصغيرة على سبيل المثال بيتر تيل (أحد مؤسسي شركة باي بال والمصنف كرابع أغنى شخص على مستوى العالم لعام 2014 بميزانية تفوق $2.2 بليون دولار) صرح بشكل رسمي أنه يلعب الشطرنج يوميًا بل وشارك في بطولات الشطرنج مرات عديدة، وإيلون ماسك (رائد الأعمال والرئيس التنفيذي لعدة شركات مثل: سبيس إكس لتصنيع مركبات الفضاء وتسلا لصناعة السيارات الكهربائية وغيرها)أكد بأنه يلعب ألعاب الفيديو والكثير غيرهم كرسوا جزءًا من وقتهم اليومي لتنمية مهارة حلّ المشاكل. ولكن يجب أن تكون حذرًا في إدارة وقت اللعب ويُفضل أن تختار الأوقات المناسبة للعب ووضع مدة زمنية مخصصة لها، وذلك لأن الهدف ليس إضاعة الوقت والتسلية فقط وإنما التدرب على حل المشاكل ولكي لا ينتهِ بك المطاف إلى قضاء اليوم بأكمله على الألعاب. الخلاصة اعتقد أنك قد كونت فكرة جيدة عن أهمية حلّ المشاكل وضرورتها في مشوارك البرمجي وكيفية بناء خطة حلّ شاملة انطلاقًا من فهم المشكلة وتحليلها ومرورًا بإعداد خطة مخصصة لكلّ مشكلة وتقسيم المشكلة إلى أجزاء ليسهل حلّها ومواجهة حالة السكتة البرمجية وكيفية التغلب عليها من خلال تنقيح الأخطاء أو مراجعة وتقييم الحلّ، وأهمية البحث عن حلول على الإنترنت لنفس المشكلة وانتهاءً بالأسلوب الصحيح للتدرب على حل المشاكل وما هي أفضل الوسائل لتحقيق ذلك سواءً بالانضمام إلى المسابقات البرمجية أو بالتدرب بإستخدام الألعاب. وختامًا، لا تتوقع أن تصبح مبرمجًا محترفًا في غضون أسبوعٍ أو شهر واحد فهذا ضرب من الخيال بل ستحتاج لحلّ الكثير من المشاكل لبناء قاعدة معرفية صلبة تمكنك من مواجهة أي مشكلة مهما كانت صعوبتها وعندها بالتأكيد سوف تستحق لقب مبرمج محترف. اقرأ أيضًا تعلم لغة PHP تعلم لغة بايثون
- 6 تعليقات
-
- 10
-
- حل المشكلات
- مشكلات برمجية
- (و 8 أكثر)
-
إن الحصول على اسم نطاق جيد لن يؤدي في كثير من الأحيان إلى زيادة عدد الزيارات إلى موقعك فحسب، بل سيسهل أيضًا تذكره - وهذا يجلب المزيد من الزيارات أيضًا. هناك كثير من الأشياء التي يجب على مصمم المواقع أن بفكر بها عندما يصمم موقع. وخصوصًا إن كانت لديه المسؤولية الكاملة عن الموقع، فيجب عليه أن يأتي باسم نطاق جيد للموقع. على الرغم من أن الكثيرين يعتقدون أن اختيار اسم جيد ليس بتلك الأهمية، إلا أن اسم النطاق مهم مثل اسم شركتك أو موقع الويب أو صفحة فيس بوك. إن العثور على اسم نطاق مثالي ليس سهلًا على الإطلاق في يومنا الحالي. كثير من النطاقات التي من الممكن أن تفكر بها إما أن تكون مستخدمة من قِبل شركة أخرى أو شخص ما اشتراها للربح أو حتى في الأعمال التجارية. غالبًا ما يمضي خبراء الويب وقتًا في العثور على اسم نطاق مناسبٍ أكثر من الوقت في تصميم هوية أو موقع ويب. العلامة التجارية تعد العلامة التجارية مفهوما مهما جدًا بالنسبة للأعمال. يوجد لكل شركة علامة تجارية، تمثل اسمها وشعارها ومخطط ألوانها وهويتها البصرية. ومع ذلك، لا نجد أن كل العلامات التجارية معروفة. وهذا هو الفرق في الوعي بالعلامة التجارية. تعتمد الشركة التي لديها وعي بالعلامة التجارية على قدرة عملائها على تذكر علامتهم التجارية والتعرف عليها في ظروف مختلفة وربطها بعناصر علامتها التجارية. وهو يتعلق برضى العملاء عن أحد المنتجات وإدراك المنتجات فور رؤيتهم للعلامة التجارية. إنه الفرق بين كوكا كولا وشركة كولا بلا اسم. أو الفرق بين منتج شركة Apple ومنتج شركة غير معروف اسمها. قبل 10 سنوات لم يكن الموضوع بهذا الحجم كما هو عليه الآن، إلّا أن اليوم إن كنت تملك شركة وتريد علامة تجارية قوية ، فإن المعرفة الأساسية في اختيار اسم نطاق لموقعك يعتبر مهمًا جدًا، كما هو الحال مع عنوان مكتبك. وحيث يمكّن الأشخاص من العثور عليك والاتصال بك. الامتدادات (Extensions) إن أكثر الامتدادات استخدامًا في العالم هي الامتدادات الثلاثة التالية com. و net. و org.. يبدو اسم النطاق مع واحدة من هذه الامتدادات مثاليًا لأي نوع من الأعمال. فاستخدام أحد الامتدادات المجانية مثل TK. قد لا يكون ناجحًا جدًا. في كثير من الأحيان لا يستطيع الزوار تذكر موقعك، لذلك هم يحاولون استخدام اسم شركتك مع هذه الامتدادات الثلاثة المشهورة. وهذا ما أفعله في كثير من الأحيان ، لذا أني أفترض أن إحدى الشركات التي سأكون مهتمًا بالتعامل معها، أو أن أحد مواقع الويب التي سأكون مهتمًا بزيارتها سيكون له واحد من هذه الامتدادات الثلاثة. وكما قلت للتو أن هذه الامتدادات ذات شعبية كبيرة، لذا يتوقع الزوار أن تستخدم الشركات هذه الامتدادات الثلاثة فقط. يقدم بعض مقدمي خدمات حجز النطاقات عروضًا لمن يشتري الامتدادات الثلاثة سيحصل على خصم. لذا فإن حصولك على الامتدادات الثلاثة سيكون مفيد جدًا لك في المستقبل - تعدّ هذه الخطوة ذكية جدًا وستضعك في موضع الصدارة. من الميزات الأخرى لاستخدام الامتدادات الثلاثة (أو واحد منها) أن أسعارهم رخيصة جدًا. ومدعومين في جميع أنحاء العالم ومن الصعب العثور على مقدم خدمات حجز النطاقات لا يبيعها. بالتأكيد أن لا أقول أنه يجب عليك أن تملك واحدًا منهم. الشريك المؤسس لووردبريس Matt Mullenweg لديه واحد من أروع أسماء النطاقات ألا وهو www.ma.Tt. نعم، لديه امتداد غريب إلا أنه يتوافق مع إسمه بشكل مثالي. إذا تمكنت من العثور على شيء ذكي مثل هذا الامتداد، فاحصل عليه مباشرة، ولكن فكر في حقيقة أن الأشخاص الذين لا يعرفون الكثير عن الويب لا يعرفون عن وجود شيء آخر غير امتدادات com. و net. و org.. وينبغي أن يكون هذا الجزء غذاء لأفكارك. ومع ذلك، يعدّ اختيار امتداد لاسم النطاق واحدًا من أسهل المهام في إنشاء علامة تجارية قوية. ولكن انتظر! هناك الكثير من الأمور التي يجب عليك متابعتها أيضًا. الموقع بالتحدث عن الإمتدادات، يعدّ الموقع شيء آخر يجب عليك التفكير فيه. إنه يعتمد بشكل كبير على نوع أعمالك والغرض منها. ويعتمد كثيرًا على الجمهور والسوق وسلوكهم. إذا كان لديك شركة دانماركية تستهدف الدنماركيين فقط، فإن وجود موقع ويب ينتهي في com. لن يكون له داعٍ لأننا نستطيع الحصول على امتداد سهل التمييز مثل dk.. جميع البلدان لديها امتدادات نطاق (Top-level Domain). وبالتأكيد dk. ليس هو الخيار الوحيد المتوفر. يُستخدم رمز البلد TLD في أغلب الأحيان للشركات والأفراد في البلد المَعني. ويكيبيديا لديها قائمة طويلة وكاملة من TLDS في جميع أنحاء العالم. من السهل ملاحظة أن بعض هذه الامتدادات يتطلب إما تسجيل شركة في البلد أو على الأقل الحصول على تصريح إقامة، لذلك فإن التسجيل عليها ليس بتلك السهولة، وخصوصًا إن كنت تتطلع أن تفعل مثلما فعل Matt في امتداد موقعه. وهو أيضا خيار للشركات التي تستهدف الدول التي لا تستخدم الأبجدية الإنجليزية، مثل الدول الآسيوية، أو العربية، أو روسيا. وتسمى IDNs (أسماء النطاقات الدولية Internationalized Domain Names) ويجب أن تستخدمها فقط لاستكمال عنوان com. (أو أي TLD آخر) وإلا فإنه سيكون مربكًا للزائرين الذين لغتهم الرئيسية الإنجليزية أو أحد الأبجديات المستخدمة في أوروبا أو أمريكا. خدع وحِيَل لقد تحدثنا عن الخدع قبل قليل وهي الأشياء الذكية التي تقوم بها لدمج اسم النطاق مع الامتداد. صدق أو لا تصدق أنه يوجد على الإنترنت أدوات مخصصة لمساعدتك في إيجاد تركيبات ممكنة لاسم موقعك وامتداده! يعدّ موقع Domai.NR من أفضل الخيارات المتوفرة للاستخدام في هذا المجال، لأنه يعطيك العديد من الخيارات في بحث بسيط. تستعمل الناس هذه الخدع ليست لأنها رائعة فحسب، بل لأنه في أغلب الأحيان يكون الأسم المناسب لشركتهم مأخوذ بالفعل وهذا محبط جدًا، وإنه يحدث في كثيرٍ من الأحيان . لأن العثور على اسم مناسب ليس سهلًا إلى هذه الدرجة. لذلك أحيانًا قد ترغب في الإطلاع على بعض الخدع. قبل الشراء رائع، الآن وجدت اسم النطاق الذي تبحث عنه وأنت مستعدٌ لشرائه. لا، أنت لست مستعد! لقد نسيت شيئًا ما. وهو تتأكد من أن اسم النطاق الخاص بك لا يؤدي بك إلى مشاكل قانونية مع شركات أخرى. تأكد من أنه ليس هنالك شركة أخرى مشابهة تقوم بنفس الأعمال التي تقوم بها. مايكروسوفت (Microsoft) ضد ميكروايسوفتيس (MikeRoweSoftis) النزاع الأكثر شعبية بشأن انتهاك اسم النطاق. حدث هذا قبل عشر سنوات تقريبًا وكانت قضية دسمة لوسائل الإعلام عندما كان مصمم المواقع الشاب مايك رو ينازع اسم نطاقه مع العملاق الأميركي بسبب تشابه اسم النطاق الخاص به مع اسم نطاق شركة مايكروسوفت. إن التقدم بطلب العلامة التجارية هو أمر تقوم به الشركات الضخمة، بما أنهم يعرفون أن مواقعهم ستكون ذات شعبية قوية بطبيعة الحال، لكن هذا أمر مكلف للغاية. وخصوصًا إذا كنت في البداية، لذا فقد ترغب في حماية اسمك بطريقة أخرى وترك فكرة شراء علامتك التجارية لوقت لاحق. بعض التقنيات التي يمكنك استخدامها تسمى الشراء بالجملة. يفضل العديد من الأفراد شراء الكثير من النطاقات ذات الأسم نفسه والامتدادات المختلفة، وإعادة توجيههم جميعًا إلى الموقع الرئيسي. خدمات Whois من الأخطاء التي يرتكبها العديد من الأفراد هي تسجيل نطاقاتهم ضمن خدمة الخصوصية WHOIS. وهذه الطريقة خطيرة للغاية، على الرغم من أنها تبدو مفيدة عندما تسمع بها لأول مرة. ولكن ماذا يحدث إذا قمت بتسجيل نطاقك ضمن خدمة خصوصية WHOIS هو أن تفاصيل الاتصال الخاصة بك مخفية. بدلاً من أن تملئ الحقول بالمعلومات الخاصة بك يجب عليك أن تملئ الحقول بالمعلومات الخاصة بالمزود الذي اشتريت من عنده. ولكن لماذا يعدّ هذا الشيء خطير؟ لأنه وفقًا لسياسات ICANN ، يكون الاسم في تفاصيل WHOIS للذي يملك النطاق (في هذه الحالة اسم الشركة الّتي اشتريت منها النطاق). هذا يعني أنه إذا تم اختراق موقعك، فلديك فرصة ضئيلة لإثبات أن النطاق هو ملكك بالفعل. إذا كان اسم المخترق في السجلات، فهو في الواقع يمتلك النطاق الذي دفعته مقابله. لذلك حتى لو كنت ترغب في حماية اسم نطاقك، لا أنصحك أن تفعل ذلك باستخدام هذه الخدمات، لأنها خطيرة جدًا. الخاتمة يمكن أن يؤدي امتلاك اسم نطاق جيد إلى تحسين تجربة المستخدم على موقع الويب الخاص بك. حتى لو كانت اليوم العديد من زيارات موقع الويب تأتي من محركات البحث والإحالات، إلا أن وجود اسم سهل للتذكر سيكون دائمًا مفيدًا. ومن المحتمل أن ينخفض نسبة الأشخاص الذين ينسون كيفية العثور عليك. ضع في حساباتك أن نطاقات المستوى الأعلى (Top-level domains) يمكن أن تساعدك في تصدر نتائج محرك البحث. وذلك لأنه يتم فهرسة .com دومًا بشكل أسرع وأفضل من .ru. إن قضاء الوقت في التفكير في كل التفاصيل والعثور على اسم النطاق الصحيح هو أمر سيستغرق الكثير من الوقت. ترجمة -وبتصرف- للمقال Essential Tips for Registering a New Domain Name
-
- نطاق
- علامة تجارية
-
(و 4 أكثر)
موسوم في:
-
كيف شَكّل نموذج الشلال (Waterfall)، والنموذج المرن (Agile)، وغيرها من أُطر التطوير الشكل الحالي DevOps؟ إليك ما اكتشفناه. إذا أردت أن تحلل حمض DevOps النووي، فما الذي ستعثر عليه في تقرير أسلافها؟ هذه المقالة ليست مقاربة منهجية، لذا فإن كنت تبحث عن نصيحة أو مناقشة حول أفضل المنهجيات المتبعة لهندسة البرمجيات، فيمكنك التوقف عن القراءة هنا. لأننا في هذا المقال سنبحث في التسلسل التاريخي للمنهجيات منذ نشأتها إلى وقتنا الحالي وسنتعرف كيف أصبحت DevOps في مقدمة التحولات الرقمية في يومنا الحالي. لقد تطورت DevOps عبر الكثير من التجربة والخطأ، مع سعي الشركات للاستجابة لطلبات العملاء وتحسين الجودة والصمود في السوق مع ازدياد التنافسية بشكل متصاعد. ومما يزيد هذا التحدي صعوبةً هو انتقال الاقتصاد العالمي القائم على المنتجات إلى الاقتصاد العالمي القائم على الخدمات والذي أدى اتصال الناس بطرق جديدة. أصبحت دورة حياة تطوير البرمجيات المبنية على الخدمات والخدمات المصغرّة متزايدة التعقيد، كلًا من المتصلة ببعضها بعضًا و المجمعّة. لذا فإن DevOps دفعت للتطور بشكل أسرع وأكبر من أي وقت مضى، وإن سرعة التغيير هذه ستمحو المنهجيات التقليدية الأبطأ مثل نموذج الشلال (waterfall). بالتأكيد نحن لا نقلل من شأن نموذج الشلال فالعديد من المنظمات لديها أسباب وجيهة للاستمرار في استخدامه. لذا ينبغي على المنظمات الواعية أن تهدف إلى الابتعاد عن العمليات أو المنهجيات التي تُهدر الوقت، وإن الكثير من الشركات الناشئة تمتلك ميزة تنافسية على الشركات التي تستخدم نهجًا تقليديًا في عملياتها اليومية. والجدير بالذكر أن أغلب النماذج الحالية من نموذج التصميم الرشيق (Lean) ونموذج (Kanban) ونموذج التصميم المستمر (Continuous) ونموذج التصميم المرن (agile) تتتبع مبادئ وعمليات تعود إلى أوائل الأربعينات، لذا لا يمكن أن ندعي DevOps أنها فكرة جديدة كليًا. دعنا نعود بالزمن لبضع سنوات وننظر إلى نماذج تطوير البرمجيات مثل نموذج الشلال، والنموذج الرشيق، والنموذج المرن. يوضح الشكل التالي المخطط الزمني لتطور دورة حياة تطوير البرمجيات. (تذكر أننا لا نبحث عن أفضل طريقة ولكننا نحاول فهم المنحى الذي أحدث تأثيرًا إيجابيًا على هندسة البرمجيات والتي قمنا بتجميعها منذ 67 عامًا والتطور الذي حصل على مرِّ هذه السنوات حتى وصولنا لعقلية DevOps). نموذج الشلال التقليدي (Waterfall) من وجهة نظرنا، تأتي أقدم الممارسات من نموذج الشلال، الذي قُدم لأول مرة من قبل الدكتور وينستون دبليو رويس في ورقة نشرت في السبعينيات. يؤكد نموذج الشلال على النهج التقدم المنطقي والتتابعي من خلال مراحل جمع المتطلبات والتحليل وصياغة الشيفرة البرمجية والاختبار وهذه المراحل في إتجاه واحد أي يجب عليك إكمال كل مرحلة على حدة، وذلك باستكمال جميع المعايير المطلوبة في المرحلة وبعدها تستطيع أن تبدأ بالمرحلة التالية. يفيد نموذج الشلال في المشاريع التي تحتاج إلى تسلسلات صارمة والتي لها مجال مفصل ويمكن التنبؤ به أو المشاريع التي القائمة على الأحداث. وخلافا للاعتقاد السائد، فإنه يسمح أيضا للفرق بتجربة وإجراء تغييرات في التصميم في وقت مبكر خلال مرحلة جمع المتطلبات ومرحلة التحليل، ومراحل التصميم. نموذج التصميم الرشيق (Lean) رغم أن التصميم الرشيق يعود إلى عام 1450 في مدينة البندقية، إلا أننا سنبدأ عندما قامت تويوتا بإنشاء نظام إنتاج تويوتا، الذي طوره المهندسون اليابانيون بين عامي 1948 و 1972. ونشرت تويوتا وصفًا رسميًا للنظام في عام 1992. يستند نموذج التصميم الرشيق إلى خمسة مبادئ: القيمة ومجرى القيمة والتدفق والسحب والكمال. ويكمن جوهر هذا النهج في فهم ودعم تدفق قيم فعال والتخلص من العمليات التي لا تضيف قيمة وتعدّ هدرًا وضياعًا وتقديم قيمة مستمرة للمستخدم. ويهدف أيضًا لإسعاد المستخدمين بدون انقطاع. نموذج Kaizen تعتمد kaizen على التحسينات الإضافية وتتكون دورة حياة kaizen المراحل التالية: التخطيط ->التنفيذ->التحقق->التصحيح. وتهدف عقلية kaizen للتوجه للتحسين المستمر. طُوّر مفهوم kaizen في الأصل لتحسين تدفق وعمليات خط التجميع، وهو يضيف أيضًا قيمة عبر سلسلة التوريد. وكان نظام إنتاج تويوتا واحدًا من أوائل المنفذين لنموذج kaizen والتحسين المستمر. يعمل كل من kaizen و DevOps معا بشكل جيد في البيئات التي ينتقل فيها سير العمل من التصميم إلى الإنتاج. يركز kaizen على مجالين: التدفق (هو التوجه نحو تدفق المواد والمعلومات) العملية (وهي تعني تحسين وضعية عمل الشخص) التسليم المستمر (Continuous Delivery) وقد ألهمت kaizen تطوير العمليات والأدوات لأتمتة الإنتاج. وقد تمكنت الشركات من تسريع الإنتاج وتحسين الجودة والتصميم والبناء والاختبار والتسليم عن طريق تقليل مصادر الهدر (بما في ذلك الثقافة والعقلية) والتوجه نحو الأتمتة قدر الإمكان وذلك باستخدام الآلات والبرمجيات والروبوتات. كما أن معظم فلسفة kaizen تنطبق أيضا على الممارسات التجارية والبرمجيات غير المحدودة ونشر التسليم المستمر لمبادئ وأهداف DevOps. نموذج التصميم المرن (Agile) ظهر بيان تطوير البرمجيات المرنة في عام 2001، والذي كتبه مجموعة من الشبان الطموحين النهج المتبع في التطوير. إن النموذج المرن لا يقوم على إهمال الحيطة والحذر، أو كسر خطط التصميم، أو بناء برمجية بخشونة أو بشكل عشوائي. وإنما هو أن تكون قادر على الإنشاء والاستجابة للتغييرات. يعتمد نموذج المرن على اثني عشر مبدأً والتي تقدر الأفراد والتعاون فيما بينهم والبرمجيات الصالحة للاستعمال والتعاون مع العملاء، والاستجابة للتغيير. النموذج المرن المنضبط (Disciplined Agile Delivery) ومنذ أن ظل البيان المرن ثابتًا لمدة 20 عامًا، بحث العديد من الممارسين للنموذج عن طرق لإضافة خيارات إضافية ومخصصة إلى النهج. بالإضافة إلى ذلك، يركز البيان المرن بشكل كبير على التطوير، لذا تكونت الحاجة الماسّة إلى الحلول بدلًا من الشيفرات البرمجية أو البرمجيات وخصيصًا في بيئة التطوير السريعة اليوم. شارك سكوت أمبلر وماركس بريز في تأليف عملية تسليم المرنة المنضبطة وإطار عمل مرن ومنضبط، استنادًا إلى خبرتهما في شركة Rational و IBM والمنظمات التي تحتاج فيها الفرق إلى مزيد من الخيارات المتاحة أو الشركات التي لم تكن ذات خبرة بما يكفي لتنفيذ المنهجية الرشيقة (lean)، أو حيث لا يتناسب دورة حياة البرمجية مع سياق النهج. تأتي أهمية النموذج المرن النضبط أو ما يعرف DAD (اختصارًا للعبارة Disciplined agile delivery) و DA (اختصارًا للعبارة Disciplined agile) من أنه يسمح لنا باتخاذ قرارت حول العمليات بشكل مبسط من أجل تقديم حلول تدريجية ومتكررة. ويعتمد DAD على العديد من ممارسات تطوير البرمجيات المرنة (Agile)، بما في ذلك ممارسات Scrum، وممارسات نموذج الرشيق (Lean)، وغيرها. إن الاستخدام المكثف للنمذجة الرشيقة وإعادة تحليل، بما في ذلك تشجيع الأتمتة من خلال نموذج التطوير القائم على الاختبار TDD (اختصارًا للعبارة Test-driven Development)، والتصميم الرشيق مثل Kanban و XP و Scrum و Rup من خلال اختيار واحدة من خمس دورات حياة رشيقة، وإدخال مفاهيم هيكلية المالك (Architecture Owner)، يكتسب الممارسين لنموذج المرن مجموعة من الأفكار وطرق التفكير وعمليات وأدوات لتحقيق DevOps بنجاح. نموذج DevOps وعلى المدى الذي يمكننا أن نجمع فيه، ظهرت DevOps خلال سلسلة من DevOpsDays في بلجيكا في عام 2009، لتصبح الأساس للعديد من التحولات الرقمية. يعرّف مدير DevOps الأساسي في شركة مايكروسوفت Donovan Brown نموذج DevOps بأنه والآن لنعود إلى سؤالنا الأصلي: إذا أردت أن تحلل حمض DevOps النووي، فما الذي ستعثر عليه في تقرير أسلافها؟ إننا ننظر إلى التاريخ الذي يعود إلى 80 و48 و26 و17 عامًا، ولكن في وقتنا الحالي إن البيئة اليومية سريعة الإيقاع وغالبًا ما تكون مضطربة. بطبيعتنا نحن البشر نختبر ونجرب باستمرار ونتعلم ونتكيّف ونورّث نقاط القوة ونحل نقاط الضعف تيمّنًا بجيناتنا الوراثية. يمكنك استخدام القياس عن بعد الذي تجمعه من مشاهدة الحل الخاص بك في الإنتاج وذلك لإدارة التجارب، وتأكيد الفرضيات، وترتيب أولويات الأعمال المتراكمة لديك. بمعنى آخر، يرث Devops من مجموعة متنوعة من الأطر والمنهجيات التي أثبتت جدارتها وتطورها، ويمكّنك من تحويل ثقافتك، واستخدام المنتجات كأدوات مساعدة، والأهم من ذلك، إرضاء عملائك. إن كنت مرتاحًا مع نموذج التصميم الرشيق أو المرن، فإنك حتمًا ستستمتع بفوائد DevOps. وإن كنت تستخدم نموذج الشلال فإن عقلية DevOps ستساعدك كثير في تعزيز التطوير. نموذج eDevOps في عام 2016، صاغ Brent Reed مصطلح eDevOps (لا توجد له مراجع في Google أو ويكيبيديا حتى هذه اللحظة)، وهو يُعرف بأنه "طريقة للعمل (Way of Work) التي تجلب التحسين المستمر في الشركة بسلاسة، من خلال الأشخاص والعمليات والأدوات. وجد برانت أن النموذج المرن فشل في شركات تقنية المعلومات فالشركات التي اعتمدت تفكيرًا رشيقًا كانت لا تحقق قيمة (Value) للعملاء ولم تحقق أيضًا التركيز والسرعة التي توقعوها خبراء النموذج المرن الموثوق بهم في مجال تكنولوجيا المعلومات. وعندما شعر بالإحباط لرؤية "برج عاجي" تم فيه فصل فريق خدمات تقنية المعلومات المستقل عن فرق الدعم في مجال الهندسة والتطوير والعمليات ومكتب المساعدة، عندها طبق معرفته السابقة بنموذج التسليم الرشيق المنضبط (DAD)، وأضاف بعض الأهداف والتطبيقات العملية لمجموعة أدوات DAD، من ضمنها: التركيز على الثقافة ودفعها من خلال عقلية التحسين المستمر (Kaizen)، والجمع بين الناس حتى عندما يكونون في غرفة واحدة. السرعة من خلال الأتمتة (TDD + إعادة تحليل كل شئ ممكن)، إزالة الهدر واعتماد نهج TOGAF، أو JBGE (وهو جيد إلى حدٍ ما) من أجل التوثيق تقديم القيمة (Value) من خلال النمذجة (نمذجة التصميم) و (shift left) لتمكين الكشف عن الأنماط المتضاربة أثناء المشاركة من خلال أنماط التعاون في مستودع رقمي أكثر تنوعا واستراتيجيا حديث باستخدام خبرته مع AI في IBM، صمم برنت نموذج eDevOps الذي يقوم بشكل متزايد بأتمتة لوحات التحكم لأغراض القياس وصنع القرار بحيث يتم التحسين المستمر من خلال النشر المستمر (أتمتة العمليات من مرحلة التطوير إلى مرحلة الإنتاج) لجلب الإمكانيات الحقيقية لأي منظمة. إن برنامج التحول الفعال إلى eDevOps الذي يُبنى على التحول المنضبط إلى DevOps يُمكننا من القيام بما يلي: تحويل نظام الأعمال إلى Bizdevops) DevOps). تحويل نظام الأمن إلى SecDevOps) DevOps). تحويل نظام المعلومات إلى DataDevOps) DevOps). تقديم خدمات تقنية مترابطة بشكل خفيف والتي تجمع بين جميع أصحاب المصلحة. بناء حلول قابلة للاستخدام كل أسبوعين أو أسرع. جمع وقياس وتحليل وعرض وأتمتة الرؤية القابلة للتنفيذ من خلال عمليات DevOps من المفهوم من خلال الاستخدام المباشر للإنتاج. تحسين مستمر امتثالًا بنموذج Kaizen ونموذج الرشيق منضبط. المرحلة التالية في تطوير DevOps في النهاية هل تعدّ DevOps مجموعة من العبارات الطنانة والأفكار التقنية التي تُلقى على الشركات؟ سوف ندرك بكل تأكيد مع مرور الوقت كيف DevOps سيتقدم ويتطور. ومع ذلك، فإن حمض DevOps النووي يجب أن يستمر في النضج وأن يُنقح، ويجب أن يفهم المطورون أنه ليس الحل السحري ولا علاج لكل الأمراض والحلّ لكل المشاكل. إذن ما هي العلاقة بين DevOps وبقية النماذج السابقة DevOps != Agile != Lean Thinking != Waterfall DevOps != Tools !=Technology إن DevOps تحتوي على ممارسات من النموذج المرن (Agile) ونموذج التصميم الرشيق (Lean) ونموذج الشلال (Waterfall). ترجمة -وبتصرف- للمقال Analyzing the DNA of DevOps لصاحبيه Willy-Peter Schaub و Brent Aaron Reed