Битва css-in-js

45

Від автора: ми на сайті HelloFresh постійно боремося зі стилями. Ще рік тому стилі на нашому сайті були поганими, заплутаними, коду було занадто багато, як і в безлічі інших компаній. Ми захотіли виправити цю ситуацію і знайшли нову бібліотеку css-in-js.js дуже крута штука.

У результаті ми прийшли до Aphrodite, тому що:

круте назву;

її написали Khan Academy, а значить, вони, швидше за все, теж її використовують.

Це був ідеальний варіант для нас, інші не підходили нам за певними критеріями. Деякі з інструментів, що були пов’язані з іншими фреймворками, наприклад, Radium пов’язаний з React.

Зараз, через пару місяців ми вирішили переглянути свій вибір (див. як ми створювали додаток тут). Нам подобався наш вибір, але ми хотіли подивитися на щось нове. На щастя коли ми створили додаток, ми вирішили додати якийсь шар інтерфейсу між реальними вихідними даними в classNames компонента і використовуваної нами бібліотекою. Рефакторинг не викликав особливих труднощів (на щастя).

Серед варіантів були (принаймні, з того, що ми знайшли):

Aphrodite, все ще потужна штука, та й назва круте.

Glamor, дуже подобається API на правилах.

JSS, відмінний логотип, гарне API, можна створювати вкладені стилі.

CXS, функціональний CSS, ЩО!?

Битва

Ми взяли всі бібліотеки і порівняли їх вага, API і продуктивність.

Продуктивність

Перший тест був простим:

const styleLib = require(‘style-lib’);
const body = `

`;
return `
${styleLib.generateCss()}
${body}
`;

Звичайно, це спрощена версія, і вона не буде працювати через style-lib. Але цей приклад добре показує саме те, як ми тестували.

Тести проводилися на Macbook Pro з великим об’ємом пам’яті і CPU, тести запускали за допомогою Benchmark.js.

Спроба 1

Результати першого тесту:

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 об’єкта і не переустановлювали його. Стандартна реалізація не така убога, як у нас.

На щастя вони зробили фікс для нас, і ми змогли створити новий екземпляр під кожен тест. Нові результати вже приблизно сходилися з очікуваннями:

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.

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 з інкрементом).

У цих тестах ми також вимірювали, яка бібліотека дасть мінімальне значення на виході.

Тест з перевантаження класів

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

Тест по перевантаженню стилів

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.

Всі бібліотеки не стали зливати класи на верхньому рівні (коли стилі однакові), це можна подивитися нижче:

Битва css-in-js

Якщо розглядати операції посекундно, JSS без предустановок виграє по всіх фронтах, а Glamor погано себе показав з різними стилями. Якщо б ви насправді використовували JSS із заданою бібліотекою, то переваги у переможця, судячи по тесту, мінімальні. CXS виразно відмінний варіант, але і JSS з попередніми налаштуваннями за замовчуванням займає гідне друге місце, їм вдалося досягти хорошої продуктивності ядра без жорстких вимог.

Переможець: CXS

Розмір пакета

Вся сила бібліотеки нічого не коштує, якщо вона багато важить. Тому далі ми порівняли розміри при запаковке через Webpack. Результати:

Size cxs 9.766 KB
Size cxs-optimized 12.668 KB
Size jss-without-preset 24.183 KB
Size jss 37.04 KB
Size aphrodite 18.919 KB
Size glamor 35.436 KB

Як бачите, JSS – найшвидша бібліотека, але і одна з найбільших. Результати CXS теж дуже хороші, розробники не збрехали. Функціональні бібліотеки повинні мати невеликий розмір. Glamor говорить у файлі README, що у них маленька вага, але насправді порівняно з іншими вагу не такий вже й маленький.

Переможець: CXS

API

Гарний час обробки і невелика вага – це дуже важливо, але нам також потрібно працювати з цими бібліотеками кожен день. API також дуже важливий аспект.

Нижче ми покажемо, як використовуються API в спрощених формах.

Aphrodite

import { css, StyleSheet, StyleSheetServer } from ‘aphrodite’;
const styles = StyleSheet.create({
container: {
backgroundColor: ‘blue’,
},
});
const className = css(styles.container);
const { html, css } = StyleSheetServer.renderStatic(() => «);

В Aphrodite код дуже докладний, і вони намагаються далеко не відходити від звичайного CSS. В API використовуються слова типу StyleSheet і css. Метод renderStatic розмальовує CSS у вигляді рядка.

CXS

import cxs from ‘cxs’;
const className = cxs({
backgroundColor: ‘blue’,
});
const { css } = cxs;

Ось цей варіант нам дуже подобається, тут не потрібно думати про classNames і іменуванні взагалі. Ви просто створюєте правила і чіпляєте їх до компонентів. До того ж, посилання на стилі у формі рядка створюються на льоту, не потрібно нічого малювати.

Glamor

import { стиль } from ‘glamor’;
import { renderStatic } from ‘glamor/server’;
const className = style({
backgroundColor: ‘blue’,
});
const { html, css, ids } = renderStatic(() => «);

API Glamor запозичує окремі елементи з API CXS і Aphrodite. Також Glamor вимагає, щоб HTML повертався в колбек-функцію.

JSS

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 бібліотеки!