مرحبا يا هبر!
نلفت انتباهك إلى النسخة الإضافية التي طال انتظارها من كتاب "
JavaScript Expressive " ، والذي جاء للتو من دار الطباعة.
بالنسبة لأولئك الذين ليسوا على دراية بعمل مؤلف الكتاب (على الرغم من الطبيعة الموسوعية ، فإن المبتدئين يعجبهم أيضًا) - نقترح أن تتعرف على المقالة الموجودة في مدونته ؛ توضح المقالة الأفكار حول تنظيم الملحقات في JavaScript.
في الوقت الحاضر ، أصبح من المألوف تنظيم أنظمة كبيرة في شكل العديد من الحزم المنفصلة. تتمثل الفكرة الأساسية وراء هذا النهج في أنه من الأفضل عدم قصر الأشخاص على ميزة معينة (تقترحها أنت) من خلال تطبيق ميزة ، ولكن توفير هذه الميزة كحزمة منفصلة يمكن للشخص تنزيلها مع حزمة النظام الأساسية.
للقيام بذلك ، بشكل عام ، ستحتاج ...
- تعد القدرة على عدم تنزيل الميزات التي لا تحتاجها مفيدة بشكل خاص على أنظمة العميل.
- القدرة على استبدال تطبيق آخر بوظائف لا تلبي أهدافك. وبالتالي ، يتم تقليل الحمل على وحدات kernel أيضًا - ليس مطلوبًا تغطية جميع الحالات العملية الممكنة بمساعدتها.
- التحقق من واجهات kernel في ظروف حقيقية - من خلال تطبيق الميزات الأساسية أعلى الواجهة التي تواجه جانب العميل ، فأنت مضطر لجعل هذه الواجهة قوية على الأقل بحيث يمكنها دعم هذه الميزات. وبالتالي ، يمكنك التأكد من أنه سيكون من الممكن البناء عليها أشياء مماثلة كتبها مطورو الطرف الثالث.
- العزلة بين أجزاء النظام. يمكن للمشاركين في المشروع ببساطة البحث عن الحزمة التي يهتمون بها. يمكن إصدار الحزم أو تعليمها على أنها غير مرغوب فيها أو استبدالها دون التأثير على الكود الأساسي.
يرتبط هذا النهج عادةً بزيادة التعقيد. لتسهيل بدء تشغيل المستخدمين ، يمكنك تزويدهم بحزمة التفاف تتضمن "كل شيء مدرج" ، ولكن عاجلاً أم آجلاً سيتعين عليهم التخلص من هذه القشرة وتثبيت وتكوين حزم مساعدة محددة ، والتي تكون في بعض الأحيان أكثر صعوبة من مجرد التبديل إلى ميزة أخرى في المكتبة متجانسة.
في هذه المقالة ، سنحاول مناقشة خيارات آليات التوسع التي تدعم "القابلية للتوسعة على نطاق واسع" وتتيح لك إنشاء نقاط امتداد جديدة حيث لم يتم توفير ذلك.
المدودية قابلى المد
ماذا نريد من نظام للمد؟ أولاً وقبل كل شيء ، بالطبع ، يجب أن يكون لديه القدرة على توسيع قدراته الخاصة باستخدام الكود الخارجي.
لكن هذا بالكاد يكفي. اسمحوا لي أن أتطرق إلى مشكلة غبية واجهتها ذات مرة. أنا تطوير محرر التعليمات البرمجية. في أحد الإصدارات السابقة من هذا المحرر ، يمكنك تعيين النمط لسطر معين في كود العميل. كان رائعا - تخطيط خط انتقائي.
باستثناء الحالة التي يتم فيها إجراء محاولات لتغيير مظهر الخط فورًا من قسمين من الكود - وتبدأ هذه المحاولات بعنف. التمديد الثاني المطبق على الخط بالكتابة فوق نمط الملحق الأول. أو عندما تحاول التعليمة البرمجية الأولى إزالة التصميم الذي قامت به في مكان ما في الجزء الإضافي من التعليمات البرمجية ، فإنها تقوم بالكتابة فوق النمط المقدم بواسطة جزء التعليمات البرمجية الثاني.
لقد نجحنا في إيجاد حل ، وتوفير القدرة على إضافة (وحذف) الامتداد ، وليس تعيينه ، بحيث يمكن أن يتفاعل الملحقان مع نفس السطر دون تخريب عمل بعضهم البعض.
بمعنى أعم ، من الضروري التأكد من أنه يمكن الجمع بين الامتدادات ، حتى لو كانت غير مدركة تمامًا لوجود بعضها البعض - دون التسبب في تعارض بينهما.
للقيام بذلك ، تحتاج إلى التأكد من أن أي عدد من الجهات الفاعلة يمكن أن يؤثر على كل نقطة توسع. كيف بالضبط آثار متعددة ستتم معالجتها يعتمد على الموقف. إليك بعض الطرق التي قد تجدها مفيدة:
- كل منهم نافذة المفعول. على سبيل المثال ، عند إضافة فئة CSS إلى عنصر أو عند عرض عنصر واجهة تعامل مستخدم ، تتم إضافة كلتا الميزتين في نفس الوقت. في كثير من الأحيان ، لا يزال يتعين فرزها بطريقة أو بأخرى: يجب أن يتم عرض عناصر واجهة المستخدم في تسلسل يمكن التنبؤ به بوضوح.
- يصطفون في شكل ناقل. مثال هو معالج يمكنه تصفية التغييرات التي تمت إضافتها إلى مستند قبل إجرائها. يتم تغذية كل تغيير أولاً إلى معالج ، والذي بدوره يمكنه تغييره. الطلب في هذه الحالة غير حاسم ، لكن يمكن أن يحدث فرقًا.
- يمكنك تطبيق نهج من يصل أولاً يخدم أولاً على معالجات الأحداث. كل معالج لديه فرصة لخدمة الحدث حتى يقول أحدهم أنه تعامل معه بالفعل ، وبعد ذلك لم يعد المستجوبون الذين يقفون في طابور الانتظار.
- يحدث أيضًا أنك تحتاج حقًا إلى اختيار قيمة محددة - على سبيل المثال ، حدد قيمة معلمة التكوين المحددة. قد يكون من المستحسن استخدام عامل معين (على سبيل المثال ، منطقي و منطقي أو الحد الأدنى أو الحد الأقصى) للحد من عدد قيم الإدخال لموضع واحد. على سبيل المثال ، قد يتحول المحرر إلى وضع القراءة فقط إذا طلب أي ملحق ذلك. يمكنك إما تعيين الحد الأقصى لقيمة المستند ، أو الحد الأدنى لعدد القيم التي يتم الإبلاغ عنها لهذا الخيار.
في كثير من هذه الحالات ، يكون الترتيب مهمًا. هذا يعني أن أولوية التأثيرات المطبقة يجب أن تكون قابلة للتحكم ويمكن التنبؤ بها.
على هذه الجبهة ، عادة ما تكون أنظمة التمديد الحتمية القائمة على استخدام الآثار الجانبية غير قادرة على التعامل معها. على سبيل المثال ،
addEventListener
عملية
addEventListener
التي يتم تنفيذها بواسطة طراز DOM الخاص بالمستعرض إلى استدعاء معالجات الأحداث بالترتيب الذي تم تسجيلهم به تمامًا. يعد هذا أمرًا طبيعيًا إذا كان يتم التحكم في جميع المكالمات بواسطة نظام واحد ، أو إذا كان ترتيب العمليات غير مهم حقًا ، ومع ذلك ، عندما يتعين عليك التعامل مع العديد من أجزاء البرامج التي تضيف معالجات بشكل مستقل ، فقد يكون من الصعب للغاية التنبؤ بأي منها سيتم استدعاءه في المقام الأول.
نهج بسيط
لإعطائك مثالاً بسيطًا: قمت أولاً بتطبيق استراتيجية نمطية على ProseMirror ، وهو نظام لتحرير النص المنسق. جوهر هذا النظام في حد ذاته هو ، من حيث المبدأ ، عديمة الفائدة - يعتمد كليا على حزم إضافية تصف هيكل المستندات ، ومفاتيح الربط ، التي تقود تاريخ الإلغاء. على الرغم من صعوبة استخدام هذا النظام بعض الشيء ، فقد تم اعتماده في المنتجات التي تتطلب تصميم نص مخصص ، وهو غير متوفر في برامج التحرير الكلاسيكية.
آلية التمديد المستخدمة في ProseMirror واضحة نسبيا. عند إنشاء المحرر ، يشير رمز العميل إلى مجموعة واحدة من الكائنات المتصلة. يمكن لكل من هذه المكونات الإضافية أن تؤثر بطريقة أو بأخرى على عمل المحرر ، على سبيل المثال ، إضافة أجزاء من بيانات الحالة أو التعامل مع أحداث الواجهة.
تم تصميم كل هذه الجوانب للعمل مع مجموعة من قيم التكوين باستخدام الاستراتيجيات الموضحة في القسم السابق. على سبيل المثال ، عند تحديد تعيينات مفاتيح متعددة ، يحدد الترتيب الذي يتم فيه تحديد مثيلات المكون الإضافي لخريطة المفاتيح الأولوية الخاصة بهم. أول خريطة مفاتيح تعرف كيفية التعامل معها تتلقى مفتاحًا محددًا في المعالجة.
عادة ما تكون هذه الآلية قوية للغاية ، وتستخدم بنشاط. ومع ذلك ، في مرحلة معينة ، يصبح الأمر معقدًا وغير مريح للعمل معها.
- إذا كان للمكون الإضافي العديد من التأثيرات ، فيمكنك إما أن تأمل في أن يتم تطبيق ذلك على المكونات الإضافية الأخرى ، أو سيكون عليك تقسيمها إلى مكونات إضافية أصغر بحيث يمكنك تنظيمها بشكل صحيح.
- بشكل عام ، يصبح تنظيم المكونات الإضافية شديد الحساسية ، حيث لا يدرك المستخدم النهائي دائمًا أي المكونات الإضافية التي يمكن أن تؤثر على تشغيل المكونات الإضافية الأخرى إذا كانت لها أولوية أعلى. عادةً ما تظهر جميع الأخطاء فقط في وقت التشغيل ، عند استخدام وظيفة محددة - لذلك ، من السهل تفويتها.
- يجب أن توثق المكونات الإضافية المستندة إلى مكونات إضافية أخرى هذه الحقيقة - ويظل من المأمول ألا ينسى المستخدمون تمكين تبعياتهم (بالترتيب الصحيح).
CodeMirror في
الإصدار 6 هو
محرر أعيد كتابة
بنفس الاسم . في الإصدار السادس ، أحاول تطوير نهج معياري. هذا يتطلب نظام تمديد أكثر تعبيرا. دعونا نلقي نظرة على بعض التحديات المرتبطة بتصميم مثل هذا النظام.
تنظيم
من السهل تصميم نظام يوفر تحكمًا كاملاً في ترتيب الملحقات. ولكن من الصعب جدًا تصميم مثل هذا النظام ، والذي سيكون في الوقت نفسه لطيفًا للاستخدام ويسمح لك بدمج كود الامتدادات المستقلة دون تدخل يدوي شامل وشامل.
عندما يتعلق الأمر بالترتيب ، فإنه يسحب لتطبيق قيم الأولوية. مثال مشابه هو خاصية C
z-index
، والتي تسمح لك بتعيين رقم يشير إلى مدى عمق العنصر في المجموعة.
نظرًا لأن أوراق الأنماط تحتوي أحيانًا
z-index
قيم
z-index
كبيرة بشكل يبعث على السخرية ، فمن الواضح أن هذه الطريقة للإشارة إلى الأولوية تمثل مشكلة. وحدة نمطية معينة بشكل فردي "لا تعرف" قيم الأولوية التي تشير إلى الوحدات الأخرى. الخيارات هي مجرد نقاط في نطاق أرقام غير محدد. يمكنك تحديد قيم باهظة (أو قيم سلبية للغاية) ، على أمل الوصول إلى نهايات هذا المقياس ، لكن كل شيء آخر هو لعبة تخمين.
يمكن تحسين هذا الموقف إلى حد ما عن طريق تحديد مجموعة محدودة من فئات الأولوية المحددة بوضوح بحيث يمكن تصنيف الامتدادات حسب "المستوى" التقريبي لأولويتها. ولكن لا يزال يتعين عليك قطع العلاقات بطريقة ما ضمن هذه الفئات.
التجميع وإلغاء البيانات المكررة
كما ذكرت أعلاه ، بمجرد البدء في الاعتماد بشكل جدي على الامتدادات ، قد تنشأ مواقف تستخدم فيها بعض الإضافات الأخرى. لا تتباين إدارة التبعية المتبادلة بشكل جيد ، لذلك سيكون من الجيد أن تتمكن من سحب مجموعة من الإضافات مرة واحدة.
ومع ذلك ، ليس هذا فقط ، في هذه الحالة ، ستفاقم مشكلة الطلب أكثر ؛ سوف تنشأ مشكلة أخرى. يمكن أن تعتمد العديد من الملحقات الأخرى على امتداد معين في وقت واحد ، وإذا كنت تمثلها كقيم ، فقد ينشأ الموقف مع التنزيلات المتعددة لنفس الامتداد. في بعض الحالات ، على سبيل المثال ، عند تعيين المفاتيح أو معالجة معالجات الأحداث ، يكون هذا أمرًا طبيعيًا. في حالات أخرى ، على سبيل المثال ، عند تتبع محفوظات الإلغاء أو العمل مع مكتبة تلميح الأدوات ، سيكون مثل هذا النهج مضيعة للموارد مع خطر كسر شيء ما.
لذلك ، بالسماح بتكوين الامتدادات ، نحن مضطرون إلى التحول إلى جزء نظام الامتداد من التعقيد المرتبط بإدارة التبعيات. يجب أن تكون قادرًا على التعرف على تلك الملحقات التي لا ينبغي تكرارها ، وتنزيل مثيل واحد فقط لكل منها.
ومع ذلك ، نظرًا لأنه في معظم الحالات يمكن تكوين الامتدادات ، وستكون جميع مثيلات ملحقًا مختلفة إلى حد ما عن بعضها البعض ، لا يمكننا فقط أخذ مثيل واحد من الامتداد واستخدامه - سنضطر إلى الجمع بينها بطريقة أو بأخرى (أو الإبلاغ عن خطأ ، عندما يكون هذا غير ممكن).
مشروع
سوف أصف هنا ما تم إنجازه في CodeMirror 6. أقترح هذا المثال كحل وليس كحل حقيقي فقط. من المحتمل أن يتطور هذا النظام مع استقرار المكتبة.
البدائية الرئيسية في هذا النهج تسمى السلوك. السلوكيات هي فقط تلك الأشياء التي يمكنك توسيعها من خلال الإشارة إلى القيم. كمثال ، ضع في اعتبارك سلوك حقل الحالة ، حيث يمكنك بمساعدة الحقول الإضافية إضافة حقول جديدة ، مع إعطاء وصف لكل حقل. أو سلوك معالج الأحداث المستند إلى المستعرض ، حيث يمكنك إضافة معالجاتك الخاصة باستخدام الملحقات.
من وجهة نظر سلوك المستهلك ، تعطي تلك السلوكيات التي تم تكوينها في حالة معينة من المحرر تسلسلًا منظمًا من القيم ، حيث تأتي القيم ذات الأولوية العليا أولاً. كل سلوك له نوع ، ويجب أن تتطابق قيمه مع هذا النوع.
يتم تمثيل
السلوك كقيمة مستخدمة لإعلان مثيل للسلوك وللإشارة إلى القيم التي قد يكون لدى السلوك. على سبيل المثال ، قد يحدد الملحق الذي يحدد خلفية رقم السطر السلوك الذي يسمح لرمز آخر بإضافة علامات جديدة إلى هذه الخلفية.
الامتداد هو قيمة يمكن استخدامها عند تكوين المحرر. يتم الإبلاغ عن مجموعة من الملحقات أثناء التهيئة. كل امتداد مسموح به في صفر أو أكثر من السلوكيات.
أبسط نوع من الامتدادات هو مثال على السلوك. عن طريق تحديد قيمة لهذا السلوك ، نحصل ردًا على قيمة الامتداد التي تنفذ هذا السلوك.
يمكن أيضًا تجميع سلسلة من الامتدادات في امتداد واحد. على سبيل المثال ، في تكوين المحرر بلغة برمجة معينة ، يمكن سحب عدد من الملحقات الأخرى ، على وجه الخصوص ، قواعد لتحليل اللغة وتسليط الضوء عليها ، ومعلومات حول كيفية المسافة البادئة ، وكذلك مصدر معلومات عن الإكمال تكمل الشفرة في تلك اللغة بذكاء. وبذلك تحصل على ملحق لغة ، والذي يجمع ببساطة كل الامتدادات الضرورية التي تعطي القيمة التراكمية.
بوصف نسخة بسيطة من هذا النظام ، يمكننا التوقف عند هذا وتناسب الملحقات المتداخلة ببساطة في مجموعة واحدة من الملحقات للسلوكيات. ثم يمكن تجميعها حسب نوع السلوك والحصول على تسلسلات مرتبة من قيم السلوك.
ومع ذلك ، ما زلنا لم نتوصل إلى اكتشاف إلغاء البيانات المكررة ، ونحن بحاجة إلى مزيد من التحكم الكامل في الطلب.
تتضمن قيم النوع الثالث
امتدادات فريدة ؛ هذه هي الآلية لضمان إلغاء البيانات المكررة. الإضافات التي لا تريد إنشاء مثيل لها مرتين في نفس المحرر هي ذلك. لتحديد مثل هذا الامتداد ، يتم تحديد نوع المواصفات ، أي نوع قيمة التكوين المتوقع من قبل مُنشئ الامتداد ، ودالة إنشاء مثيل تأخذ مجموعة من قيم المواصفات هذه وتُرجع الامتداد.
تعمل الإضافات الفريدة على تعقيد عملية حل مجموعة من الإضافات في مجموعة من السلوكيات. طالما كانت هناك امتدادات فريدة في مجموعة الامتدادات المتوافقة ، يجب أن تحدد آلية الدقة نوع الامتداد الفريد ، وتجمع كل مثيلاتها ، وتدعو وظيفة إنشاء مثيل لقيم المواصفات الخاصة بها وتستبدلها بالنتيجة (في حالة واحدة).
(توجد مشكلة إضافية واحدة - يجب حلها بترتيب معين. إذا قمت بتمكين الامتداد الفريد X لأول مرة ، ولكن بعد ذلك يتم حل الامتداد Y إلى X آخر ، فسيكون هذا خطأ ، حيث يجب دمج كل مثيلات X معًا. يقوم النظام ، في مواجهته ، بتنفيذه عن طريق التجربة والخطأ ، وإعادة تشغيل العملية - وتسجيل المعلومات الموضحة.)
أخيرًا ، دعنا نتحدث عن الأولوية. تتمثل الطريقة الأساسية في هذه الحالة في الحفاظ على الترتيب الذي تم الإبلاغ عن الامتدادات به. تتم محاذاة التمديدات المركبة وضمها في هذا الترتيب تمامًا في الموضع الذي تجتمع فيه أولاً. يتم أيضًا إدخال نتيجة حل امتداد فريد في المكان الذي يحدث فيه أولاً.
ولكن يمكن للإضافات تعيين بعض من subextensions الخاصة بهم إلى فئة ذات أولوية مختلفة. يحدد النظام أنواع هذه الفئات: التراجع (يسري مفعوله بعد حدوث أشياء أخرى) ، افتراضيًا ، قم بتوسيع (أولوية أعلى من المجموعة) وإعادة تعريف (ربما يجب أن يكون موجودًا في الجزء العلوي). يتم الترتيب الفعلي أولاً حسب الفئة ، ومن ثم عن طريق البدء بالمركز.
لذلك ، يمكن أن يمدنا ملحق به مهمة مفاتيح ذات أولوية منخفضة ومعالج حدث ذي أولوية طبيعية إلى ملحق مركب مبني على أساس ملحق مع مهمة مفتاح (في هذه الحالة ، لا تحتاج إلى معرفة السلوكيات المضمنة في ذلك ، مع أولوية "الاستعادة" بالإضافة إلى مثيل للسلوك معالج الأحداث.
يبدو أن الإنجاز الرئيسي هو أننا اكتسبنا القدرة على الجمع بين الامتدادات ، بغض النظر عن ما يتم داخل كل منها. في الإضافات التي قمنا بتصميمها حتى الآن ، من بينها: نظامان تحليليان بنفس السلوك النحوي ، تسليط الضوء على بناء الجملة ، خدمة المسافة البادئة الذكية ، سجل الإلغاء ، خلفية رقم السطر ، الإغلاق التلقائي للأقواس ، التعيينات الرئيسية والاختيار المتعدد - كل شيء يعمل بشكل جيد.
لاستخدام مثل هذا النظام ، تحتاج حقًا إلى إتقان العديد من المفاهيم الجديدة ، وهي بالتأكيد أكثر تعقيدًا من النظم الإلزامية التقليدية المقبولة في مجتمع JavaScript (استدعاء طريقة لإضافة / إزالة تأثير). ومع ذلك ، يبدو أن القدرة على ربط الملحقات بشكل صحيح تبرر هذه التكاليف.