Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/brisk-names-assign.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"css-to-react-native": patch
---

Speed up transform() on large stylesheets.
165 changes: 158 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,19 +87,170 @@ export const getStylesForProperty = (
const isRawValue = allowShorthand === false || !(propName in transforms)
const value = inputValue.trim()

const propValues = isRawValue
? { [propName]: transformRawValue(propName, value) }
: transformShorthandValue(propName, value)
if (!isRawValue) return transformShorthandValue(propName, value)

const propValues: Style = {}
propValues[propName] = transformRawValue(propName, value)
return propValues
}

// Known stylesheet names. Other names are camelized and not retained.
const knownPropertyNames: { [name: string]: string } = Object.create(null)

for (const name of [
'align-content',
'align-items',
'align-self',
'aspect-ratio',
'backface-visibility',
'background',
'background-color',
'border',
'border-bottom-color',
'border-bottom-end-radius',
'border-bottom-left-radius',
'border-bottom-right-radius',
'border-bottom-start-radius',
'border-bottom-width',
'border-color',
'border-curve',
'border-end-color',
'border-end-end-radius',
'border-end-start-radius',
'border-end-width',
'border-left-color',
'border-left-width',
'border-radius',
'border-right-color',
'border-right-width',
'border-start-color',
'border-start-end-radius',
'border-start-start-radius',
'border-start-width',
'border-style',
'border-top-color',
'border-top-end-radius',
'border-top-left-radius',
'border-top-right-radius',
'border-top-start-radius',
'border-top-width',
'border-width',
'bottom',
'box-shadow',
'color',
'column-gap',
'direction',
'display',
'elevation',
'end',
'filter',
'flex',
'flex-basis',
'flex-direction',
'flex-flow',
'flex-grow',
'flex-shrink',
'flex-wrap',
'font',
'font-family',
'font-size',
'font-style',
'font-variant',
'font-weight',
'gap',
'height',
'include-font-padding',
'inset',
'inset-block',
'inset-block-end',
'inset-block-start',
'inset-inline',
'inset-inline-end',
'inset-inline-start',
'justify-content',
'left',
'letter-spacing',
'line-height',
'margin',
'margin-block',
'margin-block-end',
'margin-block-start',
'margin-bottom',
'margin-end',
'margin-horizontal',
'margin-inline',
'margin-inline-end',
'margin-inline-start',
'margin-left',
'margin-right',
'margin-start',
'margin-top',
'margin-vertical',
'max-height',
'max-width',
'min-height',
'min-width',
'mix-blend-mode',
'object-fit',
'opacity',
'overflow',
'overlay-color',
'padding',
'padding-block',
'padding-block-end',
'padding-block-start',
'padding-bottom',
'padding-end',
'padding-horizontal',
'padding-inline',
'padding-inline-end',
'padding-inline-start',
'padding-left',
'padding-right',
'padding-start',
'padding-top',
'padding-vertical',
'place-content',
'pointer-events',
'position',
'resize-mode',
'right',
'row-gap',
'shadow-color',
'shadow-offset',
'shadow-opacity',
'shadow-radius',
'start',
'text-align',
'text-align-vertical',
'text-decoration',
'text-decoration-color',
'text-decoration-line',
'text-decoration-style',
'text-shadow',
'text-shadow-color',
'text-shadow-offset',
'text-shadow-radius',
'text-transform',
'tint-color',
'top',
'transform',
'transform-origin',
'user-select',
'vertical-align',
'width',
'writing-direction',
'z-index',
]) {
knownPropertyNames[name] = camelizeStyleName(name)
}

export const getPropertyName = (propName: string): string => {
const cached = knownPropertyNames[propName]
if (cached !== undefined) return cached

const isCustomProp = /^--\w+/.test(propName)
if (isCustomProp) {
return propName
}
return camelizeStyleName(propName)
return isCustomProp ? propName : camelizeStyleName(propName)
}

const transform = (
Expand Down
44 changes: 27 additions & 17 deletions src/transforms/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,33 @@ const singleLengthOrPercent = oneOfTypes([LENGTH, PERCENT])
const singleAngle = oneOfTypes([ANGLE])
const xyTransformFactory =
(tokenTypes: TokenDescriptor[]) =>
(key: string, valueIfOmitted?: string | number) =>
(functionStream: TokenStream): TransformPartValue => {
const x = functionStream.expect(...tokenTypes)
(key: string, valueIfOmitted?: string | number) => {
const keyY = `${key}Y`
const keyX = `${key}X`

let y: string | number | undefined
if (functionStream.hasTokens()) {
functionStream.expect(COMMA)
y = functionStream.expect(...tokenTypes)
} else if (valueIfOmitted !== undefined) {
y = valueIfOmitted
} else {
// Assumption, if x === y, then we can omit XY
// I.e. scale(5) => [{ scale: 5 }] rather than [{ scaleX: 5 }, { scaleY: 5 }]
return x
}
return (functionStream: TokenStream): TransformPartValue => {
const x = functionStream.expect(...tokenTypes)

functionStream.expectEmpty()
let y: string | number | undefined
if (functionStream.hasTokens()) {
functionStream.expect(COMMA)
y = functionStream.expect(...tokenTypes)
} else if (valueIfOmitted !== undefined) {
y = valueIfOmitted
} else {
// Assumption, if x === y, then we can omit XY
// I.e. scale(5) => [{ scale: 5 }] rather than [{ scaleX: 5 }, { scaleY: 5 }]
return x
}

return [{ [`${key}Y`]: y }, { [`${key}X`]: x }]
functionStream.expectEmpty()

const yPart: TransformStyleValue = {}
yPart[keyY] = y
const xPart: TransformStyleValue = {}
xPart[keyX] = x
return [yPart, xPart]
}
}
const xyNumber = xyTransformFactory([NUMBER])
const xyLengthOrPercent = xyTransformFactory([LENGTH, PERCENT])
Expand Down Expand Up @@ -78,7 +86,9 @@ export default (tokenStream: TokenStream): Style => {

let transformedValues = partTransform(functionStream)
if (!Array.isArray(transformedValues)) {
transformedValues = [{ [functionName]: transformedValues }]
const part: TransformStyleValue = {}
part[functionName] = transformedValues
transformedValues = [part]
}
transforms = transformedValues.concat(transforms)

Expand Down
46 changes: 25 additions & 21 deletions src/transforms/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,24 @@ import {
} from '../tokenTypes'
import type { ParsedShadow, ShadowOffset, Style } from '../types'

export const directionFactory =
({
types = [LENGTH, UNSUPPORTED_LENGTH_UNIT, PERCENT],
directions = ['Top', 'Right', 'Bottom', 'Left'],
prefix = '',
suffix = '',
}: {
types?: TokenDescriptor[]
directions?: string[]
prefix?: string
suffix?: string
}) =>
(tokenStream: TokenStream): Style => {
export const directionFactory = ({
types = [LENGTH, UNSUPPORTED_LENGTH_UNIT, PERCENT],
directions = ['Top', 'Right', 'Bottom', 'Left'],
prefix = '',
suffix = '',
}: {
types?: TokenDescriptor[]
directions?: string[]
prefix?: string
suffix?: string
}) => {
// Stable keys. Building a new string per value makes the result object slow.
const key0 = `${prefix}${directions[0]}${suffix}`
const key1 = `${prefix}${directions[1]}${suffix}`
const key2 = `${prefix}${directions[2]}${suffix}`
const key3 = `${prefix}${directions[3]}${suffix}`

return (tokenStream: TokenStream): Style => {
const values: Array<string | number> = []

// borderWidth doesn't currently allow a percent value, but may do in the future
Expand All @@ -37,15 +42,14 @@ export const directionFactory =

const [top, right = top, bottom = top, left = right] = values

const keyFor = (n: number) => `${prefix}${directions[n]}${suffix}`

return {
[keyFor(0)]: top,
[keyFor(1)]: right,
[keyFor(2)]: bottom,
[keyFor(3)]: left,
}
const style: Style = {}
style[key0] = top
style[key1] = right
style[key2] = bottom
style[key3] = left
return style
}
}

export const parseShadowOffset = (tokenStream: TokenStream): ShadowOffset => {
const width = tokenStream.expect(LENGTH)
Expand Down
Loading