Статьи

Значения кэширования от Sass Mixins

Давайте вернемся немного назад, чтобы я мог объяснить контекст этого поста. В последние недели я активно сравнивал результаты рендеринга между миксинами и заполнителями. В результате, чем больше я об этом думаю, тем больше мне кажется, что использовать миксины и заполнители, как они описаны в документации Sass, просто недостаточно.

Давайте сосредоточимся на главной цели: вывод CSS. Во всем этом эксперименте я не рассматриваю сложность или качество кода. Я только рассматриваю окончательный вывод CSS. Смысл в том, чтобы вывод CSS был максимально легким и скудным.

Если вы читали мои предыдущие статьи SitePoint, посвященные местозаполнителям, а @extend директивам @extend и @extend , вы должны знать, что у обеих функций есть несколько недостатков. Миксины не объединяют селекторы, в то время @extend директивы @extend ограничены их областью действия, поэтому они в основном непригодны в @extend контексте с блоками @media (что я рассмотрел и частично решил в своей статье о перекрестной области видимости @extend ).

Сегодня я хотел бы решить еще одну проблему: смешанная группировка.

Что такое Mixin Grouping?

Миксины действительно удобны, когда вы хотите создать ярлыки в вашем коде переменной. Например, вы можете иметь миксин, принимающий два аргумента для вывода width и height . Или миксин для офсетного позиционирования . Это мило.

Проблема с миксинами заключается в том, что каждый раз, когда вы вызываете их, они сбрасывают наборы правил CSS . Если вы вызываете их дважды с одинаковыми аргументами от двух разных селекторов, они не будут объединены в один набор правил. Вместо этого они сбросят один и тот же набор правил дважды — какой тип отстой.

В этой статье мы рассмотрим очень простой пример использования: size миксин для задания width и height . Вот как это выглядит:

 // Helper mixin to define dimensions // --- // @param [number] $width: width // @param [number] $height ($width): height // --- @mixin size($width, $height: $width) { width: $width; height: $height; } 

Используя это довольно легко.

 .element { @include size(25%, 5em); } 

Это приведет к следующему CSS:

 .element { width: 25%; height: 5em; } 

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

 .other-element { @include size(25%, 5em); } 

Пока проблем нет. Но каков окончательный результат?

 .element { width: 25%; height: 5em; } /* [...] */ .other-element { width: 25%; height: 5em; } 

Теперь вы можете увидеть проблему. Поскольку эти два набора правил абсолютно одинаковы, их следует объединить. Имея такие же наборы правил, как это довольно неубедительно, не так ли?

Инженерные вещи

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

Прежде всего, нам нужен список для хранения наших значений. На самом деле нам нужно больше, чем список — нам нужна карта. Это сугубо внутреннее; разработчик не должен знать об этом, и при этом он не должен ничего делать с этим вручную.

 $cache: ( 'width' : (), 'height' : () ); 

Теперь вернемся к нашему миксину, в котором нет изменений подписи.

 @mixin size($width, $height: $width) { // Width $stored-widths: map-get($cache, 'width'); @if not index($stored-widths, $width) { $cache-size: map-merge($cache, ('width': append($stored-widths, $width))); @at-root %width-#{length($stored-widths) + 1} { width: $width; } } // Height $stored-heights: map-get($cache-size, 'height'); @if not index($stored-heights, $height) { $cache-size: map-merge($cache, ('height': append($stored-heights, $height))); @at-root %height-#{length($stored-heights) + 1} { height: $height; } } // Actually applying values @extend %width-#{index(map-get($cache, 'width'), $width)}; @extend %height-#{index(map-get($cache, 'height'), $height)}; } 

Я должен признать, что этот код довольно резок.

Вот что происходит: во-первых, мы получаем сохраненную ширину в переменной $stored-widths (строка 3). Если в списке найдено значение $width , мы ничего не делаем. Если он не был найден (строка 4), мы не только добавляем $width в список (строка 5), но также генерируем заполнитель на корневом уровне (строка 6), названный в честь недавно добавленного индекса.

Затем мы делаем то же самое с $height (строки с 12 по 15). Когда мы закончим, у нас появятся новые сгенерированные заполнители, поэтому мы расширяем их (строки 21 и 22). Конец истории.

Если мы вернемся к нашему первоначальному примеру, вот что произойдет в первый раз, когда мы вызовем mixin (чтобы создать первый набор правил):

  1. .element вызывает size() с шириной 25% и высотой 5em .
  2. 25% не найдено в сохраненных значениях ширины, поэтому он сохраняется, а заполнитель %width-1 создается на корневом уровне.
  3. 5em не найден в сохраненных высотах, поэтому он сохраняется, и заполнитель %height-1 генерируется на корневом уровне.
  4. Оба заполнителя расширяются, что приводит к следующему CSS:
 .element { width: 25% } .element { height: 5em } 

Теперь, когда size() вызывается во второй раз с теми же аргументами (который должен создать второй набор правил, но мы увидим, почему это не так):

  1. .other-element вызывает size() с шириной 25% и высотой 5em .
  2. 25% находится в индексе 1 в нашей сохраненной ширине, поэтому %width-1 расширяется, добавляя .other-element к существующему .element { width: 25% } .
  3. 5em находится в индексе 1 в наших сохраненных высотах, поэтому %height-1 расширяется, добавляя .other-element к существующему .element { height: 5em } .
 .element, .other-element { width: 25% } .element, .other-element { height: 5em } 

Правда, здесь только два звонка, что не имеет большого значения. Но представьте большой проект с десятками и десятками вызовов одного миксина, с общими значениями, такими как 100% или 1px . Больше не выбрасываются лишние строки, и все селекторы по возможности перегруппируются.

Автоматизация вещей с помощью Cache Mixin

Хотя то, что мы сделали, круто, оно включает в себя повторение кода и может быстро стать раздражающим для установки на все миксины, особенно те, у которых довольно много CSS-вывода. Что если мы сделаем небольшой миксин cache() решит эту проблему для нас?

Идея состоит в том, чтобы передать набор свойств / значений нашему миксину, чтобы он мог кэшировать эти значения в глобальной карте кэша. Таким образом, нашему миксину для работы нужен только один параметр: карта объявлений CSS.

 // Global cache map $cache: (); // Cache mixin @mixin cache($declarations) { /* Sass magic */ } 

Теперь все, что нам нужно сделать, это перебрать все объявления на карте. Для каждого объявления:

  • Мы проверяем, существует ли значение в списке сохраненных значений для данного свойства.
  • Если этого не произойдет, мы добавим его в список и создадим заполнитель на корневом уровне.
  • Мы расширяем уже существующий или только что созданный заполнитель.
 @mixin cache($declarations) { // Looping through all properties/values from map @each $property, $value in $declarations { // Get the stored values for the current property $stored-values: map-get($cache, $property); // If the value doesn't exist in stored values @if not index($stored-values, $value) { // Add it $cache: map-merge($cache, ($property: append($stored-values or (), $value))) !global; // And create a placeholder at root level @at-root %#{$property}-#{length(map-get($cache, $property))} { #{$property}: $value; } } // Extend the placeholder @extend %#{$property}-#{index(map-get($cache, $property), $value)}; } } 

Вот и ты. Очень чистый и аккуратный кеш миксин. Теперь, если мы вернемся к нашему первоначальному примеру:

 @mixin size($width, $height: $width) { @include cache(( width: $width, height: $height )); } 

Довольно просто, не правда ли? В конце концов, он выглядит очень чистым (и говорит сам за себя, когда зачитывается вслух), он кэширует контент, чтобы получить максимальную выгоду от заполнителей Sass, и мы избегаем повторения контента. Кто мог просить больше?

Это полезно?

Трудно сказать. В среднем и крупномасштабном проекте я вижу выгоду от того, что я делаю что-то подобное со всеми миксинами. Мы использовали простой пример, но вы можете применить эту технику ко всем миксинам; вам просто нужно добавить ключи к карте кэша (например, padding , margin , color …).

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

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

Не стесняйтесь взглянуть на полностью прокомментированный код на SassMeister:

Поиграйте с этим в SassMeister.