Как увеличить производительность CSS-in-JS в 175 раз

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

  1. В том, что обязательно нужно использовать хешированные классы вместо классов пространств имён. А представьте, как может раздражать их присутствие в сторонних компонентах!
  2. В том, что применение логики в CSS всегда лучше или более удобно для восприятия человеком. А также это даёт мощный рост производительности (подробнее об этом ниже).
  3. И наконец, в том, что добавлять 25–40 Кбайт минифицированного кода JavaScript к npm-компоненту размером в 10 Кбайт  —  это хорошая идея.

Поначалу всё хорошо и беззаботно и никаких проблем с производительностью этих CSS-решений с «невероятно быстрым» временем выполнения не замечается. Но это до тех пор, пока вы не поместите всех их на одной странице в Storybook.

Обратимся к вездесущему компоненту Button, имеющему различные стили и параметры:

У нас там было множество простых и быстрых функций для создания стилей. Мы даже оптимизировали статический css, выделив его в самостоятельный фрагмент (sharedStaticButtonStyles):

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

Около 36 секунд ушло у Emotion на выполнение парсинга (см. подчёркнутое красным). Инструмент производительности Performance делает это примерно в 2–3 раза быстрее, но всё равно это слишком долго.

Дальше всё только медленнее

Сначала я думал, что проблема в компоненте Tooltip (всплывающая подсказка): может быть, это он так медленно отображается? А затем понял, что это происходит, только когда он прикреплён к Button. Стало быть, Emotion при наведении курсора снова выполняет парсинг CSS (потому что Tooltip вызывает повторное отображение) и приостанавливает основной поток примерно на 900 миллисекунд, прежде чем появится всплывающая подсказка!

Вы можете подумать: «Ну мои компоненты не такие сложные и медленные». Даже если они в два раза быстрее в крупном приложении с большим количеством компонентов на странице, тратить 1–2 лишних секунды на загрузку, пока выполняется парсинг CSS, недопустимо. Особенно в высокодинамичных приложениях, получающих большое количество обновлений в секунду. В них Emotion выполняет повторный парсинг при каждом отображении (как это происходит при наведении курсора), что обусловлено динамической природой стилевого оформления с использованием пропсов.

Как же сделать всё быстрым?

Во-первых, если вы начинаете новый проект, стоит подумать о CSS-in-JS решениях времени компиляции: таком или таком.

Если речи о новом проекте не идёт, можно увеличить производительность уже имеющегося стилевого оформления до 175 раз, сделав CSS более статичным.

Уже написано несколько статей о том, как использование пропсов при тематическом оформлении засоряет приложение специальными обёртками.

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

Но узкое место производительности не в этом

А в тех вызовах вложенных функций в css, которые принимают вдобавок к теме ещё и пропсы:

Я не рассматривал подробно CSS-код во время выполнения, но мне кажется, что при вызове функция фактически заявляет: «Я не знаю, что она вернёт, так что буду пересчитывать её при каждом отображении».

На помощь приходят атрибуты данных и CSS-переменные

Давайте поменяем то, как мы делаем этот компонент Button:

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

В ходе выполнения рефакторинга старые функции для получения цветов и размеров были сохранены и перемещены из Emotion CSS в CSS-переменные почти 1 к 1:

Эта функция выполняется в первый раз примерно за 0,002 миллисекунды. Посмотрим, как теперь выглядит CSS компонента Button:

Помните, на парсинг уходило 36 секунд? Посмотрим, что сейчас:

А теперь парсинг CSS выполняется чуть больше чем за 200 миллисекунд. В то время как Performance делал это всего в 2–3 раза быстрее (т. е. за 12–18 секунд).

Подведём итоги:

  • В новых проектах используйте CSS времени компиляции.
  • Для тематического оформления задействуйте в новых проектах CSS-переменные.
  • Делайте CSS как можно более статичным для вариаций (это самая важная оптимизация).

Читайте также:

Читайте нас в Telegram, VK и Яндекс.Дзен


Перевод статьи Dominic Tobias: How to increase CSS-in-JS performance by 175x