Skip to content
Merged
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
9 changes: 9 additions & 0 deletions packages/uniwind/src/core/native/native-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,12 @@ export function lightDark(this: UniwindRuntime, light: string, dark: string) {

return light
}

export const cloneWithAccessors = <T extends object>(obj: T) => {
const proto = Object.getPrototypeOf(obj)
const clone = Object.create(proto)

Object.defineProperties(clone, Object.getOwnPropertyDescriptors(obj))

return clone
}
73 changes: 45 additions & 28 deletions packages/uniwind/src/core/native/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { Dimensions, Platform } from 'react-native'
import { Orientation, StyleDependency } from '../../types'
import { ComponentState, GenerateStyleSheetsCallback, RNStyle, Style, StyleSheets } from '../types'
import { cloneWithAccessors } from './native-utils'
import { parseBoxShadow, parseFontVariant, parseTransformsMutation, resolveGradient } from './parsers'
import { UniwindRuntime } from './runtime'

Expand All @@ -11,8 +12,10 @@ type StylesResult = {
}

class UniwindStoreBuilder {
stylesheets = {} as StyleSheets
listeners = {
runtime = UniwindRuntime
private stylesheet = {} as StyleSheets
private vars = {} as Record<string, unknown>
private listeners = {
[StyleDependency.ColorScheme]: new Set<() => void>(),
[StyleDependency.Theme]: new Set<() => void>(),
[StyleDependency.Dimensions]: new Set<() => void>(),
Expand All @@ -21,8 +24,8 @@ class UniwindStoreBuilder {
[StyleDependency.FontScale]: new Set<() => void>(),
[StyleDependency.Rtl]: new Set<() => void>(),
}
runtime = UniwindRuntime
cache = new Map<string, StylesResult>()
private cache = new Map<string, StylesResult>()
private generateStyleSheetCallbackResult: ReturnType<GenerateStyleSheetsCallback> | null = null

subscribe(onStoreChange: () => void, dependencies: Array<StyleDependency>) {
dependencies.forEach(dep => {
Expand Down Expand Up @@ -65,19 +68,28 @@ class UniwindStoreBuilder {
}

reinit = (generateStyleSheetCallback?: GenerateStyleSheetsCallback) => {
const styleSheet = generateStyleSheetCallback?.(this.runtime) ?? this.stylesheets
const themeVars = styleSheet[`__uniwind-theme-${this.runtime.currentThemeName}`]
const platformVars = styleSheet[`__uniwind-platform-${Platform.OS}`]
const config = generateStyleSheetCallback?.(this.runtime) ?? this.generateStyleSheetCallbackResult

if (!config) {
return
}

const { scopedVars, stylesheet, vars } = config

this.generateStyleSheetCallbackResult = config
this.stylesheet = stylesheet
this.vars = vars

const themeVars = scopedVars[`__uniwind-theme-${this.runtime.currentThemeName}`]
const platformVars = scopedVars[`__uniwind-platform-${Platform.OS}`]

if (themeVars) {
Object.assign(styleSheet, themeVars)
Object.defineProperties(this.vars, Object.getOwnPropertyDescriptors(themeVars))
}

if (platformVars) {
Object.assign(styleSheet, platformVars)
Object.defineProperties(this.vars, Object.getOwnPropertyDescriptors(platformVars))
}

this.stylesheets = styleSheet
}

notifyListeners = (dependencies: Array<StyleDependency>) => {
Expand All @@ -86,15 +98,16 @@ class UniwindStoreBuilder {

private resolveStyles(classNames: string, state?: ComponentState) {
const result = {} as Record<string, any>
let vars = this.vars
const dependencies = [] as Array<StyleDependency>
const bestBreakpoints = new Map<string, Style>()

for (const className of classNames.split(' ')) {
if (!(className in this.stylesheets)) {
if (!(className in this.stylesheet)) {
continue
}

for (const style of this.stylesheets[className] as Array<Style>) {
for (const style of this.stylesheet[className] as Array<Style>) {
dependencies.push(...style.dependencies)

if (
Expand All @@ -110,16 +123,6 @@ class UniwindStoreBuilder {
continue
}

style.usedVars.forEach(varName => {
if (varName in this.stylesheets && !(varName in result)) {
Object.defineProperty(result, varName, {
configurable: true,
enumerable: false,
get: this.stylesheets[varName] as () => unknown,
})
}
})

for (const [property, valueGetter] of style.entries) {
const previousBest = bestBreakpoints.get(property)

Expand All @@ -134,11 +137,25 @@ class UniwindStoreBuilder {
continue
}

Object.defineProperty(result, property, {
configurable: true,
get: valueGetter,
enumerable: property[0] !== '-',
})
if (property[0] === '-') {
// Clone vars object if we are adding inline variables
if (vars === this.vars) {
vars = cloneWithAccessors(this.vars)
}

Object.defineProperty(vars, property, {
configurable: true,
enumerable: true,
get: valueGetter,
})
} else {
Object.defineProperty(result, property, {
configurable: true,
enumerable: true,
get: () => valueGetter.call(vars),
})
}

bestBreakpoints.set(property, style)
}
}
Expand Down
9 changes: 6 additions & 3 deletions packages/uniwind/src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { ColorScheme, Orientation, StyleDependency, UniwindConfig } from '../typ

export type Style = {
entries: Array<[string, () => unknown]>
usedVars: Array<string>
minWidth: number
maxWidth: number
orientation: Orientation | null
Expand All @@ -20,9 +19,13 @@ export type Style = {
disabled: boolean | null
}

export type StyleSheets = Record<string, Array<Style> | (() => unknown)>
export type StyleSheets = Record<string, Array<Style>>

export type GenerateStyleSheetsCallback = (rt: UniwindRuntime) => StyleSheets
export type GenerateStyleSheetsCallback = (rt: UniwindRuntime) => {
stylesheet: StyleSheets
vars: Record<string, unknown>
scopedVars: Partial<Record<string, Record<string, unknown>>>
}

type UserThemes = UniwindConfig extends { themes: infer T extends readonly string[] } ? T
: readonly string[]
Expand Down
50 changes: 31 additions & 19 deletions packages/uniwind/src/metro/compileVirtual.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { compile } from '@tailwindcss/node'
import { polyfillWeb } from './polyfillWeb'
import { ProcessorBuilder } from './processor'
import { addMetaToStylesTemplate, serializeStylesheet } from './stylesheet'
import { addMetaToStylesTemplate, serializeJS } from './stylesheet'
import { Platform, Polyfills } from './types'

type CompileVirtualConfig = {
Expand All @@ -28,23 +28,35 @@ export const compileVirtual = async ({ candidates, css, cssPath, platform, theme

Processor.transform(tailwindCSS)

return serializeStylesheet({
...Object.fromEntries(
Object.entries(Processor.vars).map(([key, value]) => {
if (key.startsWith('__uniwind-')) {
return [
key,
Object.fromEntries(
Object.entries(value).map(([nestedKey, nestedValue]) => {
return [nestedKey, `function() { return ${nestedValue as any} }`]
}),
),
]
}

return [key, `function() { return ${value} }`]
}),
const stylesheet = serializeJS(
addMetaToStylesTemplate(Processor, platform),
(key, value) => `"${key}": ${value}`,
)
const vars = serializeJS(
Object.fromEntries(
Object.entries(Processor.vars)
.filter(([key]) => !key.startsWith(`__uniwind-`)),
),
...addMetaToStylesTemplate(Processor, platform),
})
(key, value) => `get "${key}"() { return ${value} }`,
)
const scopedVars = Object.fromEntries(
Object.entries(Processor.vars)
.filter(([scopedVarsName]) => scopedVarsName.startsWith(`__uniwind-`))
.map(([scopedVarsName, scopedVars]) => [
scopedVarsName,
serializeJS(scopedVars, (key, value) => `get "${key}"() { return ${value} }`),
]),
)
const serializedScopedVars = Object.entries(scopedVars)
.map(([scopedVarsName, scopedVars]) => `"${scopedVarsName}": ({ ${scopedVars} }),`)
.join('')
const currentColorVar = `get currentColor() { return rt.colorScheme === 'dark' ? '#ffffff' : '#000000' },`

return [
'({',
`scopedVars: ({ ${serializedScopedVars} }),`,
`vars: ({ ${currentColorVar} ${vars} }),`,
`stylesheet: ({ ${stylesheet} }),`,
'})',
].join('')
}
14 changes: 0 additions & 14 deletions packages/uniwind/src/metro/stylesheet/addMetaToStylesTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,6 @@ import { ProcessorBuilder } from '../processor'
import { Platform, StyleSheetTemplate } from '../types'
import { isDefined, toCamelCase } from '../utils'

const extractVarsFromString = (value: string) => {
const thisIndexes = [...value.matchAll(/this\[/g)].map(m => m.index)

return thisIndexes.map(index => {
const afterIndex = value.slice(index + 5)
const closingIndex = afterIndex.indexOf(']')
const varName = afterIndex.slice(0, closingIndex)

return varName.replace(/[`"\\]/g, '')
})
}

export const addMetaToStylesTemplate = (Processor: ProcessorBuilder, currentPlatform: Platform) => {
const stylesheetsEntries = Object.entries(Processor.stylesheets as StyleSheetTemplate)
.map(([className, stylesPerMediaQuery]) => {
Expand Down Expand Up @@ -50,7 +38,6 @@ export const addMetaToStylesTemplate = (Processor: ProcessorBuilder, currentPlat

const dependencies: Array<StyleDependency> = []
const stringifiedEntries = JSON.stringify(entries)
const usedVars = extractVarsFromString(stringifiedEntries)

if (theme !== null || stringifiedEntries.includes('--color') || stringifiedEntries.includes('rt.lightDark')) {
dependencies.push(StyleDependency.Theme)
Expand Down Expand Up @@ -95,7 +82,6 @@ export const addMetaToStylesTemplate = (Processor: ProcessorBuilder, currentPlat
active,
focus,
disabled,
usedVars,
importantProperties: importantProperties?.map(property => property.startsWith('--') ? property : toCamelCase) ?? [],
complexity: [
minWidth !== 0,
Expand Down
15 changes: 6 additions & 9 deletions packages/uniwind/src/metro/stylesheet/serializeStylesheet.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { Logger } from '../logger'
import { addMissingSpaces, isNumber, pipe, smartSplit } from '../utils'

type Stylesheet = Record<string, any>

const FN_DECLARATION = 'function() { return'

const isJSExpression = (value: string) =>
Expand Down Expand Up @@ -169,22 +167,21 @@ const serialize = (value: any): string => {
}
}

export const serializeStylesheet = (stylesheet: Stylesheet) => {
const currentColor = `get currentColor() { return function() { return rt.colorScheme === 'dark' ? '#ffffff' : '#000000' } },`

export const serializeJS = (stylesheet: Record<string, any>, serializer: (key: string, value: string) => string) => {
// eslint-disable-next-line prefer-template
const serializedStylesheet = Object.entries(stylesheet).map(([key, value]) => {
const stringifiedValue = isNumber(value)
? String(value)
: serialize(value)

return `"${key}": ${stringifiedValue}`
}).join(',\n')
return serializer(key, stringifiedValue)
}).join(',') + ','

const js = `({ ${currentColor} ${serializedStylesheet} })`
const js = `${serializedStylesheet}`

try {
// eslint-disable-next-line @typescript-eslint/no-implied-eval, no-new-func
new Function(`function validateJS() { const fn = rt => ${js} }`)
new Function(`function validateJS() { const obj = ({ ${js} }) }`)
} catch {
Logger.error('Failed to create virtual js')
return ''
Expand Down