СоХабр закрыт.

С 13.05.2019 изменения постов больше не отслеживаются, и новые посты не сохраняются.

| сохранено

H Изоляция css стилей с помощью компонентного подхода в черновиках

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

Для того чтобы победить эту проблему, были разработаны разные методологии организации кода в css, но не все фронтендеры пользуются этими методологиям, и не все они идеальны.

Когда у нас в макете много разных по оформлению блоков, которые несут в себе один смысл, использовать понятное имя становится сложно. Чтобы добиться нужного результата приходится добавлять к имени еще что-то. Это все может сделать код трудным для восприятия (как длиннющие названия классов в беме).

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

Важную роль во всем этом играет еще то, как организована структура проекта. Чаще всего можно встретить свалку файлов в каталоге css(и не только там), тому кто пришел в проект бывает совсем не очевидно, в каком файле лежит нужный стиль и как стили одного файла влияют на стили другого файла. Хотелось бы верить, что когда все браузеры научатся понимать стандарты web components, проектов с помойной структурой станет меньше, и эти проблемы исчезнут.

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

Как делают другие?


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

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

Докатились до того, что, в js запихнули не только html, но стали писать еще и css, ломая устоявшиеся стереотипы, что так делать плохо. И тут можно сказать, что мы возвращаемся к тому с чего начали, свалка из структуры проекта перекочевала в код компонента.

Мне нравится как подошли к этому вопросу разработчики Angular. Каждый компонент изолирован, стили внутри компонента никогда не пересекутся со стилями другого компонента, даже если они будут иметь одинаковое имя класса. Это достигается путем добавления уникального рандомного атрибута к каждому тегу элемента. Этот же атрибут добавляется к каждому классу в css. Чтобы не пугать тех, кто не знаком с Angular, добавлю, что исходный код компонента html и css является “чистым”, уникальные атрибуты добавляются при сборке приложения. Все файлы лежат отдельно друг от друга.

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


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

Чтобы в проекте был порядок, необходимо разбить каждый элемент макета на отдельные компоненты из которых потом будут собираться страницы. Думаю, что всем понятно, в чем сила компонентов. Если нет, то тут learn.javascript.ru/webcomponents-intro коротко и ясно объясняется.

Структура проекта


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

В каждом компоненте должен лежать файл html и css, если есть какая-то логика, то, там же следует разместить js файл.

Каждый компонент должен иметь свой тег. Желательно, чтобы в теге был дефис, например:
<my-component></my-component>

Такие правила заложены в стандарте custom elements, который пока мало где работает, но это не беда, рано или поздно заработает везде.

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

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


Вот такая получилась структура проекта:



После отработки всех gulp тасков, можно будет увидеть во что превратился код.



Код примера
Плагин

комментарии (40)

0
serf ,   * (был изменён)

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


Можно внутри селекторы еще делать более строгими, прописывая ">", помогает избежать коллизий когда компоненты вложенные (а они часто вложенные).


В современных фреймворках есть shadow dom эмуляция (Vue.js, Angular v2+, etc), это уменьшает избыточность и более гарантированный варинт изоляции тк там скоупинг происходит не просто по одному тегу верхнего уровня а каждый отдельный элемент получает уникальный автоматически сгенерированный атрибут.

0
serf ,   * (был изменён)
Это достигается путем добавления уникального рандомного атрибута к каждому тегу элемента. Этот же атрибут добавляется к каждому классу в css.

Это называется эмуляцией Shadow DOM, в Angular v2 эта возможность опциональна, там несколько View Encapsulation Types.

+1
x07 ,  
Это называется эмуляцией Shadow DOM

Читая определение shadow dom, язык не поворачивается назвать это эмуляцией. Это больше похоже на пародию на shadow dom.
shadow dom это круто, но пока не все браузеры о нем знают. Это одна из причин, почему в современных фреймворках есть такой подход к инкапсуляции, и есть полноценный shadow dom. В моем примере ничего не мешает, добавить скрипт который создаст из простого html кода, компонент с shadow dom. Но не везде это актуально.
Взять например полимер, открыв проект на полимере в 11 осле https://shop.polymer-project.org/, в инспекторе можно увидеть что внутренность тега просто отсутствует. На это можно забить, так как все прекрасно работает, но если вылезет баг(а в осле он вылезет), починить будет не просто.
0
serf ,   * (был изменён)
shadow dom это круто, но пока не все браузеры о нем знают. Это одна из причин, почему в современных фреймворках есть такой подход к инкапсуляции, и есть полноценный shadow dom.

Именно это и написано было выше, гугление по View Encapsulation Types откроет детали


в Angular v2 эта возможность опциональна, там несколько View Encapsulation Types.
+2
+3 –1
gibson_dev ,  
Если позволяет проект то проще использовать css modules https://github.com/css-modules/css-modules — решает как раз туже задачу. Нет проблем с одинаковыми именами классов и специфичности.
–2
x07 ,  
Тут идея в том, чтобы код был чистым и находился каждый в своем файле: разметка в html, стили в css, логика в js.
В случае с css modules html запихивается в js, и css трансформируется туда же.
Сложно сказать, проще это или нет
0
Kater-auf-Dach ,  
Эм… postcss-modules?
0
x07 ,  
мысли читать не умею.
+2
vintage ,   * (был изменён)
app-city-cards .cards .card .text {

И чем это лучше чем:


.app-city-card-descr {

?

0
Dimash2 ,  
Ничем, но еще и хуже по производительсности, .app-city-card-descr — то что нужно, а еще лучше

.app-city-card__descr
0
x07 ,  
здесь можно вообще не использовать вложенность и не делать длинное имя класса. Можно использовать просто .text
–1
Dimash2 ,   * (был изменён)
Не стоит так делать )
0
x07 ,  
Не могли бы пояснить?
0
Dimash2 ,   * (был изменён)
Усложнили простое на ровном месте. Это же все нужно запускать, настраивать, для маленького проекта — не нужная рутина, для большого проекта — не удобно, будет куча папок с компонентами, по которым будет очень сложно ходить, вы видел навигатор по файлам в IDE? Ну как в большой проекта по каждому такому компоненту ходить, и если давно работали, то еще и искать.

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

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

Я лично разбиваю CSS по логическим глобальным модулям, типа — news, products и тд.

— Я если перекраивают проект, и когда уже карточка — не карточка, а стили те же, но переделанные, о боже, не люблю я это. Это из оперы

.red {
color: red;
}

А теперь поменяйте красный на зеленый ))), так и со всеми этими сложными системами «чистого кода».

Мне нравится философия BEM, мне ее хватает на любых проектах.
0
x07 ,   * (был изменён)
Усложнили простое на ровном месте. Это же все нужно запускать, настраивать, для маленького проекта — не нужная рутина, для большого проекта — не удобно, будет куча папок с компонентами, по которым будет очень сложно ходить, вы видел навигатор по файлам в IDE? Ну как в большой проекта по каждому такому компоненту ходить, и если давно работали, то еще и искать.


Не знаю какой вы там IDE пользуетесь, но в любом нормальном редакторе(даже не IDE, а в ней то тем более) есть быстрый доступ к файлам. Вводите имя и выбираете нужный файл. Спросите как узнать имя файла компонента? Если вы читали статью, то там написано, что тег компонента называется его именем, по этому тегу можно определить его название.
По факту, дев открывает сайт, находит объект в консоле, редактирует и хочет максимально быстро перенести его в файл, с такой схемой — это очень сложно, особенно, когда работаешь с многими компонентами одновременно.

Какой консоле? Где он его редактирует? Как можно работать с многими компонентами одновременно?
Я лично разбиваю CSS по логическим глобальным модулям, типа — news, products и тд.

Допустим есть новости на главной странице, вы засунули их в глобальный news, завтра поставили задачу добавить новости на страницу о компании, создадите еще один глобальный модуль? Добавите в старый модуль news еще тонну кода?
–1
norlin ,   * (был изменён)

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

–1
x07 ,  
По-возможности, стараюсь придерживаться BEM в части именования классов

то-есть часть кода по бему, часть кода как получится? Это случайно не связано с тем что лень писать километровые информативные классы?
не требует никаких сборщиков

Вы серьезно пишете все в ручную и не пользуетесь хотя-бы препроцессорами?
0
norlin ,   * (был изменён)

В рамках одного проекта должен быть один стиль кода. "По-возможности" относится к разным проектам, не всегда есть возможность выбирать.


А километровых классов по бему писать не риходится, кстати. Всё вполне в разумных пределах. Зато лучше иметь возможность сразу видеть, что это за класс, а не гадать, как из десятка .text имеется в виду.


Часто и вручную приходится чистый css писать, а что?

0
x07 ,  
Какого десятка? Вы статью читали?
–1
norlin ,   * (был изменён)

Честно говоря, лишь проглядел наискосок. Увидел какие-то страшные кастомные аттрибуты в html и испугался. Но вижу, что и в стилях, и в html у вас одинаковые имена классов для разных элементов (.card, например).

0
sfi0zy ,   * (был изменён)
Это все может сделать код трудным для восприятия (как длиннющие названия классов в беме).

Сам использую вариации RSCSS, знаю достаточно людей, которые перешли от БЭМа к этому же подходу по причине неприязни к слишком длинным классам. Разметка получается весьма приятной. Так что с одной стороны это может сделать код трудным, а с другой — нет. Как посмотреть.

После отработки всех gulp тасков, можно будет увидеть во что превратился код....

А превратился он в жуть. Идея с добавлением рандома к разметке хороша (даже очень хороша), если мы потом ничего с ней не делаем. Как в SPA по фен-шую: отрендерили на сервере — кинули клиенту и забыли. И это не только в Angular, тот же Vue сразу предлагает использовать scoped css. Но в реальном мире существует невероятное количество сайтов, которые не являются SPA, в которых мы сделав HTML шаблон, превращаем его в шаблон для нашей CMS (условно Wordpress, да не закидают меня помидорами), который уже с помощью Gulp пересобрать будет крайне проблемно. Если шаблон будет весь состоять из рандомных строк — глаза лопнут потом в него вносить изменения. Так что на мой взгляд стоит подумать над целевой аудиторией этого инструмента — те, кто делает SPA, уже имеют что-то такое, а те, кто не делают — получают не самый удобный способ справиться с проблемой.
0
serf ,  
И это не только в Angular, тот же Vue сразу предлагает использовать scoped css, только там это в виде рандомных data-аттрибутов, насколько я помню

Это эмуляция shadow dom, давайте называть вещи правильно.

0
sfi0zy ,  
Это эмуляция shadow dom, давайте называть вещи правильно.

Все же shadow dom — это несколько большее, чем просто односторонняя изоляция стилей. Так что если язык и повернется назвать это эмуляцией — то сильно неполноценной.
0
serf ,  

Да очень неполноценная, полноценно сэмулировать невозможно.

0
GodlikeRabbit ,  

Я не писал больших проектов на фронтенд фреймворке, но верстал такие.


Не сталкивался с проблемой повторения классов(если дизайнер не совсем тронут и везде ставит разные размеры шрифтов, отступов и других элементов), не могли бы добрые люди скинуть ссылку на какой нибудь пример, где действительно необходимо добавлять рандомные префиксы?

0
+1 –1
BigDflz ,  
классная штука, с удовольствием применю, жаль, что только в хроме поддерживается (можно и в FF, но надо устанавливать режим) а если ещё применить совместно с Shadow DOM, то ваще конфетка получается. кто проголосовал против, просто не вкусил.
0
x07 ,  
Рандомный атрибут работает во всех браузерах начиная с 7 осла.CSS 2.1 selectors, а вот Shadow dom реализован не во всех браузерах.
0
BigDflz ,  
тут не рандомный атрибут — а кастомный элемент. несколько есть разница.
0
x07 ,  
я понял про что вы. Вы правы, тег компонента, получается custom elements, но те браузеры, которые его не знают, воспринимают этот тег нормально и с ним можно работать
0
BigDflz ,  
жаль здесь нелья редактировать… я этот какстомный элемент применЯЮ. очень нравится.
0
ai_from ,  
Неа, мне не нравится подход. Рандомная хрень какая-то будет в коде потом, и так типа будет более читабельнее код? Сомневаюсь. Лучше включить свои мозги, а не полагаться на рандомные вставки. Да и, кстати, получается, что шанс совпадения этого рандома то есть. Понимаю, что он очень маленький, но писать код в таком стиле (игра слов), когда ты все же не совсем на 100% контролируешь ситуацию — это не ко мне.
0
x07 ,   * (был изменён)
Исходный код будет чистым, рандомный атрибут будет виден только в собранном проекте, как и сконкатенированные и минимизированные файлы css и js. Вы их тоже читать пытаетесь?
Для отладки, максимум что нужно, это увидеть название компонента и класс.
В одном соглашусь, рандомный атрибут надо было добавлять в конец тега, а не в начало. Но это первая версия плагина. Если будет необходимость, в следующей версии я это исправлю.
0
vintage ,  

Лучше путь к компоненту добавляйте, а не рандом.

0
x07 ,  
какой путь, и куда его добавлять?
0
vintage ,   * (был изменён)

image


К имени класса, конечно, и получите генерацию классов по бэму. Собственно в $mol у нас так и сделано:


Объявление компонента:


$my_food $mol_view sub /
    <= Photo $mol_image uri <= photo_small \
    <= Descr $mol_text text <= description \

Навес стилей:


[my_food] { border: 1px solid gray }
[my_food_photo] { width: 100% }
[my_food_descr] { padding: 1rem }

Использование:


<= Food!index $my_food
    photo_small <= food_photo!index \
    description <= food_description!index \
–1
ai_from ,  
Для меня нет особой разницы, будет ли рандомный код в собранном коде или в исходниках. Мне совсем не нравится идея, что кто-то будет добавлять что-то без меня в мой же код. Повторюсь, этот рандом может все же дать сбой — совпадет и все. Опять же — я понимаю, что это ничтожная доля вероятности, но она все же имеет место случиться. А мне хочется все-таки действительно контролировать каждый символ своего кода. Я не говорю, что я за то, чтобы изобретать велосипеды — нет. Но рандомные вещи, даже лишь в конечной сборке, мне не хочется иметь. Считаю, что и сборка тоже должна быть максимально чистой и понятной. Тем более, я, кстати, не вижу особых проблем, чтобы отделять блоки один от другого. Просто надо грамотно организовать рабочий процесс, структуру файлов и папок, структуру в целом. А с вложенностью, например, в less — становится еще проще. Это лишь мое скромное видение.
Вместе с тем, я считаю, что развивать всякие такие штуки и пробовать — это хорошая практика. Так что — в любом случае было интересно почитать.
0
justboris ,  
Хотелось бы верить, что когда все браузеры научатся понимать стандарты web components, проектов с помойной структурой станет меньше, и эти проблемы исчезнут.

Как аккуратная организация файлов в проектах зависит от поддержки каких-то фич в браузерах?

0
x07 ,   * (был изменён)
Как сейчас выглядит обычная структура проекта? По любому есть каталог с названием препроцессора, в котором тонна подкаталогов, и тонна файлов где описываются стили всех элементов, которые потом все бандлятся в один файл. Не всегда бывает понятно какой элемент в каком файле править, а происходит это по разным причинам, одна из которых это «заказчик», которому моча в голову стукнула и он решил поменять элементы, и вообще весь дизайн. Сроки жмут, куча правок, многие забивают на порядок, создают новые файлы, а старые, которые не используются, валяются там до лучших времен.
Когда в браузерах реализуют веб компоненты, то такая структура будет не актуальной, бем станет не нужен, структура будет понятнее и чище.
0
justboris ,  

Если есть желание, то положить стили совместно с компонентом можно и сейчас.


А если желания нет, то можно накидать все вразнобой, даже при использовании веб-компонентов.

+2
tadatuta ,  
Вот уже лет 8 как БЭМ предлагает использовать компонентный подход в том числе и для файловой структуры, а разработчики продолжают использовать
каталог с названием препроцессора, в котором тонна подкаталогов, и тонна файлов где описываются стили всех элементов, которые потом все бандлятся в один файл
:(