Как фронт-разработчик, я пишу много CSS, и использование чистого CSS в наши дни — не самый эффективный способ. Препроцессоры CSS — это то, что мне очень помогло. Мое первое впечатление было то, что я наконец нашел идеальный инструмент. У них есть множество функций, отличная поддержка, бесплатные ресурсы и так далее. Это все верно и до сих пор применяется, но после нескольких проектов я понял, что мир не так совершенен. Существует два основных CSS-препроцессора — LESS и SASS . Есть и другие, но у меня есть опыт работы только с этими двумя. В первой части этой статьи я поделюсь с вами тем, что мне не нравится в препроцессорах, а затем во второй части я покажу вам, как мне удалось решить большинство проблем, с которыми я столкнулся.
Проблемы
Настроить
Независимо от того, какой препроцессор CSS задействован, всегда требуется настройка, например, вы не можете просто начать печатать файлы .sass или .sass и ожидать получения файла .css . Меньше требуется NodeJS и SASS Ruby. В данный момент я работаю в основном над приложениями HTML / CSS / JavaScript / NodeJS. Итак, LESS кажется лучшим вариантом, потому что мне не нужно устанавливать дополнительное программное обеспечение. Знаете, добавление еще одной вещи в вашу экосистему означает больше времени на обслуживание. Кроме того, вам нужен не только необходимый инструмент, но и все ваши коллеги теперь должны интегрировать новый инструмент.
Во-первых, я выбрал LESS, потому что у меня уже был установлен NodeJS. Он хорошо играл с Грантом, и я успешно завершил два проекта с этой настройкой. После этого я начал читать о SASS. Я интересовался OOCSS , Атомным дизайном и хотел построить прочную архитектуру CSS. Очень скоро я перешел на SASS, потому что это дало мне лучшие возможности. Конечно, мне (и моим коллегам тоже) пришлось установить Ruby.
Выход
Многие разработчики не проверяют полученный CSS. Я имею в виду, что у вас могут быть действительно хорошо выглядящие файлы SASS, но в итоге мы используем скомпилированный файл .css . Если он не оптимизирован и его размер файла велик, значит, у вас проблема. Есть несколько вещей, которые мне не нравятся в обоих препроцессорах.
Допустим, у нас есть следующий код:
|
1
2
3
4
5
6
7
|
// LESS or SASS
p {
font-size: 20px;
}
p {
padding: 20px;
}
|
Не думаете ли вы, что это должно быть скомпилировано в:
|
1
2
3
4
|
p {
font-size: 20px;
padding: 20px;
}
|
Ни LESS, ни SASS так не работают. Они просто оставляют ваши стили по мере их ввода. Это может привести к дублированию кода. Что делать, если у меня сложная архитектура с несколькими слоями, и каждый слой добавляет что-то к абзацу. Будет несколько определений, которые точно не нужны. Вы можете даже иметь следующую ситуацию:
|
1
2
3
4
5
6
|
p {
font-size: 20px;
}
p {
font-size: 30px;
}
|
Правильный код в конце должен быть только следующим:
|
1
2
3
|
p {
font-size: 30px;
}
|
Теперь я знаю, что браузер позаботится и найдет правильный размер шрифта. Но не лучше ли сохранить эти операции. Я не уверен, что это повлияет на производительность вашей страницы, но наверняка повлияет на читабельность.
Объединение селекторов, которые используют одни и те же стили, — это хорошо. Насколько я знаю, LESS не делает этого. Допустим, у нас есть миксин, и мы хотим применить его к двум классам.
|
01
02
03
04
05
06
07
08
09
10
|
.reset() {
padding: 0;
margin: 0;
}
.header {
.reset();
}
.footer {
.reset();
}
|
И результат:
|
1
2
3
4
5
6
7
8
|
.header {
padding: 0;
margin: 0;
}
.footer {
padding: 0;
margin: 0;
}
|
Таким образом, эти два класса имеют одинаковые стили и могут быть объединены в одно определение.
|
1
2
3
4
|
.header, .footer {
padding: 0;
margin: 0;
}
|
Мне было интересно, если это фактическая оптимизация производительности, и я не нашел точного ответа, но это похоже на хорошую вещь. У SASS есть так называемые place holders . Он используется именно для таких ситуаций. Например:
|
01
02
03
04
05
06
07
08
09
10
|
%reset {
padding: 0;
margin: 0;
}
.header {
@extend %reset;
}
.footer {
@extend %reset;
}
|
Код выше производит именно то, что я хотел. Проблема в том, что, если я использую слишком много заполнителей, у меня может появиться много определений стилей, потому что препроцессор думает, что мне есть что комбинировать.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
%reset {
padding: 0;
margin: 0;
}
%bordered {
border: solid 1px #000;
}
%box {
display: block;
padding: 10px;
}
.header {
@extend %reset;
@extend %bordered;
@extend %box;
}
|
Есть три местозаполнителя. Класс .header расширяет их все, и окончательно скомпилированный CSS выглядит следующим образом:
|
01
02
03
04
05
06
07
08
09
10
11
|
.header {
padding: 0;
margin: 0;
}
.header {
border: solid 1px #000;
}
.header {
display: block;
padding: 10px;
}
|
Это выглядит неправильно, не так ли? Должно быть только одно определение стиля и только одно свойство padding .
|
1
2
3
4
5
6
|
.header {
padding: 10px;
margin: 0;
border: solid 1px #000;
display: block;
}
|
Конечно, есть инструменты, которые могут решить эту проблему, имея скомпилированный CSS. Но, как я уже сказал, я предпочитаю использовать как можно меньше библиотек.
Ограничение синтаксиса
Пока я работал над OrganicCSS , я столкнулся с множеством ограничений. В общем, я хотел написать CSS, как я пишу JavaScript. Я имею в виду, что у меня были некоторые идеи о сложной архитектуре, но я не смог их реализовать, потому что язык, с которым я работал, был довольно примитивным. Например, скажем, мне нужен миксин, который стилизует мои элементы. Я хочу передать тему и тип границы. Вот как это должно выглядеть в LESS:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
.theme-dark() {
color: #FFF;
background: #000;
}
.theme-light() {
color: #000;
background: #FFF;
}
.component(@theme, @border) {
border: «@{border} 1px #F00»;
.theme-@{theme}();
}
.header {
.component(«dark», «dotted»);
}
|
Конечно, у меня будет много тем, и они тоже должны быть миксинами. Таким образом, интерполяция переменных работает для свойства border, но не для имен mixin. Это просто, но в настоящее время это невозможно, или, по крайней мере, я не знаю, исправлено ли это. Если вы попытаетесь скомпилировать приведенный выше код, вы получите Syntax Error on line 11 .
SASS — это еще один шаг вперед. Интерполяция работает с заполнителями, что делает вещи немного лучше. Та же идея выглядит так:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
@mixin theme-dark() {
color: #FFF;
background: #000;
}
@mixin theme-light() {
color: #000;
background: #FFF;
}
%border-dotted {
border: dotted 1px #000;
}
@mixin component($theme, $border) {
@extend %border-#{$border};
@include theme-#{$theme};
}
.header {
@include component(«dark», «dotted»);
}
|
Итак, стилизация границ работает, но тема выдает:
|
1
|
Sass Error: Invalid CSS after » @include theme-«: expected «}», was «#{$theme};»
|
Это потому, что интерполяция в именах mixins и extends не допускается. Об этом идет долгая дискуссия , и, вероятно, она скоро будет исправлена.
И LESS, и SASS хороши, если вы хотите улучшить скорость написания, но они далеко не идеальны для создания модульного и гибкого CSS. В основном им не хватает таких вещей, как инкапсуляция, полиморфизм и абстракция. Или, по крайней мере, они не в той форме, которая мне была нужна.
Новый подход
Я боролся несколько дней с этим ограничением. Я потратил много времени на чтение документации. В конце концов я просто сдался и начал искать другие варианты. То, что у меня было недостаточно гибким, и я начал думать о написании своего собственного препроцессора. Конечно, это действительно сложная задача, и есть над чем подумать, например:
- ввод — обычно препроцессоры берут код, который выглядит как CSS. Я предполагаю, что идея состоит в том, чтобы дополнить язык, например, добавить недостающие, но необходимые функции. Также легко портировать чистый CSS, и разработчики могут сразу начать использовать его, потому что на практике это почти тот же язык. Однако, с моей точки зрения, такой подход приносит мало трудностей, потому что мне пришлось все анализировать и анализировать.
- синтаксис — даже если я напишу часть синтаксического анализа, мне пришлось изобрести свой собственный синтаксис, который является своего рода сложной работой.
- конкуренты — уже есть два действительно популярных препроцессора. У них хорошая поддержка и активное сообщество. Вы знаете, большинство самых крутых вещей в нашей сфере очень полезны благодаря вкладчикам. Если я пишу свой собственный CSS-препроцессор и не получаю достаточного количества отзывов и поддержки от людей, я могу быть единственным, кто фактически использует его.
Итак, я немного подумал и нашел решение. Нет необходимости изобретать новый язык с новым синтаксисом. Это уже там. Я мог бы использовать чистый JavaScript. Уже существует большое сообщество, и многие люди могут сразу начать использовать мою библиотеку. Вместо чтения внешних файлов, их анализа и компиляции я решил использовать экосистему NodeJS. И, конечно же, самое главное — я полностью удалил часть CSS. Написание всего на JavaScript сделало мое веб-приложение намного чище, потому что мне не приходилось иметь дело с форматом ввода и всеми теми процессами, которые производят настоящий CSS.
(Название библиотеки — AbsurdJS . Вы можете посчитать это название смешным, и это действительно так. Когда я делюсь своей идеей с несколькими друзьями, они все сказали, что написание вашего CSS на JavaScript — абсурд . Так что это было идеальное название.)
AbsurdJS
Установка
Для использования AbsurdJS вам необходимо установить NodeJS. Если у вас все еще нет этого маленького драгоценного камня в вашей системе, перейдите на nodejs.org и нажмите кнопку Install . Когда все закончится, вы можете открыть новую консоль и набрать:
|
1
|
npm install -g absurd
|
Это настроит AbsurdJS глобально. Это означает, что где бы вы ни находились, вы можете выполнить absurd команду.
Написание вашего CSS
В мире JavaScript наиболее близким к CSS является формат JSON. Итак, вот что я решил использовать. Давайте рассмотрим простой пример:
|
1
2
3
4
5
6
7
8
|
.content {
padding: 0;
margin: 0;
font-size: 20px;
}
.content p {
line-height: 30px;
}
|
Это чистый CSS. Вот как это выглядит в LESS и SASS:
|
1
2
3
4
5
6
7
8
|
.content {
padding: 0;
margin: 0;
font-size: 20px;
p {
line-height: 30px;
}
}
|
В контексте AbsurdJS фрагмент должен быть написан так:
|
01
02
03
04
05
06
07
08
09
10
11
12
|
module.exports = function(api) {
api.add({
‘.content’: {
padding: 0,
margin: 0,
‘font-size’: ’20px’,
p: {
‘line-height’: ’30px’
}
}
});
}
|
Вы можете сохранить это в файл с именем styles.js и запустить:
|
1
|
absurd -s .\styles.js
|
Это скомпилирует JavasSript для того же CSS. Идея проста. Вы пишете пакет NodeJS, который экспортирует функцию. Функция вызывается только с одним параметром — API AbsurdJS. У него есть несколько методов, и я рассмотрю их позже, но наиболее распространенным является add . Он принимает действительный JSON. Каждый объект определяет селектор. Каждое свойство этого объекта может быть свойством CSS и его значением или другим объектом.
Импорт
Размещение разных частей вашего CSS в разных файлах действительно важно. Такой подход улучшает читаемость ваших стилей. AbsurdJS имеет метод import , который действует как директива @import в препроцессорах CSS.
|
01
02
03
04
05
06
07
08
09
10
|
var cwd = __dirname;
module.exports = function(api) {
api.import(cwd + ‘/config/main.js’);
api.import(cwd + ‘/config/theme-a.js’);
api.import([
cwd + ‘/layout/grid.js’,
cwd + ‘/forms/login-form.js’,
cwd + ‘/forms/feedback-form.js’
]);
}
|
Что вам нужно сделать, это написать файл main.js который импортирует остальные стили. Вы должны знать, что есть перезапись. Я имею в виду, что если вы определяете стиль для тега body внутри /config/main.js а затем в /config/theme-a.js используете то же свойство, конечное значение будет тем, которое использовалось в последнем импортированном файле. , Например:
|
1
2
3
4
5
6
7
8
|
module.exports = function(api) {
api.add({
body: { margin: ’20px’ }
});
api.add({
body: { margin: ’30px’ }
});
}
|
Компилируется в
|
1
2
3
|
body {
margin: 30px;
}
|
Обратите внимание, что есть только один селектор. Хотя, если вы сделаете то же самое в LESS или SASS, вы получите
|
1
2
3
4
5
6
|
body {
margin: 20px;
}
body {
margin: 30px;
}
|
Переменные и миксины
Одной из наиболее ценных функций препроцессоров являются их переменные. Они дают вам возможность настроить свой CSS, например, определить настройку где-то в начале таблицы стилей и использовать ее позже. В JavaScript переменные — это нечто нормальное. Однако, поскольку у вас есть модули, размещенные в разных файлах, вам нужно что-то, что действует как мост между ними. Вы можете определить свой основной цвет бренда в одном файле, но позже использовать его в другом. Для этого AbsurdJS предлагает метод API, называемый storage . Если вы выполняете функцию с двумя параметрами, вы создаете пару: key-value . Если вы передадите только ключ, вы получите сохраненное значение.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
|
// config.js
module.exports = function(api) {
api.storage(«brandColor», «#00F»);
}
// header.js
module.exports = function(api) {
api.add({
header: {
color: api.storage(«brandColor»)
}
})
}
|
Каждый селектор может принимать не только объект, но и массив. Так что это также верно:
|
1
2
3
4
5
6
7
8
|
module.exports = function(api) {
api.add({
header: [
{ color: ‘#FF0’ },
{ ‘font-size’: ’20px’ }
]
})
}
|
Это делает возможным отправку нескольких объектов определенным селекторам. Это очень хорошо сочетается с идеей миксинов. По определению, mixin — это небольшой фрагмент кода, который можно использовать несколько раз. Это вторая особенность LESS и SASS, которая делает их привлекательными для разработчиков. В AbsurdJS миксины на самом деле являются обычными функциями JavaScript. Возможность помещать вещи в хранилище дает вам возможность обмениваться миксинами между файлами. Например:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
// A.js
module.exports = function(api) {
api.storage(«button», function(color, thickness) {
return {
color: color,
display: «inline-block»,
padding: «10px 20px»,
border: «solid » + thickness + «px » + color,
‘font-size’: «10px»
}
});
}
// B.js
module.exports = function(api) {
api.add({
‘.header-button’: [
api.storage(«button»)(«#AAA», 10),
{
color: ‘#F00’,
‘font-size’: ’13px’
}
]
});
}
|
Результат:
|
1
2
3
4
5
6
7
|
.header-button {
color: #F00;
display: inline-block;
padding: 10px 20px;
border: solid 10px #AAA;
font-size: 13px;
}
|
Обратите внимание, что определен только один селектор, а свойство font-size имеет значение второго объекта в массиве (mixin определяет некоторые базовые стили, но позже они изменяются).
Плагины
Хорошо, миксины — это круто, но я всегда хотел определить свои собственные свойства CSS. Я имею в виду использование свойств, которые обычно не существуют, но инкапсулируют действительные стили CSS. Например:
|
1
2
3
|
.header {
text: medium;
}
|
Допустим, у нас есть три типа текста: small , medium и big . Каждый из них имеет разный font-size и разную line-height . Очевидно, что я могу добиться того же с помощью миксинов, но AbsurdJS предлагает что-то лучшее — плагины. Создание плагина снова через API:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
api.plugin(«text», function(api, type) {
switch(type) {
case «small»:
return {
‘font-size’: ’12px’,
‘line-height’: ’16px’
}
break;
case «medium»:
return {
‘font-size’: ’20px’,
‘line-height’: ’22px’
}
break;
case «big»:
return {
‘font-size’: ’30px’,
‘line-height’: ’32px’
}
break;
}
});
|
Это позволяет вам применять text: medium к вашим селекторам. Вышеуказанный стиль компилируется в:
|
1
2
3
4
|
.header {
font-size: 20px;
line-height: 22px;
}
|
Медиа-запросы
Конечно, библиотека поддерживает медиа-запросы. Я также скопировал идею bubbling функции (вы можете определять точки останова непосредственно внутри элементов, а AbsurdJS позаботится обо всем остальном).
|
01
02
03
04
05
06
07
08
09
10
11
12
13
|
api.add({
‘.footer’: {
‘font-size’: ’14px’,
‘@media all and (min-width: 320px) and (max-width: 550px)’: {
‘font-size’: ’24px’
}
},
‘.content’: {
‘@media all and (min-width: 320px) and (max-width: 550px)’: {
margin: ’24px’
}
}
})
|
Результат:
|
01
02
03
04
05
06
07
08
09
10
11
|
.footer {
font-size: 14px;
}
@media all and (min-width: 320px) and (max-width: 550px) {
.footer {
font-size: 24px;
}
.content {
margin: 24px;
}
}
|
Имейте в виду, что если один и тот же медиа-запрос используется несколько раз, скомпилированный файл будет содержать только одно определение. Это на самом деле экономит много байтов. К сожалению, LESS и SASS не делают этого.
Псевдо-классы
Для этого вам просто нужно передать действительный JSON. В следующем примере показано, как использовать псевдо-классы CSS:
|
01
02
03
04
05
06
07
08
09
10
|
module.exports = function(api) {
api.add({
a: {
‘text-decoration’: ‘none’,
‘:hover’: {
‘text-decoration’: ‘underline’
}
}
});
}
|
И это скомпилировано в:
|
1
2
3
4
5
6
|
a {
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
|
интеграция
AbsurdJS работает как инструмент командной строки, но его можно использовать и в приложении NodeJS. Например:
|
1
2
3
4
5
6
7
8
9
|
var Absurd = require(«absurd»),
absurd = Absurd(),
api = absurd.api,
output = «./css/styles.css»;
api.add({ … }).import(«…»);
absurd.compileFile(output, function(err, css) {
// do something with the css
});
|
Или, если у вас есть файл, который действует как точка входа:
|
1
2
3
4
|
var Absurd = require(«absurd»);
Absurd(«./css/styles.js»).compileFile(«./css/styles.css», function(err, css) {
// do something with the css
});
|
Библиотека также поддерживает интеграцию с Grunt. Вы можете прочитать больше об этом на следующей странице Github .
Параметры интерфейса командной строки
Доступны три параметра:
- [-s] — основной исходный файл
- [-o] — выходной файл
- [-w] — каталог для просмотра
Например, следующая строка запустит наблюдателя для каталога ./css , возьмет ./css/main.js в качестве точки входа и выведет результат в ./styles.css :
|
1
|
absurd -s ./css/main.js -o ./styles.css -w ./css
|
Вывод
Не пойми меня неправильно. Доступные CSS-препроцессоры великолепны, и я до сих пор их использую. Тем не менее, они пришли со своим собственным набором проблем. Мне удалось решить их, написав AbsurdJS. Правда в том, что я просто заменил один инструмент другим. Использование этой библиотеки исключает обычное написание CSS и делает вещи действительно гибкими, потому что все является JavaScript. Он может использоваться как инструмент командной строки или может быть интегрирован непосредственно в код приложения. Если вы заинтересованы в AbsurdJS, не стесняйтесь проверить полную документацию на github.com/krasimir/absurd или раскошелиться на репо.