Atomizer vs Minimalist Notation (MN)

Minimalist Notation (MN) (минималистическая нотация) — гибкая адаптивная технология генерации стилей.


Она генерирует стили только для существующих классов разметки html, jsx, и т.п. — благодаря чему отпадает необходимость заботиться о компонентном подходе в CSS, мёртвом CSS коде, и отпадает необходимость писать CSS код вообще.


В ней поддерживаются брейкпоинты (медиа-запросы), селекторы, приоритеты, группировки, необходимые автопрефиксы и полифилы.


Применение этой технологии похоже на использование инлайновых стилей, только с гораздо более выразительным синтаксисом и множеством дополнительных возможностей, поэтому MN можно даже назвать технологией inline styles 2.0.


MN подразумевает не только правила нотации как таковые, но и, по-сути, даже усовершенствованный альтернативный синтаксис каскадного языка установки стилей, который ориентирован на инлайновое применение.


Я разработал MN в 2017 году, и с того момента успел во многом усовершенствовать эту технологию, добавить достаточно пресетов и удобных инструментов для её изучения.


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


Технология MN имеет обширную функциональность.


Для многих она является новой, и почти не имеет других первоисточников для изучения, кроме этой статьи, поэтому предупреждаю, что ниже будет МНОГО БУКВ и примеров кода.


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


Лучший способ что-то объяснить — это показать наглядные примеры.


Начнём с сравнения Minimalist-Notation с более менее известными функциональностями его первого аналога: Atomizer.


Актуальные на момент написания статьи версии пакетов:


  • minimalist-notation 1.5.17 ;
  • atomizer 3.6.2 .

Проверить примеры ниже с Atomizer можно будет здесь:
https://pankajparashar-zz.github.io/atomizer-web/


Проверить примеры ниже с MN можно можно будет здесь:
https://viewer.minimalist-notation.org/


Документация по всем предустановленным пресетам MN здесь:
https://styles.minimalist-notation.org/




Atomizer vs MN. Простые примеры


Atomizer


<div class="D(f) Jc(c) Ai(c) H(100%) Fz(20vh) C(#0f0) C(#F00) Bgc(#f00) M(-10px) P(10px) Pstart(10%) Pos(a)">...</div>

.D\(f\) {
  display: flex;
}
.Jc\(c\) {
  justify-content: center;
}
.Ai\(c\) {
  align-items: center;
}
.H\(100\%\) {
  height: 100%;
}
.Fz\(20vh\) {
  font-size: 20vh;
}
.C\(\#0f0\) {
  color: #0f0;
}
.Bgc\(\#f00\) {
  background-color: #f00;
}
.M\(-10px\) {
  margin: -10px;
}
.P\(10px\) {
  padding: 10px;
}
.Pstart\(10\%\) {
  padding-left: 10%;
}
.Pos\(a\) {
  position: absolute;
}

На что я обратил внимание в этом примере с Atomizer:


  • аббревиатуры аналогичны Emmet;
  • имена свойств в нотации представлены как функции, которые всегда именуются аббревиатурами, начинающимися с верхнего регистра;
  • значения свойств стилей в нотации обязательно заключаются в скобки;
  • аббревиатура Pstart для свойства padding-left для меня неожиданна;
  • единицы измерения в нотации нельзя пропускать;
  • некоторые значения можно задавать только предустановленными аббревиатурами, такими как, например: f - flex, c - center, однако, например, такая запись работать не будет: D(flex);
  • когда я пытаюсь задавать цвет символами верхнего регистра, например, #F00 — вместо, #f00, то стили не генерируются, либо генерируется какая-то петрушка…

Minimalist-Notation


<div class="dF jcC aiC h f20vh c0F0 bgcF00 m-10 p10 pl10% abs">...</div>

.dF {
  display: flex;
}
.jcC {
  justify-content: center;
}
.aiC {
  align-items: center;
}
.h {
  height: 100%;
}
.f20vh {
  font-size: 20vh;
}
.c0F0 {
  color: #0f0;
}
.bgcF00 {
  background-color: #f00;
}
.m-10 {
  margin: -10px;
}
.p10 {
  padding: 10px;
}
.pl10\% {
  padding-left: 10%;
}
.abs {
  position: absolute;
}

Отличия:


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


    Это связано ещё с тем, что лично я узнал о существовании Emmet и Atomizer только после того, как создал первую версию MN.
    Примеры:
    f — вместо fz для свойства font-size — ибо размер шрифта Мы задаем очень часто,
    font — вместо f для свойства font — ибо Мы редко используем его непосредственно,
    abs — вместо posA для свойства и значения position: absolute, хотя posA тоже можно использовать;


  • имена свойств в нотации именуются аббревиатурами в нижнем регистре;


  • значения свойств стилей в нотации начинаются с любого символа отличного от латинских букв в нижнем регистре.


    Кончено и здесь в нотации значения также можно заключать в скобки: d(F) — но скобки
    в MN предназначены не для параметризации, а для группировки подстрок нотации, например,
    эти записи аналогичны: p(l10|r15) === pl10 pr15.


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


  • единицы измерения не являются частью базовой нотации.


    Необходимость указания единиц измерения зависит от обработчика заданного для соответствующего имени в нотации.


    Для большинства дефолтных обработчиков указание единиц измерения являются опциональным, например, возможны вариации:
    f14px == f14,
    h100% == h,
    h10px == h10.


  • в MN также имеются предустановленные аббревиатуры для некоторых значений, такие как, например,
    F - flex, C - center — хотя Мы можем использовать полные именования.
    Вы имеете возможность использовать вместо, например, этих аббревиатур:
    dF dIB jcC aiC — различные вариации именований:
    dFlex dInlineBlock jcCenter aiCenter,
    d_flex d_inline-block jc_center ai_center .
    В большинстве дефолтных обработчиков запись для значений из формата camelCase
    просто трансформируется в формат kebabCase, а нижнии прочерки заменяются на пробелы;


  • для большинства дефолтных обработчиков предусмотрены значения по умолчанию, например, как Мы наблюдаем, для свойства height (h) значение по умолчанию равно 100% .
    Если Мы используем значение: h40 — то получим следующее:


    .h40 {height: 40px}

  • Для указания цвета не требуется решетка (#).



Более того, решетка (#) является служебным символом, необходимым для полноценных селекторов, о которых Вы узнаете ниже.


Если Вы добавите решетку, то получите не то, чего, вероятно, ожидаете:


<div class="c#0F0">...</div>

.c\#0F0#0F0 {color: #000}

В некоторых случаях решетка всё же нужна, например, для свойства outline.
Тогда служебные символы в нотации нужно экранировать:


<div class="ol_thick_double_\#32a1ce">...</div>

.ol_thick_double_\\\#32a1ce {outline: thick double #32a1ce}

В MN для дефолтных обработчиков (c, bc, olc, fill, stroke, bg, bgc, temc, tdc) цвета можно задавать разными вариациями символов — об этом подробнее будет ниже.


  • обработчики для аббревиатур свойств являются кастомизируемыми и подключаются по умолчанию из библиотеки minimalist-notation/presets/styles.

Вы имеете возможность полностью кастомизировать MN под свои нужды, путем создания собственных обработчиков:


// для свойства "padding"
mn('p', (params) => {
  return {
    style: {
      padding: (params.num || '0') + (params.unit || 'px'),
    },
  };
});

// для свойства "padding-left"
mn('pl', (params) => {
  return {
    style: {
      paddingLeft: (params.num || '0') + (params.unit || 'px'),
    },
  };
});

// для свойства "color"
mn('c', (params) => {
  return !params.negative && {
    style: {
      color: mn.utils.color(params.value || '0'),
    },
  };
}, '^(([A-Z][a-z][A-Za-z]+):camel|([A-F0-9]+):color):value(.*)?$');

Примеры обработчиков MN можно подсмотреть в репозитории:
https://github.com/mr-amirka/minimalist-notation/presets


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




Примеры немного сложнее


Atomizer vs MN. Флаг !important


Atomizer


<div class="D(f)! C(#0f0)!">...</div>

.D\(f\)\! {
  display: flex !important;
}
.C\(\#0f0\)\! {
  color: #0f0 !important;
}

Minimalist-Notation


<div class="dF-i c0F0-i">...</div>

.dF-i {
  display: flex!important;
}
.c0F0-i {
  color: #0f0!important;
}

В MN для установки флага !important не используется символ !, потому-что этот служебный символ уже применяется в нотации для реверсирования подстрок.




Группировки и реверсирование подстрок (!)


Возьмем такую запись:


<div class="cF:hover>.item">...</div>

.cF\:hover\>\.item:hover .item {
  color: #fff;
}

Если Мы добавим символ ! после :hover, то получим следующее:


<div class="cF:hover!>.item">...</div>

.cF\:hover\!\>\.item .item:hover {
  color: #fff;
}

Здесь псевдоселектор hover переместился от родительского элемента к дочернему.
Этот приведенный для наглядности способ использования реверсирования в данном случае бесполезен, ибо такого же эффекта Мы можем добиться и другим способом:


<div class="cF>.item:hover">...</div>

.cF\>\.item\:hover .item:hover {
  color: #fff;
}

Однако реверсирование может оказаться весьма полезным для сокращения записи в комбинации с группировками.


Допустим, что Мы верстаем какой-то простой сайт без шаблонизаторов и без React компонентов — возможно, это лендинг.


Мы имеем некоторый список, в котором располагаются одинаковые дочерние элементы:


<ul class="m p dBlock">
  <li class="mb5 dBlock">
    <a class="c0 c0:hover tdNone p5">...</a>
  </li>
  <li class="mb5 dBlock">
    <a class="c0 c0:hover tdNone p5">...</a>
  </li>
  <li class="mb5 dBlock">
    <a class="c0 c0:hover tdNone p5">...</a>
  </li>
  ...
</ul>

.m {
  margin: 0;
}
.p {
  padding: 0;
}
.dBlock {
  display: block;
}
.mb5 {
  margin-bottom: 5px;
}
.c0,.c0\:hover:hover {
  color: #000;
}
.tdNone {
  text-decoration: none;
}
.p5 {
  padding: 5px;
}

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


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


Рассмотрим по шагам различные способы.


Вынесем всю нотацию в родительский элемент:


<ul class="m p dBlock mb5>li dBlock>li c0>a c0>a:hover tdNone>a p5>a">
  <li>
    <a>...</a>
  </li>
  <li>
    <a>...</a>
  </li>
  <li>
    <a>...</a>
  </li>
  ...
</ul>

.m {
  margin: 0;
}
.p {
  padding: 0;
}
.dBlock,.dBlock\>li li {
  display: block;
}
.mb5\>li li {
  margin-bottom: 5px;
}
.c0\>a a,.c0\>a\:hover a:hover {
  color: #000;
}
.tdNone\>a a {
  text-decoration: none;
}
.p5\>a a {
  padding: 5px;
}

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


Для примера Мы добавим еще один уровень списка, который имеет отступ в 10 пикселей слева, а ссылки внутри него имеют подчеркивание и красный цвет.


При наведении курсора на ссылку, она становится зеленой, а при нажатии — становится синей.


<ul class="
  m p dBlock
  mb5>li dBlock>li
  p5>a tdNone>1li>1a c0>1li>1a c0>1li>1a:hover
  m>ul p>ul dBlock>ul pl10>1li>1ul
  tdUnderline>1li>1ul>1li>1a
  cRed>1li>1ul>1li>1a
  cGreen>1li>1ul>1li>1a:hover
  cBlue>1li>1ul>1li>1a:active
">
  <li>
    <a>...</a>
  </li>
  <li>
    <a>...</a>
  </li>
  <li>
    <a>...</a>
    <ul>
      <li>
        <a>...</a>
      </li>
      ...
    </ul>
  </li>
  ...
</ul>

Число перед селектором (1) просто указывает на строго определенную глубину вложенности элемента.


Кстати, обратите внимание: селекторы с одинаковыми значениями свойств в сгенерированном CSS коде группируются вместе — что позволяет минимизировать его объем на выходе.


Думаю, этого для наглядности Нам пока хватит.


Теперь попробуем группировки:


<ul class="
  (m|p|dBlock)(|>ul) (mb5|dBlock)>li
  p5>a tdNone>1li>1a c0>1li>1a(|:hover)
  pl10>1li>1ul
  (tdUnderline|cRed)>1li>1ul>1li>1a
  cGreen>1li>1ul>1li>1a:hover
  cBlue>1li>1ul>1li>1a:active
">
  <li>
    <a>...</a>
  </li>
  <li>
    <a>...</a>
  </li>
  <li>
    <a>...</a>
    <ul>
      <li>
        <a>...</a>
      </li>
      ...
    </ul>
  </li>
  ...
</ul>

Здесь группировки Нам не сильно помогли…


Вот теперь попробуем ещё и реверсирование:


<ul class="
  (m|p|dBlock)(|>ul)
  (mb5|dBlock)>li p5>a
  (tdNone|c0(|:hover!))>1li>1a
  pl10>1li>1ul
  (tdUnderline|c(Red|Green:hover!|Blue:active!))>1li>1ul>1li>1a
">
  <li>
    <a>...</a>
  </li>
  <li>
    <a>...</a>
  </li>
  <li>
    <a>...</a>
    <ul>
      <li>
        <a>...</a>
      </li>
      ...
    </ul>
  </li>
  ...
</ul>

Забавно, не правда ли?


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


<ul class="
  (m|p|dB)(|>ul)
  (mb5|dB)>li
  p5>a
  (tdN|c0(|:h!))>2a
  pl10>2ul
  (tdU|c(Red|Green:h!|Blue:a!))>4a
">
  <li>
    <a>...</a>
  </li>
  <li>
    <a>...</a>
  </li>
  <li>
    <a>...</a>
    <ul>
      <li>
        <a>...</a>
      </li>
      ...
    </ul>
  </li>
  ...
</ul>

Всё-таки, для пущей наглядности заменю имена тегов на классы:


<ul class="
  ListA
  (m|p|dB)(|.ListA)
  pl10>2.ListA
  (mb5|dB)>.ListA__Item
  (p5>|(tdN|c0(|:h!))>2|(tdU|c(Red|Green:h!|Blue:a!))>4).ListA__Link
">
  <li class="ListA__Item">
    <a class="ListA__Link">...</a>
  </li>
  <li class="ListA__Item">
    <a class="ListA__Link">...</a>
  </li>
  <li class="ListA__Item">
    <a class="ListA__Link">...</a>
  </li>
  <li>
    <a class="ListA__Link">...</a>
    <ul class="ListA">
      <li class="ListA__Item">
        <a class="ListA__Link">...</a>
      </li>
      ...
    </ul>
  </li>
  ...
</ul>

Извратились Мы знатно!


Однако, рекомендую, друзья мои, этими группировками сильно не увлекаться!


Здесь у некоторых может возникнуть вопрос, как же Нам решить проблему с неодноранговастью сгенерированных селекторов? — ибо, если Мы захотим кастомизировать отдельные элементы из этого списка, Нам могут помешать грабли перекрывающих стилей.


Ответ:


Мы, например, можем просто именовать классы дочерних уровней немного иначе и не указывать в нотации глубину их вложенности, дабы селекторы всех уровней имели равный приоритет:


<ul class="
  ListA
  (m|p|dB)(|.ListA)
  (mb5|dB)>.ListA__Item
  p5>.ListA__Link
  (tdN|c0(|:h!))>.ListA__Link_level1

  pl10>.ListA_level2
  (tdU|c(Red|Green:h!|Blue:a!))>.ListA__Link_level2

  bgF88>.ListA__Item_substyle
">
  <li class="ListA__Item ListA__Item_level1 ListA__Item_substyle">
    <a class="ListA__Link ListA__Link_level1">...</a>
  </li>
  <li class="ListA__Item ListA__Item_level1">
    <a class="ListA__Link ListA__Link_level1">...</a>
  </li>
  <li class="ListA__Item ListA__Item_level1">
    <a class="ListA__Link ListA__Link_level1">...</a>
  </li>
  <li>
    <a class="ListA__Link">...</a>
    <ul class="ListA ListA_level2">
      <li class="ListA__Item ListA__Item_level2">
        <a class="ListA__Link ListA__Link_level2">...</a>
      </li>
      <li class="ListA__Item ListA__Item_level2 ListA__Item_substyle">
        <a class="ListA__Link ListA__Link_level2">...</a>
      </li>
      ...
      ...
    </ul>
  </li>
  ...
</ul>

Многие могут заметить, что здесь Мы имеем возможность использовать сложные селекторы, которые потенциально порождают нежелательные сайд-эффекты с перекрытиями стилей.
Да. Это так. И это, отчасти, противоречит самой методологии Atomic CSS.


Тем не менее, есть несколько нюансов:


  • Нас никто не принуждает использовать именно такие конструкции;
  • Нас никто не ограничивает, и при необходимости Мы можем воспользоваться этими конструкциями;
  • Благодаря тому, что весь код как правило помещается у Нас на одном экране и не разрывается контекст между разметкой и стилями, Нам гораздо проще обнаружить, "где зарыта собака" и исключить возможные грабли с перекрытиями стилей.



Atomizer vs MN. Псевдоклассы и псевдоэлементы


Чисто ради сравнения, рассмотрим доступную в Atomizer возможность использования псевдоклассов и псевдоэлементов:


<div class="Bgc(#fff):h C(blue):a C(#0f0):hover D(n)::b">...</div>

.Bgc\(\#fff\)\:h:hover {
  background-color: #fff;
}
.C\(blue\)\:a:active {
  color: blue;
}
.C\(\#0f0\)\:hover:hover {
  color: #0f0;
}
.D\(n\) {
  display: none;
}

Мы видим, что Atomizer позволяет юзать как некоторые полные имена псевдоклассов, так и их короткие синонимы.


В документации написано, что можно использовать псевдоэлементы.


Например: b для ::before


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


Для D(n)::b я ожидал получить следующее:


.D\(n\)\:\:::before {
  display: none;
}

В MN же Мы можем всё тоже самое:


<div class="bgcF:h cBlue:a c0F0:hover dN::before">...</div>

.bgcF\:h:hover {
  background-color: #fff;
}
.cBlue\:a:active {
  color: blue;
}
.c0F0\:hover:hover {
  color: #0f0;
}
.dN\:\:before::before {
  display: none;
}

Помимо этого, Мы имеем возможность задавать вообще любые псевдоклассы и псевдоэлементы известные и неизвестные в CSS:


<div class="bgcF:hz cBlue::placeholder c0F0::-webkit-input-placeholder bgE:odd bt:first c:i">...</div>

.bgcF\:hz:hz {
  background-color: #fff;
}
.cBlue\:\:placeholder::placeholder {
  color: blue;
}
.c0F0\:\:-webkit-input-placeholder::-webkit-input-placeholder {
  color: #0f0;
}
.bgE\:odd:nth-child(2n+1) {
  background: #eee;
}
.bt\:first:first-child {
  border-top-width: 0;
}
.c\:i::placeholder {
  color: #000;
}
.c\:i:-ms-input-placeholder {
  color: #000;
}
.c\:i::-moz-placeholder {
  color: #000;
}
.c\:i::-webkit-input-placeholder {
  color: #000;
}

В MN Мы можем параметризовать псевдоклассы:


<div class="c:not[.anyClass] bg0A:not[tag[attr=value].class\:pseudo] c88F4:n[13] c01:n[3n+1]">...</div>

.c\:not\[\.anyClass\]:not(.anyClass) {
  color: #000;
}
.bg0A\:not\[tag\[attr\=value\]\.class\\\:pseudo\]:not(tag[attr=value].class:pseudo) {
  background: #000;
  background: rgba(0,0,0,.67);
}
.c88F4\:n\[13\]:nth-child(13) {
  color: #88f;
  color: rgba(136,136,255,.27);
}
.c01\:n\[3n\+1\]:nth-child(3n+1) {
  color: #000;
  color: rgba(0,0,0,.07);
}

В MN Мы также можем указать несколько псевдоклассов подряд:


<input
  type="checkbox"
  class="mh20:not[.anyClass]:n[5n+2]:c:h"
/>

.mh20\:not\[\.anyClass\]\:n\[5n\+2\]\:c\:h:not(.anyClass):nth-child(5n+2):checked:hover {
  margin-left: 20px;
  margin-right: 20px;
}

В MN можно задать собственный набор синонимов для псевдоклассов.
Пример:


mn.utils.extend(mn.states, {
  foo: [':active'],
  bar: ['.Bar_active', '.otherSelector'],
  vasya: ['[data-name=vasya]'],
});

<div class="cRed:foo cGreen:bar cBlue:vasya">...</div>

.cRed\:foo:active {
  color: red;
}
.cGreen\:bar.otherSelector,.cGreen\:bar.Bar_active {
  color: green;
}
.cBlue\:vasya[data-name=vasya] {
  color: blue;
}



Atomizer vs MN. Комбинаторы


Сравним кабинаторы.


"The underscore character ( _ )" в Atomizer:


<div class="foo">
  <div class="foo_D(n)"></div>
</div>

Аналог в MN:


<div class="foo">
  <div class="dN<.foo"></div>
</div>

.foo .dN\<\.foo {
  display: none;
}

"The right angle bracket character ( > )" в Atomizer:


<div class="foo">
  <div class="foo>D(n)"></div>
</div>

Аналог в MN:


<div class="foo">
  <div class="dN<1.foo"></div>
</div>

.foo>.dN\<1\.foo {
  display: none;
}

"The plus sign ( + )" в Atomizer:


<div class="foo"></div>
<div class="foo+D(n)"></div>

Аналог в MN:


<div class="foo"></div>
<div class="dN<.foo+"></div>

.foo+ .dN\<\.foo\+ {
  display: none;
}

Помимо этого в MN комбинатор "соседний брат" ( + ) можно использовать в обратном направлении:


<div class="dN+.foo"></div>
<div class="foo"></div>

.dN\+\.foo+.foo {
  display: none;
}

В MN можно использовать комбинатор "общий брат" ( ~ ):


<div class="foo"></div>
<div class="dN<.foo~"></div>
<div class="dN<.foo~"></div>

.foo~ .dN\<\.foo\~ {
  display: none;
}

И в обратном направлении:


<div class="dN~.foo"></div>
<div class="foo"></div>
<div class="foo"></div>

.dN\~\.foo~.foo {
  display: none;
}



Atomizer vs MN. Контекст


В Atomizer есть функциональность, называемая Context class, которая используется в случаях, когда необходимо установить некоторое поведение стилей при изменении селектора на родительском элементе.
Пример:


<div class="foo bar any">
  <div class="double">
    <div class="foo_D(n) bar:h_D(n) any_D(n):h any_double_D(n)">...</div>
  </div>
</div>

.foo .foo_D\(n\), .any_double .any_double_D\(n\) {
  display: none;
}
.bar:hover .bar\:h_D\(n\) {
  display: none;
}
.any .any_D\(n\)\:h:hover {
  display: none;
}

На что я обратил внимание в этом примере:


  • нельзя использовать последовательность из нескольких родительских классов, что я и попытался сделать в этом примере таким образом any_double_D(n);
  • оказывается, стили в конечном CSS всё же частично группируются по какому-то принципу.

В MN, как, вероятно, некоторые могли догадаться из примеров выше, можно юзать полноценные селекторы, но для начала давайте рассмотрим, каким образом можно сделать тоже самое:


<div class="foo bar any">
  <div class="double">
    <div class="dN<.foo dN<.bar:h dN:h<.any dN<.double<.any">...</div>
  </div>
</div>

.foo .dN\<\.foo,.bar:hover .dN\<\.bar\:h,.any .dN\:h\<\.any:hover,.any .double .dN\<\.double\<\.any {
  display: none;
}

При необходимости можно задать строгую глубину вложенности:


<div class="any">
  <div class="double">
    <div class="dN<1.double<1.any dN<2.any">...</div>
  </div>
</div>

.any>.double>.dN\<1\.double\<1\.any,.any>*>.dN\<2\.any {
  display: none;
}

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


<div class="dN>2.double">
  <div class="any">
    <div class="double">...</div>
  </div>
</div>

.dN\>2\.double>*>.double {
  display: none;
}

Можно комбинировать родительские селекторы с дочерними:


<div class="parent">
  <div class="dN<.parent>2.double">
    <div class="any">
      <div class="double">...</div>
    </div>
  </div>
</div>

.parent .dN\<\.parent\>2\.double>*>.double {
  display: none;
}

<div class="dN>.double<.any">
  <div class="any">
    <div class="double">...</div>
  </div>
</div>

.dN\>\.double\<\.any .any .double {
  display: none;
}

Можно обозначить поведение стиля при наличии произвольного селектора на текущем элементе:


<div class="bgF2.active[data-name=Gena]:h active" data-name="Gena">...</div>
<div class="dN#feedback" id="feedback">...</div>
<div class="o50.disable disable">...</div>

.bgF2\.active\[data-name\=Gena\]\:h.active[data-name=Gena]:hover {
  background: #fff;
  background: rgba(255,255,255,.13);
}
.dN\#feedback#feedback {
  display: none;
}
.o50\.disable.disable {
  opacity: .5;
}

Отличия:


  • в нотации MN строгий порядок.
    Фрагмент строки отвечающий за значение стиля, как основная суть нотации, всегда следует вначале, и только затем начиная с какого-нибудь служебного символа следует контекст, который уточняет, к чему стили имеют отношение.
    Если существует такое понятие как "Венгерская нотация", то мне чисто ради наблюдения, хочется здесь назвать такой способ записи "Тюркской нотацией", ибо в тюркских языках суть слова всегда находится вначале и только затем следует множество уточняющих суффиксов и окончаний.
    В глобальном смысле это даже предельно технически правильный способ подачи почти любой информации, заключающийся в последовательном уменьшении неопределенности.
    Пример со временем:
    2020-02-02 22:22:22 — каждое следующее значение в этой последовательности практически будет бесполезно без предшествующих;
    Аналогичный пример с адресом: Germany, 14193 Berlin, Kronberger Str. 12;


  • в MN можно использовать произвольные селекторы.





Atomizer vs MN. Вычисляемые значения


В Atomizer есть возможность использования вычисляемых значений.
Для некоторых свойств Мы можем использовать дроби:


<div class="W(1/2) P(1/3) M(1/4) Start(1/5) T(1/6) Pstart(1/7) Miw(1/8)">...</div>

.W\(1\/2\) {
  width: 50%;
}
.P\(1\/3\) {
  padding: 33.3333%;
}
.M\(1\/4\) {
  margin: 25%;
}
.Start\(1\/5\) {
  left: 20%;
}
.T\(1\/6\) {
  top: 16.6667%;
}
.Pstart\(1\/7\) {
  padding-left: 14.2857%;
}
.Miw\(1\/8\) {
  min-width: 12.5%;
}

В MN Мы также можем использовать вычисляемые значения, в том числе и дроби:


<div class="w1/2 p1/3 m1/4 sl1/5 st1/6 pl1/7 wmin1/8">...</div>

.w1\/2 {
  width: 50%;
}
.p1\/3 {
  padding: 33.33%;
}
.m1\/4 {
  margin: 25%;
}
.sl1\/5 {
  left: 20%;
}
.st1\/6 {
  top: 16.66%;
}
.pl1\/7 {
  padding-left: 14.28%;
}
.wmin1\/8 {
  min-width: 12.5%;
}

Однако в Atomizer почему-то нельзя вычесть или сложить значение с дробью.
Я проверил:


<div class="W(1/2-10) P(1/3+5)">...</div>

.W\(1\/2-10\) {
  width: 50%;
}

В MN Мы можем вычитать и складывать значения с дробью, и это для некоторых случаев весьма полезная возможность.
Пример:


<div class="w1/2-10 p1/3\+5">...</div>

.w1\/2-10 {
  width: calc(50% - 10px);
}
.p1\/3\\\+5 {
  padding: calc(33.33% + 5px);
}

Замечания:


  • здесь " + " — это служебный символ-комбинатор, как и в обычном CSS, поэтому он экранируется слэшем (\).



Atomizer vs MN. Установка цветов


В Atomizer для указания цвета используются шестнадцатеричные цвета из 3-х или 6-ти символов с префиксом # в качестве идентификатора значения.


Шестнадцатеричные значения для цветов должны быть написаны в нижнем регистре (т.е., #ccc, а не #CCC).


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


Например:


<div class="C(#fff) Bdc(#ff0000) Bgc(#00ff00.5)">...</div>

.C\(\#fff\) {
  color: #fff;
}
.Bdc\(\#ff0000\) {
  border-color: #ff0000;
}
.Bgc\(\#00ff00\.5\) {
  background-color: rgba(0,255,0,.5);
}

В MN для указания цвета используются шестнадцатеричные цвета от 0 до 8-ми символов с коэффициентом непрозрачности включительно, но для коэффициента непрозрачности дополнительно имеется альтернативный способ: использовать точку(.) с десятичным значением.


Например:


<div class="c cFFF bcFF0000 bgc00FF00\.5 сFF00008 сFF000080 cF bgc08 bgc0\.5 bgc1234 bgc12348">...</div>

.c {
  color: #000;
}
.cFFF {
  color: #fff;
}
.bcFF0000 {
  border-color: #f00;
}
.bgc00FF00\\\.5 {
  background-color: #0f0;
  background-color: rgba(0,255,0,.5);
}
.cFF00008 {
  color: #f00;
  color: rgba(255,0,0,.53);
}
.cFF000080 {
  color: #f00;
  color: rgba(255,0,0,.5);
}
.cF {
  color: #fff;
}
.bgc08 {
  background-color: #000;
  background-color: rgba(0,0,0,.53);
}
.bgc0\\\.5 {
  background-color: #000;
  background-color: rgba(0,0,0,.5);
}
.bgc1234 {
  background-color: #123;
  background-color: rgba(17,34,51,.27);
}
.bgc12348 {
  background-color: #123;
  background-color: rgba(17,34,51,.28);
}

Замечания:


  • здесь " . " — это служебный символ, с помощью которого указываются классы, как и в обычном CSS, поэтому он экранируется слэшем (\).
  • Вы могли обратить внимание на то, что ко всем генерируемым цветам с коэффициентом непрозрачности дополнительно добавляется значения без альфа-канала — это полифил для браузеров, которые не поддерживают коэффициент непрозрачности.
    Это поведение можно отключить в настройках MN при помощи установки опции altColor: 'off'



Градиентная цветовая заливка в MN


В MN Мы имеем два похожих пресета, которые, как известно, на практике часто могут быть взаимозаменяемыми: bgc и bg.
Пример:


<div class="bg48A">...</div>
<div class="bgc48A">...</div>

.bg48A {
  background: #48a;
}
.bgc48A {
  background-color: #48a;
}

Однако с bg Мы также можем легко и лаконично делать градиенты, путем указания последовательности цветов через знак минуса (-):


<div class="bg0-F">...</div>
<div class="bgF00-0F0-00F">...</div>

.bg0-F {
  background: #000;
  background: linear-gradient(180deg,#000 0%,#fff 100%);
}
.bgF00-0F0-00F {
  background: #f00;
  background: linear-gradient(180deg,#f00 0%,#0f0 50%,#00f 100%);
}

Имеется возможность устанавливать направление градиента:


<div class="bg0-F_g45">...</div>
<div class="bg0-F_g90">...</div>

.bg0-F_g45 {
  background: #000;
  background: linear-gradient(225deg,#000 0%,#fff 100%);
}
.bg0-F_g90 {
  background: #000;
  background: linear-gradient(270deg,#000 0%,#fff 100%);
}

Помимо линейных градиентов, Мы можем использовать радиальные градиенты:


<div class="bg0-F_r">...</div>
<div class="bg0-F_r_closestSide">...</div>
<div class="bg0-F_r_ellipse_at_top">...</div>

.bg0-F_r {
  background: #000;
  background: radial-gradient(circle,#000 0%,#fff 100%);
}
.bg0-F_r_closestSide {
  background: #000;
  background: radial-gradient(closest-side,#000 0%,#fff 100%);
}
.bg0-F_r_ellipse_at_top {
  background: #000;
  background: radial-gradient(ellipse at top,#000 0%,#fff 100%);
}

Имеется возможность устанавливать позиции переходов градиента:


<div class="bgF00-0F0p77%-00Fp90%">...</div>

.bgF00-0F0p77\%-00Fp90\% {
  background: linear-gradient(180deg,#f00 0%,#0f0 77%,#00f 90%);
}



Atomizer vs MN. Несколько значений


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


<div class="Bgp(20px,50px)">...</div>

.Bgp\(20px\,50px\) {
  background-position: 20px 50px;
}

С MN получить аналогичный результат можно похожим образом:


<div class="bgp20px_50px">...</div>

.bgp20px_50px {
  background-position: 20px 50px;
}



Atomizer vs MN. Идентификатор брейкпоинта (breakpoint identifier). Медиа-запросы


В Atomizer можно устанавливать брейкпоинт к правилу, путем добавления суффикса к записи. Брейкпоинт указывает, что это правило вступит в силу только в рамках медиа-запроса.


Значения имени и длины каждой точки останова определяются в объекте конфигурации:


{
  // ...
  breakPoints: {
    'sm': '@media(min-width:750px)', // breakpoint 1
    'md': '@media(min-width:1000px)', // breakpoint 2
    'lg': '@media(min-width:1200px)', // breakpoint 3
    // ...
  },
  // ...
}

Используются эти брейкпоинты следующим образом:


<div class="W(50%)--sm W(33%)--md W(25%)--lg">...</div>

@media(min-width:750px) {
  .W\(50\%\)--sm {
    width: 50%;
  }
}
@media(min-width:1000px) {
  .W\(33\%\)--md {
    width: 33%;
  }
}
@media(min-width:1200px) {
  .W\(25\%\)--lg {
    width: 25%;
  }
}

В MN также можно прекрасно устанавливать брейкпоинты, путем добавления суффикса к записи:


<div class="w50%@m w33%@d w25%@d2 w1/5@ie w1/6@android cr@mouse">...</div>

@media (max-width: 992px) {
  .w50\%\@m {
    width: 50%;
  }
}
@media (min-width: 992px) {
  .w33\%\@d {
    width: 33%;
  }
}
@media (min-width: 1200px) {
  .w25\%\@d2 {
    width: 25%;
  }
}
.ie .w1\/5\@ie {
  width: 20%;
}
.android .w1\/6\@android {
  width: 16.66%;
}
@media (pointer: fine) and (hover: hover) {
  .cr\@mouse {
    cursor: pointer;
  }
}

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


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


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


Пример конфигурирования брейкпоинтов:


module.exports = (mn) => {
  const {media} = mn;

  // media-queries
  media.m = {
    query: '(max-width: 992px)',
    priority: 0,
  };
  media.m2 = {
    query: '(max-width: 768px)',
    priority: 1,
  };
  media.d = {
    query: '(min-width: 992px)',
    priority: 2,
  };
  media.d2 = {
    query: '(min-width: 1200px)',
    priority: 3,
  };
  media.mouse = {
    query: '(pointer: fine) and (hover: hover)',
    priority: 4,
  };
  // ...

  // user agents
  media.mozilla = {
    selector: '.mozilla'
  };
  media.webkit = {
    selector: '.webkit'
  };
  media.ie = {
    selector: '.ie'
  };
  media.iphone = {
    selector: '.iphone'
  };
  media.android = {
    selector: '.android'
  };
  // ...
};

Если Мы используем в нотации брейкпоинт, который нигде не предустановлен, то в CSS получим значение медиа-запроса, которое непосредственно указали в записи:


<div class="w50%@print w50%@any">...</div>

@media print {
  .w50\%\@print {
    width: 50%;
  }
}
@media any {
  .w50\%\@any {
    width: 50%;
  }
}

Также помимо предустанавливаемых брейкпоинтов в нотации MN можно использовать
выражения:


<div class="w50%@768 w50%@768- w50%@768-992">...</div>
<div class="w50%@768-992x100-200 w50%@x100-200">...</div>
<div class="w50%@x100 w50%@x100-">...</div>

@media (max-width: 768px) {
  .w50\%\@768 {
    width: 50%;
  }
}
@media (min-width: 768px) {
  .w50\%\@768- {
    width: 50%;
  }
}
@media (min-width: 768px) and (max-width: 992px) {
  .w50\%\@768-992 {
    width: 50%;
  }
}
@media (min-width: 768px) and (max-width: 992px) and (min-height: 100px) and (max-height: 200px) {
  .w50\%\@768-992x100-200 {
    width: 50%;
  }
}
@media (min-height: 100px) {
  .w50\%\@x100- {
    width: 50%;
  }
}
@media (min-height: 100px) and (max-height: 200px) {
  .w50\%\@x100-200 {
    width: 50%;
  }
}
@media (max-height: 100px) {
  .w50\%\@x100 {
    width: 50%;
  }
}

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


<div class="w50%@768-992^5 w50%@768^1 w50%@992^3">...</div>

@media (max-width: 768px) {
  .w50\%\@768\^1 {
    width: 50%;
  }
}
@media (max-width: 992px) {
  .w50\%\@992\^3 {
    width: 50%;
  }
}
@media (min-width: 768px) and (max-width: 992px) {
  .w50\%\@768-992\^5 {
    width: 50%;
  }
}



Minimalist Notation. Управление приоритетами стилей


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


<div class="cF*2 c0*3 cF00.active*2">...</div>

.cF\*2.cF\*2 {
  color: #fff;
}
.c0\*3.c0\*3.c0\*3 {
  color: #000;
}
.cF00\.active\*2.cF00\.active\*2.active {
  color: #f00;
}

Таким образом, приоритет или, другими словами, специфичность стилей увеличивается за счёт повторения в каскаде генерируемого CSS комбинации класса с самим собой столько раз, сколько Мы указываем в нотации.




Minimalist Notation. Селектор подстроки


С MN можно позволить себе сокращать имена классов в нотации.


Иногда достаточно только фрагмента имени:


<div class="SomeBlock">
  <div class="bgF00>1.*_active">
    <div class="SomeBlock__SomeElement SomeBlock__SomeElement_active">
      ...
    </div>
    <div class="SomeBlock__SomeElement">
      ...
    </div>
    <div class="SomeBlock__SomeElement SomeBlock__SomeElement_active">
      ...
    </div>
  </div>
</div>

.bgF00\>1\.\*_active>[class*=_active] {
  background: #f00;
}

Например, в приложении на React Мы используем компоненты библиотеки Material-UI, которые легально кастомизируются через JSS так:


const React = require('react');
const {render} = require('react-dom');
const {withStyles} = require('@material-ui/core/styles');
const TextField = require('@material-ui/core/TextField').default;

const TextFieldGreen = withStyles({
  root: {
    '& label.Mui-focused': {
      color: 'green',
    },
    '& .MuiOutlinedInput-root': {
      '& fieldset': {
        borderColor: 'red',
      },
      '&:hover fieldset': {
        borderColor: 'yellow',
      },
      '&.Mui-focused fieldset': {
        borderColor: 'green',
      },
    },
  },
})(TextField);

function App() {
  return (
    <TextFieldGreen
      label="Label"
      required
      defaultValue="Value"
    />
  );
}

С помощью MN ради кастомизации элементов Нам придется заморачиваться меньше:


const React = require('react');
const {render} = require('react-dom');
const TextField = require('@material-ui/core/TextField').default;

function TextFieldGreen(props) {
  return (
    <TextField
      {...props}
      className={`
        cGreen>label.*-focused
        bcRed>.*OutlinedInput-root>fieldset
        bcYellow>.*OutlinedInput-root:h>fieldset
        bcGreen>.*OutlinedInput-root.*-focused>fieldset
      ` + (props.className || '')}
    />
  );
}

function App() {
  return (
    <TextFieldGreen
      label="Label"
      required
      defaultValue="Value"
    />
  );
}

Если Нам понадобится кастомизировать значительно большее количество атрибутов, то подход JSS будет разительно более громоздким, чем подход MN.


Так в Нашем случае выглядит сгенерированный CSS:


.cGreen\>label\.\*-focused label[class*=-focused] {
  color: green;
}
.bcRed\>\.\*OutlinedInput-root\>fieldset [class*=OutlinedInput-root] fieldset {
  border-color: red;
}
.bcYellow\>\.\*OutlinedInput-root\:h\>fieldset [class*=OutlinedInput-root]:hover fieldset {
  border-color: yellow;
}
.bcGreen\>\.\*OutlinedInput-root\.\*-focused\>fieldset [class*=OutlinedInput-root][class*=-focused] fieldset {
  border-color: green;
}

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


<ul class="cRed>#*menu-item-">
  <li id="menu-item-1">...</li>
  <li id="menu-item-2">...</li>
  <li id="menu-item-3">...</li>
  <li id="menu-item-4">...</li>
  ...
</ul>

.cRed\>\#\*menu-item- [id*=menu-item-] {
  color: red;
}



Заключение


Как можно видеть, MN больше ориентирован на методологию Atomic / Functional CSS.


Сейчас в ходу Scoped styles, JSS, PostCSS с BEM. Возможно и MN станет популярным где-то в свои 2050-е годы.


После MN Вам уже не захочется возвращаться к прежнему, и использование каких-либо иных классических способов покажется довольно утомительным и нелепым занятием. Ваши плечи будут тяжелеть от просьб написать CSS руками, ибо это будет также странно, как носить от ручья воду в бидонах, имея при этом под носом чистую воду из крана — хотя, конечно, подобные расточительные глупости вполне себе в норме для Нашего общества, но тем не менее...




Ссылки


Библиотека MN


MN get started example

Комментарии 9

    +5
    Сомнительная польза метода:

    — Добавляем 10 слоев и увеличиваем комплексность программы, чтобы упростить написание кода…

    — Тратим большое количество времени на изучение и создание сложной системы, с помощью которой можно будет писать код всего лишь на 10% — 20% быстрее, и в итоге окупим временные затраты через пару месяцев — лет? Хотя из примеров я так и не понял как именно будем экономить время, ведь мы пишем столько же кода, только в краткой форме, но зато, используя более сложную логику. А логика ведь обычно и является самой время-затратной частью в написании любого кода
      +2

      И при этом это ещё и читать невозможно. Что классы, что сгенерированные стили. Отладка превратится в ад…

      +4

      Таких инструментов очень много. Конкретно этот неизмеримо сложнее LESS/SASS.
      Чем дальше от нэйтива, тем сложнее learning curve.
      Такие инструменты становятся малопопулярными очень быстро.


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


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


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


      P.S.: А еще синтаксис похож на регулярные выражения. Шуток про них предостаточно.

        +4
        Я поставил статье плюс за подробность изложения, но если говорить по существу — это тихий ужас. По уровню удобочитаемости ваша система находится где-то между перлом и брейнфаком. На этом фоне я уже почти люблю Tailwind (хотя на самом деле нет).
          +3

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


          Основное преимущество атрибута class — возможности отделить семантику от представления. На уровне HTML указывается, что это за элемент ("блок комментария", "элемент меню", ...), а на уровне CSS конкретизируется, как он должен выглядеть ("черная рамка", "отступ 2 пикселя", ...).


          Ваш же подход это преимущество отвергает. По сути, вы переизобрели атрибут style, только с самопальным синтаксисом.

            0

            Народ использовал классы раньше потому что тогда не было такого распространения компонентов и классы это был возможно единственный способ не дублировать стили при добавлении какой-нибудь кнопки в разных местах.
            А теперь с появлением компонентного подхода какой смысл в классах? Нет больше той проблемы с дублированием зато есть неудобства в лишних переключениях между тегами и стилями и необходимость придумывать имена для классов.
            Вопрос — если у меня есть уже компонент кпопки или какой-то карточки, зачем мне дополнительно создавать класс "button" или "card" и выносить стили в отдельный файл, или в отдельное место в том же файле? Или вот, например, когда мне нужно что-то изменить в дизайне. Я уже зашел в файл компонента <Card/> или <Button/> с целью изменить или поправить дизайн — зачем мне еще раз видеть на теге класс "card" и затем переходить в другое место для того чтобы увидеть стили? Почему бы просто не писать стили прямо рядом с тегами если мы уже разбиваем верстку на атомарные компоненты?

              +2
              Кнопка — хоть и атомарный компонент, но в реальности может иметь несколько десятков состояний, модификаторов и их комбинаций (цвета, размеры, ховеры и так далее). Если всё это писать «рядом с тэгами», обильно обвешивая условиями — вы сильно перемешаете логику и представление, а это плохая идея всегда, независимо от фреймворка и разбиения на компоненты.
              Далее. У нас есть обычная кнопка и, скажем, кнопка с выпадайкой (иконкой, бэджиком, счетчиком — куча вариантов). Это отдельный компонент со своим поведением, но в большей части визуальных стилей он совпадает с обычной кнопкой. Общее надо куда-то выносить. Или в классы, или в какие-то миксины.
            +3
            А зачем? Везде сервера используют gzip или аналоги. Что это за экономия непонятно на чём. Убить кучу времени на понимание этой билиберды ради чего? И кто это поддерживать будет если что? Любой разумный верстальщик эту хуерду будет выпиливать нещадно.
              +1

              Похоже на очередную попытку НЕверстальщика заново изобрести вёрстку.


              Ребята, пожалуйста, прежде чем пытаться выпилить css из нашего бренного мира, разберитесь в том, как им правильно пользоваться, и тогда вы поймёте, что все потенциальные проблемы легко решаемы. Давно придумано и проверено временем множество методологий и инструментов, для проектов любой сложности. Почему бы не поисследовать рынок, вместо того, чтобы изобретать свой велосипед. Возможно, если бы вы знали тот же Emmet, то и желания создавать MN у вас не появилось бы.


              Страшно представить, как будет выглядеть исходный код более-менее оформленного дизайнерами проекта. MN сгодился бы, разве что, для построения шаблонов. И все равно, лучше уж использовать инлайн стили и специально придуманный для них атрибут style. Код будет подлиннее, зато в 100 раз понятнее.
              А ещё лучше сделать небольшое усилие, и стилизовать все с помощью минимального набора классов.

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

              Самое читаемое