Компромиссы CSS-in-JS

Фото Артема Бали

Недавно я написал обзор CSS-in-JS более высокого уровня, в основном рассказывая о проблемах, которые пытается решить этот подход. Авторы библиотеки редко тратят время на описание компромиссов своего решения. Иногда это потому, что они слишком предвзяты, а иногда они просто не знают, как пользователи применяют инструмент. Так что это попытка описать компромиссы, которые я видел до сих пор. Я думаю, что важно упомянуть, что я являюсь автором JSS, поэтому меня следует считать предвзятым.

Социальное влияние

Существует слой людей, которые работают на веб-платформе и не знают JavaScript. Этим людям платят за написание HTML и CSS. CSS-in-JS оказал огромное влияние на рабочий процесс разработчиков. Подлинно преобразующее изменение никогда не может быть осуществлено без отставания некоторых людей. Я не знаю, должен ли CSS-in-JS быть единственным способом, но массовое внедрение является явным признаком проблем с использованием CSS в современных приложениях.

Большая часть проблемы заключается в нашей неспособности точно сообщить о случаях использования CSS-in-JS и о том, как правильно использовать его для выполнения задачи. Многие энтузиасты CSS-in-JS преуспели в продвижении технологии, но не многие критики говорили о компромиссах конструктивным образом, не делая дешевых колебаний в инструментах. В результате мы оставили многие компромиссы скрытыми и не приложили особых усилий для объяснения и обходных путей.

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

Стоимость выполнения

Когда CSS генерируется из JavaScript во время выполнения, в браузере возникают накладные расходы. Время выполнения варьируется от библиотеки к библиотеке. Это хороший общий тест, но обязательно сделайте свои собственные тесты. Основные различия во время выполнения появляются в зависимости от необходимости полного разбора CSS строк шаблона, количества оптимизаций, деталей реализации динамических стилей, алгоритма хеширования и стоимости интеграции фреймворка. *

Помимо потенциальных накладных расходов во время выполнения, вам нужно рассмотреть 4 разные стратегии комплектации, потому что некоторые библиотеки CSS-in-JS поддерживают несколько стратегий, и пользователь может применять их. *

Стратегия 1: только генерация во время выполнения

Генерация CSS во время выполнения - это метод, который генерирует строку CSS в JavaScript, а затем внедряет эту строку с помощью тега стиля в документ. Этот метод создает таблицу стилей, а НЕ встроенные стили.

Компромисс между генерацией среды выполнения - невозможность предоставить стилизованный контент на ранней стадии, когда документ начинает загружаться. Такой подход обычно подходит для приложений без контента, который может быть полезен сразу. Обычно такие приложения требуют взаимодействия с пользователем, прежде чем они действительно могут стать полезными для пользователя. Часто такие приложения работают с контентом, который настолько динамичен, что он устаревает, как только вы его загружаете, поэтому вам необходимо установить конвейер обновления на ранних этапах, например, в Twitter. Кроме того, когда пользователь вошел в систему, нет необходимости предоставлять HTML для SEO.

Если для взаимодействия требуется JavaScript, пакет должен быть загружен до того, как приложение будет готово. Например, вы можете показать содержимое канала по умолчанию при загрузке Slack в документе, но вполне вероятно, что пользователь захочет изменить канал сразу после этого. Так что, если вы загрузили исходное содержимое, просто чтобы сразу выбросить его.

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

Стратегия 2: генерация во время выполнения с помощью критического CSS

Критический CSS - это минимальное количество CSS, необходимое для стилизации страницы в ее начальном состоянии. Он отображается с использованием тега стиля в заголовке документа. Этот метод широко используется с CSS-in-JS и без него. В обоих случаях вы, вероятно, удвоите загрузку правил CSS, один раз как часть Критического CSS и один раз как часть пакета JavaScript или CSS. Размер Critical CSS может быть довольно большим в зависимости от объема контента. Обычно документ не кэшируется.

Без Critical CSS статическое одностраничное приложение с интенсивным содержимым и CSS-in-JS во время выполнения будет показывать заполнители вместо содержимого. Это плохо, потому что это могло быть полезно пользователю намного раньше, улучшая доступность на устройствах низкого уровня и для соединений с низкой пропускной способностью.

С критическим CSS генерация CSS времени выполнения может быть выполнена на более позднем этапе, не блокируя пользовательский интерфейс на начальном этапе. Однако следует помнить, что на недорогих мобильных устройствах, которым более 5 лет, генерация CSS из JavaScript может отрицательно сказаться на производительности. Это сильно зависит от количества генерируемого CSS и используемой библиотеки, поэтому его нельзя обобщать.

Компромисс этой стратегии - стоимость извлечения критического CSS и стоимость генерации CSS во время выполнения.

Стратегия 3: извлечение только во время сборки

Эта стратегия используется по умолчанию в Интернете без CSS-in-JS. Некоторые библиотеки CSS-in-JS позволяют извлекать статический CSS во время сборки. * В этом случае не возникает никаких накладных расходов во время выполнения, CSS отображается на странице с помощью тега ссылки. Стоимость генерации CSS оплачивается один раз заранее.

Здесь есть 2 основных компромисса:

  1. Вы не можете использовать некоторые динамические API-предложения CSS-in-JS во время выполнения, потому что у вас нет доступа к состоянию. Часто вы все еще не можете использовать пользовательские свойства CSS, потому что они не поддерживаются в каждом браузере и не могут быть заполнены во время сборки по своей природе. В этом случае вам придется делать обходные пути для динамического создания тем и стилей на основе состояния. *
  2. Без Критического CSS и с пустым кешем вы заблокируете первую рисование, пока ваш CSS-пакет не будет загружен. Элемент ссылки в заголовке документа блокирует отображение HTML.
  3. Недетерминированная специфика с разбиением пакетов на основе страниц в одностраничных приложениях. *

Стратегия 4: извлечение во время сборки с помощью Critical CSS

Эта стратегия также не уникальна для CSS-in-JS. Полное статическое извлечение с помощью критического CSS обеспечивает наилучшую производительность при работе с более статичным приложением. Этот подход все еще имеет вышеупомянутые компромиссы статического CSS, за исключением того, что тег блокирующей ссылки может быть перемещен в конец документа.

Существует 4 основных стратегии рендеринга CSS. Только 2 из них относятся к CSS-in-JS, и ни одна из них не применима ко всем библиотекам.

доступность

CSS-in-JS может уменьшить доступность при неправильном использовании. Это произойдет, когда сайт с в значительной степени статическим контентом реализован без извлечения из Critical CSS, так что HTML не может быть нарисован до загрузки и оценки пакета JavaScript. Это также может произойти, когда огромный CSS-файл визуализируется с использованием тега блокирующей ссылки в заголовке документа, что является наиболее популярной в настоящее время проблемой традиционного встраивания и не относится к CSS-in-JS.

Разработчики должны взять на себя ответственность за доступность. Все еще существует сильная ошибочная идея, что нестабильное подключение к Интернету является проблемой экономически слабых стран. Мы склонны забывать, что у нас возникают проблемы с подключением каждый день, когда мы входим в подземную железнодорожную систему или в большое здание. Стабильная беспроводная мобильная связь - миф. Стабильное соединение WiFi даже не легко, например, сеть WI-FI 2,4 ГГц может получать помехи от микроволновой печи!

Стоимость критического CSS с рендерингом на стороне сервера

Чтобы получить Критическое извлечение CSS для CSS-in-JS, нам нужен SSR. SSR - это процесс генерации окончательного HTML-кода для данного состояния приложения на сервере. На самом деле, это может быть довольно сложный и дорогой процесс. Для каждого HTTP-запроса требуется определенное количество циклов ЦП на сервере.

CSS-in-JS обычно использует тот факт, что он подключен к конвейеру рендеринга HTML. * Он знает, какой HTML-код был отрисован и какой CSS ему нужен, чтобы он мог производить его абсолютно минимальное количество. Критический CSS добавляет дополнительные издержки при рендеринге HTML на сервере, потому что этот CSS также необходимо скомпилировать в окончательную строку CSS. В некоторых сценариях кеширование на сервере трудно или даже невозможно.

Рендеринг черного ящика

Вы должны знать, как используемая вами библиотека CSS-in-JS отображает ваш CSS. Например, люди часто не знают, как Styled Components и Emotion реализуют динамические стили. Динамические стили - это синтаксис, который позволяет использовать функции JavaScript внутри вашего объявления стилей. Эти функции принимают реквизиты и возвращают блок CSS.

Чтобы сохранить согласованность порядка исходных кодов, обе вышеназванные библиотеки генерируют новое правило CSS, если оно содержит динамическое объявление, а компонент обновляется новыми реквизитами. Чтобы продемонстрировать, что я имею в виду, я создал эту песочницу. В JSS мы решили воспользоваться другим компромиссом, который позволяет обновлять динамические свойства без создания новых правил CSS. *

Крутая кривая обучения

Для людей, которые знакомы с CSS, но не знакомы с JavaScript, начальный объем работы, чтобы освоить CSS-in-JS, может быть довольно большим.

Вам не нужно быть профессиональным разработчиком JavaScript, чтобы писать CSS-in-JS, вплоть до того момента, когда в дело вступает сложная логика. Мы не можем обобщить сложность стиля, так как это действительно зависит от варианта использования. В тех случаях, когда CSS-in-JS становится сложным, вполне вероятно, что реализация с ванильным CSS будет еще более сложной.

Для базового стиля CSS-in-JS нужно знать, как объявлять переменные, как использовать строки шаблона и интерполировать значения JavaScript. Если используется объектная нотация, нужно знать, как работать с объектами JavaScript, и использовать специфичный для библиотеки синтаксис на основе объектов. Если задействован динамический стиль, нужно знать, как использовать функции и условия JavaScript.

В целом, есть кривая обучения, мы не можем этого отрицать. Эта кривая обучения обычно не намного больше, чем обучение Sass. На самом деле, я создал этот курс, чтобы продемонстрировать это.

Нет совместимости

Большинство библиотек CSS-in-JS не совместимы. Это означает, что стили, написанные с использованием одной библиотеки, не могут быть отображены с использованием другой библиотеки. Практически это означает, что вы не можете легко переключать все свое приложение с одной реализации на другую. Это также означает, что вы не можете легко поделиться своим пользовательским интерфейсом в NPM, не добавляя выбранную библиотеку CSS-in-JS в пакет потребителя, если у вас нет статического извлечения для CSS во время сборки.

Мы начали работать над форматом ISTF, который должен решить эту проблему, но, к сожалению, у нас еще не было времени перевести его в состояние готовности к работе. *

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

Риски безопасности

С помощью CSS-in-JS можно внести утечку в систему безопасности. Как и в случае любых клиентских приложений, вам всегда нужно избегать пользовательского ввода перед его рендерингом.

Эта статья даст вам больше понимания и несколько порочных примеров.

Нечитаемые имена классов

Некоторые люди все еще думают, что важно, чтобы мы содержали в Интернете значимые читаемые имена классов. В настоящее время многие библиотеки CSS-in-JS предоставляют значимые имена классов на основе имени объявления или имени компонента в режиме разработки. Некоторые из них даже позволяют настраивать функцию генератора имен классов.

Однако в производственном режиме большинство из них генерируют более короткие имена для меньшей полезной нагрузки. Это компромисс, который пользователь библиотеки должен сделать и настроить библиотеку, если это необходимо.

Вывод

Компромиссы существуют, и я, вероятно, даже не упомянул все из них. Но большинство из них не всегда применимы ко всем CSS-in-JS. Они зависят от того, какую библиотеку вы используете и как вы ее используете.

* Для объяснения этого предложения потребуется специальная статья. Дайте мне знать в Твиттере (@ oleg008) о том, что вы хотели бы прочитать больше.