معالجة CSS غير المستخدمة في Sass لتحسين الأداء

نشرت: 2022-03-10
ملخص سريع ↬ هل تعلم تأثير CSS غير المستخدم على الأداء؟ المفسد: كثير! في هذه المقالة ، سنستكشف حلاً موجهًا لـ Sass للتعامل مع CSS غير المستخدمة ، وتجنب الحاجة إلى تبعيات Node.js المعقدة التي تتضمن متصفحات بدون رؤوس ، ومحاكاة DOM.

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

إحدى الطرق التي يمكن من خلالها تحقيق ذلك هي استخدام منهجيات مثل Object-Oriented CSS (OOCSS) ، والتي بدلاً من تنظيم CSS حول سياق الصفحة ، تشجع على فصل الهيكل (أنظمة الشبكة ، والتباعد ، والعرض ، وما إلى ذلك) عن الزخرفة (الخطوط ، العلامة التجارية والألوان وما إلى ذلك).

لذلك فإن أسماء فئات CSS مثل:

  • .blog-right-column
  • .latest_topics_list
  • .job-vacancy-ad

يتم استبدالها ببدائل أكثر قابلية لإعادة الاستخدام ، والتي تطبق نفس أنماط CSS ، ولكنها غير مرتبطة بأي سياق معين:

  • .col-md-4
  • .list-group
  • .card

يتم تنفيذ هذا النهج بشكل شائع بمساعدة إطار عمل Sass مثل Bootstrap أو Foundation أو في كثير من الأحيان بشكل متزايد إطار عمل مفصل يمكن تشكيله ليلائم المشروع بشكل أفضل.

المزيد بعد القفز! أكمل القراءة أدناه ↓

لذلك نحن الآن نستخدم فئات CSS منتقاة بعناية من إطار عمل للأنماط ومكونات واجهة المستخدم وفئات المرافق. يوضح المثال أدناه نظام شبكة مشتركًا تم إنشاؤه باستخدام Bootstrap ، والذي يتراكم عموديًا ، ثم بمجرد الوصول إلى نقطة التوقف md ، ينتقل إلى تخطيط 3 أعمدة.

 <div class="container"> <div class="row"> <div class="col-12 col-md-4">Column 1</div> <div class="col-12 col-md-4">Column 2</div> <div class="col-12 col-md-4">Column 3</div> </div> </div>

يتم هنا استخدام الفئات التي تم إنشاؤها برمجيًا مثل .col-12 و .col-md-4 لإنشاء هذا النمط. ولكن ماذا عن .col-1 حتى .col-11 أو .col-lg-4 أو .col-md-6 أو .col-sm-12 ؟ هذه كلها أمثلة للفئات التي سيتم تضمينها في ورقة أنماط CSS المجمعة ، ويتم تنزيلها وتحليلها بواسطة المتصفح ، على الرغم من عدم استخدامها.

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

قياس تأثير فئات CSS غير المستخدمة

بينما أعشق Sheffield United ، الشفرات القوية ، يتم تجميع CSS لموقع الويب الخاص بهم في ملف واحد بحجم 568 كيلوبايت ، والذي يصل إلى 105 كيلوبايت حتى عند الضغط على gz. الذي يبدو مثل الكثير.

هذا موقع Sheffield United على الإنترنت ، فريقي المحلي لكرة القدم (هذا هو فريق كرة القدم بالنسبة لك كثيرًا في المستعمرات). (معاينة كبيرة)

هل سنرى مقدار CSS المستخدم بالفعل على صفحتهم الرئيسية؟ يكشف بحث Google السريع عن الكثير من الأدوات عبر الإنترنت حتى الوظيفة ، لكنني أفضل استخدام أداة التغطية في Chrome ، والتي يمكن تشغيلها مباشرة من DevTools في Chrome. دعونا نعطيها دوامة.

أسرع طريقة للوصول إلى أداة التغطية في Developer Tools هي استخدام اختصار لوحة المفاتيح Control + Shift + P أو Command + Shift + P (Mac) لفتح قائمة الأوامر. في ذلك ، اكتب coverage ، وحدد خيار "إظهار التغطية". (معاينة كبيرة)

تُظهر النتائج أن الصفحة الرئيسية تستخدم 30 كيلو بايت فقط من CSS من ورقة أنماط 568 كيلو بايت ، فيما يتعلق 538 كيلو بايت المتبقية بالأنماط المطلوبة لبقية موقع الويب. هذا يعني أن نسبة 94.8٪ من CSS غير مستخدمة.

يمكنك مشاهدة مثل هذه الأوقات لأي أصل في Chrome في Developer Tools عبر الشبكة -> انقر فوق الأصل الخاص بك -> علامة التبويب Timing. (معاينة كبيرة)

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

لذلك مع وضع ذلك في الاعتبار ، عند تحميل موقع ويب Sheffield United باستخدام اتصال 3G جيد ، يستغرق الأمر 1.15 ثانية كاملة قبل تنزيل CSS ويمكن أن يبدأ عرض الصفحة. هذه مشكلة.

لقد أدركت Google هذا أيضًا. عند إجراء تدقيق Lighthouse ، عبر الإنترنت أو عبر المستعرض الخاص بك ، يتم تمييز أي وقت تحميل محتمل وتوفير الملفات التي يمكن تحقيقها عن طريق إزالة CSS غير المستخدمة.

في Chrome (و Chromium Edge) ، يمكنك تصحيح عمليات تدقيق Google Lighthouse بالنقر فوق علامة التبويب "تدقيق" في أدوات المطور. (معاينة كبيرة)

الحلول الحالية

الهدف هو تحديد فئات CSS غير المطلوبة وإزالتها من ورقة الأنماط. تتوفر الحلول الحالية التي تحاول أتمتة هذه العملية. يمكن استخدامها عادةً عبر برنامج نصي لبناء Node.js ، أو عبر متسابقي المهام مثل Gulp. وتشمل هذه:

  • UNCSS
  • PurifyCSS
  • تطهير CSS

تعمل هذه بشكل عام بطريقة مماثلة:

  1. على البلد ، يتم الوصول إلى موقع الويب عبر متصفح مقطوعة الرأس (على سبيل المثال: محرك الدمى) أو محاكاة DOM (على سبيل المثال: jsdom).
  2. بناءً على عناصر HTML للصفحة ، يتم تحديد أي CSS غير مستخدم.
  3. تتم إزالة هذا من ورقة الأنماط ، وترك فقط ما هو مطلوب.

في حين أن هذه الأدوات الآلية صالحة تمامًا وقد استخدمت العديد منها عبر عدد من المشاريع التجارية بنجاح ، فقد واجهت بعض العيوب على طول الطريق والتي تستحق المشاركة:

  • إذا كانت أسماء الفئات تحتوي على أحرف خاصة مثل "@" أو "/" ، فقد لا يتم التعرف عليها دون كتابة بعض التعليمات البرمجية المخصصة. أستخدم BEM-IT by Harry Roberts ، والذي يتضمن هيكلة أسماء الفئات مع لاحقات متجاوبة مثل: u-width-6/12@lg ، لذلك واجهت هذه المشكلة من قبل.
  • إذا كان موقع الويب يستخدم النشر التلقائي ، فقد يؤدي ذلك إلى إبطاء عملية الإنشاء ، خاصة إذا كان لديك الكثير من الصفحات والكثير من CSS.
  • يجب مشاركة المعرفة حول هذه الأدوات عبر الفريق ، وإلا فقد يكون هناك ارتباك وإحباط عندما تغيب CSS بشكل غامض في أوراق أنماط الإنتاج.
  • إذا كان موقع الويب الخاص بك يحتوي على العديد من البرامج النصية لجهات خارجية قيد التشغيل ، أحيانًا عند فتحها في متصفح بدون واجهة مستخدم ، فهذه البرامج لا تعمل بشكل جيد ويمكن أن تتسبب في حدوث أخطاء في عملية التصفية. لذلك عادةً ما يتعين عليك كتابة رمز مخصص لاستبعاد أي نصوص برمجية لجهات خارجية عند اكتشاف متصفح بدون رأس ، والذي قد يكون صعبًا اعتمادًا على الإعداد الخاص بك.
  • بشكل عام ، هذا النوع من الأدوات معقد ويقدم الكثير من التبعيات الإضافية لعملية الإنشاء. كما هو الحال مع جميع تبعيات الطرف الثالث ، فإن هذا يعني الاعتماد على كود شخص آخر.

مع وضع هذه النقاط في الاعتبار ، طرحت على نفسي سؤالاً:

باستخدام Sass فقط ، هل من الممكن التعامل بشكل أفضل مع Sass الذي نقوم بتجميعه بحيث يمكن استبعاد أي CSS غير مستخدم ، دون اللجوء إلى حذف أصناف المصدر في Sass بشكل فظ؟

تنبيه المفسد: الجواب نعم. هذا ما توصلت إليه.

حل موجه ساس

يحتاج الحل إلى توفير طريقة سريعة وسهلة لاختيار ما يجب تجميعه بواسطة Sass ، مع كونه بسيطًا بما يكفي بحيث لا يضيف المزيد من التعقيد إلى عملية التطوير أو يمنع المطورين من الاستفادة من أشياء مثل CSS المُنشأ برمجيًا الطبقات.

للبدء ، هناك ريبو مع نصوص بناء وبعض الأنماط التي يمكنك استنساخها من هنا.

تلميح: إذا واجهتك مشكلة ، فيمكنك دائمًا الإسناد الترافقي مع الإصدار المكتمل في الفرع الرئيسي.

cd في الريبو ، قم بتشغيل npm install ثم npm run build لتجميع أي Sass في CSS كما هو مطلوب. يجب أن يؤدي هذا إلى إنشاء ملف css بحجم 55 كيلو بايت في دليل التوزيع.

إذا فتحت بعد ذلك /dist/index.html في متصفح الويب الخاص بك ، فسترى مكونًا قياسيًا إلى حد ما ، والذي يتم توسيعه عند النقر للكشف عن بعض المحتوى. يمكنك أيضًا عرض هذا هنا ، حيث سيتم تطبيق شروط الشبكة الحقيقية ، حتى تتمكن من إجراء الاختبارات الخاصة بك.

سنستخدم مكون واجهة مستخدم الموسع هذا كموضوع اختبار عند تطوير حل موجه نحو Sass للتعامل مع CSS غير المستخدمة. (معاينة كبيرة)

التصفية على مستوى الجزئيات

في إعداد SCSS نموذجي ، من المحتمل أن يكون لديك ملف بيان واحد (على سبيل المثال: main.scss في الريبو) ، أو واحد لكل صفحة (على سبيل المثال: index.scss ، products.scss ، contact.scss ) حيث يتم استيرادها. باتباع مبادئ OOCSS ، قد تبدو هذه الواردات كما يلي:

مثال 1

 /* Undecorated design patterns */ @import 'objects/box'; @import 'objects/container'; @import 'objects/layout'; /* UI components */ @import 'components/button'; @import 'components/expander'; @import 'components/typography'; /* Highly specific helper classes */ @import 'utilities/alignments'; @import 'utilities/widths';

إذا لم يتم استخدام أي من هذه الأجزاء ، فإن الطريقة الطبيعية لتصفية CSS غير المستخدمة ستكون فقط تعطيل الاستيراد ، مما سيمنع تجميعه.

على سبيل المثال ، في حالة استخدام مكون الموسع فقط ، سيبدو البيان عادةً كما يلي:

مثال 2

 /* Undecorated design patterns */ // @import 'objects/box'; // @import 'objects/container'; // @import 'objects/layout'; /* UI components */ // @import 'components/button'; @import 'components/expander'; // @import 'components/typography'; /* Highly specific helper classes */ // @import 'utilities/alignments'; // @import 'utilities/widths';

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

في الريبو ، إذا نظرت إلى HTML الخاص بالموسع في dist/index.html ، يبدو أن هذا هو الحال. يستخدم أنماطًا من كائنات المربع والتخطيط ، ومكون الطباعة ، وأدوات العرض والمحاذاة.

dist/index.html

 <div class="c-expander"> <div class="o-box o-box--spacing-small c-expander__trigger c-expander__header" tabindex="0"> <div class="o-layout o-layout--fit u-flex-middle"> <div class="o-layout__item u-width-grow"> <h2 class="c-type-echo">Toggle Expander</h2> </div> <div class="o-layout__item u-width-shrink"> <div class="c-expander__header-icon"></div> </div> </div> </div> <div class="c-expander__content"> <div class="o-box o-box--spacing-small"> Lorum ipsum <p class="u-align-center"> <button class="c-expander__trigger c-button">Close</button> </p> </div> </div> </div>

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

خريطة الواردات الآلية

لكي يعمل نظام التبعية هذا ، بدلاً من مجرد التعليق في عبارات @import في ملف البيان ، سيحتاج منطق Sass إلى إملاء ما إذا كان سيتم تجميع الأجزاء أم لا.

في src/scss/settings ، قم بإنشاء جزء جديد يسمى _imports.scss ، @import استيراده في settings/_core.scss ، ثم قم بإنشاء خريطة SCSS التالية:

src/scss/settings/_core.scss

 @import 'breakpoints'; @import 'spacing'; @import 'imports';

src/scss/settings/_imports.scss

 $imports: ( object: ( 'box', 'container', 'layout' ), component: ( 'button', 'expander', 'typography' ), utility: ( 'alignments', 'widths' ) );

سيكون لهذه الخريطة نفس دور بيان الاستيراد مرة أخرى في المثال 1.

مثال 4

 $imports: ( object: ( //'box', //'container', //'layout' ), component: ( //'button', 'expander', //'typography' ), utility: ( //'alignments', //'widths' ) );

يجب أن تتصرف مثل مجموعة معيارية من @imports ، من حيث أنه إذا تم التعليق على أجزاء معينة (مثل ما ورد أعلاه) ، فلا ينبغي أن يتم تجميع هذا الرمز بناءً على الإنشاء.

ولكن نظرًا لأننا نريد استيراد التبعيات تلقائيًا ، يجب أن نكون قادرين أيضًا على تجاهل هذه الخريطة في ظل الظروف المناسبة.

رندر ميكسين

لنبدأ في إضافة بعض منطق Sass. قم _render.scss في src/scss/tools ، ثم قم بإضافة @import إلى tools/_core.scss .

في الملف ، أنشئ مزيجًا فارغًا يسمى العرض render() .

src/scss/tools/_render.scss

 @mixin render() { }

في mixin ، نحتاج إلى كتابة Sass الذي يقوم بما يلي:

  • يقدم - يجعل()
    "مرحبًا ، هناك $imports ، والطقس الجيد ، أليس كذلك؟ قل ، هل لديك كائن الحاوية في خريطتك؟ "
  • الواردات دولار
    false
  • يقدم - يجعل()
    "هذا عار ، يبدو أنه لن يتم تجميعه بعد ذلك. ماذا عن مكون الزر؟ "
  • الواردات دولار
    true
  • يقدم - يجعل()
    "لطيف - جيد! هذا هو الزر الذي يتم تجميعه بعد ذلك. قل مرحبا للزوجة من أجلي ".

في Sass ، هذا يترجم إلى ما يلي:

src/scss/tools/_render.scss

 @mixin render($name, $layer) { @if(index(map-get($imports, $layer), $name)) { @content; } }

بشكل أساسي ، تحقق مما إذا كان الجزء مضمنًا في متغير $imports ، وإذا كان الأمر كذلك ، فقم بعرضه باستخدام توجيه Sass ' @content ، والذي يسمح لنا بتمرير كتلة محتوى إلى mixin.

سنستخدمه على النحو التالي:

مثال 5

 @include render('button', 'component') { .c-button { // styles et al } // any other class declarations }

قبل استخدام هذا المزيج ، هناك تحسن بسيط يمكننا إجراؤه عليه. اسم الطبقة (كائن ، مكون ، فائدة ، إلخ) شيء يمكننا التنبؤ به بأمان ، لذلك لدينا فرصة لتبسيط الأمور قليلاً.

قبل إعلان عرض mixin ، قم بإنشاء متغير يسمى $layer ، وقم بإزالة المتغير المسمى نفسه من معاملات mixins. مثل ذلك:

src/scss/tools/_render.scss

 $layer: null !default; @mixin render($name) { @if(index(map-get($imports, $layer), $name)) { @content; } }

الآن ، في أجزاء _core.scss حيث توجد الكائنات والمكونات والأداة المساعدة @imports ، قم بإعادة تعريف هذه المتغيرات إلى القيم التالية ؛ تمثل نوع فئات CSS التي يتم استيرادها.

src/scss/objects/_core.scss

 $layer: 'object'; @import 'box'; @import 'container'; @import 'layout';

src/scss/components/_core.scss

 $layer: 'component'; @import 'button'; @import 'expander'; @import 'typography';

src/scss/utilities/_core.scss

 $layer: 'utility'; @import 'alignments'; @import 'widths';

بهذه الطريقة ، عندما نستخدم العرض render() mixin ، كل ما علينا فعله هو إعلان الاسم الجزئي.

قم بلف ملف mixin render() حول كل كائن ، ومكون ، وإعلان فئة المنفعة ، كما هو موضح أدناه. سيعطيك هذا استخدامًا واحدًا لمزج العرض لكل جزء.

علي سبيل المثال:

src/scss/objects/_layout.scss

 @include render('button') { .c-button { // styles et al } // any other class declarations }

src/scss/components/_button.scss

 @include render('button') { .c-button { // styles et al } // any other class declarations }

ملاحظة: بالنسبة إلى utilities/_widths.scss ، فإن التفاف وظيفة العرض render() حول الجزء الجزئي بالكامل سيؤدي إلى خطأ في الترجمة ، كما في Sass ، لا يمكنك دمج إعلانات mixin داخل استدعاءات mixin. بدلاً من ذلك ، قم فقط بلف mixin render() create-widths() ، كما هو موضح أدناه:

 @include render('widths') { // GENERATE STANDARD WIDTHS //--------------------------------------------------------------------- // Example: .u-width-1/3 @include create-widths($utility-widths-sets); // GENERATE RESPONSIVE WIDTHS //--------------------------------------------------------------------- // Create responsive variants using settings.breakpoints // Changes width when breakpoint is hit // Example: .u-width-1/3@md @each $bp-name, $bp-value in $mq-breakpoints { @include mq(#{$bp-name}) { @include create-widths($utility-widths-sets, \@, #{$bp-name}); } } // End render }

مع وجود هذا في مكانه ، عند الإنشاء ، سيتم تجميع الأجزاء المشار إليها في عمليات $imports فقط.

امزج بين المكونات التي تم التعليق عليها في عمليات $imports بالدولار وطابقها وقم بتشغيل npm run build في المحطة لتجربتها.

خريطة التبعيات

نحن الآن نستورد الجزئيات برمجيًا ، يمكننا البدء في تطبيق منطق التبعية.

في src/scss/settings ، أنشئ جزءًا جديدًا يسمى _dependencies.scss ، @import استورده في settings/_core.scss ، لكن تأكد من أنه بعد _imports.scss . ثم في ذلك ، قم بإنشاء خريطة SCSS التالية:

src/scss/settings/_dependencies.scss

 $dependencies: ( expander: ( object: ( 'box', 'layout' ), component: ( 'button', 'typography' ), utility: ( 'alignments', 'widths' ) ) );

هنا ، نعلن عن التبعيات لمكون الموسع لأنه يتطلب أنماطًا من أجزاء أخرى لتقديمها بشكل صحيح ، كما هو موضح في dist / index.html.

باستخدام هذه القائمة ، يمكننا كتابة المنطق الذي يعني أنه سيتم دائمًا تجميع هذه التبعيات جنبًا إلى جنب مع المكونات التابعة لها ، بغض النظر عن حالة المتغير $imports import.

أقل $dependencies ، قم بإنشاء مزيج يسمى dependency-setup() . هنا ، سنفعل الإجراءات التالية:

1. حلقة من خلال خريطة التبعيات.

 @mixin dependency-setup() { @each $componentKey, $componentValue in $dependencies { } }

2. إذا كان المكون يمكن العثور عليه في عمليات $imports ، فراجع قائمة التبعيات الخاصة به.

 @mixin dependency-setup() { $components: map-get($imports, component); @each $componentKey, $componentValue in $dependencies { @if(index($components, $componentKey)) { @each $layerKey, $layerValue in $componentValue { } } } }

3. إذا لم تكن التبعية في عمليات $imports ، قم بإضافتها.

 @mixin dependency-setup() { $components: map-get($imports, component); @each $componentKey, $componentValue in $dependencies { @if(index($components, $componentKey)) { @each $layerKey, $layerValue in $componentValue { @each $partKey, $partValue in $layerValue { @if not index(map-get($imports, $layerKey), $partKey) { $imports: map-merge($imports, ( $layerKey: append(map-get($imports, $layerKey), '#{$partKey}') )) !global; } } } } } }

تضمين العلامة !global يخبر Sass بالبحث عن متغير $imports في النطاق العالمي ، بدلاً من النطاق المحلي للمزج.

4. ثم انها مجرد مسألة استدعاء mixin.

 @mixin dependency-setup() { ... } @include dependency-setup();

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

قم بتكوين متغير $imports بحيث يتم استيراد مكون الموسع فقط ثم قم بتشغيل npm run build . يجب أن ترى في CSS المترجمة فئات الموسع مع كل تبعياتها.

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

تحسين استيراد التبعية

قد يتطلب المكون فئة واحدة فقط من التبعية ، لذا فإن الاستمرار في استيراد كل فئات التبعية هذه يؤدي فقط إلى نفس الانتفاخ غير الضروري الذي نحاول تجنبه.

يمكننا تحسين النظام للسماح بمزيد من التصفية الدقيقة على أساس كل فئة على حدة ، للتأكد من تجميع المكونات مع فئات التبعية التي تتطلبها فقط.

مع معظم أنماط التصميم ، سواء أكانت مزخرفة أم لا ، يوجد حد أدنى من الفئات التي يجب أن تكون موجودة في ورقة الأنماط حتى يتم عرض النمط بشكل صحيح.

بالنسبة لأسماء الفئات التي تستخدم اصطلاح تسمية راسخًا مثل BEM ، عادةً ما تكون الفئات المسماة "Block" و "Element" مطلوبة كحد أدنى ، مع كون "المعدلات" اختيارية عادةً.

ملاحظة: لا تتبع فئات الأدوات عادةً مسار BEM ، لأنها معزولة بطبيعتها بسبب تركيزها الضيق.

على سبيل المثال ، ألق نظرة على كائن الوسائط هذا ، والذي ربما يكون أشهر مثال على CSS الموجه للكائنات:

 <div class="o-media o-media--spacing-small"> <div class="o-media__image"> <img src="url" alt="Image"> </div> <div class="o-media__text"> Oh! </div> </div>

إذا كان المكون يحتوي على هذه المجموعة باعتبارها تبعية ، فمن المنطقي دائمًا تجميع .o-media و .o-media__image و .o-media__text ، لأن هذا هو الحد الأدنى من CSS المطلوب لجعل النمط يعمل. ومع ذلك ، مع .o-media--spacing-small كونها معدلاً اختياريًا ، يجب أن يتم تجميعها فقط إذا قلنا ذلك صراحةً ، حيث قد لا يكون استخدامها متسقًا عبر جميع مثيلات كائن الوسائط.

سنقوم بتعديل هيكل خريطة $dependencies للسماح لنا باستيراد هذه الفئات الاختيارية ، مع تضمين طريقة لاستيراد الكتلة والعنصر فقط في حالة عدم الحاجة إلى مُعدِّلات.

للبدء ، تحقق من الموسّع HTML في dist / index.html وقم بتدوين أي فئات تبعية قيد الاستخدام. قم بتسجيلها في خريطة $dependencies ، كما هو موضح أدناه:

src/scss/settings/_dependencies.scss

 $dependencies: ( expander: ( object: ( box: ( 'o-box--spacing-small' ), layout: ( 'o-layout--fit' ) ), component: ( button: true, typography: ( 'c-type-echo', ) ), utility: ( alignments: ( 'u-flex-middle', 'u-align-center' ), widths: ( 'u-width-grow', 'u-width-shrink' ) ) ) );

عندما يتم تعيين القيمة على true ، سنقوم بترجمتها إلى "فقط فئات كتلة الترجمة ومستوى العنصر ، لا توجد مُعدِّلات!".

تتضمن الخطوة التالية إنشاء متغير في القائمة البيضاء لتخزين هذه الفئات وأي فئات أخرى (غير تبعية) نرغب في استيرادها يدويًا. في /src/scss/settings/imports.scss ، بعد $imports ، قم بإنشاء قائمة Sass جديدة تسمى $global-filter .

src/scss/settings/_imports.scss

 $global-filter: ();

الفرضية الأساسية وراء $global-filter هي أن أي فئات مخزنة هنا سيتم تجميعها على الإنشاء طالما أن الجزء الذي تنتمي إليه يتم استيراده عبر عمليات $imports .

يمكن إضافة أسماء الفئات هذه برمجيًا إذا كانت تعتمد على مكون ، أو يمكن إضافتها يدويًا عند الإعلان عن المتغير ، كما في المثال أدناه:

مثال عامل التصفية العام

 $global-filter: ( 'o-box--spacing-regular@md', 'u-align-center', 'u-width-6/12@lg' );

بعد ذلك ، نحتاج إلى إضافة المزيد من المنطق $global-filter $dependencies @dependency-setup

أسفل هذه الكتلة:

src/scss/settings/_dependencies.scss

 @if not index(map-get($imports, $layerKey), $partKey) { }

... أضف المقتطف التالي.

src/scss/settings/_dependencies.scss

 @each $class in $partValue { $global-filter: append($global-filter, '#{$class}', 'comma') !global; }

يؤدي هذا إلى تكرار أي فئات تبعية وإضافتها إلى القائمة البيضاء $global-filter .

في هذه المرحلة ، إذا أضفت تعليمةdebug أسفل @debug dependency-setup() mixin لطباعة محتويات $global-filter في المحطة:

 @debug $global-filter;

... يجب أن ترى شيئًا كهذا عند الإنشاء:

 DEBUG: "o-box--spacing-small", "o-layout--fit", "c-box--rounded", "true", "true", "u-flex-middle", "u-align-center", "u-width-grow", "u-width-shrink"

الآن لدينا قائمة بيضاء للفصل ، نحتاج إلى فرض ذلك عبر جميع أجزاء الكائن والمكونات والمرافق المختلفة.

قم بإنشاء جزء جديد يسمى _filter.scss في src/scss/tools وأضف @import إلى ملف _core.scss الخاص بطبقة الأدوات.

في هذا الجزء الجديد ، سننشئ مزيجًا يسمى filter() . سنستخدم هذا لتطبيق المنطق الذي يعني أنه سيتم تجميع الفئات فقط إذا تم تضمينها في المتغير $global-filter .

البدء بسيطًا ، قم بإنشاء مزيج يقبل معلمة واحدة - $class التي يتحكم فيها المرشح. بعد ذلك ، إذا تم تضمين $class $ في القائمة البيضاء $global-filter ، اسمح بتجميعها.

src/scss/tools/_filter.scss

 @mixin filter($class) { @if(index($global-filter, $class)) { @content; } }

بشكل جزئي ، نلتف المزيج حول فئة اختيارية ، مثل:

 @include filter('o-myobject--modifier') { .o-myobject--modifier { color: yellow; } }

هذا يعني أن فئة .o-myobject--modifier سيتم تجميعها فقط إذا تم تضمينها في $global-filter ، والتي يمكن تعيينها إما بشكل مباشر أو غير مباشر من خلال ما تم تعيينه في $dependencies .

انتقل من خلال الريبو وقم بتطبيق filter() mixin على جميع فئات المعدلات الاختيارية عبر طبقات الكائن والمكونات. عند التعامل مع مكون الطباعة أو طبقة الأدوات المساعدة ، نظرًا لأن كل فئة مستقلة عن الفئة التالية ، فمن المنطقي جعلها جميعًا اختيارية ، حتى نتمكن بعد ذلك من تمكين الفئات حسب حاجتنا إليها.

إليك بعض الأمثلة:

src/scss/objects/_layout.scss

 @include filter('o-layout__item--fit-height') { .o-layout__item--fit-height { align-self: stretch; } }

src/scss/utilities/_alignments.scss

 // Changes alignment when breakpoint is hit // Example: .u-align-left@md @each $bp-name, $bp-value in $mq-breakpoints { @include mq(#{$bp-name}) { @include filter('u-align-left@#{$bp-name}') { .u-align-left\@#{$bp-name} { text-align: left !important; } } @include filter('u-align-center@#{$bp-name}') { .u-align-center\@#{$bp-name} { text-align: center !important; } } @include filter('u-align-right@#{$bp-name}') { .u-align-right\@#{$bp-name} { text-align: right !important; } } } }

ملاحظة: عند إضافة أسماء فئات اللاحقة المستجيبة إلى filter() mixin ، لا يتعين عليك الهروب من الرمز "@" بعلامة "\".

أثناء هذه العملية ، أثناء تطبيق filter() mixin على الأجزاء الجزئية ، ربما لاحظت (أو لا) بعض الأشياء.

فئات مجمعة

يتم تجميع بعض الفئات في قاعدة البيانات معًا وتشترك في نفس الأنماط ، على سبيل المثال:

src/scss/objects/_box.scss

 .o-box--spacing-disable-left, .o-box--spacing-horizontal { padding-left: 0; }

نظرًا لأن المرشح لا يقبل سوى فئة واحدة ، فإنه لا يأخذ في الحسبان احتمال أن تكون كتلة إعلان نمط واحدة لأكثر من فئة واحدة.

لحساب ذلك ، سنقوم بتوسيع filter() mixin ، لذلك بالإضافة إلى فئة واحدة ، يمكنها قبول قائمة Sass التي تحتوي على العديد من الفئات. مثل ذلك:

src/scss/objects/_box.scss

 @include filter('o-box--spacing-disable-left', 'o-box--spacing-horizontal') { .o-box--spacing-disable-left, .o-box--spacing-horizontal { padding-left: 0; } }

لذلك نحتاج إلى إخبار filter() mixin أنه إذا كان أي من هذه الفئات موجودًا في $global-filter ، فيسمح لك بتجميع الفئات.

سيتضمن ذلك منطقًا إضافيًا للكتابة ، تحقق من وسيطة $class mixin ، والاستجابة بحلقة إذا تم تمرير قائمة arglist للتحقق مما إذا كان كل عنصر في المتغير $global-filter .

src/scss/tools/_filter.scss

 @mixin filter($class...) { @if(type-of($class) == 'arglist') { @each $item in $class { @if(index($global-filter, $item)) { @content; } } } @else if(index($global-filter, $class)) { @content; } }

ثم الأمر يتعلق فقط بالعودة إلى الأجزاء التالية لتطبيق filter() mixin بشكل صحيح:

  • objects/_box.scss
  • objects/_layout.scss
  • utilities/_alignments.scss

في هذه المرحلة ، ارجع إلى $imports وقم بتمكين المكون الموسع فقط. في ورقة الأنماط المترجمة ، بالإضافة إلى الأنماط من الطبقات العامة وطبقات العناصر ، يجب أن ترى ما يلي فقط:

  • فئات الكتلة والعناصر التي تنتمي إلى المكون الموسع ، ولكن ليس معدله.
  • فئات الكتلة والعناصر التي تنتمي إلى تبعيات المتوسع.
  • أي فئات معدلة تنتمي إلى تبعيات الموسع والتي تم الإعلان عنها صراحةً في متغير $dependencies .

نظريًا ، إذا قررت أنك تريد تضمين المزيد من الفئات في ورقة الأنماط المترجمة ، مثل معدِّل مكونات الموسع ، فإن الأمر يتعلق فقط بإضافته إلى المتغير $global-filter في نقطة الإعلان ، أو إلحاقه في نقطة أخرى في قاعدة الشفرة (طالما أنها قبل النقطة التي تم فيها الإعلان عن المُعدِّل نفسه).

تمكين كل شيء

لذلك لدينا الآن نظام كامل للغاية ، والذي يتيح لك استيراد العناصر والمكونات والمرافق وصولاً إلى الفئات الفردية داخل هذه الأجزاء.

أثناء التطوير ، لأي سبب كان ، قد ترغب فقط في تمكين كل شيء دفعة واحدة. للسماح بذلك ، سننشئ متغيرًا جديدًا يسمى $enable-all-classes ، ثم نضيف بعض المنطق الإضافي ، لذلك إذا تم ضبط هذا على true ، فسيتم تجميع كل شيء بغض النظر عن حالة $imports import و $global-filter متغيرات $global-filter .

أولاً ، قم بتعريف المتغير في ملف البيان الرئيسي لدينا:

src/scss/main.scss

 $enable-all-classes: false; @import 'settings/core'; @import 'tools/core'; @import 'generic/core'; @import 'elements/core'; @import 'objects/core'; @import 'components/core'; @import 'utilities/core';

بعد ذلك ، نحتاج فقط إلى إجراء بعض التعديلات الطفيفة على filter() و mixins render() لإضافة بعض منطق التجاوز عندما يتم تعيين متغير $enable-all-classes على القيمة true.

أولاً ، يتم خلط filter() . قبل أي عمليات تحقق حالية ، @if عبارةif لمعرفة ما إذا تم تعيين $enable-all-classes class على true ، وإذا كان الأمر كذلك ، @content ، دون طرح أي أسئلة.

src/scss/tools/_filter.scss

 @mixin filter($class...) { @if($enable-all-classes) { @content; } @else if(type-of($class) == 'arglist') { @each $item in $class { @if(index($global-filter, $item)) { @content; } } } @else if(index($global-filter, $class)) { @content; } }

بعد ذلك في العرض render() mixin ، نحتاج فقط إلى إجراء فحص لمعرفة ما إذا كان متغير $enable-all-classes صحيحًا ، وإذا كان الأمر كذلك ، فتخط أي عمليات تحقق أخرى.

src/scss/tools/_render.scss

 $layer: null !default; @mixin render($name) { @if($enable-all-classes or index(map-get($imports, $layer), $name)) { @content; } }

الآن ، إذا كنت تريد تعيين متغير $enable-all-classes على القيمة true وإعادة البناء ، فسيتم تجميع كل فئة اختيارية ، مما يوفر لك القليل من الوقت في هذه العملية.

مقارنات

لمعرفة نوع المكاسب التي توفرها لنا هذه التقنية ، دعنا نجري بعض المقارنات ونرى ما هي الاختلافات في حجم الملفات.

للتأكد من أن المقارنة عادلة ، يجب أن نضيف كائنات الصندوق والحاوية في $imports الأمريكي ، ثم نضيف المربع o-box--spacing-regular إلى $global-filter ، مثل:

src/scss/settings/_imports.scss

 $imports: ( object: ( 'box', 'container' // 'layout' ), component: ( // 'button', 'expander' // 'typography' ), utility: ( // 'alignments', // 'widths' ) ); $global-filter: ( 'o-box--spacing-regular' );

يعمل هذا على التأكد من تجميع أنماط العناصر الأصلية للموسع كما لو كانت في حالة عدم إجراء تصفية.

الأصل مقابل أوراق الأنماط المفلترة

دعنا نقارن ورقة الأنماط الأصلية مع جميع الفئات المترجمة ، مقابل ورقة الأنماط المصفاة حيث تم تجميع CSS فقط المطلوب بواسطة المكون الموسع.

معيار
ورقة الأنماط الحجم (كيلو بايت) الحجم (gzip)
إبداعي 54.6 كيلوبايت 6.98 كيلوبايت
تمت تصفيته 15.34 كيلو بايت (72٪ أصغر) 4.91 كيلوبايت (29٪ أصغر)
  • الأصل: https://webdevluke.github.io/handlingunusedcss/dist/index2.html
  • تمت تصفيته: https://webdevluke.github.io/handlingunusedcss/dist/index.html

قد تعتقد أن نسبة التوفير في gzip تعني أن هذا لا يستحق الجهد المبذول ، حيث لا يوجد فرق كبير بين أوراق الأنماط الأصلية والمفلترة.

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

إذا أردنا توسيع نطاق كل ورقة أنماط بعامل 10 إلى أحجام أكثر نموذجية لحجم حزمة CSS لموقع الويب ، فإن الاختلاف في أحجام ملفات gzip يكون أكثر إثارة للإعجاب.

10x الحجم
ورقة الأنماط الحجم (كيلو بايت) الحجم (gzip)
الأصلي (10x) 892.07 كيلوبايت 75.70 كيلوبايت
مصفى (10x) 209.45 كيلوبايت (77٪ أصغر) 19.47 كيلوبايت (74٪ أصغر)

ورقة الأنماط المصفاة مقابل UNCSS

فيما يلي مقارنة بين ورقة الأنماط المصفاة وورقة الأنماط التي تم تشغيلها من خلال أداة UNCSS.

تمت تصفيته مقابل UNCSS
ورقة الأنماط الحجم (كيلو بايت) الحجم (gzip)
تمت تصفيته 15.34 كيلوبايت 4.91 كيلوبايت
UNCSS 12.89 كيلوبايت (أصغر بنسبة 16٪) 4.25 كيلو بايت (13٪ أصغر)

تفوز أداة UNCSS هنا بشكل هامشي ، حيث تقوم بتصفية CSS في الدلائل العامة والعناصر.

من الممكن أن يكون الاختلاف بين الطريقتين ضئيلًا على موقع ويب حقيقي ، مع وجود مجموعة أكبر من عناصر HTML المستخدمة.

تغليف

لذلك رأينا كيف - باستخدام Sass فقط - يمكنك الحصول على مزيد من التحكم في فئات CSS التي يتم تجميعها بناءً على بنائها. هذا يقلل من كمية CSS غير المستخدمة في ورقة الأنماط النهائية ويسرع مسار العرض الحرج.

في بداية المقال ، قمت بإدراج بعض عيوب الحلول الحالية مثل UNCSS. من العدل أن تنتقد هذا الحل الموجه إلى Sass بالطريقة نفسها ، لذا فإن جميع الحقائق مطروحة على الطاولة قبل أن تقرر الطريقة الأفضل بالنسبة لك:

الايجابيات

  • لا توجد تبعيات إضافية مطلوبة ، لذلك لا يتعين عليك الاعتماد على كود شخص آخر.
  • يتطلب وقت إنشاء أقل من البدائل المستندة إلى Node.js ، حيث لا يتعين عليك تشغيل متصفحات بدون رأس لتدقيق التعليمات البرمجية الخاصة بك. يعد هذا مفيدًا بشكل خاص مع التكامل المستمر حيث قد تقل احتمالية رؤية قائمة انتظار من البنيات.
  • النتائج في حجم ملف مماثل عند مقارنتها بالأدوات الآلية.
  • من خارج الصندوق ، لديك سيطرة كاملة على الكود الذي يتم تصفيته ، بغض النظر عن كيفية استخدام فئات CSS هذه في التعليمات البرمجية الخاصة بك. مع البدائل المستندة إلى Node.js ، غالبًا ما يتعين عليك الاحتفاظ بقائمة بيضاء منفصلة حتى لا يتم تصفية فئات CSS التي تنتمي إلى HTML المحقون ديناميكيًا.

سلبيات

  • من المؤكد أن الحل الموجه نحو Sass هو عملي أكثر ، بمعنى أنه يجب عليك الحفاظ على $imports $global-filter . بعد الإعداد الأولي ، فإن بدائل Node.js التي نظرنا إليها مؤتمتة إلى حد كبير.
  • إذا أضفت فئات CSS إلى $global-filter ثم أزلتها لاحقًا من HTML الخاص بك ، فعليك أن تتذكر تحديث المتغير ، وإلا فسوف تقوم بتجميع CSS لست بحاجة إليه. نظرًا لأن العديد من المطورين يعملون على المشروعات الكبيرة في وقت واحد ، فقد لا يكون من السهل إدارتها إلا إذا كنت تخطط لذلك بشكل صحيح.
  • لا أوصي بتثبيت هذا النظام على أي قاعدة شفرات CSS موجودة ، حيث سيتعين عليك قضاء وقت طويل في تجميع التبعيات معًا وتطبيق mixin render() على عدد كبير من الفئات. إنه نظام أسهل بكثير في التنفيذ مع البنيات الجديدة ، حيث لا يوجد لديك رمز موجود للتعامل معه.

آمل أن تكون قد وجدت هذا ممتعًا للقراءة حيث وجدت أنه من المثير للاهتمام تجميعه. إذا كان لديك أي اقتراحات أو أفكار لتحسين هذا النهج ، أو تريد الإشارة إلى بعض العيوب القاتلة التي فاتني تمامًا ، فتأكد من النشر في التعليقات أدناه.