От автора: мы на сайте HelloFresh постоянно боремся со стилями. Еще год назад стили на нашем сайте были плохими, запутанными, кода было слишком много, как и у множества других компаний. Мы захотели поправить эту ситуацию и нашли новенькую библиотеку css-in-js.js, очень крутая штука.
В итоге мы пришли к Aphrodite, потому что:
крутое название;
ее написали Khan Academy, а значит, они, скорее всего, тоже ее используют.
Это был идеальный вариант для нас, остальные не подходили нам по определенным критериям. Некоторые из инструментов были связаны с другими фреймворками, например, Radium связан с React.
Сейчас, пару месяцев спустя мы решили пересмотреть свой выбор (см. как мы создавали все приложение здесь). Нам нравился наш выбор, но мы хотели посмотреть на что-то новое. К счастью когда мы создали приложение, мы решили добавить некий слой интерфейса между реальными выходными данными в classNames компонента и используемой нами библиотекой. Рефакторинг не вызвал особых затруднений (к счастью).
Среди вариантов были (по крайней мере, из того, что мы нашли):
Aphrodite, все еще мощная штука, да и название крутое.
Glamor, очень нравится API на правилах.
JSS, отличный логотип, хорошее API, можно создавать вложенные стили.
CXS, функциональный CSS, ЧТО!?
Битва
Мы взяли все библиотеки и сравнили их вес, API и производительность.
Производительность
Первый тест был простым:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
const styleLib = require('style-lib'); const body = `<div className=${generateClassName({ backgroundColor: 'blue', })}></div>`; return ` <html> <head> <style type="text/css">${styleLib.generateCss()}</style> </head> <body> ${body} </body> </html> `; |
Конечно, это упрощенная версия, и она не будет работать из-за style-lib. Но этот пример хорошо показывает именно то, как мы тестировали.
Тесты проводились на Macbook Pro с большим объемом памяти и CPU, тесты запускали при помощи Benchmark.js.
Попытка 1
Результаты первого теста:
1 2 3 4 5 6 7 8 9 |
5 tests completed. aphrodite x 3,178 ops/sec ±13.32% (43 runs sampled) jss x 107 ops/sec ±67.27% (8 runs sampled) jss-without-preset x 71.44 ops/sec ±7.87% (45 runs sampled) glamor x 11,968 ops/sec ±9.31% (50 runs sampled) cxs x 7,774 ops/sec ±9.49% (56 runs sampled) Fastest is: glamor |
Как видно, медленным был JSS, очень медленным. Тьяго, один из наших front-end разработчиков завел доработку, о которой можно прочитать здесь. Оказалось, что мы все время писали поверх глобального JSS объекта и не переустанавливали его. Стандартная реализация не такая убогая, как у нас.
К счастью они сделали фикс для нас, и мы смогли создавать новый экземпляр под каждый тест. Новые результаты уже примерно сходились с ожиданиями:
1 2 3 4 5 6 7 8 9 |
5 tests completed. aphrodite x 6,088 ops/sec ±7.65% (62 runs sampled) jss x 11,291 ops/sec ±8.39% (75 runs sampled) jss-without-preset x 11,622 ops/sec ±17.20% (52 runs sampled) glamor x 6,623 ops/sec ±14.67% (67 runs sampled) cxs x 12,121 ops/sec ±3.80% (75 runs sampled) Fastest is: cxs,jss |
Помимо создания классов, с помощью компонентов React мы также рендерили HTML. Мы поняли, что, в принципе, без этого можно обойтись и продолжили тест уже просто с отрисовкой HTML в виде строки. Как видно, общая производительность поднялась достаточно хорошо.
Мы запустили тест еще раз, в этот раз с cxs/optimized, библиотекой от CXS, которая должна повысить производительность в браузере.
Мы решили измерять длину строки также на выходе из библиотеки, что будет включать отрисованный HTML и CSS.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
aphrodite length 470 jss length 447 jss-without-preset length 439 glamor length 422 cxs length 400 cxs-optimized length 445 6 tests completed. aphrodite x 8,943 ops/sec ±14.55% (68 runs sampled) jss x 11,697 ops/sec ±27.81% (55 runs sampled) jss-without-preset x 46,684 ops/sec ± 7.89% (62 runs sampled) glamor x 5,042 ops/sec ±14.27% (47 runs sampled) cxs x 19,122 ops/sec ±10.24% (69 runs sampled) cxs-optimized x 12,843 ops/sec ±10.52% (69 runs sampled) Fastest is: jss-without-preset |
Расширяем тест
Конечно, тестировать один класс не составляет труда для любой библиотеки. Поэтому мы пошли дальше и добавили тест, который создает много классов с одинаковыми стилями. И еще один, который создает много классов, но уже с разными стилями (просто padding-left с инкрементом).
В этих тестах мы также измеряли, какая библиотека даст минимальное значение на выходе.
Тест по перегрузке классов
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
aphrodite length 3044 jss length 3085 jss-without-preset length 3064 glamor length 1935 cxs length 1943 cxs-optimized length 1943 6 tests completed. aphrodite x 1,145 ops/sec ±23.06% (55 runs sampled) jss x 1,305 ops/sec ±31.53% (39 runs sampled) jss-without-preset x 2,723 ops/sec ±17.48% (38 runs sampled) glamor x 2,698 ops/sec ±18.00% (48 runs sampled) cxs x 1,697 ops/sec ±15.10% (46 runs sampled) cxs-optimized x 2,359 ops/sec ± 7.45% (72 runs sampled) Fastest is: jss-without-preset,glamor |
Тест по перегрузке стилей
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
aphrodite length 3594 jss length 3509 jss-without-preset length 3430 glamor length 3298 cxs length 3022 cxs-optimized length 3063 6 tests completed. aphrodite x 853 ops/sec ±19.51% (54 runs sampled) jss x 2,200 ops/sec ±10.85% (66 runs sampled) jss-without-preset x 4,301 ops/sec ±17.48% (55 runs sampled) glamor x 665 ops/sec ±17.53% (56 runs sampled) cxs x 1,032 ops/sec ±24.12% (43 runs sampled) cxs-optimized x 743 ops/sec ±21.16% (45 runs sampled) Fastest is: jss-without-preset |
CXS и Glamor отлично справились со слиянием классов, но в тесте с разными стилями все показали примерно одинаковые результаты. Выделился только CXS.
Все библиотеки не стали сливать классы на верхнем уровне (когда стили одинаковые), посмотреть это можно ниже:
Если рассматривать операции посекундно, JSS без предустановок выигрывает по всем фронтам, а Glamor плохо себя показал с разными стилями. Если бы вы на самом деле использовали JSS с заданной библиотекой, то преимущества у победителя, судя по тесту, минимальны. CXS определенно отличный вариант, но и JSS с предустановками по умолчанию занимает достойное второе место, им удалось достичь хорошей производительности ядра без жестких предустановок.
Победитель: CXS
Размер пакета
Вся сила библиотеки ничего не стоит, если она много весит. Поэтому дальше мы сравнили размеры при запаковке через Webpack. Результаты:
1 2 3 4 5 6 |
Size cxs 9.766KB Size cxs-optimized 12.668KB Size jss-without-preset 24.183KB Size jss 37.04KB Size aphrodite 18.919KB Size glamor 35.436KB |
Как видите, JSS – самая быстрая библиотека, но и одна из самых больших. Результаты CXS тоже очень хорошие, разработчики не соврали. Функциональные библиотеки должны иметь небольшой размер. Glamor говорит в файле README, что у них маленький вес, но на самом деле по сравнению с остальными вес не такой уж и маленький.
Победитель: CXS
API
Хорошее время обработки и небольшой вес – это очень важно, но нам также нужно работать с этими библиотеками каждый день. API также очень важный аспект.
Ниже мы покажем, как используются API в упрощенных формах.
Aphrodite
1 2 3 4 5 6 7 8 9 10 11 |
import { css, StyleSheet, StyleSheetServer } from 'aphrodite'; const styles = StyleSheet.create({ container: { backgroundColor: 'blue', }, }); const className = css(styles.container); const { html, css } = StyleSheetServer.renderStatic(() => '<html></html>'); |
В Aphrodite код очень подробный, и они стараются далеко не отходить от обычного CSS. В API используются слова типа StyleSheet и css. Метод renderStatic отрисовывает CSS в виде строки.
CXS
1 2 3 4 5 6 7 |
import cxs from 'cxs'; const className = cxs({ backgroundColor: 'blue', }); const { css } = cxs; |
Вот этот вариант нам очень нравится, тут не нужно думать о classNames и именовании вообще. Вы просто создаете правила и цепляете их к компонентам. К тому же, ссылки на стили в форме строки создаются на лету, не нужно ничего отрисовывать.
Glamor
1 2 3 4 5 6 7 8 |
import { style } from 'glamor'; import { renderStatic } from 'glamor/server'; const className = style({ backgroundColor: 'blue', }); const { html, css, ids } = renderStatic(() => '<html></html>'); |
API Glamor заимствует отдельные элементы из API CXS и Aphrodite. Также Glamor требует, чтобы HTML возвращался в колбэк-функцию.
JSS
1 2 3 4 5 6 7 8 9 |
import jss from 'jss'; const { classes } = jss.createStyleSheet({ container: { backgroundColor: 'blue', }, }).attach(); const css = jss.sheets.toString(); |
API JSS понять немного сложнее. Он заимствует часть у Aphrodite, а рендеринг CSS больше похож на CXS.
Победитель: CXS
Интеграция с клиентом
Предыдущие тесты брали в расчет только рендеринг кода на сервере. Если вы рендерите CSS на сервере, вам не нужно делать те же самые операции в клиенте. Так какая из библиотек лучше всего с этим справляется? Вычислить это с помощью тестов довольно сложно. При переходе с сервера в клиент нам нужно было бы написать что-то заумное на PhantomJS. Мы подумали, что объяснить и сравнить, что каждая библиотека делает, будет намного лучше.
Aphrodite и Glamor осторожно генерируются заново перед разворачиванием в клиенте. Glamor хорошо объясняет весь процесс здесь. Однако ни CXS ни JSS не думают о том, что уже было отрисовано на сервере. В инструкции к CXS говорится, к примеру, что нужно просто удалить тег style, который попал туда с сервера после того, как клиент сгенерирует весь CSS.
Победитель: Glamor
Заключение
Существует множество библиотек, и все они слегка отличаются. После написания первого черновика этого поста, мы получили множество запросов с еще большим количеством css-in-js библиотек, также мы создали несколько заявок на доработку библиотек, о которых сегодня рассказывали. Некоторые разработчики показали нам, как лучше работать с их библиотекой, это очень помогло.
Я считаю, что явного победителя нет. У нас есть реальная проблема с повторной генерацией. Как правило, стилей у нас много, и нам приходится повторно их генерировать для одной страницы, что довольно расточительно. Я создал заявку на доработку в репозитории JSS на эту тему, и мне дали пару хороших советов. Надеемся, что эта битва будет иметь хоть какую-то пользу!
И да, не забудьте посмотреть исходники тестов! Не стесняйтесь добавлять свои css-in-js библиотеки!
Редакция: Pepijn Senders
Источник: //engineering.hellofresh.com/
Редакция: Команда webformyself.