CSS in JS vs. Theme in JS = ?

Когда первый раз увидел CSS in JS, то подумал, что мир фронтенда поехал окончательно и бесповоротно, но с течением времени, я поменял своё мнение и вот почему…

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

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

Кроме этого, CSS in JS позволяет на лету сделать дедупликацию и не генерировать ненужных правил. Всё это делает эту идею не такой уже бредовой. Ну и инкапсуляция стилей.

Поэтому подумав, дал зеленый свет этой идее и написал для себя @artifact-project/css (по большому счету, это proof-of-concept и многое не готово, но это не главное)

Но (!), всё это конечно замечательно, но по сути это просто ещё один способ написания бессвязного CSS, который опять никак не связан с компонентами (шёпотом: BEM, тебе нужен bem… просто вспомни)

Прошло время, пришла зима и я понял, что пора написать очередной UIKit ;]

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

Спецификация

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

В итоге получился тип `Theme<ThemeSpec>`:

type ButtonProps {
size?: 'small' | 'big'; // Размер кнопки
disabled?: boolean;
... // остальные свойства
theme?: Theme<{ // описание темы
host: Pick<ButtonProps, 'size' | 'disabled'>; // модификаторы
elements: {};
}>;
}

И так, вот мы и описали тему, а именно мы сказали, что для кнопки можно задать стили для (дальше я буду писать в термина BEM: block_modifier-state, block__element_modifier-state):

  • host_disabled — кнопка задизаблена
  • host_size — размер кнопки+ для host_size-small иhost_size-big

Так же можно было определить спеку для элементов «кнопки».

Всё, теперь создаём сам компонент с получением тему в нём:

function Button(props: ButtonProps) {
const theme = getTheme(Button, props); // получаем тему компонента
const hostTheme = theme.for('host'); // тему host-элемента
  // конфигурируем тему на основе props
hostTheme.set('disabled', props.disabled);
props.size && hostTheme.set('size', props.size);

return <button className={hostTheme}>...</button>;
}

В итоге получился компонент, который не имеет «стилей», а только запрашивает тему, всё это обмазано TS, поэтому автокомплит, все дела, красотища:

Создание темы

И так, спецификацию темы описали, теперь нужен CSS к ней:

const buttonTheme = createThemeFor(Button)({
host: {
fontSize: 'inherit',
':modifiers': {
disabled: { opacity: .5 },
size: {
'small': {fontSize: '50%'},
'big': {fontSize: '200%'},
},
},
},
elements: {},
});

Вроде ничего сложно, но это не так! Всё это на TS и вот этот объект должен проверяться и описываться на основе ButtonProps['theme'] , а для это нужно было разработать мега type … и вот спустя почти две недели, в три итерации у меня получилось

Первая -> Вторая -> Финальная версия

После этого, с уверенностью могу сказать, что познал typescript-дзен :)

Применение темы

Думаю уже догадались, что темизация организована через контекст, притом ещё есть поддержка вложенности контекста, поэтому тему можно переопределять на разных уровнях, ну и как несложно догадаться, тему можно просто сунуть через props.theme а не делать Button2, если вы понимаете о чем я.

По моему это успех ;]

Конечно это не финал, т.к. архитектор из меня так себе, надо писать дальше и что-то больше, чем вшивая кнопка :]

Так что это только начало, но мне почему-то нравится ;]

PS Надеюсь вы заметили, что я нигде не использую явное указание дженериков, типы выводятся на основе входных параметров, поэтому запомните, если вы в ТС явно описываете типы при использовании, значит что-то пошло не так.

PSS Моя спел. чекер совсем умер, поэтому не ругайте меня.