Всем привет, меня зовут Виталик, я senior фронтенд-разработчик Skyeng. Наша команда делает онлайн-платформу Vimbox для изучения английского языка. Примерно год назад мы с дизайнером доделали небольшой UI kit, искоренивший хаос в интерфейсе и кодовой базе. Оказалось, что в компании не мы одни хотели UI kit, и к нам стали приходить другие команды за советом «как написать собственный». Нам удалось отговорить их от этой затеи, пообещав темизировать свой – это сэкономило компании сотни часов разработки. Выбирая решение, мы рассмотрели Angular Material, кастомизированные сборки и CSS Variables и в итоге остановились на последних, несмотря на их слабую совместимость с SCSS, основой имевшегося UI kit. Под катом – подробности того, что мы сделали.  Первый UI kit состоял из шрифтов, палитры, набора элементов для создания форм (инпут, кнопка и тд), системы управления svg иконками. Ещё был реализован popup и tooltip на основе Angular materials. Он был заточен под работу только с «классическим» Vimbox: многие вещи были осознанно намертво зашиты и не допускали изменений извне. А у Skyeng начали появляться новые продукты на той же платформе, например, для детей. Разработчики новых направлений, зная, что у нас что-то есть, пришли за советами. Причем, к нашему удивлению, приходили уже с макетами своих UI kit'ов: они собирались разрабатывать свои решения с нуля, т.к. им нужен был другой внешний вид компонентов. Было ясно, что что-то идет не так, и мы предложили доработать нашу библиотеку, добавив возможность ее темизации. Аргументация была простая: на проектирование нашего UI kit ушло 200 часов UX дизайна и более 500 часов разработки. Это время, необходимое для создания системы шрифтов, цветов и около 10 базовых компонентов. Соответственно, если писать для каждого продукта отдельную библиотеку, компания потратит N*500 часов времени разработчиков. Мы считали, что совершенствование нашего UI kit обойдется дешевле, плюс это действие не придется повторять для каждого продукта. Наши аргументы были приняты, смежные направления согласились подождать, и мы отправились искать техническое решение. Наши инструменты: Angular, SCSS. Поддерживаем мы только современные браузеры и, с некоторыми ограничениями, IE11. Что здорово упрощает жизнь. Все наши UI kit компоненты были пронизаны общими стилями, которые мы складывали в UI kit.var.scss в качестве SCSS констант: Итак, результат от нас ждут вчера, надо быстро провести технические ревью и обсудить варианты. На первых встречах мы определили круг возможных решений: Мы не любим писать велосипеды, поэтому в первую очередь обратились к Angular Material. В компонентах динамические стили вынесены в отдельный {component}-theme.scss файл. Эти стили привязываются к глобальному селектору компонента. У нас отличный повод попробовать модный CSS Variables. План – пересадить кастомизируемые части UI kit на CSS Variables. В компонентах используются те же SCSS-константы, но вместо конкретных значений в них прописаны CSS vars. Мы любим простые решения, почему бы не попробовать изменить сборку? Каждая команда создает свой файлик с настройкой темы. При сборке для всех кастомных тем создаются отдельные бандлы со своей темой. Неделю мы изучали каждый вариант, обсуждали, откладывали решение и снова изучали. Мы любим новые технологии и следим за ними, но внедряем только в том случае, если они дают нам реальные бонусы. Мы знали о CSS Variables, нам хотелось их попробовать, но отсутствие функций SCSS вызывало большую печаль. И все же плюсы этого варианта были очевидны, мы решили разобраться, как и какие функции SCSS мы используем, можно ли подружить их с CSS vars. Поэкспериментировав, мы поняли, что основная проблема состоит в отсутствии поддержки #hex в CSS: в SCSS мы пишем rgba(#ffffff, 0.4), а в CSS то же самое требует другого набора параметров — rgba(255, 255, 255, 0.4). У нас все работает с #hex, и мы очень, очень не хотим это менять. Мы нашли решения, расскажу в порядке поступления. Наш дизайнер придумал палитру, состоящую из небольшого количества базовых цветов, расширяющуюся за счет SCSS-функций lighten и darken: Мы попробовали найти аналог lighten и darken в CSS, но ничего не обнаружили. Думали несколько дней, пока не осознали, что для кастомизации нам нужно избавиться от этих функций внутри библиотеки, вынеся их наружу. Ведь каждая команда может захотеть придумать свою собственную формулу изменения цвета при изменении фокуса – например, коллегам из Kids нужно больше контрастности. Получилось простое решение – перенести наши преобразования на сторону платформы, которая будет инициализировать тему. И вот для платформы мы пишем функцию, которая автоматически создает нужные значения: Платформа использует ее при инициализации цветов: В своем UI kit мы используем функцию rgba. С ее помощью мы регулируем прозрачность базовых цветов. Но если в SCSS rgba работает с форматом #hex, то CSS этого не может. Пришлось написать функцию, которая раскладывает #hex значение в r/g/b: Ну и поскольку мы не хотим ко всей палитре генерировать RGB значения ручками, создаем отдельную функцию, делающую это рекурсивно для каждого цвета в коллекции: В итоге вот так выглядит инициализация палитры со сгенерированными значениями RGB: На выходе получаем карту цветов SCSS, которую затем можно отдать в метод, превращающий ее в CSS variables. Для того, чтобы достать из темы RGB значение, написали функцию: Первый шаг – завести зеркальную структуру (аналогичную SCSS), в которой хранятся имена CSS Variables: getColorCssVar – метод, добавляющий префиксы названиям переменных. Добавляем префикс --sky, чтобы избежать коллизии с внешними библиотеками. А также добавляем к --sky префикс библиотеки - UI kit, чтобы избежать коллизий с внутренними библиотеками. Получился --sky- UI kit: Например, для getColorCssVar(text, base) на выходе получим --sky- UI kit-color-text-base. Финальный штрих – рекурсивный миксин, инициализирующий значения из структуры SCSS в переменные с названиями из структуры CSS Var: Пример использования темы на платформе: Мы смогли использовать CSS Variables, cохранив возможность использования SCSS функций. Создали возможность кастомизации внешности компонентов. Написали пару рекурсивных методов для автоматизации расширения темы. Ну и главное – потратили 30 часов разработки вместо N*500.  Profit! Team leader + Senior Frontend Developer