Особая благодарность автору идеи @akopyl Его статья по теме.

Идея нехитрая, но очень заманчивая — пишешь CSS, пишется и CSS и HTML одновременно. Это ускорит и упростит разработку, да и реализовать несложно — HTML очень простой язык, по большому счёту состоящий из тегов, атрибутов и текста. У него есть и некоторые специфические особенности, вроде карт импорта и shadow root, используемые нечасто, да и порой лишь с помощью скриптов, например, когда речь идёт о пользовательских элементах.
Попытка соединить функцию «разметки» и стилизации не выглядит сложной, уж очень простые языки.

Изначально идея имела реализации: раз, и даже две, но:

  1. Одна (а то и обе, ибо ссылаются на API document), насколько я понял, работают исключительно в рамках веб‑страницы, то есть, не могут быть использованы, как самодостаточные библиотеки.

  2. Криво реализуют текст в тегах — либо через псевдо‑элементы (sic!), либо через свойство content, что уже лучше, но не совсем валидно (теоретически, может произойти коллизия, по этой логике добавляли data-* атрибуты для HTML).

  3. Писать атрибуты для элементов — сущий ад, ибо делается это только через селектор.

Автором статьи преследовалась цель создать улучшенный вариант библиотеки, которая работает вне рамок веб‑страницы (как отдельная библиотека), имеет усовершенствованные механизмы добавления тегов, текста… И по мелочи.

Что это и где достать

Проект на npm и GitHub.
Суть библиотеки в том, чтобы конвертировать это:

section#some-id {
  --text: 'Это текст, товарищ!';
  --attr-title: 'Заголовок';
  background: red;
  color: aliceblue;
}
section#some-id header[data-attribute='v'] {
  --text: 'Это текст шапки';
  color: blue;
}
section#some-id span {
  --text: 'Это внутренний текст';
  --text-after: 'А это текст после';
  color: peru;
}

В это:

<section id="some-id" title="Заголовок">
  Это текст, товарищ!
  <header data-attribute="v">Это текст шапки</header>
  <span> Это внутренний текст </span>А это текст после
</section>

Далее — принцип работы библиотеки невкратце.

Элементы

Создать элемент можно через селектор:

div.classname#id[attr-1][attr-2='v'] {
  /* Ни одна из частей селектора обязательной не является */
  /* Но хоть что-то нужно оставить */
}

div {}
div span {
  /* Поддерживается вложенность */
}
<div id="id" class="classname" attr-1 attr-2="v"></div>

<div>
  <span></span>
</div>

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

  • Псевдо‑класс

  • Псевдо‑элемент

  • Один из этих селекторов: *, +, >, ||, |, ~

  • Или оберните в @at-rule

Пример — эти селекторы будут проигнорированы:

> div.classname#id[attr-1][attr-2='v'] {
}
div span * {
}
div::before {
  /* Да, и этот тоже */
}
div:not(:has(span)) {
  /* И этот тоже! */
}
@container (width > 1440px) {
  span[data-a='Этот элемент будет проигнорирован тоже'] {}
}

Текст и атрибуты

Атрибуты можно задать через селектор (бывает полезно при стилизации), а можно через кастомные свойства --attr-* и --attrs

/* В селекторе */
a[title='Title!']{
  
  /* Точечно (внимание на то, что после attr-) */
  --attr-href:'./index.html';
  
  /* И массово! */
  --attrs:'target="_self" rel="noopener noreferrer"';
}
<a title="Title!"
   href="./index.html"
   target="_self"
   rel="noopener noreferrer"
></a>

Вписать текст в элемент не проблема - через специальное кастомное свойство:

div {
  --text: 'И вновь продолжается бой';
}
<div> И вновь продолжается бой </div>

Если нужно вставить тег посреди текста, придётся немного вывернуться.
Технически, можно просто вписать тег в переменную текста:

div { --text: 'Никогда <span>не</span> делайте так!'; }

Но, как и указано, лучше так не делать.
Для этого есть специальные кастомные свойства, позволяющие вписать текст до и после тега:

div {
  --text: 'Текст внутри div';
}
div span {
  --text: 'Это текст внутри';
  --text-before: '| до тега';
  --text-after: '| после тега';
}
<div>
  Текст внутри div | до тега<span> Это текст внутри </span>| после тега
</div>

API

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

import { CssToHtml } from 'css2html'

let result = new CssToHtml({ css: "div{}", })
console.log(result.outputHTML)

Этот код выводит в терминал/консоль результат обработки простейшего CSS из одного тега.
Но это, конечно, не всё, это же минимум.

Для записи html непосредственно в файл, добавьте параметр write
(Внимание! Весь файл будет перезаписан)

new CssToHtml({
 ...,
  write: {
    in: "Ваш_путь_к_html_файлу",
  },
})

Однако, есть один нюанс. Дело в том, что html имеет некоторый функционал, который через css передать так трудно, что лучше не пытаться. Например — скрипты написанные прямо в HTML или карты импорта.

А по умолчанию файл перезаписывается полностью, что ведёт к утрате бесценного кода, который не описать нормально в рамках css. В таких случаях нужно указывать конкретно, куда писать, вместо перезаписи всего.
С помощью параметров after и before как раз это и можно сделать — указывается область, которая будет доступна для перезаписи.

new CssToHtml({
  ...,
  write: {
    in: "Ваш_путь_к_html_файлу",
    after: "<body>",
    before: "</body>",
  },
})

А ещё, HTML который создаёт библиотека кое‑как форматируется. За это отвечает плагин js‑beatify, которому можно передать параметры:

new CssToHtml({
  ...,
  formatterOptions: {
    indent_size: 2,
  },
})

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

Нечто большее

Попытки объединить разметку, стилизацию и даже скрипты предпринимались неоднократно. JSX, описание стилей через JS‑объект, даже Tailwind по сути занимается встраиванием стилей в HTML, брр…
Tailwind достоин отдельных тёплых слов, а по поводу остальных — попытки достойные, но результат недостаточный — они не объединяют. Они, грубо говоря, позволяют написать всё «в одну кучу» за счёт разбиения на зоны того или иного языка внутри одного файла. Это как взять кирпичи, бетон, стекло и пытаться сделать из них цельную стену.

Эта библиотека— попытка свести к минимуму или вовсе отказаться от одного из инструментов фронт‑энд разработки — HTML, перенеся его функции на другой инструмент — CSS. Можно пойти ещё дальше (за пределами этой библиотеки, естественно), соединив функции разметки, стилизации и скриптов — получился бы этакий единый язык веб‑страниц (название временное, придумано на "ход"у).

Очень большой соблазн повторить JSX — он кажется подходящим, но нет. Помимо причин, описанных выше, он слишком «жёсткий» для стилизации (либо через строки, либо через объекты, где селекторы в кавычках — отдельный «шедевр»), а попытки впилить в него ещё и CSS в чистом виде сделают его ещё больше походящим на кучу стройматериала.

Как и автор идеи этой библиотеки, я приведу свои мысли. На этот раз — по поводу языка, которого ещё нет.

  • Менять интернет из‑за нового языка никто не будет, ломать совместимость — последнее дело для таких больших структур. Лучше всего делать язык как препроцессор.

  • Язык должен одновременно описывать стили, разметку и логику используя одну лексику и грамматику, без разбиения на «диалекты».

  • Стоит вдохновиться чем-нибудь вроде 11l. Честно, C‑образные языки уже достали, да и громоздкие они для таких задач. Желательно без однобуквенных операторов.

Сейчас мне на такую задачу не хватит времени. Посмотрим, хватит ли у кого смелости, желания и умения.

Комментарии (13)


  1. yarkov
    02.10.2024 07:22

    Какая нечитабельная жесть


    1. LeninIvanov Автор
      02.10.2024 07:22

      Пожалуйста укажите, как мне её написать понятней - буду премного благодарен!


      1. yarkov
        02.10.2024 07:22

        Прошу прощения, я непонятно выразился. Это я не про статью, а про синтаксис шаблонов "до".


        1. LeninIvanov Автор
          02.10.2024 07:22

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


  1. delphinpro
    02.10.2024 07:22

    Когда шутка затягивается, она перестаёт быть интересной.

    Автор поста, на который вы ссылаетесь в первом предложении, просто пошутил.

    вовсе отказаться от одного из инструментов фронт‑энд разработки

    Такие попытки есть, и они даже работают. Только за основу берут javascript. Решения css-in-js интегрируют стилевые таблицы в css, а, например, библиотека bem.js позволяет описывать html в json структуре. И это выглядит более логичным, чем запихнуть html в css.


    1. LeninIvanov Автор
      02.10.2024 07:22

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

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


      1. delphinpro
        02.10.2024 07:22

        Если это не шутка, то какая вообще цель преследуется и какие проблемы решаются?

        Вы вообще ничего об этом не написали, а автор той статьи говорит лишь о DRY - якобы избавляется от проблем с повторением сущностей. Он пишет:

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

        Резонно. Смотрим, как это у вас решается:

        .block {
        ....
        }

        /* много кода или где-то на другой странице */

        /* тут нужно повторить блок */

        .block {}


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

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


        1. LeninIvanov Автор
          02.10.2024 07:22

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

          Суть то в том, что CSS нередко дублирует структуру HTML - так зачем писать одно и тоже на разных языках в двух файлах, если можно написать всё в одном и оно автоматически продублируется в другой?


  1. monochromer
    02.10.2024 07:22

    Скоро будет работать во всех браузерах:

    <section id="some-id">
      <style>
        @scope {
          :scope {
            border: 2px solid;
            padding: 1rem;
    
            & > header {
              font-weight: 600;
            }
    
            & > p {
              font-style: italic;
            }
          }      
        }
      </style>
    
      Some Text
      <header>Section Title</header>
      <p>Section Text</p>
    </section>
    

    Гораздо читабельнее.


    1. LeninIvanov Автор
      02.10.2024 07:22

      А, вы про вложенность? Да, это отличная штука, жаль что сделана кривовато (не работает как конкатенация, очень ограничивает).
      Поддержка библиотекой данной функции, впрочем, не от меня зависит - она зависит от того, будет ли кто её встраивать в свои преобразователи CSS в AST, вроде этой. Они не торопятся, я не видел ни одной, что поддерживала бы.


      1. monochromer
        02.10.2024 07:22
        +1

        нет, вложенность тут не главное. Конкатенация селекторов по типу `&__title` это зло.


  1. isumix
    02.10.2024 07:22

    Подскажите как создавать сложные вложенные элементы внутри других элементов?
    Еще также не понятно как создать один стиль или шаблон и применить его ко списку элементов?


    1. LeninIvanov Автор
      02.10.2024 07:22

      создавать сложные вложенные элементы внутри других элементов

      Тут нужен пример, слишком абстрактная формулировка.

      как создать один стиль или шаблон и применить его ко списку элементов

      По принципу миксина - написал там, используешь где угодно - никак, это делается пост/предпроцессорами.
      Если вам нужно создать стиль, который будет применён на группу элементов, но без создания элемента - вот пример:

      :is(span, p) {
        color: red;
      }
      /* или */
      main > span,
      main > p {
        color: red;
      }
      /* или */
      * span,
      * p {
        color: red;
      }
      
      span {
        --text: 'Span 1';
      }
      p {
        --text: 'Text 2';
      }