Обработка неиспользованных CSS в Sass для повышения производительности

В современной разработке front-end разработчики должны стремиться написать CSS, который масштабируемый и обслуживаемый. В противном случае они рискуют потерять контроль над такими особенностями, как каскад и селектор, по мере роста базы кода и вносить свой вклад.

Одним из способов достижения этой цели является использование таких методологий, как объектно-ориентированная CSS (OOCSS), которая вместо того, чтобы организовывать CSS вокруг контекста страницы, поощряет разделение структуры (сетевые системы, интервалы, ширины и т.д.) от украшения (шрифты, бренд, цвета и т.д.).

Таким образом, имена классов CSS, такие как:

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

Заменяются более многоразовые альтернативы, которые применяются те же стили CSS, но не привязаны к какому-либо конкретному контексту:

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

Этот подход обычно реализуется с помощью sass рамки, такие как Bootstrap, Фонд, или все чаще, заказ рамки, которые могут быть сформированы, чтобы лучше соответствовать проекту.

Итак, теперь мы используем классы CSS, отобранные из рамок шаблонов, компонентов uI и классов утилит. Ниже приведен пример иллюстрирует общую систему сетки, построенную с помощью 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

Хотя я обожаю Шеффилде, могучие лезвия, их веб-сайт CSS в комплекте в один 568kb minified файл, который доходит до 105kb даже тогда, когда gzipped. Это кажется много.

Это веб-сайт Шеффилда Соединенных, моя местная футбольная команда (это футбол для вас много более в колониях 🇺🇸). (Большой предварительный просмотр)

Увидим ли мы, сколько из этого CSS на самом деле используется на их главной странице? Быстрый поиск Google показывает много онлайн-инструментов до работы, но я предпочитаю использовать инструмент покрытия в Chrome, который может быть запущен прямо из Chrome DevTools. Давайте дадим ему вихрь.

Самый быстрый способ получить доступ к инструменту покрытия в Developer Tools — это использовать клавиатуру для быстрого доступа Control-Shift-P или Command-Shift-P (Mac) для открытия меню команд. В нем введите coverage и выберите опцию «Показать покрытие». (Большой предварительный просмотр)

Результаты показывают, что только 30kb CSS из 568kb таблицы используется на главной странице, а остальные 538kb, связанные со стилями, необходимыми для остальной части веб-сайта. Это означает, что колоссальные 94,8% CSS не используется.

Вы можете увидеть тайминги, как это для любого актива в Chrome в разработчик аграрных инструментов через сеть — »gt; Нажмите на ваш актив — gt; Сроки вкладке. (Большой предварительный просмотр)

CSS является частью критического пути визуализации веб-страницы, который включает в себя все различные шаги, которые браузер должен выполнить, прежде чем он может начать визуализацию страницы. Это делает CSS блокирующим активом.

Так что с учетом этого, при загрузке веб-сайта Шеффилда Соединенных с помощью хорошего 3G-соединения, он принимает целых 1.15s, прежде чем CSS загружается и рендеринга страниц может начаться. Это проблема.

Google признал это, как хорошо. При запуске аудита Маяка, онлайн или через браузер, выделяется любое потенциальное время загрузки и экономия файлов, которые могут быть сделаны путем удаления неиспользованных CSS.

В Chrome (и Chromium Edge) можно правильно провести аудит Google Lighthouse, нажав на вкладку Аудит в инструментах разработчиков. (Большой предварительный просмотр)

Существующие решения

Цель состоит в том, чтобы определить, какие классы CSS не требуются, и удалить их из таблицы стилей. Доступны существующие решения, которые пытаются автоматизировать этот процесс. Обычно они могут использоваться через скрипт сборки Node.js или через бегунов задач, таких как Gulp. К ним относятся:

Они, как правило, работают аналогичным образом:

  1. На bulld, веб-сайт доступен через обезглавленный браузер (например: кукловод) или ЭМуляция DOM (Например: jsdom).
  2. На основе HTML-элементов страницы идентифицируется любая неиспользуемая CSS.
  3. Это удаляется из таблицы стилей, оставляя только то, что необходимо.

Хотя эти автоматизированные инструменты являются совершенно действительными, и я использовал многие из них в ряде коммерческих проектов успешно, я столкнулся с несколькими недостатками на этом пути, которые стоит поделиться:

  • Если имена классов содержат специальные символы, такие как «Я» или «/»,, они не могут быть распознаны без написания какого-либо пользовательского кода. Я использую BEM-IT Гарри Робертс, который включает в себя структурирование названия классов с отзывчивыми суффиксами, как: u-width-6/[email protected] , так что я ударил этот вопрос раньше.
  • Если веб-сайт использует автоматическое развертывание, он может замедлить процесс сборки, особенно если у вас есть много страниц и много CSS.
  • Знания об этих инструментах должны быть общими для всей команды, в противном случае может возникнуть путаница и разочарование, когда CSS таинственно отсутствует в таблицах стилей производства.
  • Если ваш сайт имеет много сторонних скриптов работает, иногда при открытии в безголовый браузер, они не играют красиво и может привести к ошибкам с процессом фильтрации. Поэтому обычно вы должны написать пользовательский код, чтобы исключить любые сценарии третьей стороны, когда обезглавленный браузер обнаружен, что в зависимости от вашей установки, может быть сложно.
  • Как правило, такого рода инструменты являются сложными и ввести много дополнительных зависимостей в процессе сборки. Как и в случае со всеми зависимостями от третьих сторон, это означает использование чужого кода.

Имея в виду эти моменты, я задал себе вопрос:

Используя только Sass, можно ли лучше обрабатывать Sass мы компилировать так что любые неиспользованные CSS могут быть исключены, не прибегая к просто грубо удаляя исходные классы в Sass прямо?

Спойлер оповещения: Ответ да. Вот что я придумала.

Сасс-ориентированное решение

Решение должно обеспечить быстрый и простой способ выбрать то, что Sass должны быть составлены, будучи достаточно простым, что он не добавляет больше сложности в процессе разработки или предотвратить разработчиков от использования преимуществ вещи, как программно классы CSS.

Для начала, есть репо со сценарием сборки и несколько стилей образца, которые вы можете клонировать отсюда.

Совет: Если вы застряли, вы всегда можете перекрестную ссылку с завершенной версией на основной ветке.

cdв репо, запустить, npm install а затем npm run build собрать любой Sass в CSS по мере необходимости. Это должно создать файл 55kb css в каталоге dist.

Если вы откроете /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 будет, в том, что если некоторые частичные комментируются (как выше), то этот код не должен быть составлен на сборку.

Но поскольку мы хотим импортировать зависимости автоматически, мы также должны быть в состоянии игнорировать эту карту при правильных обстоятельствах.

Render Mixin

Давайте начнем добавлять некоторые Sass логики. Создать _render.scss в , а затем src/scss/tools добавить его в @import tools/_core.scss .

В файле создайте пустой миксин под названием render() .

src/scss/tools/_render.scss
@mixin render() {

}

В миксине, мы должны написать Sass, который делает следующее:

  • рендер()
    $imports«Эй, хорошая погода не так ли? Скажите, есть ли у вас контейнерный объект на карте?
  • $imports
    false
  • рендер()
    «Это позор, похоже, его не будет составлен тогда. Как насчет компонента кнопки?
  • $imports
    true
  • рендер()
    «Хорошо! Это кнопка, компиляция тогда. Поздоровайся с женой за меня.

В Sass это означает следующее:

src/scss/tools/_render.scss
@mixin render($name, $layer) {
   @if(index(map-get($imports, $layer), $name)) {
      @content;
   }
}

В основном, проверьте, включена ли часть в $imports переменную, и если да, то сделать ее с помощью @content директивы Sass, которая позволяет нам передавать блок содержимого в миксин.

Мы будем использовать его так:

Пример 5
@include render('button', 'component') {
   .c-button {
      // styles et al
   }

   // any other class declarations
}

Перед использованием этого миксина, есть небольшое улучшение, которое мы можем сделать к нему. Название слоя (объект, компонент, утилита и т.д.) является то, что мы можем безопасно предсказать, так что у нас есть возможность упорядочить вещи немного.

Перед объявлением миксина рендера создайте вызванную переменную $layer и удалите идентичную переменную из параметров миксинов. Как так:

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() миксин, все, что нам нужно сделать, это объявить частичное имя.

Оберните render() миксина вокруг каждого объекта, компонента и декларации класса утилиты, в рамках ниже. Это даст вам один визы mixin использования на частичное.

Например:

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 вы не можете гнездить миксин декларации в миксина вызовов. Вместо этого, просто оберните 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/[email protected]

@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 переменной.

Ниже, $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 dependency-setup() {
   ...
}
@include dependency-setup();

Так что сейчас мы имеем расширенную систему частичного импорта, где, если компонент импортируется, разработчику не нужно вручную импортировать каждый из его различных частик зависимостей.

Налажить $imports переменную так, чтобы импортировался только компонент расширителя, а затем npm run build запускался. Вы должны увидеть в компилированных csS классы расширителя вместе со всеми его зависимостями.

Тем не менее, это на самом деле не приносит ничего нового в таблице с точки зрения фильтрации неиспользованных CSS, так как такое же количество Sass по-прежнему импортируется, программные или нет. Давайте улучшим это.

Улучшение импорта зависимостей

Компонент может потребовать только одного класса от зависимости, так что затем перейти и импортировать все классы зависимости просто приводит к тому же ненужным раздуваться мы пытаемся избежать.

Мы можем усовершенствовать систему, чтобы обеспечить более детальную фильтрацию на основе класса по классу, чтобы убедиться, что компоненты компилируются только с требуемыми классами зависимостей.

С большинством шаблонов дизайна, украшенных или нет, существует минимальное количество классов, которые должны присутствовать в таблице стилей для правильного отображения шаблона.

Для имен классов, использующих установленную конвенцию именования, такую как BEM,обычно как минимум требуется классы имен «Блок» и «Элемент», при этом «модификаторы» обычно неявляются.

Примечание: Утилита классов, как правило, не следуют 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-расширяемый 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'
           )
       )
   )
);

Там, где значение настроено на достоверное значение, мы переведем это в «Только компилировать классы уровней блока и элементов, без модификаторов!».

Следующий шаг включает в себя создание переменной белого списка для хранения этих классов и любых других (независимых) классов, которые мы хотим импортировать вручную. В /src/scss/settings/imports.scss , после , создать $imports новый список Sass называется $global-filter .

src/scss/settings/_imports.scss
$global-filter: ();

Основная предпосылка позади заключается в $global-filter том, что любые классы, хранящиеся здесь, будут компилироваться по сборке до тех пор, пока частично они принадлежат импортируется через $imports .

Эти названия классов могут быть добавлены программно, если они являются компонентной зависимостью, или могут быть добавлены вручную, когда объявлена переменная, как в приведенном ниже примере:

Пример глобального фильтра
$global-filter: (
   '[email protected]',
   'u-align-center',
   'u-width-6/[email protected]'
);

Далее, мы должны добавить немного больше логики в @dependency-setup миксин, так что любые классы, упомянутые в $dependencies автоматически добавляются в наш $global-filter белый список.

Ниже этого блока:

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 заявление ниже dependency-setup() миксина, распечатать $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() миксин ко всем дополнительным классам модификаторов в разных слоях объектов и компонентов. При обработке компонента типографии или слоя утилит, так как каждый класс независит от следующего, имеет смысл сделать их все необязательными, поэтому мы можем просто включить классы, как мы в них нуждается.

Вот несколько примеров:

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: [email protected]
@each $bp-name, $bp-value in $mq-breakpoints {
    @include mq(#{$bp-name}) {
        @include filter('[email protected]#{$bp-name}') {
            [email protected]#{$bp-name} {
                text-align: left !important;
            }
        }

        @include filter('[email protected]#{$bp-name}') {
            [email protected]#{$bp-name} {
                text-align: center !important;
            }
        }

        @include filter('[email protected]#{$bp-name}') {
            [email protected]#{$bp-name} {
                text-align: right !important;
            }
        }
    }
}

Примечание: При добавлении отзывчивых суффикс-классов в filter() миксин, вам не нужно ускользать от символа » с ‘к’.

Во время этого процесса, при применении filter() миксина к частичным, вы можете (или не можете) заметили несколько вещей.

Группированные классы

Некоторые классы в кодной базе сгруппированы и имеют одни и те же стили, например:

src/scss/objects/_box.scss
.o-box--spacing-disable-left,
.o-box--spacing-horizontal {
    padding-left: 0;
}

Поскольку фильтр принимает только один класс, он не учитывает возможность того, что один блок объявления стилей может быть для более чем одного класса.

Чтобы объяснить это, мы расширим filter() миксин так в дополнение к одному классу, он в состоянии принять Sass arglist, содержащий много классов. Как так:

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 аргумента миксина, отвечая с циклом, если аргарлист передается, чтобы проверить, находится ли каждый элемент в $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() миксин:

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

На этом этапе вернитесь к $imports компоненту расширителя и включите только его. В составленной таблице стилей, помимо стилей из общих слоев и элементов, следует видеть только следующее:

  • Классы блоков и элементов, принадлежащие компоненту расширителя, но не его модификатор.
  • Классы блоков и элементов, относящиеся к зависимостям расширителя.
  • Любые классы модификаторов, относящиеся к зависимостям расширителя, которые прямо заявлены в $dependencies переменной.

Теоретически, если вы решили включить больше классов в составленную таблицу стилей, таких как модификатор компонентов расширения, это просто вопрос добавления его в $global-filter переменную в точке объявления, или добавление его в какой-то другой момент в кодовой базе ( До тех пор, пока это до точки, где модификатор сам объявлен).

Включение всего

Таким образом, теперь у нас есть довольно полная система, которая позволяет импортировать объекты, компоненты и утилиты до отдельных классов в рамках этих частичных.

Во время разработки, по какой причине, вы можете просто хотите, чтобы все за один раз. Для этого мы создадим новую $enable-all-classes переменную, называемую, а затем добавим в некоторую дополнительную логику, так что если это будет верно, все компилируется независимо от состояния $imports $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() и render() mixins добавить некоторые переопределить логику, когда $enable-all-classes переменная установлена на истину.

Во-первых, filter() миксин. Перед любыми существующими проверками мы добавим @if заявление, чтобы увидеть, если $enable-all-classes установлен на верно, и если да, сделать @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() миксине, мы просто должны сделать проверку, чтобы увидеть, если $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 переменную для истины и перестроить, каждый дополнительный класс будет составлен, экономя вам довольно много времени в этом процессе.

Сравнения

Чтобы увидеть, какой тип прибыли этот метод дает нам, давайте запустим некоторые сравнения и посмотрим, что файлообразующие различия.

Чтобы убедиться, что сравнение является справедливым, мы должны добавить поле и контейнерных объектов в $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, требуемый компонентом расширителя.

Стандартный
Размер таблицы (kb) Размер (gzip)
Исходный текст 54,6 кб 6.98kb
Фильтруется 15.34kb (72% меньше) 4.91kb (29% меньше)

Вы можете думать, что gzip процент экономии означает, что это не стоит усилий, так как нет большой разницы между оригинальной и фильтрованных таблиц стилей.

Стоит подчеркнуть, что сжатие gzip лучше работает с большими и более повторяющимися файлами. Поскольку отфильтрованная таблица стилей является единственным доказательством концепции и содержит только CSS для компонента расширителя, сжатие не так много, как в реальном проекте.

Если бы мы были масштабировать каждый стиль в 10 раз до размеров, более типичных для csS размера расслоения веб-сайта, разница в размерах файлов gzip являются гораздо более впечатляющими.

10x Размер
Размер таблицы (kb) Размер (gzip)
Оригинал (10x) 892.07kb 75.70kb
Отфильтрованный (10x) 209.45kb (77% меньше) 19.47kb (74% меньше)

Фильтрованная таблица стилей против UNCSS

Вот сравнение между отфильтрованным стилем и таблицей стилей, которая была запущена через инструмент UNCSS.

Фильтруется против UNCSS
Размер таблицы (kb) Размер (gzip)
Фильтруется 15.34kb 4.91kb
УНКСС 12.89kb (16% меньше) 4.25kb (13% меньше)

Инструмент UNCSS выигрывает здесь незначительно, так как он отфильтровывает CSS в общих каталогах и элементах.

Вполне возможно, что на реальном веб-сайте, с большим разнообразием HTML элементов в использовании, разница между 2 методами будет незначительным.

Упаковка

Таким образом, мы видели, как — используя только Sass — вы можете получить больше контроля над тем, что csS классов в настоящее время компилируются на сборку. Это уменьшает количество неиспользуемой CSS в окончательной таблице стилей и ускоряет критический путь рендеринга.

В начале статьи я перечислил некоторые недостатки существующих решений, таких, как СУОН. Это только справедливо критиковать это Sass-ориентированное решение таким же образом, так что все факты на столе, прежде чем решить, какой подход лучше для вас:

Плюсы

  • Дополнительные зависимости не требуются, поэтому вам не придется полагаться на чужой код.
  • Меньше времени на сборку требуется, чем альтернативы на основе Node.js, так как вам не придется запускать безголовые браузеры для аудита кода. Это особенно полезно при непрерывной интеграции, так как у вас может быть меньше шансов увидеть очередь сборок.
  • Результаты в аналогичном размере файла по сравнению с автоматизированными инструментами.
  • Из коробки, у вас есть полный контроль над тем, какой код фильтруется, независимо от того, как эти классы CSS используются в вашем коде. С альтернативами на основе Node.js часто приходится поддерживать отдельный белый список, чтобы классы CSS, принадлежащие к динамически впрыскиваемому HTML, не отфильтровывались.

Минусы

  • Sass-ориентированное решение, безусловно, более практический, в том смысле, что вы должны держать на вершине $imports и $global-filter переменных. Помимо первоначальной установки, альтернативы Node.js, которые мы рассмотрели, в значительной степени автоматизированы.
  • Если вы добавите классы CSS, $global-filter а затем удалите их из HTML, необходимо не запомнить обновление переменной, в противном случае вы будете компиляции CSS вам не нужно. С большими проектами, над которыми работают несколько разработчиков в любой момент времени, это может быть нелегко управлять, если вы должным образом не планируете это.
  • Я бы не рекомендовал болтами эту систему на любой существующей базы кода CSS, как вам придется потратить довольно много времени piecing вместе зависимостей и применения render() mixin к большому кругу классов. Это система, намного проще внедряемых с помощью новых сборок, где у вас нет существующего кода, с которым можно было бы бороться.

Многообещающе вы находили это как интересно для того чтобы прочитать по мере того как я считал его интересным положить совместно. Если у вас есть какие-либо предложения, идеи, чтобы улучшить этот подход, или хотите указать на некоторые фатальные недостатки, которые я пропустил полностью, не забудьте разместить в комментариях ниже.

Источник: smashingmagazine.com

Великолепный Журнал

Великолепный, сокрушительный, разящий (см. перевод smashing) независимый журнал о веб-разработке. Основан в 2006 году в Германии. Имеет няшный дизайн и кучу крутых авторов, которых читают 2 млн человек в месяц.

Добавить комментарий

%d такие блоггеры, как: