From 917f645e028b941ea4fd9b0db20e39244f5b80cf Mon Sep 17 00:00:00 2001 From: Mathieu Acthernoene Date: Mon, 13 Jan 2025 18:28:25 +0100 Subject: [PATCH] Memoize hash function --- package.json | 2 +- src/hash.ts | 4 ++++ src/hyphenateName.ts | 13 +++++------- src/normalizeValue.ts | 49 +++++++++++++++++++------------------------ src/sheet.ts | 10 ++++----- src/utils.ts | 28 +++++++++++++++++++++++++ 6 files changed, 64 insertions(+), 42 deletions(-) create mode 100644 src/hash.ts diff --git a/package.json b/package.json index c196b9d..861cb3a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@swan-io/css", - "version": "0.1.1", + "version": "0.1.2", "license": "MIT", "description": "A lightweight and performant atomic CSS-in-JS library", "author": "Mathieu Acthernoene ", diff --git a/src/hash.ts b/src/hash.ts new file mode 100644 index 0000000..044a92f --- /dev/null +++ b/src/hash.ts @@ -0,0 +1,4 @@ +import hashRaw from "@emotion/hash"; +import { memoizeOne } from "./utils"; + +export const hash = memoizeOne(hashRaw); diff --git a/src/hyphenateName.ts b/src/hyphenateName.ts index f1295a2..f8c6f88 100644 --- a/src/hyphenateName.ts +++ b/src/hyphenateName.ts @@ -1,12 +1,9 @@ // https://github.com/facebook/react/blob/v19.0.0/packages/react-dom-bindings/src/shared/hyphenateStyleName.js -const uppercasePattern = /([A-Z])/g; -const hyphenateNameCache: Record = {}; +import { memoizeOne } from "./utils"; -export const hyphenateName = (name: string): string => { - hyphenateNameCache[name] ??= name - .replace(uppercasePattern, "-$1") - .toLowerCase(); +const uppercasePattern = /([A-Z])/g; - return hyphenateNameCache[name]; -}; +export const hyphenateName = memoizeOne((value: string): string => + value.replace(uppercasePattern, "-$1").toLowerCase(), +); diff --git a/src/normalizeValue.ts b/src/normalizeValue.ts index 0ba0467..0f7a4c2 100644 --- a/src/normalizeValue.ts +++ b/src/normalizeValue.ts @@ -1,7 +1,6 @@ import normalizeColor from "@react-native/normalize-colors"; import { Property } from "./types"; - -const normalizeValueCache: Record = {}; +import { memoizeTwo } from "./utils"; /** * CSS properties which accept numbers but are not in units of "px" @@ -64,37 +63,31 @@ const isWebColor = (color: string): boolean => color === "inherit" || color.indexOf("var(") === 0; -export const normalizeValue = ( - value: string | number | undefined, - property: string, -): string | undefined => { - if (typeof value === "number") { - return unitlessProperties.has(property) ? String(value) : `${value}px`; - } - - if (colorProperties.has(property)) { - if (value == null || isWebColor(value)) { - return value; +export const normalizeValue = memoizeTwo( + (key: string, value: string | number): string => { + if (typeof value === "number") { + return unitlessProperties.has(key) ? String(value) : `${value}px`; } - if (normalizeValueCache[value] != null) { - return normalizeValueCache[value]; - } + if (colorProperties.has(key)) { + if (isWebColor(value)) { + return value; + } - const normalizedColor = normalizeColor(value); + const normalizedColor = normalizeColor(value); - if (normalizedColor != null) { - const int = ((normalizedColor << 24) | (normalizedColor >>> 8)) >>> 0; + if (normalizedColor != null) { + const int = ((normalizedColor << 24) | (normalizedColor >>> 8)) >>> 0; - const r = (int >> 16) & 255; - const g = (int >> 8) & 255; - const b = int & 255; - const a = ((int >> 24) & 255) / 255; + const r = (int >> 16) & 255; + const g = (int >> 8) & 255; + const b = int & 255; + const a = ((int >> 24) & 255) / 255; - normalizeValueCache[value] = `rgba(${r}, ${g}, ${b}, ${a.toFixed(2)})`; - return normalizeValueCache[value]; + return `rgba(${r}, ${g}, ${b}, ${a.toFixed(2)})`; + } } - } - return value; -}; + return value; + }, +); diff --git a/src/sheet.ts b/src/sheet.ts index ceb9aaa..4378218 100644 --- a/src/sheet.ts +++ b/src/sheet.ts @@ -1,4 +1,4 @@ -import hash from "@emotion/hash"; +import { hash } from "./hash"; import { hyphenateName } from "./hyphenateName"; import { normalizeValue } from "./normalizeValue"; import { ClassNames, Keyframes, Nestable, Style } from "./types"; @@ -53,15 +53,15 @@ const insertRule = (sheet: CSSMediaRule, rule: string): void => { } }; -const stringifyRule = (name: string, value: string | number): string => { - if (name === "appearance") { +const stringifyRule = (key: string, value: string | number): string => { + if (key === "appearance") { return `-webkit-appearance: ${value}; appearance: ${value};`; } - if (name === "lineClamp") { + if (key === "lineClamp") { return `-webkit-line-clamp: ${value}; line-clamp: ${value};`; } - return `${hyphenateName(name)}: ${normalizeValue(value, name)};`; + return `${hyphenateName(key)}: ${normalizeValue(key, value)};`; }; const extractClassNames = (items: ClassNames, acc: string[]): string[] => { diff --git a/src/utils.ts b/src/utils.ts index 0854ae6..6ad693a 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -14,3 +14,31 @@ export const forEach = >( } } }; + +export const memoizeOne = (fn: (value: string) => T) => { + const cache: Record = Object.create(null); + + return (value: string): T => { + if (cache[value] === undefined) { + cache[value] = fn(value); + } + + return cache[value]; + }; +}; + +export const memoizeTwo = ( + fn: (key: string, value: string | number) => T, +) => { + const cache: Record = Object.create(null); + + return (key: string, value: string | number): T => { + const cacheKey = key + ":" + value; + + if (cache[cacheKey] === undefined) { + cache[cacheKey] = fn(key, value); + } + + return cache[cacheKey]; + }; +};