Skip to content

Commit

Permalink
Unify getCss and expandResponsive (#6335)
Browse files Browse the repository at this point in the history
* refactor: refactor getCss

* perf: unify getCss and expandResponsive to not iterate styles twice

* docs: add changeset

* chore: add a benchmark script for styled-system

* fix: import index file correctly
  • Loading branch information
itkrt2y committed Aug 8, 2022
1 parent ff0dfb2 commit e0913e5
Show file tree
Hide file tree
Showing 8 changed files with 234 additions and 108 deletions.
5 changes: 5 additions & 0 deletions .changeset/nasty-pans-develop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@chakra-ui/styled-system": patch
---

fix not to loop style processing twice for performance
109 changes: 109 additions & 0 deletions packages/styled-system/benchmarks/benchmark.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// @ts-check

const { isMainThread } = require("worker_threads")
const { css, toCSSVar } = require("../dist/index.cjs.js")

const theme = toCSSVar({
breakpoints: {
sm: "40em",
md: "52em",
lg: "64em",
xl: "80em",
},
colors: {
red: {
500: "#ff0000",
},
primary: "tomato",
secondary: "cyan",
},
fontSizes: [12, 14, 16, 24, 36],
space: [0, 4, 8, 16, 32, 64, 128, 256, 512],
fonts: {
monospace: "Menlo, monospace",
},
lineHeights: {
body: 1.5,
},
fontWeights: {
bold: 600,
},
sizes: {
small: 4,
medium: 8,
large: 16,
sidebar: 320,
},
buttons: {
primary: {
p: 3,
fontWeight: "bold",
color: "white",
bg: "primary",
borderRadius: 2,
},
},
text: {
caps: {
fontSize: [1, 2],
letterSpacing: "0.1em",
textTransform: "uppercase",
},
title: {
fontSize: [3, 4],
letterSpacing: ["-0.01em", "-0.02em"],
},
},
borderWidths: {
thin: 1,
},
borderStyles: {
thick: "solid",
},
radii: {
small: 5,
},
textTransform: {
header: "uppercase",
},
transition: {
duration: {
slow: "1s",
},
easing: {
smooth: "ease-in-out",
},
property: {
common: "opacity, transform, background-color, color",
},
},
})

async function main() {
const { cronometro } = await import("cronometro")

return cronometro(
{
systemProps() {
css({
color: "primary",
h1: {
py: [3, 4],
},
})(theme)
},
},
{},
(err, _results) => {
if (err) {
throw err
}
},
)
}

if (isMainThread) {
main()
} else {
module.exports = main
}
6 changes: 4 additions & 2 deletions packages/styled-system/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,16 @@
},
"devDependencies": {
"@emotion/react": "^11.9.0",
"@emotion/styled": "^11.8.1"
"@emotion/styled": "^11.8.1",
"cronometro": "^1.1.0"
},
"sideEffects": false,
"scripts": {
"build": "tsup src/index.ts --dts",
"dev": "pnpm build -- --watch",
"clean": "rimraf dist .turbo",
"typecheck": "tsc --noEmit",
"build:fast": "tsup src/index.ts"
"build:fast": "tsup src/index.ts",
"benchmark": "node benchmarks/benchmark.js"
}
}
116 changes: 73 additions & 43 deletions packages/styled-system/src/css.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,9 @@
import {
Dict,
isCssVar,
isObject,
isString,
mergeWith as merge,
runIfFn,
} from "@chakra-ui/utils"
import { Dict, isCssVar, isObject, isString, runIfFn } from "@chakra-ui/utils"
import * as CSS from "csstype"
import { pseudoSelectors } from "./pseudos"
import { systemProps as systemPropConfigs } from "./system"
import { StyleObjectOrFn } from "./system.types"
import { expandResponsive } from "./utils/expand-responsive"
import { Config, PropConfig } from "./utils/prop-config"
import { Config } from "./utils/prop-config"
import { CssTheme } from "./utils/types"

const isCSSVariableTokenValue = (key: string, value: any): value is string =>
Expand Down Expand Up @@ -39,20 +31,58 @@ interface GetCSSOptions {
export function getCss(options: GetCSSOptions) {
const { configs = {}, pseudos = {}, theme } = options

/**
* Before any style can be processed, the user needs to call `toCSSVar`
* which analyzes the theme's breakpoint and appends a `__breakpoints` property
* to the theme with more details of the breakpoints.
*
* To learn more, go here: packages/utils/src/breakpoint.ts #analyzeBreakpoints
*/
if (!theme.__breakpoints) return () => ({})
const { isResponsive, toArrayValue, media: medias } = theme.__breakpoints

const css = (stylesOrFn: Dict, nested = false) => {
const _styles = runIfFn(stylesOrFn, theme)
const styles = expandResponsive(_styles)(theme)
const styles = runIfFn(stylesOrFn, theme)

let computedStyles: Dict = {}

for (let key in styles) {
const valueOrFn = styles[key]

/**
* allows the user to pass functional values
* boxShadow: theme => `0 2px 2px ${theme.colors.red}`
*/
let value = runIfFn(valueOrFn, theme)
let value = runIfFn(styles[key], theme)
if (value == null) continue

if (Array.isArray(value) || (isObject(value) && isResponsive(value))) {
let values = Array.isArray(value) ? value : toArrayValue(value)
values = values.slice(0, medias.length)

for (let index = 0; index < values.length; index++) {
const media = medias[index]
const val = values[index]

if (media) {
if (val == null) {
computedStyles[media] ??= {}
} else {
computedStyles[media] = Object.assign(
{},
computedStyles[media],
css({ [key]: val }, true),
)
}
} else {
computedStyles = Object.assign(
{},
computedStyles,
css({ ...styles, [key]: val }, false),
)
}
}

continue
}

/**
* converts pseudo shorthands to valid selector
Expand All @@ -73,19 +103,25 @@ export function getCss(options: GetCSSOptions) {
value = resolveTokenValue(theme, value)
}

let config = configs[key]
if (isObject(value)) {
computedStyles[key] = Object.assign(
{},
computedStyles[key],
css(value, true),
)
continue
}

let config = configs[key]
if (config === true) {
config = { property: key } as PropConfig
config = { property: key }
}

if (isObject(value)) {
computedStyles[key] = computedStyles[key] ?? {}
computedStyles[key] = merge({}, computedStyles[key], css(value, true))
continue
if (!nested && config?.static) {
const staticStyles = runIfFn(config.static, theme)
computedStyles = Object.assign({}, computedStyles, staticStyles)
}

let rawValue = config?.transform?.(value, theme, _styles) ?? value
let rawValue = config?.transform?.(value, theme, styles) ?? value

/**
* Used for `layerStyle`, `textStyle` and `apply`. After getting the
Expand All @@ -96,6 +132,11 @@ export function getCss(options: GetCSSOptions) {
*/
rawValue = config?.processResult ? css(rawValue, true) : rawValue

if (isObject(rawValue)) {
computedStyles = Object.assign({}, computedStyles, rawValue)
continue
}

/**
* allows us to define css properties for RTL and LTR.
*
Expand All @@ -104,33 +145,22 @@ export function getCss(options: GetCSSOptions) {
* }
*/
const configProperty = runIfFn(config?.property, theme)

if (!nested && config?.static) {
const staticStyles = runIfFn(config.static, theme)
computedStyles = merge({}, computedStyles, staticStyles)
}

if (configProperty && Array.isArray(configProperty)) {
for (const property of configProperty) {
computedStyles[property] = rawValue
if (configProperty) {
if (Array.isArray(configProperty)) {
for (const property of configProperty) {
computedStyles[property] = rawValue
}
continue
}
continue
}

if (configProperty) {
if (configProperty === "&" && isObject(rawValue)) {
computedStyles = merge({}, computedStyles, rawValue)
computedStyles = Object.assign({}, computedStyles, rawValue)
} else {
computedStyles[configProperty as string] = rawValue
computedStyles[configProperty] = rawValue
}
continue
}

if (isObject(rawValue)) {
computedStyles = merge({}, computedStyles, rawValue)
continue
}

computedStyles[key] = rawValue
}

Expand All @@ -140,7 +170,7 @@ export function getCss(options: GetCSSOptions) {
return css
}

export const css = (styles: StyleObjectOrFn) => (theme: any) => {
export const css = (styles: StyleObjectOrFn) => (theme: CssTheme) => {
const cssFn = getCss({
theme,
pseudos: pseudoSelectors,
Expand Down
60 changes: 0 additions & 60 deletions packages/styled-system/src/utils/expand-responsive.ts

This file was deleted.

5 changes: 2 additions & 3 deletions packages/styled-system/tests/css.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -513,15 +513,14 @@ test("returns correct media query 2nd order", () => {
expect(keys).toMatchInlineSnapshot(`
Array [
"flexDirection",
"justifyContent",
"@media screen and (min-width: 40em)",
"@media screen and (min-width: 52em)",
"color",
"height",
"paddingInlineStart",
"paddingInlineEnd",
"paddingTop",
"paddingBottom",
"@media screen and (min-width: 40em)",
"@media screen and (min-width: 52em)",
]
`)
})
Expand Down

1 comment on commit e0913e5

@vercel
Copy link

@vercel vercel bot commented on e0913e5 Aug 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.