Анатомия StyleX

от автора

Hello world!

По данным 2023 JavaScript Rising Stars библиотека StyleX заняла второе место в разделе Styling / CSS in JS (первое место вполне ожидаемо занял TailwindCSS).

stylex — это решение CSS в JS от Facebook, которое недавно стало открытым и быстро набрало популярность (на сегодняшний день у библиотеки 7500 звезд на Github). Это обусловлено ее легковесностью, производительностью и небольшим размером итоговой таблицы стилей.

В этой статье мы разберемся, как stylex работает. Но давайте начнем с примера ее использования.

Код проекта, который мы создадим, включая выдержки из исходного кода stylex (директория stylex), можно найти здесь.

Пример

В качестве примера создадим простой компонент кнопки, предусматривающий разные варианты стилизации.

Создаем шаблон приложения с помощью Vite:

# stylex-testing - название проекта # react-ts - используемый шаблон yarn create vite stylex-testing --template react-ts # или npm create vite@latest stylex-testing -- --template react-ts

Переходим в директорию и устанавливаем зависимости:

cd stylex-testing yarn add @stylexjs/stylex yarn add -D vite-plugin-stylex-dev # или npm i @stylexjs/stylex npm i -D vite-plugin-stylex-dev

vite-plugin-stylex-dev — неофициальный плагин stylex для vite.

Редактируем файл vite.config.ts:

import react from '@vitejs/plugin-react' import { defineConfig } from 'vite' import { stylexPlugin } from 'vite-plugin-stylex-dev'  export default defineConfig({   plugins: [react(), stylexPlugin()], })

Определяем минимальные стили в файле index.css:

html, body, #root {   height: 100%; } body {   margin: 0; }

Переходим к stylex.

Начнем с определения переменных/токенов. Создаем файл tokens.stylex.ts следующего содержания:

import stylex from '@stylexjs/stylex'  // Медиа-запрос темной цветовой схемы const DARK = '@media (prefers-color-scheme: dark)'  // Цвета export const colors = stylex.defineVars({   light: { default: '#FBFBFB', [DARK]: '#332D2D' },   dark: { default: '#332D2D', [DARK]: '#FBFBFB' },   primary: '#3B71CA',   success: '#14A44D', })  // Отступы export const spacing = stylex.defineVars({   none: '0px',   xsmall: '4px',   small: '8px',   medium: '12px',   large: '16px', })

Создаем файл components/Button.tsx для компонента кнопки. Определяем стили кнопки:

import stylex, { type StyleXStyles } from '@stylexjs/stylex' import type { HTMLAttributes, PropsWithChildren } from 'react' import { colors, spacing } from '../tokens.stylex'  // 3 варианта стилизации const styles = stylex.create({   default: {     // Используем переменные     // https://stylexjs.com/docs/learn/theming/using-variables/     padding: `${spacing.small} ${spacing.large}`,     backgroundColor: colors.light,     border: `1px solid ${colors.dark}`,     outline: 'none',     borderRadius: spacing.small,     boxShadow: {       default: '0 2px 3px rgba(0, 0, 0, 0.25)',       ':active': 'none',     },     cursor: 'pointer',     transition: 'all 0.25s ease-in-out',     ':hover': {       backgroundColor: colors.dark,       color: colors.light,     },   },   primary: {     backgroundColor: colors.primary,     color: colors.light,     ':hover': {       backgroundColor: null,       color: null,     },   },   dark: {     backgroundColor: colors.dark,     color: colors.light,     ':hover': {       backgroundColor: colors.light,       color: colors.dark,     },   }, })

Определение стилей с помощью stylex сильно напоминает то, как это делается в React Native.

Определяем типы пропов и компонент кнопки:

type Props = PropsWithChildren<   HTMLAttributes<HTMLButtonElement> & {     // Вариант перезаписывает стилизацию `default`     variant?: 'primary' | 'dark'     // Кастомные цвета фона и текста, определенные с помощью `stylex`     // С точки зрения типизации стилей `stylex` лучше `tailwindcss`     customStyles?: StyleXStyles<{       backgroundColor?: string       color?: string     }>   } >  export default function Button({   children,   variant,   customStyles,   ...rest }: Props) {   return (     <button       // Применяем стили       // https://stylexjs.com/docs/learn/styling-ui/using-styles/       {...stylex.props(         styles.default,         variant && styles[variant],         customStyles,       )}       {...rest}     >       {children}     </button>   ) }

Определяем стили контейнера и кастомной кнопки и рендерим несколько кнопок в файле App.tsx:

import stylex from '@stylexjs/stylex' import Button from './components/Button' import { colors, spacing } from './tokens.stylex'  // Стили контейнера и кастомной кнопки const styles = stylex.create({   app: {     width: '100%',     height: '100%',     display: 'flex',     justifyContent: 'center',     alignItems: 'center',     gap: spacing.medium,   },   customButton: {     backgroundColor: colors.success,     color: colors.light,   }, })  export default function App() {   return (     <div {...stylex.props(styles.app)}>       <Button>Default</Button>       <Button variant='dark'>Dark</Button>       <Button variant='primary'>Primary</Button>       <Button customStyles={styles.customButton}>         Custom       </Button>     </div>   ) }

Запускаем сервер для разработки:

yarn dev # или npm run dev

Результат:

Это почти весь функционал, предоставляемый stylex. Мы не рассмотрели только создание тем путем перезаписи переменных, но, думаю, вы сами легко с этим разберетесь.

Давайте взглянем на стили и пропы, генерируемые stylex. Редактируем App.tsx следующим образом:

import stylex from '@stylexjs/stylex'  const stylesForLog = stylex.create({   root: {     display: 'flex',     flexDirection: 'column',   }, }) const stylesForLog2 = stylex.create({   root2: {     display: 'flex',   }, })  // Утилита для красивого вывода const stringify = (obj: Record<string, unknown>) => JSON.stringify(obj, null, 2)  export default function App() {   console.log('result of create:', stringify(stylesForLog))   console.log('result of create 2:', stringify(stylesForLog2))   console.log(stylesForLog.root.display === stylesForLog2.root2.display)   console.log(     'result of props:',     stringify(stylex.props(stylesForLog.root, stylesForLog2.root2)),   )   console.log(     'result of props 2:',     stringify(       // @ts-ignore       stylex.props(stylesForLog.root, stylesForLog2.root2, { display: 'flex' }),     ),   )    return (     <div {...stylex.props(stylesForLog.root)}></div>   ) }

Вот что мы видим в консоли:

Строки типа App__stylesForLog.root нужны для отладки, поэтому их можно игнорировать.

display: "flex" превратилось в display: "x78zum5", причем поле display в обоих случаях имеет одно и тоже значение, что подтверждается сравнением stylesForLog.root.display === stylesForLog2.root2.display. Это наводит на мысль о том, что строки типа x78zum5 — это хеш правил CSS (свойство: значение).

Значения полей объекта styles объединяются и становятся значением поля className объекта props (например, className: "xdt5ytf x78zum5"). className путем распаковки объекта props передается компоненту (устанавливается элементу button).

При передаче display: flex в виде простого объекта возвращается поле style со значением в виде этого объекта. При этом, соответствующий хеш-класс опускается.

Запомните поле $$css: true, мы вернемся к нему позже.

Взглянем на разметку:

В таблице стилей с data-vite-dev-id=" vite-plugin:stylex.css" мы видим «хеш-классы» и соответствующие им стили: .x78zum5{display:flex}.xdt5ytf{flex-direction:column}. Те же хеш-классы мы видим у контейнера: <div class="x78zum5 xdt5ytf"></div>.

Выполняем сборку приложения:

yarn build # или npm run build

Открываем файл dist/assets/index-[hash].css:

/* stylex */ .x78zum5 {   display: flex; } .xdt5ytf {   flex-direction: column; } /* index.css */ html, body, #root {   height: 100%; } body {   margin: 0; }

Отлично. Начнем погружаться в исходный код stylex.

Реверс-инжиниринг

Обратите внимание: дальнейший разбор кода актуален для stylex@0.4.1. В будущем код может и наверняка изменится, возможно, до неузнаваемости ?

Также обратите внимание, что с целью упрощения кода для облегчения его восприятия я беспощадно удалял строки и даже целые блоки кода ?

Мы ограничимся изучением работы методов create (создание стилей) и props (применение стилей).

Выдержки из кода соответствуют порядку применения переменных и функций, а не порядку их определения.

Начнем с файла packages/stylex/src/stylex.js:

// 294 - номер строки кода export default _stylex  // 239 function _stylex(...styles) {   const [className] = styleq(styles)   return className } _stylex.props = props _stylex.create = create  // 36 // Об этой библиотеке поговорим позже import { styleq } from 'styleq'  // 136 export const create = stylexCreate  // 76 function stylexCreate(styles) {   if (__implementations.create != null) {     const create = __implementations.create     return create(styles)   }   throw new Error(     'stylex.create should never be called. It should be compiled away.',   ) }  // 280 const __implementations = {}  // 282 export function __monkey_patch__(key, implementation) {   __implementations[key] = implementation }

Метод create извлекается из объекта __implementations, который инициализируется с помощью функции __monkey_patch__.

Следуем за __monkey_patch__() в файл packages/dev-runtime/src/index.js:

// 10 import { __monkey_patch__ } from '@stylexjs/stylex' import { styleSheet } from '@stylexjs/stylex/lib/StyleXSheet';  // 20 import getStyleXCreate from './stylex-create';  // 45 export default function inject({   insert = defaultInsert,   ...config }) {   // Инициализация `create()`   __monkey_patch__('create', getStyleXCreate({ ...config, insert })); }  // 24 const defaultInsert = (   key,   ltrRule,   priority,   // На это можно не обращать особого внимания,   // это стили для направления текста "справа налево"   rtlRule, ) => {   if (priority === 0) {     if (injectedVariableObjs.has(key)) {       throw new Error('A VarGroup with this name already exists: ' + key);     } else {       injectedVariableObjs.add(key);     }   }   styleSheet.insert(ltrRule, priority, rtlRule); };  // 22 const injectedVariableObjs = new Set();

Метод create — это функция, возвращаемая getStyleXCreate({ ...config, defaultInsert }).

В функции defaultInsert вызывается метод insert объекта styleSheet. Этот объект создается в файле packages/stylex/src/StyleXSheet.js:

// 364 export const styleSheet = new StyleXSheet({   supportsVariables: true,   rootTheme: {},   rootDarkTheme: {}, });  // 85 export class StyleXSheet {   static LIGHT_MODE_CLASS_NAME = LIGHT_MODE_CLASS_NAME;   static DARK_MODE_CLASS_NAME = DARK_MODE_CLASS_NAME;    constructor(opts) {     this.tag = null;     this.injected = false;     this.ruleForPriority = new Map();     this.rules = [];      this.rootTheme = opts.rootTheme;     this.rootDarkTheme = opts.rootDarkTheme;   }    // Цветовые схемы   rootTheme;   rootDarkTheme;    // Массив, содержащий все добавленные правила. Используется для   // отслеживания индексов правил в таблице стилей   rules;    // Индикатор добавления тега `style` в `document`   injected;    // Элемент `style` для добавления правил   tag;    // Для поддержки приоритетов необходимо хранить правило,   // которое находится в начале приоритета   ruleForPriority;    /**    * Извлекает тег `style`    */   getTag() {     const { tag } = this;     invariant(tag != null, 'expected tag');     return tag;   }    /**    * Добавляет тег `style` в `head`    */   inject() {     if (this.injected) {       return;     }      this.injected = true;      // Создаем тег `style`     this.tag = makeStyleTag();     this.injectTheme();   }    /**    * Вставляет стили темы - переменные/токены    */   injectTheme() {     if (this.rootTheme != null) {       this.insert(         buildTheme(`:root, .${LIGHT_MODE_CLASS_NAME}`, this.rootTheme),         0,       );     }     if (this.rootDarkTheme != null) {       this.insert(         buildTheme(           `.${DARK_MODE_CLASS_NAME}:root, .${DARK_MODE_CLASS_NAME}`,           this.rootDarkTheme,         ),         0,       );     }   }    /**    * Добавляет правила в таблицу стилей    */   insert(rawLTRRule, priority) {     // Добавляем таблицу стилей при отсутствии     if (this.injected === false) {       this.inject();     }      const rawRule = rawLTRRule;      // Не добавляем правило при наличии (исключаем дубликаты)     if (this.rules.includes(rawRule)) {       return;     }      // Нормализованное правило с определенной специфичностью,     // это нас не интересует     const rule = this.normalizeRule(       addSpecificityLevel(rawRule, Math.floor(priority / 1000)),     );      // Получаем позицию для вставки правила по его приоритету,     // это нас не интересует     const insertPos = this.getInsertPositionForPriority(priority);     this.rules.splice(insertPos, 0, rule);      // Устанавливаем правило как конец группы приоритета     this.ruleForPriority.set(priority, rule);      const tag = this.getTag();     const sheet = tag.sheet;      if (sheet != null) {       try {         // https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet/insertRule         sheet.insertRule(rule, Math.min(insertPos, sheet.cssRules.length));       } catch (err) {         console.error('insertRule error', err, rule, insertPos);       }     }   } }  // 344 /**  * Функция добавления `:not(#\#)` для повышения специфичности; полифилл для @layer  */ function addSpecificityLevel(selector, index) {   if (selector.startsWith('@keyframes')) {     return selector;   }   // Чем больше `not(#\\#)`, тем выше специфичность   const pseudo = Array.from({ length: index })     .map(() => ':not(#\\#)')     .join('');    const lastOpenCurly = selector.includes('::')     ? selector.indexOf('::')     : selector.lastIndexOf('{');   const beforeCurly = selector.slice(0, lastOpenCurly);   const afterCurly = selector.slice(lastOpenCurly);    return `${beforeCurly}${pseudo}${afterCurly}`; }  // 45 /**  * Создает тег `style` и добавляет его в `head`  */ function makeStyleTag() {   const tag = document.createElement('style');   tag.setAttribute('type', 'text/css');   tag.setAttribute('data-stylex', 'true');    const head = document.head || document.getElementsByTagName('head')[0];   invariant(head, 'expected head');   head.appendChild(tag);    return tag; }  // 28 /**  * Принимает тему и генерирует переменные CSS  */ function buildTheme(selector, theme) {   const lines = [];   lines.push(`${selector} {`);    for (const key in theme) {     const value = theme[key];     lines.push(`  --${key}: ${value};`);   }    lines.push('}');    return lines.join('\n'); }

Здесь основной является строка sheet.insertRule(rule, position);, отвечающая за однократное добавление переданного правила в таблицу стилей с учетом его приоритета.

Возвращаемся в файл packages/dev-runtime/src/index.js и следуем за getStyleXCreate в файл packages/dev-runtime/src/stylex-create.js:

// 177 export default function getStyleXCreate(   config, ) {   const stylexCreate = (     styles,   ) => {     return createWithFns(styles, config);   };    return stylexCreate; }  // 106 // Значением поля объекта стилей может быть функция, // мы не будем рассматривать такой вариант function createWithFns(   styles,   { insert, ...config }, ) {   const stylesWithoutFns = {};   for (const key in styles) {     const value = styles[key];     stylesWithoutFns[key] = value;   }   // Одна из самых важных строк!   const [compiledStyles, injectedStyles] = create(stylesWithoutFns, config);   // Добавляем хеш-классы и стили в таблицу стилей   for (const key in injectedStyles) {     const { ltr, priority, rtl } = injectedStyles[key];     insert(key, ltr, priority, rtl);   }    const temp = compiledStyles;    const finalStyles = { ...temp };    // Возвращаем скомпилированные стили   return finalStyles; }  // 16 import { create, IncludedStyles, utils } from '@stylexjs/shared';

Функция getStyleXCreate возвращает функцию stylexCreate, которая возвращает функцию createWithFns, которая вызывает функцию create, добавляет одни стили (injectedStyles) в таблицу стилей и возвращает другие (finalStyles). finalStyles — это стили, которые впоследствии передаются в метод stylex.props, которая находится в файле packages/stylex/src/stylex.js:

// 47 export function props(   this,   ...styles ) {   const options = this;   if (__implementations.props) {     return __implementations.props.call(options, styles);   }   // Мы подробнее поговорим об этой библиотеке в конце   const [className, style] = styleq(styles);   const result = {};   if (className != null && className !== '') {     result.className = className;   }   if (style != null && Object.keys(style).length > 0) {     result.style = style;   }   // result = { className: "хеш-классы", style: { ...стили } }   return result; }

Следуем за функцией create в файл packages/shared/src/index.js:

// 43 export const create = styleXCreateSet;  // 23 import styleXCreateSet from './stylex-create';

Без комментариев ? Следуем за функцией styleXCreateSet в файл packages/shared/src/stylex-create.js:

// 24 // Эта функция принимает объект со стилями, передаваемый в метод `stylex.create` и преобразует его. // Преобразование заключается в замене значений стилей на названия CSS-классов. // // Функция также собирает все внедряемые (injected) стили. // Возвращает кортеж с преобразованным объектом стилей и объектом внедряемых стилей. // // Перед возвратом выполняется проверка отсутствия дубликатов во внедряемых стилях. export default function styleXCreateSet(   namespaces,   options = defaultOptions, ) {   const resolvedNamespaces = {};   const injectedStyles = {};    for (const namespaceName of Object.keys(namespaces)) {     const namespace = namespaces[namespaceName];      const flattenedNamespace = flattenRawStyleObject(namespace, options);     const compiledNamespaceTuples = flattenedNamespace.map(([key, value]) => {       return [key, value.compiled(options)];     });      const compiledNamespace = objFromEntries(compiledNamespaceTuples);      const namespaceObj = {};     for (const key of Object.keys(compiledNamespace)) {       const value = compiledNamespace[key];       if (value instanceof IncludedStyles) {         namespaceObj[key] = value;       } else {         const classNameTuples =           value.map((v) => (Array.isArray(v) ? v : null)).filter(Boolean);         const className =           classNameTuples.map(([className]) => className).join(' ') || null;         namespaceObj[key] = className;         for (const [className, injectable] of classNameTuples) {           if (injectedStyles[className] == null) {             injectedStyles[className] = injectable;           }         }       }     }     // `$$css: true` требуется для `styleq`     resolvedNamespaces[namespaceName] = { ...namespaceObj, $$css: true };   }    return [resolvedNamespaces, injectedStyles]; }

Весь рассмотренный код можно найти в файле stylex/source-code.js нашего проекта.

Код функции styleXCreateSet и всех используемых ей утилит занимает почти 800 строк. Я вынес его в отдельный файл stylex/create-props.js. Вы можете внимательно изучить его самостоятельно, я же остановлюсь только на основных моментах.

Посмотрим на результат, возвращаемый функцией styleXCreateSet:

const [compiledStyles, injectedStyles] = styleXCreateSet({   root: {     display: 'flex',     flexDirection: 'column',   }, }) console.log(stringify({ compiledStyles, injectedStyles }))

Результат:

{   "compiledStyles": {     "root": {       "display": "x78zum5",       "flexDirection": "xdt5ytf",       "$$css": true     }   },   "injectedStyles": {     "x78zum5": {       "priority": 3000,       "ltr": ".x78zum5{display:flex}",       "rtl": null     },     "xdt5ytf": {       "priority": 3000,       "ltr": ".xdt5ytf{flex-direction:column}",       "rtl": null     }   } }

injectedStyles добавляются в таблицу стилей через метод styleSheet.insert. compiledStyles пропускаются через библиотеку styleq и передаются компоненту.

Вызовем функцию props:

const result = props(compiledStyles.root) console.log(stringify({ result })) const result2 = props(compiledStyles.root, { display: 'flex' }) console.log(stringify({ result2 }))

Результат:

{   "result": {     "className": "x78zum5 xdt5ytf"   } }  {   "result2": {     // Обратите внимание на отсутствие хеш-класса для `display: flex`     "className": "xdt5ytf",     "style": {       "display": "flex"     }   } }

Посмотрим на цепочку преобразований такого объекта:

{   root: {     display: 'flex',   }, }

в такие:

{   "compiledStyles": {     "root": {       "display": "x78zum5",       "$$css": true     }   },   "injectedStyles": {     "x78zum5": {       "priority": 3000,       "ltr": ".x78zum5{display:flex}",       "rtl": null     }   } }

function styleXCreateSet(namespaces, options = defaultOptions) {   const resolvedNamespaces = {}   const injectedStyles = {}    for (const namespaceName of Object.keys(namespaces)) {     const namespace = namespaces[namespaceName]     // !     console.log(stringify({ namespace }))      const flattenedNamespace = flattenRawStyleObject(namespace, options)     // !     console.log(stringify({ flattenedNamespace }))      const compiledNamespaceTuples = flattenedNamespace.map(([key, value]) => {       return [key, value.compiled(options)]     })     // !     console.log(stringify({ compiledNamespaceTuples }))      const compiledNamespace = objFromEntries(compiledNamespaceTuples)     // !     console.log(stringify({ compiledNamespace }))      const namespaceObj = {}     for (const key of Object.keys(compiledNamespace)) {       const value = compiledNamespace[key]       const classNameTuples = value         .map((v) => (Array.isArray(v) ? v : null))         .filter(Boolean)       const className =         classNameTuples.map(([className]) => className).join(' ') || null       namespaceObj[key] = className       for (const [className, injectable] of classNameTuples) {         if (injectedStyles[className] == null) {           injectedStyles[className] = injectable         }       }     }     resolvedNamespaces[namespaceName] = { ...namespaceObj, $$css: true }   }    return [resolvedNamespaces, injectedStyles] }

Результат:

{   "namespace": {     "display": "flex"   } }  {   "flattenedNamespace": [     [       "display",       {         "property": "display",         "value": "flex",         "pseudos": [],         "atRules": []       }     ]   ] }  {   "compiledNamespaceTuples": [     [       "display",       [         [           "x78zum5",           {             "priority": 3000,             "ltr": ".x78zum5{display:flex}",             "rtl": null           }         ]       ]     ]   ] }  {   "compiledNamespace": {     "display": [       [         "x78zum5",         {           "priority": 3000,           "ltr": ".x78zum5{display:flex}",           "rtl": null         }       ]     ]   } }

За генерацию хеша на основе правила CSS отвечает функция convertStyleToClassName:

function convertStyleToClassName(   objEntry,   pseudos,   atRules,   options = defaultOptions, ) {   // !   console.log(stringify({ objEntry, pseudos, atRules }))    const { classNamePrefix = 'x' } = options   const [key, rawValue] = objEntry   const dashedKey = dashify(key)    const value = Array.isArray(rawValue)     ? rawValue.map((eachValue) => transformValue(key, eachValue, options))     : transformValue(key, rawValue, options)    const sortedPseudos = arraySort(pseudos ?? [])   const sortedAtRules = arraySort(atRules ?? [])    const atRuleHashString = sortedPseudos.join('')   const pseudoHashString = sortedAtRules.join('')    const modifierHashString = atRuleHashString + pseudoHashString || 'null'    const stringToHash = Array.isArray(value)     ? dashedKey + value.join(', ') + modifierHashString     : dashedKey + value + modifierHashString   // !   console.log(stringify({ dashedKey, value, modifierHashString, stringToHash }))    // Обратите внимание: `<>` используется для обеспечения стабильности хешей.   // Это должно быть удалено в будущих версиях   const className = classNamePrefix + createHash('<>' + stringToHash)    const cssRules = generateRule(className, dashedKey, value, pseudos, atRules)   // !   console.log(stringify({ key, className, cssRules }))   return [key, className, cssRules] }

Результат:

{   "objEntry": [     "display",     "flex"   ],   "pseudos": [],   "atRules": [] }  {   "dashedKey": "display",   "value": "flex",   "modifierHashString": "null",   // Строка для хеширования   "stringToHash": "displayflexnull" }  {   "key": "display",   // Результат хеширования   "className": "x78zum5",   "cssRules": {     "priority": 3000,     "ltr": ".x78zum5{display:flex}",     "rtl": null   } }

Строка "displayflexnull" преобразуется/хешируется в x78zum5

Настоящая магия происходит в функции createHash:

function createHash(str) {   return murmurhash2_32_gc(str, 1).toString(36) }  function murmurhash2_32_gc(str, seed = 0) {   let l = str.length,     h = seed ^ l,     i = 0,     k    while (l >= 4) {     k =       (str.charCodeAt(i) & 0xff) |       ((str.charCodeAt(++i) & 0xff) << 8) |       ((str.charCodeAt(++i) & 0xff) << 16) |       ((str.charCodeAt(++i) & 0xff) << 24)      k = (k & 0xffff) * 0x5bd1e995 + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16)     k ^= k >>> 24     k = (k & 0xffff) * 0x5bd1e995 + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16)      h =       ((h & 0xffff) * 0x5bd1e995 +         ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)) ^       k      l -= 4     ++i   }    switch (l) {     case 3:       h ^= (str.charCodeAt(i + 2) & 0xff) << 16     case 2:       h ^= (str.charCodeAt(i + 1) & 0xff) << 8     case 1:       h ^= str.charCodeAt(i) & 0xff       h =         (h & 0xffff) * 0x5bd1e995 + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)   }    h ^= h >>> 13   h = (h & 0xffff) * 0x5bd1e995 + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)   h ^= h >>> 15    return h >>> 0 }

MurmurHash2 — это простая и быстрая хеш-функция общего назначения, разработанная Остином Эпплби. Она не является криптографически-безопасной и возвращает 32-разрядное беззнаковое число.

В заключение, поговорим о функции styleq, которая вызывается в функции props:

const [className, style] = styleq(styles);

styleq — это быстрая и небольшая среда выполнения JavaScript для объединения названий классов HTML, созданных компиляторами CSS. Вызов styleq(...styles) объединяет объекты стилей и генерирует строку className и объект встроенных (inline) стилей (с названиями свойств в стиле camelCase):

const [className, inlineStyle] = styleq(styles.root, { opacity });

Функция styleq эффективно объединяет глубоко вложенные массивы как извлеченных (extracted), так и встроенных объектов стилей:

  • компилируемые стили должны содержать свойство $$css со значением true (помните resolvedNamespaces[namespaceName] = { ...namespaceObj, $$css: true }?)
  • объект стилей без свойства $$css считается встраиваемым стилем
  • компилируемые стили должны быть статичными для лучшей производительности
  • ключи объектов компилируемых стилей не обязательно должны совпадать с названиями свойств CSS; разрешена любая строка
  • значения компилируемого объекта стилей должны быть строками классов HTML

const styles = {   root: {     // Обязательное поле     $$css: true,     // Классы для отладки     'debug::file:styles.root': 'debug::file:styles.root',     // Атомарные классы     display: 'display-flex-class',     alignItems: 'alignItems-center-class'   } };  const [className, inlineStyle] = styleq(styles.root, props.style);

Таким образом, styleq обеспечивает эффективное объединение хеш-классов в одну строку className без дублирования и с учетом встроенных стилей, которые возвращаются в виде объекта style. className и style возвращаются в виде, пригодном для прямой передачи компоненту React для установки элементу HTML в качестве соответствующих атрибутов.

Пожалуй, это все, о чем я хотел рассказать вам в этой статье.

Happy coding!


Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud — в нашем Telegram-канале


ссылка на оригинал статьи https://habr.com/ru/articles/795473/