rnwind 0.0.3 → 0.0.5
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/normalize-classname.cjs +25 -0
- package/lib/cjs/core/normalize-classname.cjs.map +1 -0
- package/lib/cjs/core/normalize-classname.d.ts +10 -0
- package/lib/cjs/core/style-builder/build-style.cjs +258 -58
- package/lib/cjs/core/style-builder/build-style.cjs.map +1 -1
- package/lib/cjs/core/style-builder/build-style.d.ts +6 -1
- package/lib/cjs/core/style-builder/union-builder.cjs +37 -3
- package/lib/cjs/core/style-builder/union-builder.cjs.map +1 -1
- package/lib/cjs/core/style-builder/union-builder.d.ts +21 -1
- package/lib/cjs/metro/css-imports.cjs +81 -0
- package/lib/cjs/metro/css-imports.cjs.map +1 -0
- package/lib/cjs/metro/css-imports.d.ts +8 -0
- package/lib/cjs/metro/dts.cjs +7 -16
- package/lib/cjs/metro/dts.cjs.map +1 -1
- package/lib/cjs/metro/dts.d.ts +2 -4
- package/lib/cjs/metro/state.cjs +38 -86
- package/lib/cjs/metro/state.cjs.map +1 -1
- package/lib/cjs/metro/state.d.ts +8 -25
- package/lib/cjs/metro/transformer.cjs +193 -34
- package/lib/cjs/metro/transformer.cjs.map +1 -1
- package/lib/cjs/metro/with-config.cjs +2 -2
- package/lib/cjs/metro/with-config.cjs.map +1 -1
- package/lib/cjs/metro/with-config.d.ts +11 -26
- package/lib/cjs/metro/wrap-imports.cjs +273 -0
- package/lib/cjs/metro/wrap-imports.cjs.map +1 -0
- package/lib/cjs/metro/wrap-imports.d.ts +26 -0
- package/lib/cjs/runtime/components/rnwind-provider.cjs +0 -17
- package/lib/cjs/runtime/components/rnwind-provider.cjs.map +1 -1
- package/lib/cjs/runtime/components/rnwind-provider.d.ts +0 -14
- package/lib/cjs/runtime/hooks/use-css.cjs +16 -10
- package/lib/cjs/runtime/hooks/use-css.cjs.map +1 -1
- package/lib/cjs/runtime/hooks/use-css.d.ts +15 -9
- package/lib/cjs/runtime/index.cjs +11 -13
- package/lib/cjs/runtime/index.cjs.map +1 -1
- package/lib/cjs/runtime/index.d.ts +4 -9
- package/lib/cjs/runtime/lookup-css.cjs +10 -0
- package/lib/cjs/runtime/lookup-css.cjs.map +1 -1
- package/lib/cjs/runtime/lookup-css.d.ts +7 -0
- package/lib/cjs/runtime/resolve.cjs +348 -0
- package/lib/cjs/runtime/resolve.cjs.map +1 -0
- package/lib/cjs/runtime/resolve.d.ts +61 -0
- package/lib/cjs/runtime/wrap.cjs +254 -0
- package/lib/cjs/runtime/wrap.cjs.map +1 -0
- package/lib/cjs/runtime/wrap.d.ts +37 -0
- package/lib/cjs/testing/index.cjs +81 -50
- package/lib/cjs/testing/index.cjs.map +1 -1
- package/lib/esm/core/normalize-classname.d.ts +10 -0
- package/lib/esm/core/normalize-classname.mjs +23 -0
- package/lib/esm/core/normalize-classname.mjs.map +1 -0
- package/lib/esm/core/style-builder/build-style.d.ts +6 -1
- package/lib/esm/core/style-builder/build-style.mjs +258 -58
- package/lib/esm/core/style-builder/build-style.mjs.map +1 -1
- package/lib/esm/core/style-builder/union-builder.d.ts +21 -1
- package/lib/esm/core/style-builder/union-builder.mjs +37 -3
- package/lib/esm/core/style-builder/union-builder.mjs.map +1 -1
- package/lib/esm/metro/css-imports.d.ts +8 -0
- package/lib/esm/metro/css-imports.mjs +79 -0
- package/lib/esm/metro/css-imports.mjs.map +1 -0
- package/lib/esm/metro/dts.d.ts +2 -4
- package/lib/esm/metro/dts.mjs +7 -16
- package/lib/esm/metro/dts.mjs.map +1 -1
- package/lib/esm/metro/state.d.ts +8 -25
- package/lib/esm/metro/state.mjs +39 -85
- package/lib/esm/metro/state.mjs.map +1 -1
- package/lib/esm/metro/transformer.mjs +194 -35
- package/lib/esm/metro/transformer.mjs.map +1 -1
- package/lib/esm/metro/with-config.d.ts +11 -26
- package/lib/esm/metro/with-config.mjs +2 -2
- package/lib/esm/metro/with-config.mjs.map +1 -1
- package/lib/esm/metro/wrap-imports.d.ts +26 -0
- package/lib/esm/metro/wrap-imports.mjs +250 -0
- package/lib/esm/metro/wrap-imports.mjs.map +1 -0
- package/lib/esm/runtime/components/rnwind-provider.d.ts +0 -14
- package/lib/esm/runtime/components/rnwind-provider.mjs +1 -17
- package/lib/esm/runtime/components/rnwind-provider.mjs.map +1 -1
- package/lib/esm/runtime/hooks/use-css.d.ts +15 -9
- package/lib/esm/runtime/hooks/use-css.mjs +16 -10
- package/lib/esm/runtime/hooks/use-css.mjs.map +1 -1
- package/lib/esm/runtime/index.d.ts +4 -9
- package/lib/esm/runtime/index.mjs +4 -4
- package/lib/esm/runtime/index.mjs.map +1 -1
- package/lib/esm/runtime/lookup-css.d.ts +7 -0
- package/lib/esm/runtime/lookup-css.mjs +10 -1
- package/lib/esm/runtime/lookup-css.mjs.map +1 -1
- package/lib/esm/runtime/resolve.d.ts +61 -0
- package/lib/esm/runtime/resolve.mjs +341 -0
- package/lib/esm/runtime/resolve.mjs.map +1 -0
- package/lib/esm/runtime/wrap.d.ts +37 -0
- package/lib/esm/runtime/wrap.mjs +251 -0
- package/lib/esm/runtime/wrap.mjs.map +1 -0
- package/lib/esm/testing/index.mjs +84 -53
- package/lib/esm/testing/index.mjs.map +1 -1
- package/package.json +2 -1
- package/src/core/normalize-classname.ts +19 -0
- package/src/core/style-builder/build-style.ts +286 -55
- package/src/core/style-builder/union-builder.ts +36 -3
- package/src/metro/css-imports.ts +75 -0
- package/src/metro/dts.ts +7 -19
- package/src/metro/state.ts +38 -83
- package/src/metro/transformer.ts +190 -34
- package/src/metro/with-config.ts +13 -28
- package/src/metro/wrap-imports.ts +260 -0
- package/src/runtime/components/rnwind-provider.tsx +0 -17
- package/src/runtime/hooks/use-css.ts +17 -11
- package/src/runtime/index.ts +3 -26
- package/src/runtime/lookup-css.ts +10 -0
- package/src/runtime/resolve.ts +381 -0
- package/src/runtime/wrap.tsx +267 -0
- package/src/testing/index.ts +106 -56
- package/lib/cjs/core/parser/text-truncate.cjs +0 -78
- package/lib/cjs/core/parser/text-truncate.cjs.map +0 -1
- package/lib/cjs/metro/transform-ast.cjs +0 -1472
- package/lib/cjs/metro/transform-ast.cjs.map +0 -1
- package/lib/cjs/metro/transform-ast.d.ts +0 -88
- package/lib/cjs/runtime/haptics.cjs +0 -113
- package/lib/cjs/runtime/haptics.cjs.map +0 -1
- package/lib/cjs/runtime/haptics.d.ts +0 -48
- package/lib/cjs/runtime/interactive-box.cjs +0 -35
- package/lib/cjs/runtime/interactive-box.cjs.map +0 -1
- package/lib/cjs/runtime/interactive-box.d.ts +0 -40
- package/lib/esm/core/parser/text-truncate.mjs +0 -75
- package/lib/esm/core/parser/text-truncate.mjs.map +0 -1
- package/lib/esm/metro/transform-ast.d.ts +0 -88
- package/lib/esm/metro/transform-ast.mjs +0 -1451
- package/lib/esm/metro/transform-ast.mjs.map +0 -1
- package/lib/esm/runtime/haptics.d.ts +0 -48
- package/lib/esm/runtime/haptics.mjs +0 -110
- package/lib/esm/runtime/haptics.mjs.map +0 -1
- package/lib/esm/runtime/interactive-box.d.ts +0 -40
- package/lib/esm/runtime/interactive-box.mjs +0 -33
- package/lib/esm/runtime/interactive-box.mjs.map +0 -1
- package/src/metro/transform-ast.ts +0 -1729
- package/src/runtime/haptics.ts +0 -120
- package/src/runtime/interactive-box.tsx +0 -57
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
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
|
-
import type { KeyframeBlock, SchemedStyle, TailwindParser } from '../parser'
|
|
4
|
+
import type { GradientAtomInfo, HapticRequest, KeyframeBlock, SchemedStyle, TailwindParser } from '../parser'
|
|
5
5
|
import { buildSchemeSources, type AtomSerializedCache } from './build-style'
|
|
6
6
|
|
|
7
7
|
/** Manifest module basename — the file SchemeProvider imports via the resolver. */
|
|
@@ -90,6 +90,17 @@ class UnionBuilder {
|
|
|
90
90
|
private readonly parser: TailwindParser
|
|
91
91
|
private readonly unionAtoms = new Map<string, SchemedStyle>()
|
|
92
92
|
private readonly unionKeyframes = new Map<string, KeyframeBlock>()
|
|
93
|
+
/** atom name → gradient role/colour, surfaced into the manifest's `registerGradients`. */
|
|
94
|
+
private readonly unionGradients = new Map<string, GradientAtomInfo>()
|
|
95
|
+
/** atom name → haptic request, surfaced into the manifest's `registerHaptics`. */
|
|
96
|
+
private readonly unionHaptics = new Map<string, HapticRequest>()
|
|
97
|
+
/**
|
|
98
|
+
* Distinct literal className strings seen across all files, pre-merged
|
|
99
|
+
* into per-scheme molecules at write time. Accumulate-only (like
|
|
100
|
+
* `unionAtoms`): orphaned literals just yield unused molecules and get
|
|
101
|
+
* reaped on the next cold start, so no refcount is needed.
|
|
102
|
+
*/
|
|
103
|
+
private readonly unionLiterals = new Set<string>()
|
|
93
104
|
/**
|
|
94
105
|
* Responsive breakpoints captured from the parser. Refreshed on every
|
|
95
106
|
* `recordFile` / `ensureProjectScanned` so user-defined
|
|
@@ -169,6 +180,8 @@ class UnionBuilder {
|
|
|
169
180
|
const parsed = await this.parser.parseProject()
|
|
170
181
|
for (const [name, style] of parsed.atoms) this.unionAtoms.set(name, style)
|
|
171
182
|
for (const [name, kf] of parsed.keyframes) this.unionKeyframes.set(name, kf)
|
|
183
|
+
for (const [name, gradient] of parsed.gradientAtoms) this.unionGradients.set(name, gradient)
|
|
184
|
+
for (const [name, haptic] of parsed.hapticAtoms) this.unionHaptics.set(name, haptic)
|
|
172
185
|
this.breakpoints = parsed.breakpoints
|
|
173
186
|
this.projectScanned = true
|
|
174
187
|
})()
|
|
@@ -187,6 +200,7 @@ class UnionBuilder {
|
|
|
187
200
|
* @param file Absolute source file path.
|
|
188
201
|
* @param atoms Per-atom resolved schemed styles from this transform.
|
|
189
202
|
* @param keyframes Keyframe blocks referenced by this file's atoms.
|
|
203
|
+
* @param literals
|
|
190
204
|
* @returns `{ changed: true }` when the union shifted (new atom name,
|
|
191
205
|
* removed atom name, or new keyframe) — the transformer uses this
|
|
192
206
|
* to skip the serializer + `writeSchemes` when nothing changed.
|
|
@@ -195,8 +209,10 @@ class UnionBuilder {
|
|
|
195
209
|
file: string,
|
|
196
210
|
atoms: ReadonlyMap<string, SchemedStyle>,
|
|
197
211
|
keyframes: ReadonlyMap<string, KeyframeBlock>,
|
|
212
|
+
literals: readonly string[] = [],
|
|
198
213
|
): Promise<{ changed: boolean }> {
|
|
199
214
|
await this.ensureProjectScanned()
|
|
215
|
+
const literalAdded = this.recordLiterals(literals)
|
|
200
216
|
const newAtomNames = new Set(atoms.keys())
|
|
201
217
|
const previous = this.fileAtomSets.get(file)
|
|
202
218
|
if (previous && setsEqual(previous, newAtomNames)) {
|
|
@@ -211,12 +227,29 @@ class UnionBuilder {
|
|
|
211
227
|
if (!this.unionKeyframes.has(name)) keyframeAdded = true
|
|
212
228
|
this.unionKeyframes.set(name, kf)
|
|
213
229
|
}
|
|
214
|
-
return { changed: keyframeAdded }
|
|
230
|
+
return { changed: keyframeAdded || literalAdded }
|
|
215
231
|
}
|
|
216
232
|
this.applyDiff(file, newAtomNames, atoms, keyframes)
|
|
217
233
|
return { changed: true }
|
|
218
234
|
}
|
|
219
235
|
|
|
236
|
+
/**
|
|
237
|
+
* Merge a file's literal classNames into the union. A literal the
|
|
238
|
+
* union hasn't seen flips `changed` so `writeSchemes` re-emits the
|
|
239
|
+
* scheme files with the new molecule.
|
|
240
|
+
* @param literals Distinct literal className strings.
|
|
241
|
+
* @returns Whether any literal was new to the union.
|
|
242
|
+
*/
|
|
243
|
+
private recordLiterals(literals: readonly string[]): boolean {
|
|
244
|
+
let added = false
|
|
245
|
+
for (const literal of literals) {
|
|
246
|
+
if (this.unionLiterals.has(literal)) continue
|
|
247
|
+
this.unionLiterals.add(literal)
|
|
248
|
+
added = true
|
|
249
|
+
}
|
|
250
|
+
return added
|
|
251
|
+
}
|
|
252
|
+
|
|
220
253
|
/**
|
|
221
254
|
* Forget one source file's contribution. Idempotent — repeated calls
|
|
222
255
|
* for a file that's already dropped are no-ops. Does NOT remove the
|
|
@@ -246,7 +279,7 @@ class UnionBuilder {
|
|
|
246
279
|
public async writeSchemes(): Promise<{ changedSchemes: readonly string[] }> {
|
|
247
280
|
await this.ensureProjectScanned()
|
|
248
281
|
const sortedAtomNames = [...this.unionAtoms.keys()].toSorted((a, b) => a.localeCompare(b))
|
|
249
|
-
const result = buildSchemeSources(sortedAtomNames, this.unionAtoms, this.unionKeyframes, this.serializedCache, this.breakpoints)
|
|
282
|
+
const result = buildSchemeSources(sortedAtomNames, this.unionAtoms, this.unionKeyframes, this.serializedCache, this.breakpoints, this.unionGradients, this.unionHaptics, [...this.unionLiterals])
|
|
250
283
|
this.serializedMissesCount += result.serializedMisses
|
|
251
284
|
const { schemeSources, manifestSource } = result
|
|
252
285
|
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { createRequire } from 'node:module'
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Inline `@import` resolution for the theme entry CSS.
|
|
6
|
+
*
|
|
7
|
+
* rnwind's scheme extractors (`extractThemeVars`, `extractSchemeAliases`,
|
|
8
|
+
* `extractCustomVariantSchemes`) are plain text passes over the entry
|
|
9
|
+
* CSS — they don't follow `@import`. Tailwind's own compiler DOES follow
|
|
10
|
+
* imports, so utilities still compile, but a user who keeps their theme
|
|
11
|
+
* in a separate file and points the entry at it:
|
|
12
|
+
*
|
|
13
|
+
* ```css
|
|
14
|
+
* @import "@acme/ui/theme.css"; // global.css — the cssEntryFile
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* would hand the extractors a file with no `@theme` / `@variant` /
|
|
18
|
+
* `@custom-variant` in sight → every scheme collapses to base and theme
|
|
19
|
+
* switching dies. This module flattens those user imports first so the
|
|
20
|
+
* extractors (and the compiler) see the real declarations.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/** Matches a bare `@import "spec";` / `@import 'spec';` (no media/layer suffix). */
|
|
24
|
+
const CSS_IMPORT = /@import\s+(["'])([^"']+)\1\s*;/g
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Specs left untouched for the Tailwind compiler to resolve itself.
|
|
28
|
+
* `tailwindcss` resolves to JS (not inlinable) and `rnwind/css` is the
|
|
29
|
+
* framework preset — both are the compiler's job, not user theme files.
|
|
30
|
+
*/
|
|
31
|
+
const FRAMEWORK_SPECS = new Set(['tailwindcss', 'rnwind/css', 'rnwind'])
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Read `filePath` and replace each user `@import "<spec>";` whose spec
|
|
35
|
+
* resolves (Node resolution, honouring `exports` maps and workspace
|
|
36
|
+
* symlinks) to a readable `.css` file with that file's inlined +
|
|
37
|
+
* recursively-resolved contents. Unresolvable specs, non-CSS targets,
|
|
38
|
+
* and {@link FRAMEWORK_SPECS} are left as-is for the compiler.
|
|
39
|
+
* @param filePath Absolute path of the CSS file being inlined.
|
|
40
|
+
* @param seen Visited set guarding against import cycles.
|
|
41
|
+
* @returns CSS text with user imports flattened in place.
|
|
42
|
+
*/
|
|
43
|
+
function inlineImports(filePath: string, seen: Set<string>): string {
|
|
44
|
+
if (seen.has(filePath)) return ''
|
|
45
|
+
seen.add(filePath)
|
|
46
|
+
let css: string
|
|
47
|
+
try {
|
|
48
|
+
css = readFileSync(filePath, 'utf8')
|
|
49
|
+
} catch {
|
|
50
|
+
return ''
|
|
51
|
+
}
|
|
52
|
+
const request = createRequire(filePath)
|
|
53
|
+
return css.replaceAll(CSS_IMPORT, (full: string, _quote: string, spec: string): string => {
|
|
54
|
+
if (FRAMEWORK_SPECS.has(spec)) return full
|
|
55
|
+
let resolved: string
|
|
56
|
+
try {
|
|
57
|
+
resolved = request.resolve(spec)
|
|
58
|
+
} catch {
|
|
59
|
+
return full
|
|
60
|
+
}
|
|
61
|
+
if (!resolved.endsWith('.css') || !existsSync(resolved)) return full
|
|
62
|
+
return inlineImports(resolved, seen)
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Flatten user `@import`s in a theme entry CSS file so rnwind's text-based
|
|
68
|
+
* scheme extractors see the real `@theme` / `@variant` / `@custom-variant`
|
|
69
|
+
* declarations even when the entry only re-exports them via `@import`.
|
|
70
|
+
* @param entryFilePath Absolute path to the theme entry CSS (the cssEntryFile).
|
|
71
|
+
* @returns Inlined CSS, or `''` when the entry can't be read.
|
|
72
|
+
*/
|
|
73
|
+
export function resolveThemeCss(entryFilePath: string): string {
|
|
74
|
+
return inlineImports(entryFilePath, new Set<string>())
|
|
75
|
+
}
|
package/src/metro/dts.ts
CHANGED
|
@@ -56,21 +56,15 @@ const CONTENT_CONTAINER_INTERFACES: ReadonlySet<string> = new Set([
|
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
58
|
* Build the body of one interface augmentation: `className?: string`
|
|
59
|
-
* plus
|
|
60
|
-
*
|
|
61
|
-
* scan and diff.
|
|
59
|
+
* plus `contentContainerClassName?: string` for the scroll interfaces
|
|
60
|
+
* that natively expose `contentContainerStyle`. Emits a single-line
|
|
61
|
+
* body so the file stays easy to scan and diff.
|
|
62
62
|
* @param interfaceName Bare interface name (generic parameters stripped).
|
|
63
|
-
* @param userPrefixes Extra prefixes from the Metro config — applied to
|
|
64
|
-
* every interface the same way `className` is.
|
|
65
63
|
* @returns Space-separated property declarations.
|
|
66
64
|
*/
|
|
67
|
-
function buildInterfaceBody(interfaceName: string
|
|
65
|
+
function buildInterfaceBody(interfaceName: string): string {
|
|
68
66
|
const props = ['className?: string']
|
|
69
67
|
if (CONTENT_CONTAINER_INTERFACES.has(interfaceName)) props.push(`${BUILTIN_PREFIX}ClassName?: string`)
|
|
70
|
-
for (const prefix of userPrefixes) {
|
|
71
|
-
if (prefix === BUILTIN_PREFIX) continue
|
|
72
|
-
props.push(`${prefix}ClassName?: string`)
|
|
73
|
-
}
|
|
74
68
|
return props.join('; ')
|
|
75
69
|
}
|
|
76
70
|
|
|
@@ -83,17 +77,11 @@ function buildInterfaceBody(interfaceName: string, userPrefixes: readonly string
|
|
|
83
77
|
* `Scheme` union so `useScheme()` returns the actual names.
|
|
84
78
|
*
|
|
85
79
|
* Called once at Metro-config time — overwrite-on-rewrite so the file
|
|
86
|
-
* stays in sync with the user's current theme CSS
|
|
80
|
+
* stays in sync with the user's current theme CSS.
|
|
87
81
|
* @param targetPath Absolute path to write (typically `rnwind-types.d.ts` at project root).
|
|
88
82
|
* @param schemes Scheme names from the user's `@variant` blocks (empty when none declared).
|
|
89
|
-
* @param classNamePrefixes Extra prefixes from the Metro config — merged
|
|
90
|
-
* on top of the built-in `'contentContainer'`. Defaults to empty.
|
|
91
83
|
*/
|
|
92
|
-
export function writeDtsFile(
|
|
93
|
-
targetPath: string,
|
|
94
|
-
schemes: readonly string[],
|
|
95
|
-
classNamePrefixes: readonly string[] = [],
|
|
96
|
-
): void {
|
|
84
|
+
export function writeDtsFile(targetPath: string, schemes: readonly string[]): void {
|
|
97
85
|
const lines: string[] = [
|
|
98
86
|
'// Auto-generated by rnwind — do not edit by hand.',
|
|
99
87
|
'// Overwritten on Metro start / theme CSS change.',
|
|
@@ -102,7 +90,7 @@ export function writeDtsFile(
|
|
|
102
90
|
]
|
|
103
91
|
for (const entry of INTERFACES) {
|
|
104
92
|
const name = typeof entry === 'string' ? entry : entry.name
|
|
105
|
-
const body = buildInterfaceBody(name
|
|
93
|
+
const body = buildInterfaceBody(name)
|
|
106
94
|
if (typeof entry === 'string') {
|
|
107
95
|
lines.push(` interface ${entry} { ${body} }`)
|
|
108
96
|
continue
|
package/src/metro/state.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { existsSync, readFileSync
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import { createHash } from 'node:crypto'
|
|
4
4
|
import { UnionBuilder } from '../core/style-builder'
|
|
5
5
|
import { TailwindParser, type SourceEntry } from '../core/parser'
|
|
6
|
+
import { resolveThemeCss } from './css-imports'
|
|
7
|
+
import { buildWrapModules } from './wrap-imports'
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* Default oxide Scanner globs — walk every JS/TS source under the
|
|
@@ -48,12 +50,8 @@ const CSS_ENTRY_ENV = 'RNWIND_CSS_ENTRY_FILE'
|
|
|
48
50
|
const CACHE_DIR_ENV = 'RNWIND_CACHE_DIR'
|
|
49
51
|
/** Env var carrying `watchFolders` from Metro config (NUL-separated). */
|
|
50
52
|
const WATCH_FOLDERS_ENV = 'RNWIND_WATCH_FOLDERS'
|
|
51
|
-
/** Env var carrying extra
|
|
52
|
-
const
|
|
53
|
-
/** Env var carrying extra import sources whose JSX exports get className→style rewrites. Comma-separated. */
|
|
54
|
-
const HOST_SOURCES_ENV = 'RNWIND_HOST_SOURCES'
|
|
55
|
-
/** Env var carrying extra JSX tag names (verbatim, may contain `.`) treated as hosts. Comma-separated. */
|
|
56
|
-
const HOST_COMPONENTS_ENV = 'RNWIND_HOST_COMPONENTS'
|
|
53
|
+
/** Env var carrying extra modules whose component exports get `wrap()`-ed. Comma-separated. */
|
|
54
|
+
const WRAP_MODULES_ENV = 'RNWIND_WRAP_MODULES'
|
|
57
55
|
|
|
58
56
|
/** Memoised library fingerprint — read once per worker process. */
|
|
59
57
|
let libraryFingerprint: string | undefined
|
|
@@ -62,19 +60,18 @@ let libraryFingerprint: string | undefined
|
|
|
62
60
|
let cached: RnwindState | null = null
|
|
63
61
|
|
|
64
62
|
/**
|
|
65
|
-
* Cheap content-hash readout. SHA-256 prefix of the
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* when the
|
|
63
|
+
* Cheap content-hash readout. SHA-256 prefix of the FULLY-RESOLVED theme
|
|
64
|
+
* CSS — `@import`s flattened — so an edit to a theme file the entry only
|
|
65
|
+
* re-exports (`@import "@acme/ui/theme.css"`) still rotates the hash and
|
|
66
|
+
* invalidates Metro's cache. Returns `'missing'` when the entry can't be
|
|
67
|
+
* read so the cache key stays deterministic.
|
|
69
68
|
* @param cssPath Absolute CSS path.
|
|
70
69
|
* @returns 16-char hex content hash.
|
|
71
70
|
*/
|
|
72
71
|
function readThemeHashFor(cssPath: string): string {
|
|
73
72
|
if (!existsSync(cssPath)) return 'missing'
|
|
74
73
|
try {
|
|
75
|
-
|
|
76
|
-
const mtime = statSync(cssPath).mtimeMs.toString()
|
|
77
|
-
return createHash('sha256').update(bytes).update(mtime).digest('hex').slice(0, 16)
|
|
74
|
+
return createHash('sha256').update(resolveThemeCss(cssPath)).digest('hex').slice(0, 16)
|
|
78
75
|
} catch {
|
|
79
76
|
return 'missing'
|
|
80
77
|
}
|
|
@@ -86,10 +83,10 @@ function readThemeHashFor(cssPath: string): string {
|
|
|
86
83
|
* dev OR npm install of a new version) the file bytes change, the
|
|
87
84
|
* fingerprint rotates, and Metro's transform cache invalidates.
|
|
88
85
|
*
|
|
89
|
-
* Includes the
|
|
90
|
-
* style-builder so a change to the transformer —
|
|
91
|
-
* injected
|
|
92
|
-
* on the next dev run. Without this, a user upgrading rnwind in-place
|
|
86
|
+
* Includes the import-rewriter (`wrap-imports`) and runtime resolver
|
|
87
|
+
* alongside the parser / style-builder so a change to the transformer —
|
|
88
|
+
* e.g. renaming the injected wrap helper — invalidates every stale
|
|
89
|
+
* per-file cache entry on the next dev run. Without this, a user upgrading rnwind in-place
|
|
93
90
|
* would keep loading the old transformed bytes; React-refresh would
|
|
94
91
|
* then preserve fiber state across the version bump and the rendered
|
|
95
92
|
* hook list could shift, surfacing as "change in the order of Hooks"
|
|
@@ -105,14 +102,14 @@ function getLibraryFingerprint(): string {
|
|
|
105
102
|
path.resolve(here, '..', 'core', 'style-builder', 'build-style.cjs'),
|
|
106
103
|
path.resolve(here, '..', 'core', 'parser', 'tw-parser.mjs'),
|
|
107
104
|
path.resolve(here, '..', 'core', 'parser', 'tw-parser.cjs'),
|
|
108
|
-
path.resolve(here, '
|
|
109
|
-
path.resolve(here, '
|
|
105
|
+
path.resolve(here, 'wrap-imports.mjs'),
|
|
106
|
+
path.resolve(here, 'wrap-imports.cjs'),
|
|
110
107
|
path.resolve(here, 'transformer.mjs'),
|
|
111
108
|
path.resolve(here, 'transformer.cjs'),
|
|
112
109
|
// Source-tree fallback for tests + workspace dev (no built lib yet).
|
|
113
110
|
path.resolve(here, '..', '..', 'src', 'core', 'style-builder', 'build-style.ts'),
|
|
114
111
|
path.resolve(here, '..', '..', 'src', 'core', 'parser', 'tw-parser.ts'),
|
|
115
|
-
path.resolve(here, '..', '..', 'src', 'metro', '
|
|
112
|
+
path.resolve(here, '..', '..', 'src', 'metro', 'wrap-imports.ts'),
|
|
116
113
|
path.resolve(here, '..', '..', 'src', 'metro', 'transformer.ts'),
|
|
117
114
|
]
|
|
118
115
|
const hash = createHash('sha256')
|
|
@@ -148,18 +145,14 @@ export interface RnwindState {
|
|
|
148
145
|
* can rebuild the same state without re-reading the Metro config.
|
|
149
146
|
* @param cssEntryFile Absolute path to the user's theme CSS.
|
|
150
147
|
* @param cacheDir Absolute path to the cache dir (`.rnwind`).
|
|
151
|
-
* @param watchFolders
|
|
152
|
-
* @param
|
|
153
|
-
* @param hostSources
|
|
154
|
-
* @param hostComponents
|
|
148
|
+
* @param watchFolders Monorepo watch folders to scan for atoms.
|
|
149
|
+
* @param wrapModules Extra modules whose component exports get `wrap()`-ed.
|
|
155
150
|
*/
|
|
156
151
|
export function configureRnwindState(
|
|
157
152
|
cssEntryFile: string,
|
|
158
153
|
cacheDir: string,
|
|
159
154
|
watchFolders: readonly string[] = [],
|
|
160
|
-
|
|
161
|
-
hostSources?: readonly string[],
|
|
162
|
-
hostComponents?: readonly string[],
|
|
155
|
+
wrapModules?: readonly string[],
|
|
163
156
|
): void {
|
|
164
157
|
process.env[CSS_ENTRY_ENV] = cssEntryFile
|
|
165
158
|
process.env[CACHE_DIR_ENV] = cacheDir
|
|
@@ -168,59 +161,23 @@ export function configureRnwindState(
|
|
|
168
161
|
} else {
|
|
169
162
|
process.env[WATCH_FOLDERS_ENV] = watchFolders.join('\0')
|
|
170
163
|
}
|
|
171
|
-
if (!
|
|
172
|
-
delete process.env[
|
|
164
|
+
if (!wrapModules || wrapModules.length === 0) {
|
|
165
|
+
delete process.env[WRAP_MODULES_ENV]
|
|
173
166
|
} else {
|
|
174
|
-
process.env[
|
|
175
|
-
}
|
|
176
|
-
if (!hostSources || hostSources.length === 0) {
|
|
177
|
-
delete process.env[HOST_SOURCES_ENV]
|
|
178
|
-
} else {
|
|
179
|
-
process.env[HOST_SOURCES_ENV] = hostSources.join(',')
|
|
180
|
-
}
|
|
181
|
-
if (!hostComponents || hostComponents.length === 0) {
|
|
182
|
-
delete process.env[HOST_COMPONENTS_ENV]
|
|
183
|
-
} else {
|
|
184
|
-
process.env[HOST_COMPONENTS_ENV] = hostComponents.join(',')
|
|
167
|
+
process.env[WRAP_MODULES_ENV] = wrapModules.join(',')
|
|
185
168
|
}
|
|
186
169
|
cached = null
|
|
187
170
|
}
|
|
188
171
|
|
|
189
172
|
/**
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
* either way.
|
|
194
|
-
* @returns User-supplied extra prefixes.
|
|
173
|
+
* Effective module → wrap-policy map: the built-in defaults merged with
|
|
174
|
+
* any extra modules the Metro config supplied.
|
|
175
|
+
* @returns Module → policy map the import-rewrite consults.
|
|
195
176
|
*/
|
|
196
|
-
export function
|
|
197
|
-
const raw = process.env[
|
|
198
|
-
|
|
199
|
-
return
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Read the caller-configured extra host module sources out of the
|
|
204
|
-
* worker environment. Empty array when unset — the transformer applies
|
|
205
|
-
* its built-in default list on top either way.
|
|
206
|
-
* @returns User-supplied extra host sources.
|
|
207
|
-
*/
|
|
208
|
-
export function getHostSources(): readonly string[] {
|
|
209
|
-
const raw = process.env[HOST_SOURCES_ENV]
|
|
210
|
-
if (!raw || raw.length === 0) return []
|
|
211
|
-
return raw.split(',').filter((entry) => entry.length > 0)
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Read the caller-configured extra host JSX tag names out of the worker
|
|
216
|
-
* environment. Verbatim names — may include `.` for member expressions
|
|
217
|
-
* like `'Animated.View'`.
|
|
218
|
-
* @returns User-supplied extra host component names.
|
|
219
|
-
*/
|
|
220
|
-
export function getHostComponents(): readonly string[] {
|
|
221
|
-
const raw = process.env[HOST_COMPONENTS_ENV]
|
|
222
|
-
if (!raw || raw.length === 0) return []
|
|
223
|
-
return raw.split(',').filter((entry) => entry.length > 0)
|
|
177
|
+
export function getWrapModules(): ReturnType<typeof buildWrapModules> {
|
|
178
|
+
const raw = process.env[WRAP_MODULES_ENV]
|
|
179
|
+
const extra = raw && raw.length > 0 ? raw.split(',').filter((entry) => entry.length > 0) : undefined
|
|
180
|
+
return buildWrapModules(extra)
|
|
224
181
|
}
|
|
225
182
|
|
|
226
183
|
/**
|
|
@@ -240,7 +197,7 @@ export function getRnwindState(projectRoot: string): RnwindState {
|
|
|
240
197
|
if (!cacheDir) throw new Error('rnwind: RNWIND_CACHE_DIR is not set — did `withRnwindConfig` run?')
|
|
241
198
|
const currentHash = readThemeHashFor(cssEntry)
|
|
242
199
|
if (cached?.themeHash === currentHash && cached.projectRoot === projectRoot) return cached
|
|
243
|
-
const themeCss =
|
|
200
|
+
const themeCss = resolveThemeCss(cssEntry)
|
|
244
201
|
const parser = new TailwindParser({
|
|
245
202
|
themeCss,
|
|
246
203
|
sources: defaultSources(projectRoot, cacheDir, readWatchFolders()),
|
|
@@ -261,14 +218,12 @@ export function getRnwindState(projectRoot: string): RnwindState {
|
|
|
261
218
|
*/
|
|
262
219
|
export function getRnwindCacheKey(): string {
|
|
263
220
|
const cssEntry = process.env[CSS_ENTRY_ENV] ?? ''
|
|
264
|
-
|
|
265
|
-
//
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
const hostComponents = process.env[HOST_COMPONENTS_ENV] ?? ''
|
|
271
|
-
return `rnwind:${cssEntry}:${readThemeHashFor(cssEntry)}|lib:${getLibraryFingerprint()}|pfx:${prefixes}|hs:${hostSources}|hc:${hostComponents}`
|
|
221
|
+
// Wrap-module config changes which import sites get `wrap()`-ed, so it
|
|
222
|
+
// MUST flip the cache key — otherwise Metro replays stale transforms
|
|
223
|
+
// (a newly-opted-in module keeps its raw import, a removed one keeps
|
|
224
|
+
// the wrap).
|
|
225
|
+
const wrapModules = process.env[WRAP_MODULES_ENV] ?? ''
|
|
226
|
+
return `rnwind:${cssEntry}:${readThemeHashFor(cssEntry)}|lib:${getLibraryFingerprint()}|wm:${wrapModules}`
|
|
272
227
|
}
|
|
273
228
|
|
|
274
229
|
/** Drop the cached state — call after editing the theme CSS. */
|