Skip to content

Commit 2a46b6c

Browse files
authored
feat: performance improvements (#75)
* feat: minimal cache layer * feat: dont compute styles if className is empty * feat: convert stylesheet to be fully computable * fix: add currentColor to used vars * fix: dont override inline vars with global * fix: padding and margin parsing
1 parent 2a2dfe6 commit 2a46b6c

17 files changed

Lines changed: 302 additions & 292 deletions

File tree

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { describe, expect, test } from 'bun:test'
2+
import { getStyleSheetsFromCandidates, injectMocks } from './utils'
3+
4+
describe('Converts tailwind colors to hex', () => {
5+
injectMocks()
6+
7+
test('Tailwind built-in', async () => {
8+
const className = 'bg-red-500 border-blue-500/50 text-black'
9+
10+
await getStyleSheetsFromCandidates(...className.split(' '))
11+
12+
const { UniwindStore } = await import('../src/core/native')
13+
const styles = UniwindStore.getStyles(className).styles
14+
15+
expect(styles).toHaveProperty('backgroundColor', '#fb2c36')
16+
expect(styles).toHaveProperty('borderColor', '#2b7fff80')
17+
expect(styles).toHaveProperty('color', '#000000')
18+
})
19+
20+
test('Custom colors', async () => {
21+
const className = 'bg-[#ff0000] text-[#ff000080]'
22+
23+
await getStyleSheetsFromCandidates(...className.split(' '))
24+
25+
const { UniwindStore } = await import('../src/core/native')
26+
const styles = UniwindStore.getStyles(className).styles
27+
28+
expect(styles).toHaveProperty('backgroundColor', '#ff0000')
29+
expect(styles).toHaveProperty('color', '#ff000080')
30+
})
31+
})

packages/uniwind/specs/resolver/gradient.test.ts renamed to packages/uniwind/specs/gradient.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, expect, test } from 'bun:test'
2-
import { getStyleSheetsFromCandidates, injectMocks } from '../utils'
2+
import { getStyleSheetsFromCandidates, injectMocks } from './utils'
33

44
describe('Converts tailwind linear gradients', () => {
55
injectMocks()
@@ -9,7 +9,7 @@ describe('Converts tailwind linear gradients', () => {
99

1010
await getStyleSheetsFromCandidates(...className.split(' '))
1111

12-
const { UniwindStore } = await import('../../src/core/native')
12+
const { UniwindStore } = await import('../src/core/native')
1313
const styles = UniwindStore.getStyles(className).styles
1414

1515
expect(styles).toEqual({
@@ -41,7 +41,7 @@ describe('Converts tailwind linear gradients', () => {
4141

4242
await getStyleSheetsFromCandidates(...className.split(' '))
4343

44-
const { UniwindStore } = await import('../../src/core/native')
44+
const { UniwindStore } = await import('../src/core/native')
4545
const styles = UniwindStore.getStyles(className).styles
4646

4747
expect(styles).toEqual({
@@ -79,7 +79,7 @@ describe('Converts tailwind linear gradients', () => {
7979

8080
await getStyleSheetsFromCandidates(...className.split(' '))
8181

82-
const { UniwindStore } = await import('../../src/core/native')
82+
const { UniwindStore } = await import('../src/core/native')
8383
const styles = UniwindStore.getStyles(className).styles
8484

8585
expect(styles).toEqual({
@@ -111,7 +111,7 @@ describe('Converts tailwind linear gradients', () => {
111111

112112
await getStyleSheetsFromCandidates(...className.split(' '))
113113

114-
const { UniwindStore } = await import('../../src/core/native')
114+
const { UniwindStore } = await import('../src/core/native')
115115
const styles = UniwindStore.getStyles(className).styles
116116

117117
expect(styles).toEqual({

packages/uniwind/specs/parser/colors.test.ts

Lines changed: 0 additions & 40 deletions
This file was deleted.

packages/uniwind/specs/parser/spacing.test.ts

Lines changed: 0 additions & 46 deletions
This file was deleted.

packages/uniwind/specs/resolver/shadow.test.ts renamed to packages/uniwind/specs/shadow.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, expect, test } from 'bun:test'
2-
import { getStyleSheetsFromCandidates, injectMocks } from '../utils'
2+
import { getStyleSheetsFromCandidates, injectMocks } from './utils'
33

44
describe('Converts tailwind shadow system', () => {
55
injectMocks()
@@ -9,7 +9,7 @@ describe('Converts tailwind shadow system', () => {
99
'shadow-xl',
1010
)
1111

12-
const { UniwindStore } = await import('../../src/core/native')
12+
const { UniwindStore } = await import('../src/core/native')
1313
const styles = UniwindStore.getStyles('shadow-xl').styles
1414

1515
expect(styles).toEqual({
@@ -26,7 +26,7 @@ describe('Converts tailwind shadow system', () => {
2626

2727
await getStyleSheetsFromCandidates(...candidates)
2828

29-
const { UniwindStore } = await import('../../src/core/native')
29+
const { UniwindStore } = await import('../src/core/native')
3030
const styles = UniwindStore.getStyles(candidates.join(' ')).styles
3131

3232
expect(styles).toEqual({
@@ -38,7 +38,7 @@ describe('Converts tailwind shadow system', () => {
3838
test('Ring', async () => {
3939
await getStyleSheetsFromCandidates('ring-2')
4040

41-
const { UniwindStore } = await import('../../src/core/native')
41+
const { UniwindStore } = await import('../src/core/native')
4242
const styles = UniwindStore.getStyles('ring-2').styles
4343

4444
expect(styles).toEqual({
@@ -56,7 +56,7 @@ describe('Converts tailwind shadow system', () => {
5656

5757
await getStyleSheetsFromCandidates(...candidates)
5858

59-
const { UniwindStore } = await import('../../src/core/native')
59+
const { UniwindStore } = await import('../src/core/native')
6060
const styles = UniwindStore.getStyles(candidates.join(' ')).styles
6161

6262
expect(styles).toEqual({
@@ -72,7 +72,7 @@ describe('Converts tailwind shadow system', () => {
7272
]
7373
await getStyleSheetsFromCandidates(...candidates)
7474

75-
const { UniwindStore } = await import('../../src/core/native')
75+
const { UniwindStore } = await import('../src/core/native')
7676
const styles = UniwindStore.getStyles(candidates.join(' ')).styles
7777

7878
expect(styles).toEqual({
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { describe, expect, test } from 'bun:test'
2+
import { UniwindRuntimeMock } from './mocks'
3+
import { getStyleSheetsFromCandidates, injectMocks, twSize } from './utils'
4+
5+
describe('Converts tailwind spacings', () => {
6+
injectMocks()
7+
8+
test('Built in', async () => {
9+
const className = 'px-4 m-2'
10+
11+
await getStyleSheetsFromCandidates(...className.split(' '))
12+
13+
const { UniwindStore } = await import('../src/core/native')
14+
const styles = UniwindStore.getStyles(className).styles
15+
16+
expect(styles).toHaveProperty('paddingHorizontal', twSize(4))
17+
expect(styles).toHaveProperty('margin', twSize(2))
18+
})
19+
20+
test.only('Custom', async () => {
21+
const className = 'px-[16px] m-[8px]'
22+
23+
await getStyleSheetsFromCandidates(...className.split(' '))
24+
25+
const { UniwindStore } = await import('../src/core/native')
26+
const styles = UniwindStore.getStyles(className).styles
27+
28+
expect(styles).toHaveProperty('paddingHorizontalStart', 16)
29+
expect(styles).toHaveProperty('paddingHorizontalEnd', 16)
30+
expect(styles).toHaveProperty('marginTop', 8)
31+
expect(styles).toHaveProperty('marginBottom', 8)
32+
expect(styles).toHaveProperty('marginLeft', 8)
33+
expect(styles).toHaveProperty('marginRight', 8)
34+
})
35+
36+
test('Safe area', async () => {
37+
const className = 'pt-safe'
38+
39+
await getStyleSheetsFromCandidates(...className.split(' '))
40+
41+
const { UniwindStore } = await import('../src/core/native')
42+
const styles = UniwindStore.getStyles(className).styles
43+
44+
expect(styles).toHaveProperty('paddingTop', UniwindRuntimeMock.insets.top)
45+
})
46+
})

packages/uniwind/specs/utils.ts

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { mock } from 'bun:test'
22
import { readFileSync } from 'fs'
3-
import { RNStyle } from '../src/core/types'
43
import { compileVirtual } from '../src/metro/compileVirtual'
54
import { Platform } from '../src/metro/types'
65
import { UniwindRuntimeMock } from './mocks'
@@ -15,6 +14,7 @@ export const getStyleSheetsFromCandidates = async <T extends string>(...candidat
1514
candidates,
1615
platform: Platform.iOS,
1716
cssPath: testCSSPath,
17+
polyfills: {},
1818
themes: ['light', 'dark'],
1919
})
2020

@@ -23,20 +23,6 @@ export const getStyleSheetsFromCandidates = async <T extends string>(...candidat
2323
return globalThis.__uniwind__computeStylesheet(UniwindRuntimeMock)
2424
}
2525

26-
export const getStylesFromCandidates = async <T extends string>(...candidates: Array<T>) => {
27-
const stylesheets = await getStyleSheetsFromCandidates(...candidates)
28-
29-
return Object.fromEntries(
30-
Object.entries(stylesheets).map(([key, value]) => {
31-
if (!Array.isArray(value)) {
32-
return null
33-
}
34-
35-
return [key, value.map(entry => Object.fromEntries(entry.entries))]
36-
}).filter(Boolean),
37-
) as Record<T, Array<RNStyle>>
38-
}
39-
4026
export const twSize = (size: number) => size * 4
4127

4228
export const injectMocks = () => {

packages/uniwind/src/components/native/useStyle.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
1+
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
12
import { useEffect, useMemo, useReducer } from 'react'
23
import { UniwindStore } from '../../core/native'
3-
import { ComponentState } from '../../core/types'
4+
import { ComponentState, RNStyle } from '../../core/types'
5+
import { StyleDependency } from '../../types'
6+
7+
const emptyState = { styles: {} as RNStyle, dependencies: [] as Array<StyleDependency> }
48

59
export const useStyle = (className?: string, state?: ComponentState) => {
610
const [_, rerender] = useReducer(() => ({}), {})
711
const styleState = useMemo(
8-
() => UniwindStore.getStyles(className, state),
12+
() => className ? UniwindStore.getStyles(className, state) : emptyState,
913
[className, _, state?.isDisabled, state?.isFocused, state?.isPressed],
1014
)
1115

1216
useEffect(() => {
13-
const dispose = UniwindStore.subscribe(() => rerender(), styleState.dependencies)
17+
if (styleState.dependencies.length > 0) {
18+
const dispose = UniwindStore.subscribe(() => rerender(), styleState.dependencies)
1419

15-
return dispose
20+
return dispose
21+
}
1622
}, [styleState])
1723

1824
return styleState.styles
Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable max-depth */
12
const transforms = [
23
'translateX',
34
'translateY',
@@ -18,28 +19,35 @@ export const parseTransformsMutation = (styles: Record<string, any>) => {
1819
const transformTokens = typeof styles.transform === 'string'
1920
? styles.transform
2021
.split(' ')
21-
.filter(token => token !== 'undefined')
22+
.filter(token => token === 'undefined')
2223
: []
23-
const transformsResult = transforms.reduce<Array<Record<string, any>>>((acc, transform) => {
24-
// Transforms inside transform - transform: rotate(45deg);
25-
transformTokens
26-
.filter(token => token.startsWith(transform))
27-
.forEach(token => {
24+
25+
const transformsResult = []
26+
27+
for (const transform of transforms) {
28+
if (transformTokens.length > 0) {
29+
// Transforms inside transform - transform: rotate(45deg);
30+
for (const token of transformTokens) {
31+
if (!token.startsWith(transform)) {
32+
continue
33+
}
34+
2835
const transformValue = token.slice(transform.length + 1, -1)
2936

30-
acc.push({ [transform]: transformValue })
31-
})
37+
transformsResult.push({ [transform]: transformValue })
38+
}
39+
}
3240

3341
// Transforms outside of transform - { rotate: '45deg' }
3442
if (styles[transform] !== undefined) {
35-
acc.push({ [transform]: styles[transform] })
43+
transformsResult.push({ [transform]: styles[transform] })
3644
delete styles[transform]
3745
}
38-
39-
return acc
40-
}, [])
46+
}
4147

4248
if (transformsResult.length > 0) {
43-
styles.transform = transformsResult
49+
Object.defineProperty(styles, 'transform', {
50+
value: transformsResult,
51+
})
4452
}
4553
}

0 commit comments

Comments
 (0)