تجاوز التعبيرات العادية: تحسين تحليل قيم CSS في "أدوات مطوري البرامج في Chrome"

Philip Pfaffe
Ergün Erdogmus
Ergün Erdogmus

هل لاحظت أنّ سمات CSS في علامة التبويب الأنماط في "أدوات مطوّري البرامج في Chrome" أصبحت أكثر دقة مؤخرًا؟ إنّ هذه التعديلات، التي تم طرحها بين الإصدار 121 من Chrome والإصدار 128، هي نتيجة تحسين كبير في طريقة تحليل قيم CSS وعرضها. في هذه المقالة، سنرشدك إلى التفاصيل الفنية لهذا التحويل، أي الانتقال من نظام مطابقة التعبيرات العادية إلى محلل أكثر فعالية.

لنقارن بين "أدوات المطوّر" الحالية والإصدار السابق:

في أعلى الشاشة، يظهر أحدث إصدار من Chrome، وفي أسفل الشاشة، يظهر الإصدار 121 من Chrome.

فرق كبير، أليس كذلك؟ في ما يلي تفاصيل التحسينات الرئيسية:

  • color-mix: معاينة مفيدة تمثّل بصريًا وسيطتَي اللون ضمن دالة color-mix.
  • pink. معاينة لون يمكن النقر عليها للون المُعنوَن pink انقر عليه لفتح أداة اختيار الألوان لإجراء تعديلات بسهولة.
  • var(--undefined, [fallback value]). معالجة محسّنة للمتغيّرات غير المحدّدة، مع ظهور المتغيّر غير المحدّد مُميّزًا باللون الرمادي وقيمة العنصر الاحتياطي النشط (في هذه الحالة، لون HSL) معروضة مع معاينة لون يمكن النقر عليها.
  • hsl(…): معاينة أخرى للألوان يمكن النقر عليها لدالة الألوان hsl، ما يتيح الوصول السريع إلى أداة اختيار الألوان.
  • 177deg: ساعة زاوية قابلة للنقر تتيح لك سحب قيمة الزاوية وتعديلها بشكل تفاعلي.
  • var(--saturation, …): رابط قابل للنقر يؤدي إلى تعريف السمة المخصّصة، ما يسهّل الانتقال إلى البيان ذي الصلة.

الفرق واضح. لتحقيق ذلك، كان علينا تعليم DevTools فهم قيم سمات CSS بشكل أفضل بكثير من ذي قبل.

ألم تكن هذه المعاينات متاحة من قبل؟

على الرغم من أنّ رموز المعاينة هذه قد تبدو مألوفة، إلا أنّه لم يتم عرضها دائمًا بشكلٍ متسق، خاصةً في بنية CSS المعقدة مثل المثال أعلاه. وحتى في الحالات التي كانت فيها هذه الحلول ناجحة، كان يتطلّب الأمر في أغلب الأحيان بذل جهد كبير لجعلها تعمل بشكل صحيح.

ويعود السبب في ذلك إلى أنّ نظام تحليل القيم كان يتطوّر بشكلٍ طبيعي منذ الأيام الأولى لاستخدام DevTools. ومع ذلك، لم يتمكّن من مواكبة الميزات الجديدة الرائعة التي نحصل عليها من CSS والزيادة المقابلة في تعقيد اللغة. كان النظام بحاجة إلى إعادة تصميم كاملة لمواكبة التطور، وهذا ما فعلناه بالضبط.

كيفية معالجة قيم خصائص CSS

في DevTools، تنقسم عملية عرض وتزيين بيانات السمات في علامة التبويب الأنماط إلى مرحلتين مختلفتَين:

  1. التحليل الهيكلي تُحلّل هذه المرحلة الأولية بيان الموقع لتحديد مكوّناته الأساسية وعلاقاتها. على سبيل المثال، في البيان border: 1px solid red، سيتعرّف على 1px كطول وsolid كسلسلة وred كبُنيّة لون.
  2. العرض استنادًا إلى التحليل الهيكلي، تحوّل مرحلة العرض هذه المكوّنات إلى تمثيل HTML. ويؤدي ذلك إلى إثراء نص الموقع المعروض بعناصر تفاعلية وإشارات مرئية. على سبيل المثال، يتم عرض قيمة اللون red باستخدام رمز لون قابل للنقر يؤدي عند النقر عليه إلى إظهار أداة اختيار ألوان لتسهيل التعديل.

التعبيرات العادية

في السابق، كنا نعتمد على التعبيرات العادية لتحليل قيم السمات من أجل التحليل الهيكلي. حافظنا على قائمة بالتعبيرات العادية لمطابقة أجزاء من قيم السمات التي اعتبرناها زينة. على سبيل المثال، كانت هناك تعبيرات تتطابق مع ألوان CSS وأطوالها وزواياها وتعبيرات فرعية أكثر تعقيدًا، مثل طلبات دالة var وما إلى ذلك. لقد فحصنا النص من اليمين إلى اليسار لإجراء تحليل القيم، بحثًا باستمرار عن التعبير الأول من القائمة الذي يتطابق مع الجزء التالي من النص.

على الرغم من أنّ هذا الإجراء كان يعمل بشكل جيد في معظم الأحيان، إلا أنّ عدد الحالات التي لم يعمل فيها هذا الإجراء بشكل جيد استمر في الازدياد. على مرّ السنين، تلقّينا عددًا كبيرًا من تقارير الأخطاء التي لم يتم فيها إجراء المطابقة بشكل صحيح. أثناء إصلاح هذه المشاكل، كان علينا إعادة النظر في نهجنا لتجنّب تراكم الديون الفنية. لنلقِ نظرة على بعض المشاكل.

المطابقة color-mix()

كان التعبير العادي الذي استخدمناه لدالة color-mix() على النحو التالي:

/color-mix\(.*,\s*(?<firstColor>.+)\s*,\s*(?<secondColor>.+)\s*\)/g

والتي تتطابق مع بنية الجملة:

color-mix(<color-interpolation-method>, [<color> && <percentage [0,100]>?]#{2})

جرِّب تنفيذ المثال التالي لعرض المطابقات.

const re = /color-mix\(.*,\s*(?<firstColor>.+)\s*,\s*(?<secondColor>.+)\s*\)/g;

// it works - simpler example
const simpler = re.exec('color-mix(in srgb, pink, hsl(127deg 100% 50%))');
console.table(simpler.groups);

re.exec('');

// it doesn't work - complex example
const complex = re.exec('color-mix(in srgb, pink, var(--undefined, hsl(127deg var(--saturation, 100%) 50%)))');
console.table(complex.groups);

مطابقة نتيجة دالة مزج الألوان

يعمل المثال البسيط بشكل جيد. في المثال الأكثر تعقيدًا، تكون مطابقة <firstColor> هي hsl(177deg var(--saturation ومطابقة <secondColor> هي 100%) 50%))، ما لا معنى له على الإطلاق.

لقد علمنا أنّ هذه مشكلة. بعد كل شيء، لغة CSS الرسمية ليست عادية، لذلك سبق أن أدرجنا معالجة خاصة للتعامل مع وسيطات الدوالّ الأكثر تعقيدًا، مثل دوالّ var. ومع ذلك، كما هو موضّح في لقطة الشاشة الأولى، لم تنجح هذه الطريقة في جميع الحالات.

المطابقة tan()

كان أحد الأخطاء الأكثر طرافة التي تم الإبلاغ عنها متعلقًا بالدالة المثلثية tan() . كان التعبير العادي الذي كنا نستخدمه لمطابقة الألوان يتضمّن تعبيرًا فرعيًا \b[a-zA-Z]+\b(?!-) لمطابقة الألوان المُسمّاة، مثل الكلمة الرئيسية red. بعد ذلك، تحقّقنا ممّا إذا كان الجزء المطابق هو لون مُعنوَن، وتبيّن أنّ tan هو لون مُعنوَن أيضًا. وبالتالي، فسّرنا تعبيرات tan() على أنّها ألوان.

المطابقة var()

لنلقِ نظرة على مثال آخر، وهو دوال var() التي تحتوي على عنصر احتياطي يحتوي على مراجع var() أخرى: var(--non-existent, var(--margin-vertical)).

سيتطابق التعبير العادي لسمة var() مع هذه القيمة. إلا أنّه سيتوقّف عن المطابقة عند أول قوس إغلاق. وبالتالي، تتم مطابقة النص أعلاه على النحو التالي: var(--non-existent, var(--margin-vertical). هذا هو أحد القيود الأساسية لمطابقة التعبير العادي. إنّ اللغات التي تتطلّب مطابقة الأقواس ليست منتظمة بشكل أساسي.

الانتقال إلى محلل CSS

عندما يتوقّف تحليل النصوص باستخدام التعبيرات العادية (لأنّ اللغة التي تم تحليلها ليست عادية)، تتوفّر خطوة أساسية تالية: استخدام محلِّل لقواعد نحوية من النوع الأعلى. بالنسبة إلى CSS، يعني ذلك استخدام محلِّل للغات غير المستندة إلى السياق. في الواقع، كان نظام التحليل هذا متوفّرًا في قاعدة بيانات DevTools: Lezer في CodeMirror، وهو الأساس الذي يستند إليه، على سبيل المثال، تمييز البنية في CodeMirror، المحرِّر الذي يمكنك العثور عليه في لوحة المصادر. سمح لنا محلل CSS في Lezer بإنشاء أشجار نحوية (غير مجردة) لقواعد CSS وكان جاهزًا للاستخدام. علامة النصر.

شجرة نحوية لقيمة السمة hsl(177deg var(--saturation, 100%) 50%)‏. وهي نسخة مبسّطة من النتيجة التي ينتج عنها محلل Lezer، مع استبعاد العقد النحوية البحتة للفواصل والقوسين.

ولكن تبيّن لنا أنّه من غير الممكن نقل البيانات من المطابقة المستندة إلى التعبير العادي إلى المطابقة المستندة إلى التحليل مباشرةً، لأنّ الطريقتَين تعملان من اتجاهَين متضادَّين. عند مطابقة أجزاء من القيم مع التعبيرات العادية، ستفحص أدوات المطوّرين الإدخال من اليمين إلى اليسار، وستحاول بشكل متكرّر العثور على أقرب مطابقة من قائمة منظمة من الأنماط. باستخدام شجرة بنية الجملة، سيبدأ المطابقة من الأسفل إلى الأعلى، على سبيل المثال، تحليل وسيطات الاستدعاء أولاً، قبل محاولة مطابقة استدعاء الدالة. يمكنك اعتبار ذلك تقييمًا لتعبير حسابي، حيث تأخذ أولاً في الاعتبار التعبيرات بين قوسين، ثم عوامل الضرب، ثم عوامل الإضافة. في هذا الإطار، تتوافق المطابقة المستندة إلى التعبير العادي مع تقييم التعبير الحسابي من اليمين إلى اليسار. لم نكن نريد إعادة كتابة نظام المطابقة بالكامل من البداية: كان هناك 15 زوجًا مختلفًا من المطابقين والمعروضين، مع آلاف أسطر الرموز البرمجية، ما جعل من غير المرجّح أن نتمكّن من طرحه في إنجاز واحد.

لذلك، توصّلنا إلى حلّ سمح لنا بإجراء تغييرات تدريجية، وسنوضّح ذلك أدناه بمزيد من التفصيل. باختصار، احتفظنا بالنهج المكوّن من مرحلتين، ولكن في المرحلة الأولى نحاول مطابقة التعبيرات الفرعية من الأسفل إلى الأعلى (وبالتالي نخالف مسار التعبير العادي)، وفي المرحلة الثانية نعرضها من الأعلى إلى الأسفل. في كلتا المرحلتَين، تمكّنا من استخدام المطابقات وعمليات التحويل المستندة إلى التعبير العادي الحالية بدون تغييرات تقريبًا، وبالتالي تمكّنا من نقلها واحدة تلو الأخرى.

المرحلة 1: المطابقة من الأسفل إلى الأعلى

تؤدي المرحلة الأولى على وجه التحديد ما يُقال على الغلاف بشكل حصري تقريبًا. ننتقل في الشجرة بالترتيب من الأسفل إلى الأعلى ونحاول مطابقة التعبيرات الفرعية في كل عقدة من شجرة البنية التي نزورها. لمطابقة تعبير فرعي معيّن، يمكن لمطابقة استخدام التعبير العادي تمامًا كما كان في النظام الحالي. اعتبارًا من الإصدار 128، ما زلنا نفعل ذلك في بعض الحالات، على سبيل المثال، لمطابقة الأطوال. بدلاً من ذلك، يمكن للمطابق تحليل بنية الشجرة الفرعية التي تتأصل في العقدة الحالية. ويتيح له ذلك رصد أخطاء البنية وتسجيل المعلومات الهيكلية في الوقت نفسه.

راجِع مثال شجرة البنية أعلاه:

المرحلة 1: المطابقة من الأسفل إلى الأعلى في شجرة البنية

بالنسبة إلى هذه الشجرة، سيتم تطبيق المطابقات بالترتيب التالي:

  1. hsl(177degvar(--saturation, 100%) 50%): أولاً، نتعرّف على الوسيطة الأولى لاستدعاء الدالة hsl، وهي زاوية درجة اللون. ونطابقها مع أداة مطابقة الزاوية، حتى نتمكّن من تزيين قيمة الزاوية برمز الزاوية.
  2. hsl(177degvar(--saturation, 100%)50%): ثانيًا، نرصد طلب استدعاء الدالة var باستخدام مطابق var. بالنسبة إلى هذه المكالمات، نريد إجراء أمرَين بشكل أساسي:
    • ابحث عن بيان المتغيّر واحسب قيمته، وأضِف رابطًا وإطار معلومات منبثقًا إلى اسم المتغيّر للربط بهما على التوالي.
    • يمكنك تزيين الطلب برمز لون إذا كانت القيمة المحسوبة لونًا. هناك نقطة ثالثة، ولكن سنتحدث عنها لاحقًا.
  3. hsl(177deg var(--saturation, 100%) 50%): أخيرًا، نطابق تعبير الاستدعاء لدالة hsl حتى نتمكّن من تزيينها برمز اللون.

بالإضافة إلى البحث عن التعبيرات الفرعية التي نريد تزيينها، هناك ميزة ثانية ننفّذها كجزء من عملية المطابقة. يُرجى العِلم أنّنا ذكرنا في الخطوة 2 أنّنا نبحث عن القيمة المحسوبة لاسم متغيّر. في الواقع، نأخذ هذه الخطوة إلى الأمام وننشر النتائج في أعلى الشجرة. وليس فقط للمتغيّر، بل أيضًا للقيمة الاحتياطية. نضمن لك أنّه عند الانتقال إلى عقدة دالة var، يتم الانتقال إلى العقد الفرعية لها مسبقًا، لذا نعرف نتائج أي دوال var قد تظهر في القيمة الاحتياطية. لذلك، يمكننا بسهولة وبدون تكلفة استبدال دوال var بنتائجها على الفور، ما يتيح لنا الإجابة بسهولة عن أسئلة مثل "هل نتيجة طلب var هذا هي لون؟"، كما فعلنا في الخطوة 2.

المرحلة 2: التقديم من الأعلى إلى الأسفل

في المرحلة الثانية، نعكس الاتجاه. استنادًا إلى نتائج المطابقة من المرحلة 1، نعرض الشجرة بتنسيق HTML من خلال التنقّل فيها بالترتيب من الأعلى إلى الأسفل. بالنسبة إلى كل عقدة تمت زيارتها، نتحقّق مما إذا كانت مطابقة، وإذا كان الأمر كذلك، نُطلِق أداة العرض المقابلة لمطابقة المحتوى. نتجنب الحاجة إلى معالجة خاصة للعقد التي تحتوي على نص فقط (مثل NumberLiteral "‎50%") من خلال تضمين أداة مطابقة وعرض تلقائية للعقد النصية. تُخرج أدوات العرض ببساطة عقد HTML التي تؤدي عند تجميعها إلى تمثيل قيمة السمة بما في ذلك زينتها.

المرحلة 2: المعالجة من الأعلى إلى الأسفل في شجرة البنية

في ما يلي ترتيب عرض قيمة السمة في مثال الشجرة:

  1. انتقِل إلى استدعاء الدالة hsl. تطابقت، لذا استدعى أداة عرض دالة الألوان. وينفِّذ هذا الإجراء شيئَين:
    • تُحتسب قيمة اللون الفعلية باستخدام آلية الاستبدال الفوري لأيّ وسيطات var، ثمّ ترسم رمز لون.
    • تعرِض هذه الوظيفة بشكلٍ متكرّر عناصر CallExpression. يُتولى هذا تلقائيًا عرض اسم الدالة والأقواس والفواصل، وهي مجرد نص.
  2. انتقِل إلى الوسيطة الأولى لدالة hsl. تطابقت القيمة، لذا استدعى معالِج الزاوية الذي يرسم رمز الزاوية ونصه.
  3. انتقِل إلى الوسيطة الثانية، وهي طلب var. تطابقت القيمة، لذا استدِع المتغيّر المشغِّل الذي يعرض ما يلي:
    • النص var( في البداية
    • اسم المتغيّر وتزيينه إما برابط يؤدي إلى تعريف المتغيّر أو بلون نص رمادي للإشارة إلى أنّه غير محدّد. ويضيف أيضًا نافذة منبثقة إلى المتغيّر لعرض معلومات عن قيمته.
    • تعرِض الفاصلة القيمة الاحتياطية بشكلٍ متكرّر.
    • قوس إغلاق
  4. انتقِل إلى الوسيطة الأخيرة لاستدعاء hsl. لم تتطابق، لذا ما عليك سوى عرض محتوى النص.

هل لاحظت أنّه في هذه الخوارزمية، يتحكّم العنصر المعروض بالكامل في كيفية عرض العناصر الفرعية للعقدة المطابقة؟ إنّ عرض العناصر الفرعية بشكل متكرّر هو إجراء استباقي. وقد سمحت هذه الحيلة بنقل البيانات بشكل تدريجي من العرض المستنِد إلى التعبير العادي إلى العرض المستنِد إلى شجرة البنية. بالنسبة إلى العقد التي تمت مطابقتها مع أداة مطابقة تعبير عادي قديم، يمكن استخدام أداة العرض المقابلة في شكلها الأصلي. وبعبارة أخرى، في شجرة البنية النحوية، ستتحمّل هذه البنية مسؤولية عرض الشجرة الفرعية بأكملها، ويمكن توصيل نتيجتها (عقدة HTML) بشكلٍ نظيف في عملية العرض المحيطة. وقد منحنا ذلك خيار نقل المطابقين وأدوات العرض بشكلٍ مُزوَّدَين، واستبدالهما واحدًا تلو الآخر.

من الميزات الرائعة الأخرى لأدوات الربط التي تتحكّم في عرض عناصر العقدة المطابقة أنّها تمنحنا إمكانية الاستدلال على التبعيات بين الرموز التي نضيفها. في المثال أعلاه، يعتمد اللون الذي تنشئه الدالة hsl بوضوح على قيمة تدرج اللون. وهذا يعني أنّ اللون الذي يعرضه رمز اللون يعتمد على الزاوية التي يعرضها رمز الزاوية. إذا فتح المستخدم محرِّر الزاوية من خلال هذا الرمز وغيّر الزاوية، يمكننا الآن تعديل لون رمز اللون في الوقت الفعلي:

كما هو موضّح في المثال أعلاه، نستخدم هذه الآلية مع مجموعات الرموز الأخرى أيضًا، مثل color-mix() وقناتَي الألوان، أو وظائف var التي تعرض لونًا من القيمة الاحتياطية.

التأثير على الأداء

عند الاطّلاع على هذه المشكلة لتحسين الموثوقية وحلّ المشاكل المستمرة، توقّعنا حدوث بعض التراجع في الأداء نظرًا لأنّنا بدأنا تشغيل محلِّل نحوي كامل. لاختبار ذلك، أنشأنا معيارًا يعرض حوالي 3.5 ألف بيان خاصية، ووضعنا ملفًا شخصيًا لكل من الإصدارَين المستنِدَين إلى التعبير العادي والمستندَين إلى المُحلِّل، مع خفض السرعة بمقدار 6 مرات على جهاز M1.

كما توقّعنا، تبيّن أنّ المنهج المستنِد إلى التحليل كان أبطأ بنسبة% 27 من المنهج المستنِد إلى التعبير العادي في هذه الحالة. استغرق النهج المستنِد إلى التعبير العادي 11 ثانية للعرض، واستغرق النهج المستنِد إلى التحليل 15 ثانية للعرض.

ونظرًا للفوائد التي نحصل عليها من هذا النهج الجديد، قرّرنا المضي قدمًا به.

الشكر والتقدير

نشكر بشدة صوفيا إميليانوفا وجيسيلين يين على مساعدتهما القيّمة في تعديل هذا المنشور.

تنزيل قنوات المعاينة

ننصحك باستخدام إصدار Canary أو Dev أو الإصدار التجريبي من Chrome كمتصفّح التطوير التلقائي. تتيح لك قنوات المعاينة هذه الوصول إلى أحدث ميزات DevTools، وتتيح لك اختبار واجهات برمجة تطبيقات منصات الويب المتطوّرة، وتساعدك في العثور على المشاكل في موقعك الإلكتروني قبل أن يعثر عليها المستخدمون.

التواصل مع فريق "أدوات مطوّري البرامج في Chrome"

استخدِم الخيارات التالية لمناقشة الميزات الجديدة أو التحديثات أو أي شيء آخر مرتبط بـ "أدوات مطوّري البرامج".