rnwind 0.0.8 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cjs/core/parser/color.cjs +33 -1
- package/lib/cjs/core/parser/color.cjs.map +1 -1
- package/lib/cjs/core/parser/color.d.ts +10 -0
- package/lib/cjs/core/parser/declaration.cjs +121 -9
- package/lib/cjs/core/parser/declaration.cjs.map +1 -1
- package/lib/cjs/core/parser/gradient.cjs +46 -12
- package/lib/cjs/core/parser/gradient.cjs.map +1 -1
- package/lib/cjs/core/parser/gradient.d.ts +2 -1
- package/lib/cjs/core/parser/keyframes.cjs +27 -12
- package/lib/cjs/core/parser/keyframes.cjs.map +1 -1
- package/lib/cjs/core/parser/keyframes.d.ts +11 -0
- package/lib/cjs/core/parser/layout-dispatcher.cjs +33 -10
- package/lib/cjs/core/parser/layout-dispatcher.cjs.map +1 -1
- package/lib/cjs/core/parser/length.cjs +17 -1
- package/lib/cjs/core/parser/length.cjs.map +1 -1
- package/lib/cjs/core/parser/safe-area.cjs +24 -3
- package/lib/cjs/core/parser/safe-area.cjs.map +1 -1
- package/lib/cjs/core/parser/theme-vars.cjs +58 -8
- package/lib/cjs/core/parser/theme-vars.cjs.map +1 -1
- package/lib/cjs/core/parser/tokens.cjs +77 -9
- package/lib/cjs/core/parser/tokens.cjs.map +1 -1
- package/lib/cjs/core/parser/tokens.d.ts +9 -0
- package/lib/cjs/core/parser/transform.cjs +18 -9
- package/lib/cjs/core/parser/transform.cjs.map +1 -1
- package/lib/cjs/core/parser/tw-parser.cjs +136 -34
- package/lib/cjs/core/parser/tw-parser.cjs.map +1 -1
- package/lib/cjs/core/parser/tw-parser.d.ts +20 -0
- package/lib/cjs/core/parser/typography-dispatcher.cjs +19 -1
- package/lib/cjs/core/parser/typography-dispatcher.cjs.map +1 -1
- package/lib/cjs/core/parser/typography.cjs +15 -18
- package/lib/cjs/core/parser/typography.cjs.map +1 -1
- package/lib/cjs/core/parser/typography.d.ts +5 -5
- package/lib/cjs/core/style-builder/build-style.cjs +12 -3
- package/lib/cjs/core/style-builder/build-style.cjs.map +1 -1
- package/lib/cjs/core/style-builder/build-style.d.ts +3 -1
- package/lib/cjs/core/style-builder/union-builder.cjs +9 -11
- package/lib/cjs/core/style-builder/union-builder.cjs.map +1 -1
- package/lib/cjs/core/style-builder/union-builder.d.ts +7 -8
- package/lib/cjs/metro/dts.cjs +6 -1
- package/lib/cjs/metro/dts.cjs.map +1 -1
- package/lib/cjs/metro/transformer.cjs +42 -77
- package/lib/cjs/metro/transformer.cjs.map +1 -1
- package/lib/cjs/metro/with-config.cjs +9 -29
- package/lib/cjs/metro/with-config.cjs.map +1 -1
- package/lib/cjs/runtime/hooks/use-scheme.cjs +17 -11
- package/lib/cjs/runtime/hooks/use-scheme.cjs.map +1 -1
- package/lib/cjs/runtime/hooks/use-scheme.d.ts +7 -4
- package/lib/cjs/runtime/index.cjs +2 -1
- package/lib/cjs/runtime/index.cjs.map +1 -1
- package/lib/cjs/runtime/index.d.ts +2 -2
- package/lib/cjs/runtime/lookup-css.cjs +41 -0
- package/lib/cjs/runtime/lookup-css.cjs.map +1 -1
- package/lib/cjs/runtime/lookup-css.d.ts +29 -0
- package/lib/cjs/runtime/resolve.cjs +8 -6
- package/lib/cjs/runtime/resolve.cjs.map +1 -1
- package/lib/cjs/runtime/wrap.cjs +50 -57
- package/lib/cjs/runtime/wrap.cjs.map +1 -1
- package/lib/cjs/runtime/wrap.d.ts +10 -4
- package/lib/cjs/testing/index.cjs +1 -1
- package/lib/cjs/testing/index.cjs.map +1 -1
- package/lib/esm/core/parser/color.d.ts +10 -0
- package/lib/esm/core/parser/color.mjs +34 -3
- package/lib/esm/core/parser/color.mjs.map +1 -1
- package/lib/esm/core/parser/declaration.mjs +122 -10
- package/lib/esm/core/parser/declaration.mjs.map +1 -1
- package/lib/esm/core/parser/gradient.d.ts +2 -1
- package/lib/esm/core/parser/gradient.mjs +45 -11
- package/lib/esm/core/parser/gradient.mjs.map +1 -1
- package/lib/esm/core/parser/keyframes.d.ts +11 -0
- package/lib/esm/core/parser/keyframes.mjs +27 -12
- package/lib/esm/core/parser/keyframes.mjs.map +1 -1
- package/lib/esm/core/parser/layout-dispatcher.mjs +33 -10
- package/lib/esm/core/parser/layout-dispatcher.mjs.map +1 -1
- package/lib/esm/core/parser/length.mjs +17 -1
- package/lib/esm/core/parser/length.mjs.map +1 -1
- package/lib/esm/core/parser/safe-area.mjs +24 -3
- package/lib/esm/core/parser/safe-area.mjs.map +1 -1
- package/lib/esm/core/parser/theme-vars.mjs +58 -8
- package/lib/esm/core/parser/theme-vars.mjs.map +1 -1
- package/lib/esm/core/parser/tokens.d.ts +9 -0
- package/lib/esm/core/parser/tokens.mjs +77 -10
- package/lib/esm/core/parser/tokens.mjs.map +1 -1
- package/lib/esm/core/parser/transform.mjs +18 -9
- package/lib/esm/core/parser/transform.mjs.map +1 -1
- package/lib/esm/core/parser/tw-parser.d.ts +20 -0
- package/lib/esm/core/parser/tw-parser.mjs +138 -36
- package/lib/esm/core/parser/tw-parser.mjs.map +1 -1
- package/lib/esm/core/parser/typography-dispatcher.mjs +19 -1
- package/lib/esm/core/parser/typography-dispatcher.mjs.map +1 -1
- package/lib/esm/core/parser/typography.d.ts +5 -5
- package/lib/esm/core/parser/typography.mjs +15 -18
- package/lib/esm/core/parser/typography.mjs.map +1 -1
- package/lib/esm/core/style-builder/build-style.d.ts +3 -1
- package/lib/esm/core/style-builder/build-style.mjs +12 -3
- package/lib/esm/core/style-builder/build-style.mjs.map +1 -1
- package/lib/esm/core/style-builder/union-builder.d.ts +7 -8
- package/lib/esm/core/style-builder/union-builder.mjs +9 -11
- package/lib/esm/core/style-builder/union-builder.mjs.map +1 -1
- package/lib/esm/metro/dts.mjs +6 -1
- package/lib/esm/metro/dts.mjs.map +1 -1
- package/lib/esm/metro/transformer.mjs +42 -77
- package/lib/esm/metro/transformer.mjs.map +1 -1
- package/lib/esm/metro/with-config.mjs +10 -30
- package/lib/esm/metro/with-config.mjs.map +1 -1
- package/lib/esm/runtime/hooks/use-scheme.d.ts +7 -4
- package/lib/esm/runtime/hooks/use-scheme.mjs +17 -11
- package/lib/esm/runtime/hooks/use-scheme.mjs.map +1 -1
- package/lib/esm/runtime/index.d.ts +2 -2
- package/lib/esm/runtime/index.mjs +2 -2
- package/lib/esm/runtime/index.mjs.map +1 -1
- package/lib/esm/runtime/lookup-css.d.ts +29 -0
- package/lib/esm/runtime/lookup-css.mjs +39 -1
- package/lib/esm/runtime/lookup-css.mjs.map +1 -1
- package/lib/esm/runtime/resolve.mjs +9 -7
- package/lib/esm/runtime/resolve.mjs.map +1 -1
- package/lib/esm/runtime/wrap.d.ts +10 -4
- package/lib/esm/runtime/wrap.mjs +50 -57
- package/lib/esm/runtime/wrap.mjs.map +1 -1
- package/lib/esm/testing/index.mjs +2 -2
- package/lib/esm/testing/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/core/parser/color.ts +32 -1
- package/src/core/parser/declaration.ts +119 -10
- package/src/core/parser/gradient.ts +48 -11
- package/src/core/parser/keyframes.ts +31 -3
- package/src/core/parser/layout-dispatcher.ts +32 -9
- package/src/core/parser/length.ts +18 -1
- package/src/core/parser/safe-area.ts +23 -2
- package/src/core/parser/theme-vars.ts +75 -8
- package/src/core/parser/tokens.ts +76 -9
- package/src/core/parser/transform.ts +19 -8
- package/src/core/parser/tw-parser.ts +148 -31
- package/src/core/parser/typography-dispatcher.ts +20 -1
- package/src/core/parser/typography.ts +15 -15
- package/src/core/style-builder/build-style.ts +12 -1
- package/src/core/style-builder/union-builder.ts +10 -12
- package/src/metro/dts.ts +6 -1
- package/src/metro/transformer.ts +42 -78
- package/src/metro/with-config.ts +10 -29
- package/src/runtime/hooks/use-scheme.ts +17 -10
- package/src/runtime/index.ts +2 -1
- package/src/runtime/lookup-css.ts +42 -0
- package/src/runtime/resolve.ts +9 -7
- package/src/runtime/wrap.tsx +57 -61
- package/src/testing/index.ts +3 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { KeyframeBlock, RNStyle, SchemedStyle } from '../parser'
|
|
2
|
+
import type { ThemeTables } from '../types'
|
|
2
3
|
import { normalizeClassName } from '../normalize-classname'
|
|
3
4
|
|
|
4
5
|
/** Match atom names like `border-hairline`, `h-hairline`, `border-t-hairline`, etc. */
|
|
@@ -300,6 +301,7 @@ function serializeBreakpoints(breakpoints: ReadonlyMap<string, number>): string
|
|
|
300
301
|
* @param breakpoints Responsive breakpoint name → px-threshold map.
|
|
301
302
|
* @param gradients Atom → gradient info for `registerGradients`.
|
|
302
303
|
* @param haptics Atom → haptic request for `registerHaptics`.
|
|
304
|
+
* @param themeTokens
|
|
303
305
|
* @returns JS source text.
|
|
304
306
|
*/
|
|
305
307
|
function renderManifest(
|
|
@@ -307,15 +309,19 @@ function renderManifest(
|
|
|
307
309
|
breakpoints: ReadonlyMap<string, number>,
|
|
308
310
|
gradients: ReadonlyMap<string, unknown>,
|
|
309
311
|
haptics: ReadonlyMap<string, unknown>,
|
|
312
|
+
themeTokens: ThemeTables,
|
|
310
313
|
): string {
|
|
314
|
+
const hasTokens = Object.keys(themeTokens).length > 0
|
|
311
315
|
const imports = ['registerSchemeLoader', 'registerBreakpoints']
|
|
312
316
|
if (gradients.size > 0) imports.push('registerGradients')
|
|
313
317
|
if (haptics.size > 0) imports.push('registerHaptics')
|
|
318
|
+
if (hasTokens) imports.push('registerThemeTokens')
|
|
314
319
|
const lines: string[] = [`import { ${imports.join(', ')} } from 'rnwind'`, `import './common.style'`]
|
|
315
320
|
for (const variant of variants) lines.push(`import ${JSON.stringify(`./${variant}.style`)}`)
|
|
316
321
|
lines.push(``, `registerBreakpoints(${serializeBreakpoints(breakpoints)})`)
|
|
317
322
|
if (gradients.size > 0) lines.push(`registerGradients(${serializeFeatureMap(gradients)})`)
|
|
318
323
|
if (haptics.size > 0) lines.push(`registerHaptics(${serializeFeatureMap(haptics)})`)
|
|
324
|
+
if (hasTokens) lines.push(`registerThemeTokens(${JSON.stringify(themeTokens)})`)
|
|
319
325
|
lines.push(
|
|
320
326
|
``,
|
|
321
327
|
`function ensureSchemeLoaded(_name) {}`,
|
|
@@ -679,6 +685,7 @@ const EMPTY_BREAKPOINTS: ReadonlyMap<string, number> = new Map()
|
|
|
679
685
|
* @param literals Distinct literal className strings — pre-merged into
|
|
680
686
|
* per-scheme molecules so the runtime resolver's O(1) molecule-first
|
|
681
687
|
* path is populated. Empty for legacy/test callers (atom path only).
|
|
688
|
+
* @param themeTokens
|
|
682
689
|
* @returns Per-scheme sources, manifest source, variant list.
|
|
683
690
|
*/
|
|
684
691
|
export function buildSchemeSources(
|
|
@@ -690,6 +697,7 @@ export function buildSchemeSources(
|
|
|
690
697
|
gradients: ReadonlyMap<string, unknown> = EMPTY_FEATURE_MAP,
|
|
691
698
|
haptics: ReadonlyMap<string, unknown> = EMPTY_FEATURE_MAP,
|
|
692
699
|
literals: readonly string[] = EMPTY_LITERALS,
|
|
700
|
+
themeTokens: ThemeTables = EMPTY_THEME_TOKENS,
|
|
693
701
|
): BuildSchemeSourcesOutput {
|
|
694
702
|
const variants = collectVariantSchemes(resolved)
|
|
695
703
|
const commonEntries: (readonly [string, string])[] = []
|
|
@@ -715,7 +723,7 @@ export function buildSchemeSources(
|
|
|
715
723
|
|
|
716
724
|
return {
|
|
717
725
|
schemeSources,
|
|
718
|
-
manifestSource: renderManifest(variants, breakpoints, gradients, haptics),
|
|
726
|
+
manifestSource: renderManifest(variants, breakpoints, gradients, haptics, themeTokens),
|
|
719
727
|
variants,
|
|
720
728
|
serializedMisses: misses,
|
|
721
729
|
}
|
|
@@ -727,5 +735,8 @@ const EMPTY_FEATURE_MAP: ReadonlyMap<string, unknown> = new Map()
|
|
|
727
735
|
/** Shared empty literal-list default (atom-only callers). */
|
|
728
736
|
const EMPTY_LITERALS: readonly string[] = []
|
|
729
737
|
|
|
738
|
+
/** Shared empty theme-token default (callers without theme tokens). */
|
|
739
|
+
const EMPTY_THEME_TOKENS: ThemeTables = {}
|
|
740
|
+
|
|
730
741
|
/** Registry key the runtime uses for the always-loaded fallback. */
|
|
731
742
|
export const COMMON_SCHEME_NAME: string = COMMON_SCHEME
|
|
@@ -2,6 +2,7 @@ import { createHash, randomBytes } from 'node:crypto'
|
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs'
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
import type { GradientAtomInfo, HapticRequest, KeyframeBlock, SchemedStyle, TailwindParser } from '../parser'
|
|
5
|
+
import type { ThemeTables } from '../types'
|
|
5
6
|
import { buildSchemeSources, type AtomSerializedCache } from './build-style'
|
|
6
7
|
|
|
7
8
|
/** Manifest module basename — the file SchemeProvider imports via the resolver. */
|
|
@@ -110,6 +111,13 @@ class UnionBuilder {
|
|
|
110
111
|
* snapshot is sufficient.
|
|
111
112
|
*/
|
|
112
113
|
private breakpoints: ReadonlyMap<string, number> = new Map()
|
|
114
|
+
/**
|
|
115
|
+
* Per-scheme theme token tables captured from the parser. Refreshed on
|
|
116
|
+
* every `recordFile` / `ensureProjectScanned` so theme-token edits land in
|
|
117
|
+
* the manifest's `registerThemeTokens({...})` — the data source for
|
|
118
|
+
* `useColor` / `useToken` / `useSize`.
|
|
119
|
+
*/
|
|
120
|
+
private themeTokens: ThemeTables = {}
|
|
113
121
|
/** file → set of atom names this file currently contributes. */
|
|
114
122
|
private readonly fileAtomSets = new Map<string, Set<string>>()
|
|
115
123
|
/** atom name → how many files currently contribute it (refcount). */
|
|
@@ -143,17 +151,6 @@ class UnionBuilder {
|
|
|
143
151
|
return path.join(this.cacheDir, MANIFEST_BASENAME)
|
|
144
152
|
}
|
|
145
153
|
|
|
146
|
-
/**
|
|
147
|
-
* Snapshot of every source file the builder has recorded atoms for
|
|
148
|
-
* this worker session. Used by `withRnwindConfig`'s CSS watcher to
|
|
149
|
-
* touch `mtime` on each and nudge Metro into re-transforming them
|
|
150
|
-
* with the new theme values.
|
|
151
|
-
* @returns Absolute source paths.
|
|
152
|
-
*/
|
|
153
|
-
public recordedFiles(): readonly string[] {
|
|
154
|
-
return [...this.fileAtomSets.keys()]
|
|
155
|
-
}
|
|
156
|
-
|
|
157
154
|
/** Cumulative cache-miss count — exposed for tests to assert cache behaviour. */
|
|
158
155
|
public get serializedMisses(): number {
|
|
159
156
|
return this.serializedMissesCount
|
|
@@ -183,6 +180,7 @@ class UnionBuilder {
|
|
|
183
180
|
for (const [name, gradient] of parsed.gradientAtoms) this.unionGradients.set(name, gradient)
|
|
184
181
|
for (const [name, haptic] of parsed.hapticAtoms) this.unionHaptics.set(name, haptic)
|
|
185
182
|
this.breakpoints = parsed.breakpoints
|
|
183
|
+
this.themeTokens = parsed.themeTokens
|
|
186
184
|
this.projectScanned = true
|
|
187
185
|
})()
|
|
188
186
|
try {
|
|
@@ -279,7 +277,7 @@ class UnionBuilder {
|
|
|
279
277
|
public async writeSchemes(): Promise<{ changedSchemes: readonly string[] }> {
|
|
280
278
|
await this.ensureProjectScanned()
|
|
281
279
|
const sortedAtomNames = [...this.unionAtoms.keys()].toSorted((a, b) => a.localeCompare(b))
|
|
282
|
-
const result = buildSchemeSources(sortedAtomNames, this.unionAtoms, this.unionKeyframes, this.serializedCache, this.breakpoints, this.unionGradients, this.unionHaptics, [...this.unionLiterals])
|
|
280
|
+
const result = buildSchemeSources(sortedAtomNames, this.unionAtoms, this.unionKeyframes, this.serializedCache, this.breakpoints, this.unionGradients, this.unionHaptics, [...this.unionLiterals], this.themeTokens)
|
|
283
281
|
this.serializedMissesCount += result.serializedMisses
|
|
284
282
|
const { schemeSources, manifestSource } = result
|
|
285
283
|
|
package/src/metro/dts.ts
CHANGED
|
@@ -100,7 +100,12 @@ export function writeDtsFile(targetPath: string, schemes: readonly string[]): vo
|
|
|
100
100
|
lines.push('}', '')
|
|
101
101
|
if (schemes.length > 0) {
|
|
102
102
|
lines.push(`declare module 'rnwind' {`, ` export interface RnwindConfig {`)
|
|
103
|
-
|
|
103
|
+
// Escape backslash / single-quote so a scheme name with a quote (only
|
|
104
|
+
// reachable via the public `writeDtsFile`, since CSS idents can't contain
|
|
105
|
+
// one) can't emit invalid TS that breaks the whole file and drops the
|
|
106
|
+
// `className` augmentation project-wide. Single-quoted to match the rest
|
|
107
|
+
// of the generated declaration.
|
|
108
|
+
const schemeLiterals = schemes.map((s) => `'${s.replaceAll('\\', '\\\\').replaceAll("'", String.raw`\'`)}'`).join(', ')
|
|
104
109
|
lines.push(` themes: readonly [${schemeLiterals}]`, ` }`, '}', '')
|
|
105
110
|
}
|
|
106
111
|
// The `export {}` is mandatory — without at least one top-level
|
package/src/metro/transformer.ts
CHANGED
|
@@ -17,14 +17,6 @@ interface UpstreamTransformer {
|
|
|
17
17
|
/** Env var that points at the upstream `babelTransformerPath` we override. */
|
|
18
18
|
const UPSTREAM_ENV = 'RNWIND_UPSTREAM_TRANSFORMER'
|
|
19
19
|
|
|
20
|
-
/**
|
|
21
|
-
* Matches a `useCss(` call. Files that resolve classes via the `useCss`
|
|
22
|
-
* hook (no JSX `className=`) must still be scanned so the classes inside
|
|
23
|
-
* `useCss("…")` get registered — common for theme/accent hooks living in a
|
|
24
|
-
* shared package the project scan may not reach.
|
|
25
|
-
*/
|
|
26
|
-
const USE_CSS_CALL = /\buseCss\s*\(/
|
|
27
|
-
|
|
28
20
|
/** Cached upstream module — required once, reused across every transform call. */
|
|
29
21
|
let cachedUpstream: UpstreamTransformer | null = null
|
|
30
22
|
|
|
@@ -145,44 +137,53 @@ function isThemeCssEntry(filename: string): boolean {
|
|
|
145
137
|
* @returns Rewritten source text.
|
|
146
138
|
*/
|
|
147
139
|
async function rewriteSource(args: BabelTransformerArgs): Promise<string> {
|
|
148
|
-
|
|
149
|
-
|
|
140
|
+
// SCAN FIRST — exactly like Tailwind: oxide extracts class candidates from
|
|
141
|
+
// the WHOLE file content (className, cva/clsx, plain strings, anywhere),
|
|
142
|
+
// and the compiler is the only filter. No babel parse needed to scan, so
|
|
143
|
+
// the common "file with no classes" case stays cheap and the source is
|
|
144
|
+
// returned untouched. This is what makes adding a class ANYWHERE register
|
|
145
|
+
// on hot-reload, not just inside a `className=` or a known helper call.
|
|
146
|
+
const state = getRnwindState(projectRootOf(args))
|
|
147
|
+
const extension = extensionOf(args.filename)
|
|
148
|
+
const parsed = await state.parser.parseAtoms({ content: args.src, extension })
|
|
149
|
+
const hasAtoms = parsed.atoms.size > 0
|
|
150
150
|
|
|
151
151
|
const hasClassName = /classname=/i.test(args.src)
|
|
152
|
-
const hasUseCss = USE_CSS_CALL.test(args.src)
|
|
153
152
|
const hasSpread = /\{\s*\.\.\./.test(args.src)
|
|
154
153
|
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const wrapped = hasClassName || hasSpread ? rewriteWrapImports(ast, getWrapModules()) : false
|
|
161
|
-
|
|
162
|
-
if (!hasClassName && !hasUseCss) {
|
|
163
|
-
// Import-only file: nothing to compile. Drop any stale atom
|
|
164
|
-
// contribution (className may have just been removed) and emit the
|
|
165
|
-
// wrapped imports — or the untouched source when nothing wrapped.
|
|
166
|
-
dropFileSafely(args.filename, projectRootOf(args))
|
|
167
|
-
return wrapped ? generateModule(ast).code : args.src
|
|
154
|
+
// Nothing for rnwind to do: no Tailwind class compiled AND no host
|
|
155
|
+
// wrapping needed. Drop any stale contribution and emit the file as-is.
|
|
156
|
+
if (!hasAtoms && !hasClassName && !hasSpread) {
|
|
157
|
+
state.builder.dropFile(args.filename)
|
|
158
|
+
return args.src
|
|
168
159
|
}
|
|
169
160
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
161
|
+
const ast = parseUserSource(args.src)
|
|
162
|
+
if (!ast) {
|
|
163
|
+
// Can't parse to wrap/inject, but we DID scan — still record the atoms so
|
|
164
|
+
// they register (a malformed-but-recoverable file shouldn't lose styles).
|
|
165
|
+
if (hasAtoms) {
|
|
166
|
+
await state.builder.recordFile(args.filename, parsed.atoms, parsed.keyframes, [])
|
|
167
|
+
await state.builder.writeSchemes()
|
|
168
|
+
} else {
|
|
169
|
+
state.builder.dropFile(args.filename)
|
|
170
|
+
}
|
|
171
|
+
return args.src
|
|
172
|
+
}
|
|
176
173
|
|
|
177
|
-
|
|
174
|
+
// Wrap host imports ONLY when className flows through a component — written
|
|
175
|
+
// (`hasClassName`) or forwarded (`{...rest}`). A `useCss`/`cva`-only file
|
|
176
|
+
// resolves manually, so its imports (e.g. skia drawing primitives) are
|
|
177
|
+
// left untouched.
|
|
178
|
+
const wrapped = hasClassName || hasSpread ? rewriteWrapImports(ast, getWrapModules()) : false
|
|
178
179
|
|
|
179
|
-
if (
|
|
180
|
+
if (!hasAtoms) {
|
|
181
|
+
// Wrap-only forwarder — no classes to record/register.
|
|
180
182
|
state.builder.dropFile(args.filename)
|
|
181
|
-
|
|
182
|
-
injectThemeSignatureImport(ast)
|
|
183
|
-
return generateModule(ast).code
|
|
183
|
+
return wrapped ? generateModule(ast).code : args.src
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
warnUnknownClasses(args.src, parsed.candidates, parsed.atoms, args.filename, [parsed.gradientAtoms, parsed.hapticAtoms])
|
|
186
187
|
const literals = collectClassNameLiterals(ast)
|
|
187
188
|
const { changed } = await state.builder.recordFile(args.filename, parsed.atoms, parsed.keyframes, literals)
|
|
188
189
|
if (changed) await state.builder.writeSchemes()
|
|
@@ -192,21 +193,6 @@ async function rewriteSource(args: BabelTransformerArgs): Promise<string> {
|
|
|
192
193
|
return generateModule(ast).code
|
|
193
194
|
}
|
|
194
195
|
|
|
195
|
-
/**
|
|
196
|
-
* Drop a file's union contribution, swallowing the "state not configured"
|
|
197
|
-
* error unit tests hit when they call the transformer without
|
|
198
|
-
* `configureRnwindState`.
|
|
199
|
-
* @param filename Absolute source path.
|
|
200
|
-
* @param projectRoot Project root for state lookup.
|
|
201
|
-
*/
|
|
202
|
-
function dropFileSafely(filename: string, projectRoot: string): void {
|
|
203
|
-
try {
|
|
204
|
-
getRnwindState(projectRoot).builder.dropFile(filename)
|
|
205
|
-
} catch {
|
|
206
|
-
// State not configured (standalone/unit test). Nothing to drop.
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
196
|
/**
|
|
211
197
|
* Whether a JSX attribute names a className-style prop (`className` or
|
|
212
198
|
* any `<prefix>ClassName`).
|
|
@@ -404,19 +390,12 @@ function loadUpstream(): UpstreamTransformer | null {
|
|
|
404
390
|
*/
|
|
405
391
|
function isRewriteCandidate(args: BabelTransformerArgs): boolean {
|
|
406
392
|
if (!/\.(?:tsx|ts|jsx|js)$/i.test(args.filename)) return false
|
|
407
|
-
//
|
|
408
|
-
//
|
|
409
|
-
//
|
|
410
|
-
//
|
|
411
|
-
//
|
|
412
|
-
//
|
|
413
|
-
// forwarded className must still get its import wrapped (no literal
|
|
414
|
-
// appears in this file). A style-less `<View/>` with none is left
|
|
415
|
-
// alone so it never pays for an unused wrapper.
|
|
416
|
-
const hasClassName = /classname=/i.test(args.src)
|
|
417
|
-
const hasUseCss = USE_CSS_CALL.test(args.src)
|
|
418
|
-
const isForwarder = /\{\s*\.\.\./.test(args.src) && mentionsWrapModule(args.src)
|
|
419
|
-
if (!hasClassName && !hasUseCss && !isForwarder) return false
|
|
393
|
+
// EVERY user source file is scanned — exactly like Tailwind walks its
|
|
394
|
+
// whole content set. The compiler is the only filter (see rewriteSource);
|
|
395
|
+
// no className/helper pre-filter, because that "filter magic" missed
|
|
396
|
+
// classes living in cva/clsx/plain-string files and broke their hot-reload.
|
|
397
|
+
// (Cheap when the file has no classes: oxide finds nothing, no babel parse,
|
|
398
|
+
// source returned untouched.)
|
|
420
399
|
if (!args.filename.includes('/node_modules/')) return true
|
|
421
400
|
// node_modules in path → could be a workspace symlink; resolve it.
|
|
422
401
|
try {
|
|
@@ -427,21 +406,6 @@ function isRewriteCandidate(args: BabelTransformerArgs): boolean {
|
|
|
427
406
|
}
|
|
428
407
|
}
|
|
429
408
|
|
|
430
|
-
/**
|
|
431
|
-
* Cheap pre-parse check: does the source import from any configured
|
|
432
|
-
* wrap-module? A quoted specifier match is enough — `rewriteWrapImports`
|
|
433
|
-
* re-verifies precisely on the AST, so a false positive only costs a
|
|
434
|
-
* no-op parse.
|
|
435
|
-
* @param source Source text.
|
|
436
|
-
* @returns True when a wrap-module specifier appears in the source.
|
|
437
|
-
*/
|
|
438
|
-
function mentionsWrapModule(source: string): boolean {
|
|
439
|
-
for (const moduleName of getWrapModules().keys()) {
|
|
440
|
-
if (source.includes(`'${moduleName}'`) || source.includes(`"${moduleName}"`)) return true
|
|
441
|
-
}
|
|
442
|
-
return false
|
|
443
|
-
}
|
|
444
|
-
|
|
445
409
|
/**
|
|
446
410
|
* Fallback parse when no upstream is configured AND Metro didn't hand
|
|
447
411
|
* us an AST. Used by unit tests and standalone setups.
|
package/src/metro/with-config.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, mkdirSync,
|
|
1
|
+
import { existsSync, mkdirSync, watch as watchFile } from 'node:fs'
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import { writeDtsFile } from './dts'
|
|
4
4
|
import { createRnwindResolver, type ResolveRequestFn } from './resolver'
|
|
@@ -15,14 +15,15 @@ const DEFAULT_CACHE_DIR = '.rnwind'
|
|
|
15
15
|
let activeCssWatcher: { cssPath: string; close: () => void } | null = null
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
* Watch the theme CSS for edits. On change,
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
18
|
+
* Watch the theme CSS for edits. On change, rebuild state against the fresh
|
|
19
|
+
* CSS and rewrite the per-scheme files (`onThemeChange` → full rescan →
|
|
20
|
+
* `writeSchemes`). That's the entire HMR signal: every transformed source
|
|
21
|
+
* imports `rnwind/__generated/schemes`, which eager-imports
|
|
22
|
+
* `common.style.js`; the rewrite changes its bytes, Metro's content-SHA1
|
|
23
|
+
* dedup notices, and every importer is invalidated + re-bundled with the new
|
|
24
|
+
* style values. The rewritten JSX references atoms by NAME (theme-independent),
|
|
25
|
+
* so no source-file re-transform / mtime nudge is needed — the dep graph
|
|
26
|
+
* carries the signal.
|
|
26
27
|
* @param cssPath Absolute path to the theme CSS to watch.
|
|
27
28
|
* @param projectRoot Metro's project root (for `getRnwindState`).
|
|
28
29
|
*/
|
|
@@ -40,7 +41,6 @@ function watchThemeCss(cssPath: string, projectRoot: string): void {
|
|
|
40
41
|
pending = false
|
|
41
42
|
try {
|
|
42
43
|
await onThemeChange(projectRoot)
|
|
43
|
-
touchRecordedFiles(projectRoot)
|
|
44
44
|
} catch {
|
|
45
45
|
// Invalidation is best-effort — never crash the dev server.
|
|
46
46
|
}
|
|
@@ -49,25 +49,6 @@ function watchThemeCss(cssPath: string, projectRoot: string): void {
|
|
|
49
49
|
activeCssWatcher = { cssPath, close: () => watcher.close() }
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
/**
|
|
53
|
-
* Bump the mtime on every file the builder has transformed. Metro's
|
|
54
|
-
* file watcher keys on `mtime`, so this is what makes it invalidate
|
|
55
|
-
* those modules and re-transform them.
|
|
56
|
-
* @param projectRoot Metro's project root.
|
|
57
|
-
*/
|
|
58
|
-
function touchRecordedFiles(projectRoot: string): void {
|
|
59
|
-
const state = getRnwindState(projectRoot)
|
|
60
|
-
const files = state.builder.recordedFiles()
|
|
61
|
-
const now = new Date()
|
|
62
|
-
for (const file of files) {
|
|
63
|
-
try {
|
|
64
|
-
if (existsSync(file)) utimesSync(file, now, now)
|
|
65
|
-
} catch {
|
|
66
|
-
// One file's stat failure shouldn't stop the others.
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
52
|
/**
|
|
72
53
|
* Where the rnwind babel transformer lives — resolved relative to this
|
|
73
54
|
* module so the path works from both the `src/` tree (tests) and the
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ThemeTable } from '../../core/types'
|
|
2
2
|
import { useRnwind } from '../components/rnwind-provider'
|
|
3
|
+
import { getThemeTokens } from '../lookup-css'
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Synthetic scheme name applied when tokens aren't declared under any
|
|
@@ -20,10 +21,13 @@ const BASE_SCHEME = 'base'
|
|
|
20
21
|
*/
|
|
21
22
|
export function useTheme(): ThemeTable {
|
|
22
23
|
const { scheme, tables } = useRnwind()
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
// The build registers token tables on the manifest so `useColor` works out
|
|
25
|
+
// of the box; an explicit `tables` prop layers on top (the prop wins).
|
|
26
|
+
const registered = getThemeTokens()
|
|
27
|
+
const base = { ...registered[BASE_SCHEME], ...tables[BASE_SCHEME] }
|
|
28
|
+
const schemeTable = { ...registered[scheme], ...tables[scheme] }
|
|
29
|
+
// Base tokens apply everywhere (CSS `:root` cascade); the active scheme's
|
|
30
|
+
// own entries override on overlap.
|
|
27
31
|
if (Object.keys(schemeTable).length === 0) return base
|
|
28
32
|
return { ...base, ...schemeTable }
|
|
29
33
|
}
|
|
@@ -42,22 +46,25 @@ export function useToken(cssVariable: string): string | number | undefined {
|
|
|
42
46
|
|
|
43
47
|
/**
|
|
44
48
|
* Read a color token by shorthand name — `useColor('primary')` resolves
|
|
45
|
-
* `--color-primary` for the active scheme.
|
|
46
|
-
*
|
|
49
|
+
* `--color-primary` for the active scheme. A fully-qualified name
|
|
50
|
+
* (`--color-primary`) is accepted as-is, so the call doesn't silently miss by
|
|
51
|
+
* double-prefixing into `--color---color-primary`.
|
|
52
|
+
* @param name Token suffix after `--color-`, or the full `--color-*` name.
|
|
47
53
|
* @returns Resolved color string, or undefined when the token is missing
|
|
48
54
|
* or its value isn't a string.
|
|
49
55
|
*/
|
|
50
56
|
export function useColor(name: string): string | undefined {
|
|
51
|
-
const value = useToken(`--color-${name}`)
|
|
57
|
+
const value = useToken(name.startsWith('--') ? name : `--color-${name}`)
|
|
52
58
|
return typeof value === 'string' ? value : undefined
|
|
53
59
|
}
|
|
54
60
|
|
|
55
61
|
/**
|
|
56
62
|
* Read a spacing token by shorthand name — `useSize('4')` resolves
|
|
57
|
-
* `--spacing-4` for the active scheme.
|
|
58
|
-
*
|
|
63
|
+
* `--spacing-4` for the active scheme. A fully-qualified `--spacing-*` name is
|
|
64
|
+
* accepted as-is (no double-prefix miss).
|
|
65
|
+
* @param name Token suffix after `--spacing-`, or the full `--spacing-*` name.
|
|
59
66
|
* @returns Resolved spacing value, or undefined when the token is missing.
|
|
60
67
|
*/
|
|
61
68
|
export function useSize(name: string): number | string | undefined {
|
|
62
|
-
return useToken(`--spacing-${name}`)
|
|
69
|
+
return useToken(name.startsWith('--') ? name : `--spacing-${name}`)
|
|
63
70
|
}
|
package/src/runtime/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ export {
|
|
|
2
2
|
lookupCss,
|
|
3
3
|
registerAtoms,
|
|
4
4
|
registerBreakpoints,
|
|
5
|
+
registerThemeTokens,
|
|
5
6
|
registerSchemeLoader,
|
|
6
7
|
setWindowHeightProvider,
|
|
7
8
|
getBreakpoints,
|
|
@@ -28,4 +29,4 @@ export type { Scheme, RnwindConfig } from './types'
|
|
|
28
29
|
* string when integrating with build tooling that may see multiple rnwind
|
|
29
30
|
* copies (e.g. workspace overrides).
|
|
30
31
|
*/
|
|
31
|
-
export const VERSION = '0.0.
|
|
32
|
+
export const VERSION = '0.0.8' as const
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import type { RnwindState } from './components/rnwind-provider'
|
|
19
|
+
import type { ThemeTables } from '../core/types'
|
|
19
20
|
|
|
20
21
|
/** Empty sentinel returned when the input is null / undefined / empty. */
|
|
21
22
|
const EMPTY_STYLES: readonly unknown[] = []
|
|
@@ -62,6 +63,8 @@ const cache = {
|
|
|
62
63
|
atoms: Object.create(null) as Partial<Record<string, SchemeAtomsRecord>>,
|
|
63
64
|
breakpoints: Object.create(null) as Partial<Record<string, number>>,
|
|
64
65
|
breakpointList: [] as readonly BreakpointEntry[],
|
|
66
|
+
/** Per-scheme theme token tables (`--color-*`, `--spacing-*`, …) the manifest registers for `useColor` / `useToken` / `useSize`. */
|
|
67
|
+
themeTokens: {} as ThemeTables,
|
|
65
68
|
}
|
|
66
69
|
|
|
67
70
|
/**
|
|
@@ -280,6 +283,20 @@ function tierFor(windowWidth: number): number {
|
|
|
280
283
|
return tier
|
|
281
284
|
}
|
|
282
285
|
|
|
286
|
+
/**
|
|
287
|
+
* Public breakpoint-tier for a width — the count of registered breakpoints
|
|
288
|
+
* whose threshold is reached. Used by the runtime resolver as its width
|
|
289
|
+
* cache dimension: the numeric tier is bounded AND exact, unlike the
|
|
290
|
+
* clamped `activeBreakpoint` NAME (which collapses tier-0 into the smallest
|
|
291
|
+
* breakpoint, so widths straddling that threshold would share a cache key
|
|
292
|
+
* and serve a stale style).
|
|
293
|
+
* @param windowWidth Live window width in px.
|
|
294
|
+
* @returns Tier 0..N.
|
|
295
|
+
*/
|
|
296
|
+
export function breakpointTier(windowWidth: number): number {
|
|
297
|
+
return tierFor(windowWidth)
|
|
298
|
+
}
|
|
299
|
+
|
|
283
300
|
/**
|
|
284
301
|
* Build the style array for a (hoist, scheme, state, width) tuple.
|
|
285
302
|
* Walks the atom list, applies the interact-state and breakpoint
|
|
@@ -539,6 +556,30 @@ export function getBreakpoints(): readonly BreakpointEntry[] {
|
|
|
539
556
|
return cache.breakpointList
|
|
540
557
|
}
|
|
541
558
|
|
|
559
|
+
/**
|
|
560
|
+
* Register the per-scheme theme token tables the manifest module emits at
|
|
561
|
+
* load time — the data source for `useColor` / `useToken` / `useSize`. The
|
|
562
|
+
* build lowers `--color-*` tokens to sRGB before registering them, so these
|
|
563
|
+
* are RN-safe. Replaces the prior tables; bumps `atomVersion` so a theme HMR
|
|
564
|
+
* cycle re-resolves. The build registers tokens here so the hooks work out of
|
|
565
|
+
* the box, without the user manually threading a `tables` prop on the provider.
|
|
566
|
+
* @param tables Scheme name → (token name → value) map.
|
|
567
|
+
*/
|
|
568
|
+
export function registerThemeTokens(tables: ThemeTables): void {
|
|
569
|
+
cache.themeTokens = tables
|
|
570
|
+
atomVersion += 1
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* The manifest-registered theme token tables. The provider merges these under
|
|
575
|
+
* any explicit `tables` prop (the prop wins), so `useColor` resolves from the
|
|
576
|
+
* build by default.
|
|
577
|
+
* @returns Registered per-scheme token tables.
|
|
578
|
+
*/
|
|
579
|
+
export function getThemeTokens(): ThemeTables {
|
|
580
|
+
return cache.themeTokens
|
|
581
|
+
}
|
|
582
|
+
|
|
542
583
|
/**
|
|
543
584
|
* Sentinel name returned by {@link activeBreakpointFor} ONLY when no
|
|
544
585
|
* breakpoints are registered at all (bundle without rnwind-transformed
|
|
@@ -621,6 +662,7 @@ export function __resetLookupCssState(): void {
|
|
|
621
662
|
for (const key of Object.keys(cache.atoms)) delete cache.atoms[key]
|
|
622
663
|
for (const key of Object.keys(cache.breakpoints)) delete cache.breakpoints[key]
|
|
623
664
|
cache.breakpointList = []
|
|
665
|
+
cache.themeTokens = {}
|
|
624
666
|
windowHeightProvider = null
|
|
625
667
|
schemeLoader = null
|
|
626
668
|
WARNED_MISSING_INSETS = false
|
package/src/runtime/resolve.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getStyleVersion, lookupCss, type InteractState } from './lookup-css'
|
|
1
|
+
import { breakpointTier, getStyleVersion, lookupCss, type InteractState } from './lookup-css'
|
|
2
2
|
import type { RnwindState } from './components/rnwind-provider'
|
|
3
3
|
import { normalizeClassName } from '../core/normalize-classname'
|
|
4
4
|
import type { GradientAtomInfo, GradientDirection } from '../core/parser/gradient'
|
|
@@ -148,17 +148,19 @@ const stateSignatureCache = new WeakMap<RnwindState, string>()
|
|
|
148
148
|
|
|
149
149
|
/**
|
|
150
150
|
* Cache key dimension for the reactive context — everything that can
|
|
151
|
-
* change a resolved style. Uses the breakpoint TIER (
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
151
|
+
* change a resolved style. Uses the numeric breakpoint TIER (count of
|
|
152
|
+
* thresholds reached) from `breakpointTier(windowWidth)`, NOT the
|
|
153
|
+
* `activeBreakpoint` NAME: the name clamps tier-0 into the smallest
|
|
154
|
+
* breakpoint, so widths straddling that threshold (e.g. 320 vs 700 with
|
|
155
|
+
* `sm=640`) would collide on one cache key and serve a stale style. The
|
|
156
|
+
* tier is exact AND bounded — two widths in the same tier gate every
|
|
157
|
+
* `sm:`/`md:`/… atom identically, so they resolve the same.
|
|
156
158
|
* @param state Rnwind context.
|
|
157
159
|
* @returns Compact signature string.
|
|
158
160
|
*/
|
|
159
161
|
function stateSignature(state: RnwindState): string {
|
|
160
162
|
const { insets } = state
|
|
161
|
-
return `${state.scheme}|${insets.top},${insets.right},${insets.bottom},${insets.left}|${state.fontScale}|${state.
|
|
163
|
+
return `${state.scheme}|${insets.top},${insets.right},${insets.bottom},${insets.left}|${state.fontScale}|${breakpointTier(state.windowWidth)}`
|
|
162
164
|
}
|
|
163
165
|
|
|
164
166
|
/**
|