Продолжение перевода статьи «Reimagine Atomic CSS» двухлетней давности одного из членов команды Vue core Anthony Fu, автора UnoCSS, в которой рассматривается уже сам UnoCSS.

Часть первая.

Введение в UnoCSS

UnoCSS — мгновенный (instant) атомарный CSS‑движок с максимальной производительностью и гибкостью.

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

Двигатель

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

Мы представляем себе, что UnoCSS сможет имитировать функциональность большинства существующих атомарных CSS‑фреймворков. И, возможно, будет использован в качестве движка для создания новых атомарных CSS‑фреймворков! Например:

import UnocssPlugin from '@unocss/vite'

// the following presets do not exist at this moment,
// contribution welcome!
import PresetTachyons from '@unocss/preset-tachyons'
import PresetBootstrap from '@unocss/preset-bootstrap'
import PresetTailwind from '@unocss/preset-tailwind'
import PresetWindi from '@unocss/preset-windi'
import PresetAntfu from '@antfu/oh-my-cool-unocss-preset'

export default {
  plugins: [
    UnocssPlugin({
      presets: [
        // PresetTachyons,
        PresetBootstrap,
        // PresetTailwind,
        // PresetWindi,
        // PresetAntfu

        // pick one... or multiple!
      ]
    })
  ]
}

Давайте посмотрим, как это стало возможным.

Интуитивный и полностью настраиваемый

Основные цели UnoCSS — интуитивность и настраиваемость. Он позволяет создавать собственные утилиты буквально за считанные секунды.

Далее вы найдете краткое руководство:

Статические правила

Атомарный CSS может оказаться огромным по объему. Важно, чтобы определение правил было простым и легко читаемым. Чтобы создать пользовательское правило для UnoCSS, вы можете написать его следующим образом:

rules: [
  ['m-1', { margin: '0.25rem' }]
]

Когда m-1 будет обнаружен в кодовой базе пользователя, будет сгенерирован следующий CSS:

.m-1 { margin: 0.25rem; }

Динамические правила

Чтобы сделать его динамическим, измените matcher на RegExp, а body на функцию:

rules: [
  [/^m-(\d+)$/, ([, d]) => ({ margin: `${d / 4}rem` })],
  [/^p-(\d+)$/, match => ({ padding: `${match[1] / 4}rem` })],
]

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

Например, при использовании:

<div class="m-100">
  <button class="m-3">
    <icon class="p-5" />
    My Button
  </button>
</div>

Соответствующий CSS будет сгенерирован:

.m-100 { margin: 25rem; }
.m-3 { margin: 0.75rem; }
.p-5 { padding: 1.25rem; }

Вот и все. Осталось только добавить другие утилиты, использующие тот же шаблон, и теперь у вас есть свой собственный атомарный CSS!

Варианты (Variants)

Варианты в UnoCSS также просты и в то же время мощны. Вот несколько примеров:

variants: [
  // support `hover:` for all rules
  {
    match: s => s.startsWith('hover:') ? s.slice(6) : null,
    selector: s => `${s}:hover`,
  },
  // support `!` prefix to make the rule important
  {
    match: s => s.startsWith('!') ? s.slice(1) : null,
    rewrite: (entries) => {
      // append ` !important` to all css values
      entries.forEach(e => e[1] += ' !important')
      return entries
    },
  }
]

Конфигурации вариантов могут быть немного расширены. Из‑за большого объема статьи я опущу подробное объяснение здесь, вы можете обратиться к документации за более подробной информацией.

Пресеты (presets)

Сейчас вы можете упаковать свои пользовательские правила и варианты в пресеты и поделиться ими с другими — или создать даже свой собственный атомарный CSS‑фреймворк поверх UnoCSS!

Кроме того, мы поставляем несколько пресетов, чтобы вы могли быстро освоить их.

Однако стоит упомянуть, что стандартный @unocss/preset-uno пресет (все еще экспериментальный) является общим суперсетом популярных фреймворков, включая Tailwind CSS, Windi CSS, Bootstrap, Tachyons и т. д.

Например, оба ml-3 (Tailwind), ms-2 (Bootstrap), ma4 (Tachyons), mt-10px (Windi CSS) являются валидными.

.ma4 { margin: 1rem; }
.ml-3 { margin-left: 0.75rem; }
.ms-2 { margin-inline-start: 0.5rem; }
.mt-10px { margin-top: 10px; }

Узнайте больше о предустановке по умолчанию.

Гибкость

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

Давайте раскроем истинную силу UnoCSS:

Режим атрибутирования (Attributify Mode)

Режим атрибутирования - одна из любимых функций Windi CSS. Он помогает вам лучше организовать и сгруппировать ваши утилиты с помощью атрибутов.

Вот так преобразуется ваш код Tailwind:

<button class="bg-blue-400 hover:bg-blue-500 text-sm text-white font-mono font-light py-2 px-4 rounded border-2 border-blue-200 dark:bg-blue-500 dark:hover:bg-blue-600">
  Button
</button>

к:

<button 
  bg="blue-400 hover:blue-500 dark:blue-500 dark:hover:blue-600"
  text="sm white"
  font="mono light"
  p="y-2 x-4"
  border="2 rounded blue-200"
>
  Button
</button>

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

В UnoCSS мы реализовали режим Attributify Mode, используя только один вариант и один экстрактор с менее чем 100 строками кода в общей сложности! Что еще более важно, он напрямую работает для любых пользовательских правил, которые вы определили!

В дополнение к режиму Windi's Attributify Mode мы также поддерживаем атрибуты без значений с помощью нескольких строк изменений:

<div class="m-2 rounded text-teal-400" />

теперь можно

<div m-2 rounded text-teal-400 />

Режим атрибутирования предоставляется через пресет @unocss/preset-attributify, подробное использование смотрите в его документации.

Чистые CSS-иконки

Если вы читали мой предыдущий пост Journey with Icons Continues, то должны знать, что я очень увлечен иконками и активно ищу решения для иконок. На этот раз, благодаря гибкости UnoCSS, у нас даже могут быть чисто CSS‑иконки! Да, вы не ослышались, только на CSS и ноль JavaScript! Давайте посмотрим, как это выглядит:

<!-- A basic anchor icon from Phosphor icons -->
<div class="i-ph-anchor-simple-thin" />
<!-- An orange alarm from Material Design Icons -->
<div class="i-mdi-alarm text-orange-400 hover:text-teal-400" />
<!-- A large Vue logo -->
<div class="i-logos-vue text-3xl" />
<!-- Sun in light mode, Moon in dark mode, from Carbon -->
<button class="i-carbon-sun dark:i-carbon-moon" />
<!-- Twemoji of laugh, turns to tear on hovering -->
<div class="i-twemoji-grinning-face-with-smiling-eyes hover:i-twemoji-face-with-tears-of-joy" />

Комбинируя варианты, вы можете даже переключать иконки на основе состояния наведения курсора или даже цветовой схемы. Поиграйте с демонстрацией выше и убедитесь в этом (демонстрация доступна в оригинальной статье — прим. переводчика). Благодаря потрясающему проекту Iconify у вас есть доступ к более чем 10 000 иконок из более чем 100 популярных наборов иконок по требованию.

И снова эта функция написана менее чем на 100 строк кода. Ознакомьтесь с реализацией пресета @unocss/preset-icons, чтобы узнать, как это работает!

Обновление: Читайте мой новый пост Иконки в чистом CSS, чтобы узнать об этом больше!

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

Область видимости (scoping)

Еще одна проблема, с которой я столкнулся при использовании Tailwind/Windi, — это префлайт (preflight). Preflight сбрасывает нативные элементы и обеспечивает некоторый откат для переменных CSS. Это замечательно при разработке нового приложения, использующего исключительно Tailwind/Windi, но когда вы хотите заставить их работать с другими фреймворками пользовательского интерфейса или совместно использовать некоторые компоненты с помощью утилит Tailwind, префлайт часто вносит множество конфликтов, которые ломают существующий пользовательский интерфейс.

Поэтому UnoCSS сделал еще один агрессивный шаг, отказавшись от поддержки префлайтов. Вместо этого он оставил контроль над сбросом CSS полностью на усмотрение пользователей (или фреймворков поверх UnoCSS), которые могут использовать то, что соответствует их потребностям (Normalize.css, Reset.css, сброс UI‑фреймворков и т. д.)

Это также позволяет UnoCSS иметь больше возможностей для CSS Scoping. Например, у нас есть экспериментальный режим scoped‑vue в плагине Vite для генерации scoped‑стилей для каждого компонента, чтобы вы могли безопасно поставлять их как библиотеку компонентов, используя атомарный CSS, не беспокоясь о конфликте с CSS пользователей. Например:

<template>
  <div class="m-2 rounded"><slot></div>
<template>

<!-- the following will be inject in the bundler -->
<style scoped>
.m-2{margin:0.5rem;}
.rounded{border-radius:0.25rem;}
</style>

Производительность

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

10/21/2021, 2:17:45 PM
1656 utilities | x50 runs

none                            8.75 ms /    0.00 ms
unocss       v0.0.0            13.72 ms /    4.97 ms (x1.00)
windicss     v3.1.9           980.41 ms /  971.66 ms (x195.36)
tailwindcss  v3.0.0-alpha.1  1258.54 ms / 1249.79 ms (x251.28)

Оказывается, UnoCSS может быть 200x быстрее, чем JIT Tailwind и Windi CSS. Честно говоря, при подходе «по требованию» и Windi, и Tailwind JIT уже очень быстры, поэтому прирост производительности UnoCSS может быть не очень ощутимым. Однако практически нулевые накладные расходы означают, что вы можете интегрировать UnoCSS в существующий проект для работы с другими фреймворками в качестве дополнительного решения, не беспокоясь о снижении производительности.

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

Нет парсинга, нет AST

Внутренне Tailwind полагался на модификацию AST PostCSS, в то время как Windi написал собственный парсер и AST. Учитывая тот факт, что изменения в утилитах не часто ожидаются во время разработки, UnoCSS генерирует утилиты с помощью очень дешевой конкатенации строк, вместо того чтобы внедрять целый процесс парсера и генерации. При этом UnoCSS агрессивно кэширует имена классов с их сгенерированной CSS‑строкой, что позволяет обойти весь процесс подбора и генерации при повторной встрече с теми же утилитами.

Одиночное прохождение

Как уже упоминалось в предыдущем разделе, и Windi CSS, и Tailwind JIT полагаются на предварительное сканирование файловой системы и используют fs watcher для HMR. Файловый ввод‑вывод неизбежно влечет за собой некоторые накладные расходы, в то время как ваши инструменты сборки фактически должны загрузить их еще раз. Так почему бы нам не использовать содержимое, которое уже было прочитано инструментами разработки, напрямую?

Помимо независимого ядра генератора, UnoCSS намеренно предоставляет только плагин Vite, что позволяет ему сосредоточиться на наилучшей интеграции с Vite.

Обновление: Теперь он также предоставляет плагин Webpack и время выполнения CSS‑in‑JS.

В Vite хук transform будет итерироваться по всем файлам с их содержимым. Поэтому мы можем написать плагин, который будет собирать их, например:

export default {
  plugins: [
    {
      name: 'unocss',
      transform(code, id) {
        // filter out the files you don't want to scan
        if (!filter(id))
          return

        // scan the code (also handles invalidate on dev)
        scan(code, id)

        // we just want the content, so we don't transform the code
        return null
      },
      resolveId(id) {
        return id === VIRTUAL_CSS_ID ? id : null
      },
      async load(id) {
        // generated css is provide as a virtual module
        if (id === VIRTUAL_CSS_ID)
          return { code: await generate() }
      }
    }
  ]
}

Поскольку Vite также обрабатывает HMR и будет снова задействовать хук transform при изменении файла, это позволяет UnoCSS завершить все за один проход без дублирования файлового ввода‑вывода и fs watcher. Кроме того, при таком подходе сканирование опирается на граф модулей, а не на глобализацию файлов. Это означает, что только те модули, которые были включены в ваше приложение, будут влиять на генерируемый CSS, а не любые файлы в ваших папках.

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

Можно ли использовать это сейчас?

Короткий ответ: Да, но с осторожностью.

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

Он не предназначен для замены Windi CSS или Tailwind (лучше подождать Windi CSS v4). Мы не рекомендуем полностью переводить существующие проекты на UnoCSS. Вы можете попробовать его в новых проектах или использовать в качестве дополнения к существующему CSS‑фреймворку (например, отключить предустановки по умолчанию и использовать предустановку иконок исключительно для иконок с чистым CSS или создать свои собственные правила).

О, кстати, сайт, который вы читаете, теперь исключительно на UnoCSS, чтобы вы могли ссылаться:P.

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

Завершение

Большое спасибо за то, что дочитали до конца! Если это вас заинтересовало, не забудьте заглянуть в репозиторий unocss/unocss за подробностями и поиграть с ним в Online Playground.

Anthony Fu


Дополнение (от переводчика)

На данный момент UnoCSS давно уже находится в стабильном состоянии, стал довольно популярен и может быть использован для рабочих проектов. Его сайт — unocss.dev.

Чем UnoCSS отличается от Windi CSS?

UnoCSS был создан одним из членов команды Windi CSS, при этом многое было взято из работы, которую мы проделали в Windi CSS.Хотя Windi CSS больше активно не поддерживается (по состоянию на март 2023 года), вы можете рассматривать UnoCSS как «духовного наследника» Windi CSS. UnoCSS унаследовал от Windi характер работы по требованию, режим атрибутирования, ярлыки, группы вариантов, режим компиляции и многое другое.

Кроме того, UnoCSS создан с учетом максимальной расширяемости и производительности, что позволяет нам внедрять такие новые возможности, как чистые CSS‑иконки, атрибутирование без значений, tagify, веб‑шрифты и т. д. Самое главное, UnoCSS создан как атомарный CSS‑движок, где все функции являются необязательными, что позволяет легко создавать собственные конвенции, собственную систему дизайна и собственные пресеты — с теми комбинациями функций, которые вы хотите.

Чем UnoCSS отличается от Tailwind CSS?

И Windi CSS, и UnoCSS черпали вдохновение в Tailwind CSS.Так как UnoCSS создан с нуля, мы можем получить отличный обзор того, как атомарный CSS был разработан с учетом предшествующих искусств и абстрагирован в элегантный и мощный API. Поскольку цели разработки UnoCSS совершенно разные, сравнивать его с Tailwind CSS не совсем корректно. Но мы постараемся перечислить несколько отличий.

Tailwind CSS — это плагин для PostCSS, в то время как UnoCSS — это изоморфный движок с кучей первоклассных интеграций с инструментами сборки (включая плагин для PostCSS).Это означает, что UnoCSS может быть гораздо более гибким для использования в различных местах (например, CDN Runtime, который генерирует CSS на лету) и иметь глубокие интеграции с инструментами сборки для обеспечения лучшего HMR, производительности и опыта разработчика (например, Inspector).

Если не принимать во внимание технические компромиссы, UnoCSS также разработан как полностью расширяемый и настраиваемый, в то время как Tailwind CSS намного менее гибок (opinionated). Создать собственную систему дизайна (или маркеры дизайна) поверх Tailwind CSS может быть сложно, и вы не сможете отойти от условностей Tailwind CSS.С UnoCSS вы можете создавать практически все, что захотите, с полным контролем.

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

Благодаря гибкости, которую обеспечивает UnoCSS, мы можем экспериментировать с множеством инновационных функций, например:

«Недостатком» UnoCSS по сравнению с Tailwind CSS можно назвать то, что он не поддерживает систему плагинов и конфигураций Tailwind, что может затруднить миграцию с сильно настроенного проекта Tailwind CSS. Это решение было принято для того, чтобы сделать UnoCSS высокопроизводительным и расширяемым, и мы считаем, что этот компромисс того стоит.


Узнать интересную и полезную информацию о Vue.js и фронтенде в целом, а также изучить фреймворк по переводу лучшего учебника по данной теме «Vue.js 3 — Шаблоны проектирования и Лучшие практики» можно на нашем сайте: Vue‑FAQ.org.

Также заходите на наш Телеграм‑канал, посвященный Vue.js и фронтенду в целом: https://t.me/vuefaq

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


  1. Fragster
    04.12.2023 10:14
    +2

    А теперь открываем документацию tailwind и смотрим на неё. Практически сразу все понятно. Четко описано как получить то, что нужно, какие возможности есть. Система плагинов дается в самом конце.

    После этого пытаемся открыть документацию на unoCSS и понять, как им пользоваться, просто чтобы получить нужный результат. Представим, что вы до этого документацию по tailwind или чему-то другому не видели и что это первый "атомарный фреймворк", с которым вы решили познакомиться, как с "самым гибким и от создателей vue".

    Лично у меня не возникло желания даже попробовать. И даже зная и применяя tailwind я не хочу пробовать uno, потому что наверняка придется спотыкаться на чем-то, что не будет работать так, как мне надо, а искать ошибки и обходные решения в стиле "смотрите код" прям очень не хочется.

    Отдельно странно то, что на сам vue почти идеальная документация.

    Hidden text


    1. zede
      04.12.2023 10:14
      +1

      Скорее всего если вы искали документацию по tailwind, то ее конечно нет на unoCSS. Базовая информация представлена по кнопке Getting Started. И ключевое что нужно было понять: это не новый Tailwind а буквально фреймворк для создания CSS с атомик подходом (там как раз пример с 1 правилом).

      И из коробки он имеет ровно 0 выхлопа и вам показывают как создавать ваши правила для создания утилити классов.

      Дальше вам сообщается что вот все это объединяется в preset-ы. А сами пресеты уже подключаются к проекту: вас отсылают к стандартным пресетам по ссылке. И уже от выбранных пресетов зависит подход (в том числе есть пресет для подобия tw)

      Дальше есть ссылки на плейграунд и интерактивную документацию по стандартным пресетам.

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


      1. Fragster
        04.12.2023 10:14

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

        Плейграунд и интерактивная документация? Вы пробовали сравнить их и документацию на tailwind(или windy, при том, что они не на 100% совместимы)? Вы действительно не видите разницу в том, чтобы показать что есть и "вот тут вы можете найти если знаете, что искать". К тому же она тормозит (попробуйте поискать аналог https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-parent-state).

        Про отсылки к документации на tailwind - я уже выше написал, что не готов вслепую тыкаться в то, что же в их "стандартных зуб даю совместимых" пресетах работает не так как я ожидаю. Ну и кроме tailwind (он же windy) есть какой-то минимальный пресет mini, да и другие тоже.