rnwind 0.0.11 → 0.0.12
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 +3 -1
- package/lib/cjs/core/normalize-classname.cjs.map +1 -1
- package/lib/cjs/core/parser/border-dispatcher.cjs +20 -10
- package/lib/cjs/core/parser/border-dispatcher.cjs.map +1 -1
- package/lib/cjs/core/parser/color-properties-dispatcher.cjs +7 -5
- package/lib/cjs/core/parser/color-properties-dispatcher.cjs.map +1 -1
- package/lib/cjs/core/parser/color.cjs +194 -10
- package/lib/cjs/core/parser/color.cjs.map +1 -1
- package/lib/cjs/core/parser/color.d.ts +18 -3
- package/lib/cjs/core/parser/declaration.cjs +62 -4
- package/lib/cjs/core/parser/declaration.cjs.map +1 -1
- package/lib/cjs/core/parser/layout-dispatcher.cjs +32 -2
- package/lib/cjs/core/parser/layout-dispatcher.cjs.map +1 -1
- package/lib/cjs/core/parser/shorthand.cjs +10 -3
- package/lib/cjs/core/parser/shorthand.cjs.map +1 -1
- package/lib/cjs/core/parser/tokens.cjs +9 -0
- package/lib/cjs/core/parser/tokens.cjs.map +1 -1
- package/lib/cjs/core/parser/tw-parser.cjs +6 -0
- package/lib/cjs/core/parser/tw-parser.cjs.map +1 -1
- package/lib/cjs/core/parser/typography-dispatcher.cjs +15 -8
- package/lib/cjs/core/parser/typography-dispatcher.cjs.map +1 -1
- package/lib/cjs/core/style-builder/union-builder.cjs +81 -2
- package/lib/cjs/core/style-builder/union-builder.cjs.map +1 -1
- package/lib/cjs/core/style-builder/union-builder.d.ts +28 -0
- package/lib/cjs/metro/state.cjs +74 -13
- package/lib/cjs/metro/state.cjs.map +1 -1
- package/lib/cjs/metro/state.d.ts +18 -0
- package/lib/cjs/metro/transformer.cjs +10 -4
- package/lib/cjs/metro/transformer.cjs.map +1 -1
- package/lib/cjs/metro/with-config.cjs +57 -0
- package/lib/cjs/metro/with-config.cjs.map +1 -1
- package/lib/cjs/metro/with-config.d.ts +12 -0
- package/lib/cjs/metro/wrap-imports.cjs +36 -1
- package/lib/cjs/metro/wrap-imports.cjs.map +1 -1
- package/lib/cjs/runtime/hooks/use-scheme.cjs +14 -7
- package/lib/cjs/runtime/hooks/use-scheme.cjs.map +1 -1
- package/lib/cjs/runtime/resolve.cjs +6 -2
- package/lib/cjs/runtime/resolve.cjs.map +1 -1
- package/lib/cjs/runtime/resolve.d.ts +5 -1
- package/lib/esm/core/normalize-classname.mjs +3 -1
- package/lib/esm/core/normalize-classname.mjs.map +1 -1
- package/lib/esm/core/parser/border-dispatcher.mjs +21 -11
- package/lib/esm/core/parser/border-dispatcher.mjs.map +1 -1
- package/lib/esm/core/parser/color-properties-dispatcher.mjs +8 -6
- package/lib/esm/core/parser/color-properties-dispatcher.mjs.map +1 -1
- package/lib/esm/core/parser/color.d.ts +18 -3
- package/lib/esm/core/parser/color.mjs +195 -12
- package/lib/esm/core/parser/color.mjs.map +1 -1
- package/lib/esm/core/parser/declaration.mjs +63 -5
- package/lib/esm/core/parser/declaration.mjs.map +1 -1
- package/lib/esm/core/parser/layout-dispatcher.mjs +32 -2
- package/lib/esm/core/parser/layout-dispatcher.mjs.map +1 -1
- package/lib/esm/core/parser/shorthand.mjs +11 -4
- package/lib/esm/core/parser/shorthand.mjs.map +1 -1
- package/lib/esm/core/parser/tokens.mjs +10 -1
- package/lib/esm/core/parser/tokens.mjs.map +1 -1
- package/lib/esm/core/parser/tw-parser.mjs +6 -0
- package/lib/esm/core/parser/tw-parser.mjs.map +1 -1
- package/lib/esm/core/parser/typography-dispatcher.mjs +15 -8
- package/lib/esm/core/parser/typography-dispatcher.mjs.map +1 -1
- package/lib/esm/core/style-builder/union-builder.d.ts +28 -0
- package/lib/esm/core/style-builder/union-builder.mjs +82 -3
- package/lib/esm/core/style-builder/union-builder.mjs.map +1 -1
- package/lib/esm/metro/state.d.ts +18 -0
- package/lib/esm/metro/state.mjs +75 -14
- package/lib/esm/metro/state.mjs.map +1 -1
- package/lib/esm/metro/transformer.mjs +10 -4
- package/lib/esm/metro/transformer.mjs.map +1 -1
- package/lib/esm/metro/with-config.d.ts +12 -0
- package/lib/esm/metro/with-config.mjs +58 -2
- package/lib/esm/metro/with-config.mjs.map +1 -1
- package/lib/esm/metro/wrap-imports.mjs +36 -1
- package/lib/esm/metro/wrap-imports.mjs.map +1 -1
- package/lib/esm/runtime/hooks/use-scheme.mjs +14 -7
- package/lib/esm/runtime/hooks/use-scheme.mjs.map +1 -1
- package/lib/esm/runtime/resolve.d.ts +5 -1
- package/lib/esm/runtime/resolve.mjs +6 -2
- package/lib/esm/runtime/resolve.mjs.map +1 -1
- package/package.json +1 -1
- package/src/core/normalize-classname.ts +4 -1
- package/src/core/parser/border-dispatcher.ts +22 -11
- package/src/core/parser/color-properties-dispatcher.ts +7 -5
- package/src/core/parser/color.ts +182 -11
- package/src/core/parser/declaration.ts +61 -5
- package/src/core/parser/layout-dispatcher.ts +34 -2
- package/src/core/parser/shorthand.ts +9 -3
- package/src/core/parser/tokens.ts +10 -1
- package/src/core/parser/tw-parser.ts +5 -0
- package/src/core/parser/typography-dispatcher.ts +15 -6
- package/src/core/style-builder/union-builder.ts +83 -3
- package/src/metro/state.ts +117 -12
- package/src/metro/transformer.ts +9 -4
- package/src/metro/with-config.ts +59 -1
- package/src/metro/wrap-imports.ts +36 -1
- package/src/runtime/hooks/use-scheme.ts +13 -6
- package/src/runtime/resolve.ts +6 -2
|
@@ -5,20 +5,27 @@ var tokens = require('./tokens.cjs');
|
|
|
5
5
|
|
|
6
6
|
/** RN-supported `textDecorationStyle` values (`wavy` has no RN equivalent). */
|
|
7
7
|
const RN_DECORATION_STYLES = new Set(['solid', 'double', 'dotted', 'dashed']);
|
|
8
|
+
/**
|
|
9
|
+
* The only `textDecorationLine` keywords React Native renders. CSS `overline`
|
|
10
|
+
* has no RN analog, so any line string containing it (or any other unknown
|
|
11
|
+
* keyword) is dropped rather than leaked as a value RN warns on + ignores.
|
|
12
|
+
*/
|
|
13
|
+
const RN_DECORATION_LINES = new Set(['none', 'underline', 'line-through', 'underline line-through']);
|
|
8
14
|
/**
|
|
9
15
|
* Build the RN `textDecorationLine` entry — string identity for the
|
|
10
|
-
* single-line cases, joined-string for the array shape.
|
|
16
|
+
* single-line cases, joined-string for the array shape. Drops any value
|
|
17
|
+
* outside RN's enum (`overline`, `overline underline`, …) so no invalid
|
|
18
|
+
* keyword reaches the StyleSheet.
|
|
11
19
|
* @param value Typed text-decoration-line.
|
|
12
|
-
* @returns Single-entry list with `textDecorationLine
|
|
20
|
+
* @returns Single-entry list with a valid `textDecorationLine`, or empty.
|
|
13
21
|
*/
|
|
14
22
|
function textDecorationLineToEntries(value) {
|
|
15
|
-
if (value === 'none')
|
|
16
|
-
return [['textDecorationLine', 'none']];
|
|
17
23
|
if (typeof value === 'string')
|
|
18
|
-
return [['textDecorationLine', value]];
|
|
19
|
-
if (Array.isArray(value))
|
|
20
|
-
return [
|
|
21
|
-
|
|
24
|
+
return RN_DECORATION_LINES.has(value) ? [['textDecorationLine', value]] : [];
|
|
25
|
+
if (!Array.isArray(value))
|
|
26
|
+
return [];
|
|
27
|
+
const line = value.join(' ');
|
|
28
|
+
return RN_DECORATION_LINES.has(line) ? [['textDecorationLine', line]] : [];
|
|
22
29
|
}
|
|
23
30
|
/**
|
|
24
31
|
* Build the RN `aspectRatio` entry from lightningcss's typed value.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typography-dispatcher.cjs","sources":["../../../../../src/core/parser/typography-dispatcher.ts"],"sourcesContent":["import type { Declaration as LcDeclaration } from 'lightningcss'\nimport { lineHeightToEntries } from './typography'\nimport { firstConcreteFontFamily } from './tokens'\nimport type { RNEntry } from './types'\n\n/** RN-supported `textDecorationStyle` values (`wavy` has no RN equivalent). */\nconst RN_DECORATION_STYLES: ReadonlySet<string> = new Set(['solid', 'double', 'dotted', 'dashed'])\n\n/**\n * Build the RN `textDecorationLine` entry — string identity for the\n * single-line cases, joined-string for the array shape.\n * @param value Typed text-decoration-line.\n * @returns Single-entry list with `textDecorationLine
|
|
1
|
+
{"version":3,"file":"typography-dispatcher.cjs","sources":["../../../../../src/core/parser/typography-dispatcher.ts"],"sourcesContent":["import type { Declaration as LcDeclaration } from 'lightningcss'\nimport { lineHeightToEntries } from './typography'\nimport { firstConcreteFontFamily } from './tokens'\nimport type { RNEntry } from './types'\n\n/** RN-supported `textDecorationStyle` values (`wavy` has no RN equivalent). */\nconst RN_DECORATION_STYLES: ReadonlySet<string> = new Set(['solid', 'double', 'dotted', 'dashed'])\n\n/**\n * The only `textDecorationLine` keywords React Native renders. CSS `overline`\n * has no RN analog, so any line string containing it (or any other unknown\n * keyword) is dropped rather than leaked as a value RN warns on + ignores.\n */\nconst RN_DECORATION_LINES: ReadonlySet<string> = new Set(['none', 'underline', 'line-through', 'underline line-through'])\n\n/**\n * Build the RN `textDecorationLine` entry — string identity for the\n * single-line cases, joined-string for the array shape. Drops any value\n * outside RN's enum (`overline`, `overline underline`, …) so no invalid\n * keyword reaches the StyleSheet.\n * @param value Typed text-decoration-line.\n * @returns Single-entry list with a valid `textDecorationLine`, or empty.\n */\nfunction textDecorationLineToEntries(value: LcDeclaration['value']): readonly RNEntry[] {\n if (typeof value === 'string') return RN_DECORATION_LINES.has(value) ? [['textDecorationLine', value]] : []\n if (!Array.isArray(value)) return []\n const line = value.join(' ')\n return RN_DECORATION_LINES.has(line) ? [['textDecorationLine', line]] : []\n}\n\n/**\n * Build the RN `aspectRatio` entry from lightningcss's typed value.\n * Drops `auto` (no RN equivalent).\n * @param value Typed aspect-ratio value.\n * @param value.auto Whether the value resolved to `auto`.\n * @param value.ratio Numeric `[width, height]` ratio (or null/undefined).\n * @returns Single-entry list or empty.\n */\nfunction aspectRatioToEntries(value: { auto?: boolean; ratio?: readonly [number, number] | null }): readonly RNEntry[] {\n if (value.auto) return []\n if (!value.ratio) return []\n const [w, h] = value.ratio\n if (h === 0) return []\n return [['aspectRatio', w / h]]\n}\n\n/**\n * Build the RN `letterSpacing` entry. RN expects pixel numbers; rem\n * lengths are scaled to px (16-px base).\n * @param value Typed letter-spacing value.\n * @returns Single-entry list or empty.\n */\nfunction letterSpacingToEntries(value: LcDeclaration['value']): readonly RNEntry[] {\n if (typeof value !== 'object') return []\n const tagged = value as { type?: string; value?: { type?: string; value?: { unit?: string; value?: number } } }\n if (tagged.type === 'normal') return [['letterSpacing', 0]]\n const inner = tagged.value\n if (inner?.type !== 'value' || !inner.value) return []\n const { unit, value: px } = inner.value\n if (typeof px !== 'number') return []\n const resolved = unit === 'px' ? px : px * 16\n // Round off lightningcss f32 noise (`0.1em` → `1.600000023841858`).\n return [['letterSpacing', Math.round(resolved * 10_000) / 10_000]]\n}\n\n/**\n * Lower a CSS `text-align` keyword to one RN's `textAlign` accepts. RN\n * has no logical `start`/`end`, so map them to physical sides (LTR\n * default); every other keyword (left/right/center/justify/auto) is\n * already valid and passes through.\n * @param align CSS text-align keyword.\n * @returns RN-valid textAlign keyword.\n */\nfunction physicalTextAlign(align: string): string {\n if (align === 'start') return 'left'\n if (align === 'end') return 'right'\n return align\n}\n\n/**\n * Dispatch typography declarations rnwind cares about (text-align,\n * text-transform, text-decoration-line, line-height, letter-spacing,\n * aspect-ratio). Returns null when the property isn't one of these so\n * the caller can fall through to its main switch.\n * @param decl One lightningcss declaration.\n * @returns RN entries when the property matched, else `null`.\n */\nexport function dispatchTypographyDeclaration(decl: LcDeclaration): readonly RNEntry[] | null {\n switch (decl.property) {\n case 'text-align': {\n return [['textAlign', physicalTextAlign(String(decl.value))]]\n }\n case 'text-transform': {\n return [['textTransform', decl.value.case ?? 'none']]\n }\n case 'text-decoration-line': {\n return textDecorationLineToEntries(decl.value)\n }\n case 'text-decoration-style': {\n // RN <Text> supports textDecorationStyle (solid/double/dotted/dashed).\n const style = String(decl.value)\n return RN_DECORATION_STYLES.has(style) ? [['textDecorationStyle', style]] : []\n }\n case 'font-family': {\n // Typed `font-family` is a fallback LIST (`font-sans`, `font-mono`,\n // `font-[Inter]`). RN takes one concrete typeface; an all-generic\n // stack (default `font-sans`) emits nothing → system font. The themed\n // `var(--font-*)` path goes through `coerceFontFamily` in declaration.ts.\n const family = firstConcreteFontFamily(decl.value as readonly unknown[])\n return family === undefined ? [] : [['fontFamily', family]]\n }\n case 'aspect-ratio': {\n return aspectRatioToEntries(decl.value)\n }\n case 'line-height': {\n return lineHeightToEntries(decl.value)\n }\n case 'letter-spacing': {\n return letterSpacingToEntries(decl.value)\n }\n default: {\n return null\n }\n }\n}\n"],"names":["firstConcreteFontFamily","lineHeightToEntries"],"mappings":"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"}
|
|
@@ -7,6 +7,10 @@ var buildStyle = require('./build-style.cjs');
|
|
|
7
7
|
|
|
8
8
|
/** Manifest module basename — the file SchemeProvider imports via the resolver. */
|
|
9
9
|
const MANIFEST_BASENAME = 'schemes.js';
|
|
10
|
+
/** Suffix every per-scheme style file carries on disk. */
|
|
11
|
+
const SCHEME_FILE_SUFFIX = '.style.js';
|
|
12
|
+
/** Registry key for the always-loaded fallback scheme — never reaped. */
|
|
13
|
+
const COMMON_SCHEME = 'common';
|
|
10
14
|
/**
|
|
11
15
|
* Atomic file write — stage to a `.tmp.<pid>.<nonce>` sibling, then
|
|
12
16
|
* `rename()` into place. Skips the write entirely when the existing
|
|
@@ -68,7 +72,36 @@ function setsEqual(a, b) {
|
|
|
68
72
|
* @returns Absolute path, e.g. `<cacheDir>/dark.style.js`.
|
|
69
73
|
*/
|
|
70
74
|
function schemeFilePath(cacheDir, scheme) {
|
|
71
|
-
return path.join(cacheDir, `${scheme}
|
|
75
|
+
return path.join(cacheDir, `${scheme}${SCHEME_FILE_SUFFIX}`);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* List scheme names whose `<scheme>.style.js` exists on disk but is NOT in
|
|
79
|
+
* the set the current build emits — orphans left by a removed variant
|
|
80
|
+
* (e.g. user drops `@variant dark`, or a theme swap via git pull). The
|
|
81
|
+
* always-loaded `common` scheme is never an orphan. The manifest
|
|
82
|
+
* (`schemes.js`) doesn't carry the `.style.js` suffix, so it's skipped.
|
|
83
|
+
* @param cacheDir Absolute cache directory.
|
|
84
|
+
* @param liveSchemes Scheme names the current build writes.
|
|
85
|
+
* @returns Orphaned scheme names safe to delete.
|
|
86
|
+
*/
|
|
87
|
+
function findOrphanedSchemes(cacheDir, liveSchemes) {
|
|
88
|
+
let names;
|
|
89
|
+
try {
|
|
90
|
+
names = node_fs.readdirSync(cacheDir);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
const orphans = [];
|
|
96
|
+
for (const name of names) {
|
|
97
|
+
if (!name.endsWith(SCHEME_FILE_SUFFIX))
|
|
98
|
+
continue;
|
|
99
|
+
const scheme = name.slice(0, -SCHEME_FILE_SUFFIX.length);
|
|
100
|
+
if (scheme === COMMON_SCHEME || liveSchemes.has(scheme))
|
|
101
|
+
continue;
|
|
102
|
+
orphans.push(scheme);
|
|
103
|
+
}
|
|
104
|
+
return orphans;
|
|
72
105
|
}
|
|
73
106
|
/**
|
|
74
107
|
* In-memory atom union + per-scheme style-file emitter.
|
|
@@ -153,6 +186,14 @@ class UnionBuilder {
|
|
|
153
186
|
get serializedMisses() {
|
|
154
187
|
return this.serializedMissesCount;
|
|
155
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* Snapshot of the scheme keys currently tracked in `schemeSignatures` —
|
|
191
|
+
* exposed for tests to assert orphan-signature cleanup.
|
|
192
|
+
* @returns Scheme signature keys (includes the `__manifest` sentinel).
|
|
193
|
+
*/
|
|
194
|
+
schemeSignatureKeys() {
|
|
195
|
+
return [...this.schemeSignatures.keys()];
|
|
196
|
+
}
|
|
156
197
|
/**
|
|
157
198
|
* Absolute path of one scheme's style file.
|
|
158
199
|
* @param scheme Registry key.
|
|
@@ -283,7 +324,7 @@ class UnionBuilder {
|
|
|
283
324
|
for (const [scheme, source] of Object.entries(schemeSources)) {
|
|
284
325
|
const signature = signatureOf(source);
|
|
285
326
|
const target = schemeFilePath(this.cacheDir, scheme);
|
|
286
|
-
if (this.
|
|
327
|
+
if (this.canSkipWrite(scheme, signature, target, source))
|
|
287
328
|
continue;
|
|
288
329
|
if (writeIfChanged(target, source))
|
|
289
330
|
changed.push(scheme);
|
|
@@ -296,8 +337,46 @@ class UnionBuilder {
|
|
|
296
337
|
changed.push('__manifest');
|
|
297
338
|
this.schemeSignatures.set('__manifest', manifestSignature);
|
|
298
339
|
}
|
|
340
|
+
this.reapOrphanedSchemes(new Set(Object.keys(schemeSources)));
|
|
299
341
|
return { changedSchemes: changed };
|
|
300
342
|
}
|
|
343
|
+
/**
|
|
344
|
+
* Whether the current write for one scheme can be skipped. A skip is
|
|
345
|
+
* safe only when the cached signature matches AND the bytes on disk
|
|
346
|
+
* still equal the expected source — an `existsSync` pass alone would
|
|
347
|
+
* keep a truncated or externally-modified file (corrupt content with a
|
|
348
|
+
* stale-but-matching signature). The byte read happens only on a
|
|
349
|
+
* signature match, so the common no-change path stays cheap.
|
|
350
|
+
* @param scheme Scheme registry key.
|
|
351
|
+
* @param signature Signature of the source about to be written.
|
|
352
|
+
* @param target Absolute path of the scheme file.
|
|
353
|
+
* @param source Expected file content.
|
|
354
|
+
* @returns Whether writing this scheme can be skipped.
|
|
355
|
+
*/
|
|
356
|
+
canSkipWrite(scheme, signature, target, source) {
|
|
357
|
+
if (this.schemeSignatures.get(scheme) !== signature)
|
|
358
|
+
return false;
|
|
359
|
+
try {
|
|
360
|
+
return node_fs.readFileSync(target, 'utf8') === source;
|
|
361
|
+
}
|
|
362
|
+
catch {
|
|
363
|
+
// Missing or unreadable on disk — must rewrite.
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Delete `<scheme>.style.js` files left behind by a scheme that's no
|
|
369
|
+
* longer part of the build (removed `@variant`, theme swap), and drop
|
|
370
|
+
* their cached signatures so a later re-introduction rewrites cleanly.
|
|
371
|
+
* The `common` file and the manifest are never touched.
|
|
372
|
+
* @param liveSchemes Scheme names the current build wrote.
|
|
373
|
+
*/
|
|
374
|
+
reapOrphanedSchemes(liveSchemes) {
|
|
375
|
+
for (const scheme of findOrphanedSchemes(this.cacheDir, liveSchemes)) {
|
|
376
|
+
node_fs.rmSync(schemeFilePath(this.cacheDir, scheme), { force: true });
|
|
377
|
+
this.schemeSignatures.delete(scheme);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
301
380
|
/**
|
|
302
381
|
* Ensure the manifest + common scheme files exist on disk so Metro's
|
|
303
382
|
* resolver can SHA1 them at boot before the first transform runs.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"union-builder.cjs","sources":["../../../../../src/core/style-builder/union-builder.ts"],"sourcesContent":["import { createHash, randomBytes } from 'node:crypto'\nimport { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs'\nimport path from 'node:path'\nimport type { GradientAtomInfo, HapticRequest, KeyframeBlock, SchemedStyle, TailwindParser } from '../parser'\nimport type { ThemeTables } from '../types'\nimport { buildSchemeSources, type AtomSerializedCache } from './build-style'\n\n/** Manifest module basename — the file SchemeProvider imports via the resolver. */\nconst MANIFEST_BASENAME = 'schemes.js'\n\n/**\n * Atomic file write — stage to a `.tmp.<pid>.<nonce>` sibling, then\n * `rename()` into place. Skips the write entirely when the existing\n * content matches.\n * @param target Final destination path.\n * @param content Bytes to write.\n * @returns Whether the file was actually rewritten.\n */\nfunction writeIfChanged(target: string, content: string): boolean {\n if (existsSync(target)) {\n try {\n if (readFileSync(target, 'utf8') === content) return false\n } catch {\n // Unreadable — fall through to rewrite.\n }\n }\n mkdirSync(path.dirname(target), { recursive: true })\n const temporary = `${target}.${process.pid}.${randomBytes(4).toString('hex')}.tmp`\n try {\n writeFileSync(temporary, content, 'utf8')\n renameSync(temporary, target)\n return true\n } catch (error) {\n rmSync(temporary, { force: true })\n throw error\n }\n}\n\n/**\n * SHA-256 prefix of a string — cheap signature used to detect whether a\n * per-scheme file's source has changed since the last write.\n * @param text Input text.\n * @returns 16-char hex digest.\n */\nfunction signatureOf(text: string): string {\n return createHash('sha256').update(text).digest('hex').slice(0, 16)\n}\n\n/**\n * Compare two `Set<string>`s for equality — same size + every element\n * of `a` present in `b`.\n * @param a First set.\n * @param b Second set.\n * @returns Whether the two sets contain identical values.\n */\nfunction setsEqual(a: ReadonlySet<string>, b: ReadonlySet<string>): boolean {\n if (a.size !== b.size) return false\n for (const v of a) if (!b.has(v)) return false\n return true\n}\n\n/**\n * Compute the absolute path of a per-scheme style file under the cache dir.\n * @param cacheDir Absolute cache directory.\n * @param scheme Registry key (`'common'` or a variant name).\n * @returns Absolute path, e.g. `<cacheDir>/dark.style.js`.\n */\nfunction schemeFilePath(cacheDir: string, scheme: string): string {\n return path.join(cacheDir, `${scheme}.style.js`)\n}\n\n/**\n * In-memory atom union + per-scheme style-file emitter.\n *\n * Correctness under multi-worker Metro relies on `ensureProjectScanned`:\n * the FIRST `recordFile` / `writeSchemes` call in every worker drives\n * the oxide Scanner across ALL project sources and hydrates the union\n * with the complete set of candidates. Subsequent per-file\n * `recordFile` calls only layer in atoms the scan already knew about,\n * so writes are idempotent — different workers can't clobber each\n * other's scheme files with partial views.\n *\n * Per-file deltas (atom set unchanged → early return) skip\n * serialization entirely. On a theme-CSS change, `getRnwindState`\n * builds a fresh parser + builder; the next call re-runs\n * `ensureProjectScanned` against the new parser, producing scheme\n * files with the new theme values.\n */\nclass UnionBuilder {\n private readonly cacheDir: string\n private readonly parser: TailwindParser\n private readonly unionAtoms = new Map<string, SchemedStyle>()\n private readonly unionKeyframes = new Map<string, KeyframeBlock>()\n /** atom name → gradient role/colour, surfaced into the manifest's `registerGradients`. */\n private readonly unionGradients = new Map<string, GradientAtomInfo>()\n /** atom name → haptic request, surfaced into the manifest's `registerHaptics`. */\n private readonly unionHaptics = new Map<string, HapticRequest>()\n /**\n * Distinct literal className strings seen across all files, pre-merged\n * into per-scheme molecules at write time. Accumulate-only (like\n * `unionAtoms`): orphaned literals just yield unused molecules and get\n * reaped on the next cold start, so no refcount is needed.\n */\n private readonly unionLiterals = new Set<string>()\n /**\n * Responsive breakpoints captured from the parser. Refreshed on every\n * `recordFile` / `ensureProjectScanned` so user-defined\n * `--breakpoint-*` overrides land in the manifest the next time it's\n * written. Identical for every parser call within one parser instance\n * (theme is fixed for the parser's lifetime), so storing the latest\n * snapshot is sufficient.\n */\n private breakpoints: ReadonlyMap<string, number> = new Map()\n /**\n * Per-scheme theme token tables captured from the parser. Refreshed on\n * every `recordFile` / `ensureProjectScanned` so theme-token edits land in\n * the manifest's `registerThemeTokens({...})` — the data source for\n * `useColor` / `useToken` / `useSize`.\n */\n private themeTokens: ThemeTables = {}\n /** file → set of atom names this file currently contributes. */\n private readonly fileAtomSets = new Map<string, Set<string>>()\n /** atom name → how many files currently contribute it (refcount). */\n private readonly atomRefCount = new Map<string, number>()\n /** scheme → last-written source SHA. Skips re-writing unchanged schemes. */\n private readonly schemeSignatures = new Map<string, string>()\n /**\n * Per-atom serialized-value cache — identity-keyed on each atom's\n * SchemedStyle reference. Carried across `writeSchemes` calls so the\n * typical \"user added one className\" FR case re-stringifies ONE atom\n * instead of all 175+. Cleared on `ensureProjectScanned` (full\n * rescan replaces every reference) and individually invalidated for\n * any atom `applyDiff` mutates.\n */\n private readonly serializedCache: AtomSerializedCache = new Map()\n /** Running count of stringify passes (cache misses). Test telemetry. */\n private serializedMissesCount = 0\n /** Set after `ensureProjectScanned` completes. */\n private projectScanned = false\n /** Promise guard so concurrent first-calls await ONE scan. */\n private pendingScan: Promise<void> | null = null\n\n constructor(cacheDir: string, parser: TailwindParser) {\n this.cacheDir = cacheDir\n this.parser = parser\n mkdirSync(this.cacheDir, { recursive: true })\n }\n\n /** Absolute path of the manifest module (`rnwind/__generated/schemes`). */\n public get manifestPath(): string {\n return path.join(this.cacheDir, MANIFEST_BASENAME)\n }\n\n /** Cumulative cache-miss count — exposed for tests to assert cache behaviour. */\n public get serializedMisses(): number {\n return this.serializedMissesCount\n }\n\n /**\n * Absolute path of one scheme's style file.\n * @param scheme Registry key.\n * @returns Absolute path.\n */\n public schemePath(scheme: string): string {\n return schemeFilePath(this.cacheDir, scheme)\n }\n\n /**\n * One-shot oxide scan + compile across every source the parser was\n * configured with. Idempotent — safe to call from any entry point.\n * Concurrent callers share the same in-flight promise.\n */\n public async ensureProjectScanned(): Promise<void> {\n if (this.projectScanned) return\n if (this.pendingScan) return this.pendingScan\n this.pendingScan = (async () => {\n const parsed = await this.parser.parseProject()\n for (const [name, style] of parsed.atoms) this.unionAtoms.set(name, style)\n for (const [name, kf] of parsed.keyframes) this.unionKeyframes.set(name, kf)\n for (const [name, gradient] of parsed.gradientAtoms) this.unionGradients.set(name, gradient)\n for (const [name, haptic] of parsed.hapticAtoms) this.unionHaptics.set(name, haptic)\n this.breakpoints = parsed.breakpoints\n this.themeTokens = parsed.themeTokens\n this.projectScanned = true\n })()\n try {\n await this.pendingScan\n } finally {\n this.pendingScan = null\n }\n }\n\n /**\n * Record one source file's resolved atoms + keyframes. Short-circuits\n * when the file's atom name set hasn't changed — the common case on\n * every Fast Refresh save of a file whose className literals are\n * unchanged.\n * @param file Absolute source file path.\n * @param atoms Per-atom resolved schemed styles from this transform.\n * @param keyframes Keyframe blocks referenced by this file's atoms.\n * @param literals\n * @returns `{ changed: true }` when the union shifted (new atom name,\n * removed atom name, or new keyframe) — the transformer uses this\n * to skip the serializer + `writeSchemes` when nothing changed.\n */\n public async recordFile(\n file: string,\n atoms: ReadonlyMap<string, SchemedStyle>,\n keyframes: ReadonlyMap<string, KeyframeBlock>,\n literals: readonly string[] = [],\n ): Promise<{ changed: boolean }> {\n await this.ensureProjectScanned()\n const literalAdded = this.recordLiterals(literals)\n const newAtomNames = new Set(atoms.keys())\n const previous = this.fileAtomSets.get(file)\n if (previous && setsEqual(previous, newAtomNames)) {\n // Atom set unchanged — skip the unionAtoms update entirely. The\n // project scan already populated them, and re-setting a fresh\n // object ref here would invalidate the per-atom serialization\n // cache on every FR save for no gain (values are identical).\n // Theme edits go through `getRnwindState` → new builder → fresh\n // scan, so stale cache is impossible.\n let keyframeAdded = false\n for (const [name, kf] of keyframes) {\n if (!this.unionKeyframes.has(name)) keyframeAdded = true\n this.unionKeyframes.set(name, kf)\n }\n return { changed: keyframeAdded || literalAdded }\n }\n this.applyDiff(file, newAtomNames, atoms, keyframes)\n return { changed: true }\n }\n\n /**\n * Merge a file's literal classNames into the union. A literal the\n * union hasn't seen flips `changed` so `writeSchemes` re-emits the\n * scheme files with the new molecule.\n * @param literals Distinct literal className strings.\n * @returns Whether any literal was new to the union.\n */\n private recordLiterals(literals: readonly string[]): boolean {\n let added = false\n for (const literal of literals) {\n if (this.unionLiterals.has(literal)) continue\n this.unionLiterals.add(literal)\n added = true\n }\n return added\n }\n\n /**\n * Forget one source file's contribution. Idempotent — repeated calls\n * for a file that's already dropped are no-ops. Does NOT remove the\n * atom from the union when another file (or the project scan) still\n * references it.\n * @param file Absolute source file path.\n */\n public dropFile(file: string): void {\n const previous = this.fileAtomSets.get(file)\n if (!previous) return\n for (const name of previous) {\n const count = (this.atomRefCount.get(name) ?? 0) - 1\n if (count <= 0) this.atomRefCount.delete(name)\n else this.atomRefCount.set(name, count)\n }\n this.fileAtomSets.delete(file)\n }\n\n /**\n * Serialize the union into per-scheme files + manifest, writing only\n * files whose source bytes changed. Called after every `recordFile`\n * from the transformer — and once at Metro startup via\n * `ensureFilesExist` to seed disk from the project scan alone.\n * @returns List of scheme keys whose files were rewritten (empty\n * when the union is byte-identical to the last flush).\n */\n public async writeSchemes(): Promise<{ changedSchemes: readonly string[] }> {\n await this.ensureProjectScanned()\n const sortedAtomNames = [...this.unionAtoms.keys()].toSorted((a, b) => a.localeCompare(b))\n const result = buildSchemeSources(sortedAtomNames, this.unionAtoms, this.unionKeyframes, this.serializedCache, this.breakpoints, this.unionGradients, this.unionHaptics, [...this.unionLiterals], this.themeTokens)\n this.serializedMissesCount += result.serializedMisses\n const { schemeSources, manifestSource } = result\n\n const changed: string[] = []\n for (const [scheme, source] of Object.entries(schemeSources)) {\n const signature = signatureOf(source)\n const target = schemeFilePath(this.cacheDir, scheme)\n if (this.schemeSignatures.get(scheme) === signature && existsSync(target)) continue\n if (writeIfChanged(target, source)) changed.push(scheme)\n this.schemeSignatures.set(scheme, signature)\n }\n\n const manifestSignature = signatureOf(manifestSource)\n const manifestTarget = path.join(this.cacheDir, MANIFEST_BASENAME)\n if (this.schemeSignatures.get('__manifest') !== manifestSignature || !existsSync(manifestTarget)) {\n if (writeIfChanged(manifestTarget, manifestSource)) changed.push('__manifest')\n this.schemeSignatures.set('__manifest', manifestSignature)\n }\n\n return { changedSchemes: changed }\n }\n\n /**\n * Ensure the manifest + common scheme files exist on disk so Metro's\n * resolver can SHA1 them at boot before the first transform runs.\n */\n public async ensureFilesExist(): Promise<void> {\n if (existsSync(this.manifestPath) && existsSync(schemeFilePath(this.cacheDir, 'common'))) {\n // Still trigger the scan so the in-memory union is complete; file\n // bytes may already be authoritative from a prior Metro run.\n await this.ensureProjectScanned()\n return\n }\n await this.writeSchemes()\n }\n\n /**\n * Apply one file's atom-name diff to the in-memory refcount + union.\n * @param file Source file path.\n * @param newAtoms New atom-name set for the file.\n * @param resolvedAtoms Fresh parser output — carries the resolved\n * styles for every entry in `newAtoms`.\n * @param newKeyframes Keyframes this file's atoms reference.\n */\n private applyDiff(\n file: string,\n newAtoms: ReadonlySet<string>,\n resolvedAtoms: ReadonlyMap<string, SchemedStyle>,\n newKeyframes: ReadonlyMap<string, KeyframeBlock>,\n ): void {\n const previous = this.fileAtomSets.get(file) ?? new Set<string>()\n for (const name of previous) {\n if (newAtoms.has(name)) continue\n const count = (this.atomRefCount.get(name) ?? 0) - 1\n if (count <= 0) this.atomRefCount.delete(name)\n else this.atomRefCount.set(name, count)\n // Do NOT remove `name` from `unionAtoms` — the project scan still\n // references it (orphaned atoms get reaped on the next Metro\n // cold start when the scanner re-walks disk).\n }\n for (const name of newAtoms) {\n if (!previous.has(name)) this.atomRefCount.set(name, (this.atomRefCount.get(name) ?? 0) + 1)\n // Only install the resolved style when the atom is new to the\n // union. Replacing an existing entry with a fresh parser-\n // produced object would swap the identity guard the per-atom\n // serialization cache uses and force a re-stringify for every\n // atom on every FR save. CSS edits rebuild the whole builder\n // (via `getRnwindState`) so stale values aren't possible.\n if (!this.unionAtoms.has(name)) {\n const style = resolvedAtoms.get(name)\n if (style) this.unionAtoms.set(name, style)\n }\n }\n this.fileAtomSets.set(file, new Set(newAtoms))\n for (const [name, kf] of newKeyframes) this.unionKeyframes.set(name, kf)\n }\n}\n\nexport { UnionBuilder }\n"],"names":["existsSync","readFileSync","mkdirSync","randomBytes","writeFileSync","renameSync","rmSync","createHash","buildSchemeSources"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"union-builder.cjs","sources":["../../../../../src/core/style-builder/union-builder.ts"],"sourcesContent":["import { createHash, randomBytes } from 'node:crypto'\nimport { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs'\nimport path from 'node:path'\nimport type { GradientAtomInfo, HapticRequest, KeyframeBlock, SchemedStyle, TailwindParser } from '../parser'\nimport type { ThemeTables } from '../types'\nimport { buildSchemeSources, type AtomSerializedCache } from './build-style'\n\n/** Manifest module basename — the file SchemeProvider imports via the resolver. */\nconst MANIFEST_BASENAME = 'schemes.js'\n\n/** Suffix every per-scheme style file carries on disk. */\nconst SCHEME_FILE_SUFFIX = '.style.js'\n\n/** Registry key for the always-loaded fallback scheme — never reaped. */\nconst COMMON_SCHEME = 'common'\n\n/**\n * Atomic file write — stage to a `.tmp.<pid>.<nonce>` sibling, then\n * `rename()` into place. Skips the write entirely when the existing\n * content matches.\n * @param target Final destination path.\n * @param content Bytes to write.\n * @returns Whether the file was actually rewritten.\n */\nfunction writeIfChanged(target: string, content: string): boolean {\n if (existsSync(target)) {\n try {\n if (readFileSync(target, 'utf8') === content) return false\n } catch {\n // Unreadable — fall through to rewrite.\n }\n }\n mkdirSync(path.dirname(target), { recursive: true })\n const temporary = `${target}.${process.pid}.${randomBytes(4).toString('hex')}.tmp`\n try {\n writeFileSync(temporary, content, 'utf8')\n renameSync(temporary, target)\n return true\n } catch (error) {\n rmSync(temporary, { force: true })\n throw error\n }\n}\n\n/**\n * SHA-256 prefix of a string — cheap signature used to detect whether a\n * per-scheme file's source has changed since the last write.\n * @param text Input text.\n * @returns 16-char hex digest.\n */\nfunction signatureOf(text: string): string {\n return createHash('sha256').update(text).digest('hex').slice(0, 16)\n}\n\n/**\n * Compare two `Set<string>`s for equality — same size + every element\n * of `a` present in `b`.\n * @param a First set.\n * @param b Second set.\n * @returns Whether the two sets contain identical values.\n */\nfunction setsEqual(a: ReadonlySet<string>, b: ReadonlySet<string>): boolean {\n if (a.size !== b.size) return false\n for (const v of a) if (!b.has(v)) return false\n return true\n}\n\n/**\n * Compute the absolute path of a per-scheme style file under the cache dir.\n * @param cacheDir Absolute cache directory.\n * @param scheme Registry key (`'common'` or a variant name).\n * @returns Absolute path, e.g. `<cacheDir>/dark.style.js`.\n */\nfunction schemeFilePath(cacheDir: string, scheme: string): string {\n return path.join(cacheDir, `${scheme}${SCHEME_FILE_SUFFIX}`)\n}\n\n/**\n * List scheme names whose `<scheme>.style.js` exists on disk but is NOT in\n * the set the current build emits — orphans left by a removed variant\n * (e.g. user drops `@variant dark`, or a theme swap via git pull). The\n * always-loaded `common` scheme is never an orphan. The manifest\n * (`schemes.js`) doesn't carry the `.style.js` suffix, so it's skipped.\n * @param cacheDir Absolute cache directory.\n * @param liveSchemes Scheme names the current build writes.\n * @returns Orphaned scheme names safe to delete.\n */\nfunction findOrphanedSchemes(cacheDir: string, liveSchemes: ReadonlySet<string>): readonly string[] {\n let names: readonly string[]\n try {\n names = readdirSync(cacheDir)\n } catch {\n return []\n }\n const orphans: string[] = []\n for (const name of names) {\n if (!name.endsWith(SCHEME_FILE_SUFFIX)) continue\n const scheme = name.slice(0, -SCHEME_FILE_SUFFIX.length)\n if (scheme === COMMON_SCHEME || liveSchemes.has(scheme)) continue\n orphans.push(scheme)\n }\n return orphans\n}\n\n/**\n * In-memory atom union + per-scheme style-file emitter.\n *\n * Correctness under multi-worker Metro relies on `ensureProjectScanned`:\n * the FIRST `recordFile` / `writeSchemes` call in every worker drives\n * the oxide Scanner across ALL project sources and hydrates the union\n * with the complete set of candidates. Subsequent per-file\n * `recordFile` calls only layer in atoms the scan already knew about,\n * so writes are idempotent — different workers can't clobber each\n * other's scheme files with partial views.\n *\n * Per-file deltas (atom set unchanged → early return) skip\n * serialization entirely. On a theme-CSS change, `getRnwindState`\n * builds a fresh parser + builder; the next call re-runs\n * `ensureProjectScanned` against the new parser, producing scheme\n * files with the new theme values.\n */\nclass UnionBuilder {\n private readonly cacheDir: string\n private readonly parser: TailwindParser\n private readonly unionAtoms = new Map<string, SchemedStyle>()\n private readonly unionKeyframes = new Map<string, KeyframeBlock>()\n /** atom name → gradient role/colour, surfaced into the manifest's `registerGradients`. */\n private readonly unionGradients = new Map<string, GradientAtomInfo>()\n /** atom name → haptic request, surfaced into the manifest's `registerHaptics`. */\n private readonly unionHaptics = new Map<string, HapticRequest>()\n /**\n * Distinct literal className strings seen across all files, pre-merged\n * into per-scheme molecules at write time. Accumulate-only (like\n * `unionAtoms`): orphaned literals just yield unused molecules and get\n * reaped on the next cold start, so no refcount is needed.\n */\n private readonly unionLiterals = new Set<string>()\n /**\n * Responsive breakpoints captured from the parser. Refreshed on every\n * `recordFile` / `ensureProjectScanned` so user-defined\n * `--breakpoint-*` overrides land in the manifest the next time it's\n * written. Identical for every parser call within one parser instance\n * (theme is fixed for the parser's lifetime), so storing the latest\n * snapshot is sufficient.\n */\n private breakpoints: ReadonlyMap<string, number> = new Map()\n /**\n * Per-scheme theme token tables captured from the parser. Refreshed on\n * every `recordFile` / `ensureProjectScanned` so theme-token edits land in\n * the manifest's `registerThemeTokens({...})` — the data source for\n * `useColor` / `useToken` / `useSize`.\n */\n private themeTokens: ThemeTables = {}\n /** file → set of atom names this file currently contributes. */\n private readonly fileAtomSets = new Map<string, Set<string>>()\n /** atom name → how many files currently contribute it (refcount). */\n private readonly atomRefCount = new Map<string, number>()\n /** scheme → last-written source SHA. Skips re-writing unchanged schemes. */\n private readonly schemeSignatures = new Map<string, string>()\n /**\n * Per-atom serialized-value cache — identity-keyed on each atom's\n * SchemedStyle reference. Carried across `writeSchemes` calls so the\n * typical \"user added one className\" FR case re-stringifies ONE atom\n * instead of all 175+. Cleared on `ensureProjectScanned` (full\n * rescan replaces every reference) and individually invalidated for\n * any atom `applyDiff` mutates.\n */\n private readonly serializedCache: AtomSerializedCache = new Map()\n /** Running count of stringify passes (cache misses). Test telemetry. */\n private serializedMissesCount = 0\n /** Set after `ensureProjectScanned` completes. */\n private projectScanned = false\n /** Promise guard so concurrent first-calls await ONE scan. */\n private pendingScan: Promise<void> | null = null\n\n constructor(cacheDir: string, parser: TailwindParser) {\n this.cacheDir = cacheDir\n this.parser = parser\n mkdirSync(this.cacheDir, { recursive: true })\n }\n\n /** Absolute path of the manifest module (`rnwind/__generated/schemes`). */\n public get manifestPath(): string {\n return path.join(this.cacheDir, MANIFEST_BASENAME)\n }\n\n /** Cumulative cache-miss count — exposed for tests to assert cache behaviour. */\n public get serializedMisses(): number {\n return this.serializedMissesCount\n }\n\n /**\n * Snapshot of the scheme keys currently tracked in `schemeSignatures` —\n * exposed for tests to assert orphan-signature cleanup.\n * @returns Scheme signature keys (includes the `__manifest` sentinel).\n */\n public schemeSignatureKeys(): readonly string[] {\n return [...this.schemeSignatures.keys()]\n }\n\n /**\n * Absolute path of one scheme's style file.\n * @param scheme Registry key.\n * @returns Absolute path.\n */\n public schemePath(scheme: string): string {\n return schemeFilePath(this.cacheDir, scheme)\n }\n\n /**\n * One-shot oxide scan + compile across every source the parser was\n * configured with. Idempotent — safe to call from any entry point.\n * Concurrent callers share the same in-flight promise.\n */\n public async ensureProjectScanned(): Promise<void> {\n if (this.projectScanned) return\n if (this.pendingScan) return this.pendingScan\n this.pendingScan = (async () => {\n const parsed = await this.parser.parseProject()\n for (const [name, style] of parsed.atoms) this.unionAtoms.set(name, style)\n for (const [name, kf] of parsed.keyframes) this.unionKeyframes.set(name, kf)\n for (const [name, gradient] of parsed.gradientAtoms) this.unionGradients.set(name, gradient)\n for (const [name, haptic] of parsed.hapticAtoms) this.unionHaptics.set(name, haptic)\n this.breakpoints = parsed.breakpoints\n this.themeTokens = parsed.themeTokens\n this.projectScanned = true\n })()\n try {\n await this.pendingScan\n } finally {\n this.pendingScan = null\n }\n }\n\n /**\n * Record one source file's resolved atoms + keyframes. Short-circuits\n * when the file's atom name set hasn't changed — the common case on\n * every Fast Refresh save of a file whose className literals are\n * unchanged.\n * @param file Absolute source file path.\n * @param atoms Per-atom resolved schemed styles from this transform.\n * @param keyframes Keyframe blocks referenced by this file's atoms.\n * @param literals\n * @returns `{ changed: true }` when the union shifted (new atom name,\n * removed atom name, or new keyframe) — the transformer uses this\n * to skip the serializer + `writeSchemes` when nothing changed.\n */\n public async recordFile(\n file: string,\n atoms: ReadonlyMap<string, SchemedStyle>,\n keyframes: ReadonlyMap<string, KeyframeBlock>,\n literals: readonly string[] = [],\n ): Promise<{ changed: boolean }> {\n await this.ensureProjectScanned()\n const literalAdded = this.recordLiterals(literals)\n const newAtomNames = new Set(atoms.keys())\n const previous = this.fileAtomSets.get(file)\n if (previous && setsEqual(previous, newAtomNames)) {\n // Atom set unchanged — skip the unionAtoms update entirely. The\n // project scan already populated them, and re-setting a fresh\n // object ref here would invalidate the per-atom serialization\n // cache on every FR save for no gain (values are identical).\n // Theme edits go through `getRnwindState` → new builder → fresh\n // scan, so stale cache is impossible.\n let keyframeAdded = false\n for (const [name, kf] of keyframes) {\n if (!this.unionKeyframes.has(name)) keyframeAdded = true\n this.unionKeyframes.set(name, kf)\n }\n return { changed: keyframeAdded || literalAdded }\n }\n this.applyDiff(file, newAtomNames, atoms, keyframes)\n return { changed: true }\n }\n\n /**\n * Merge a file's literal classNames into the union. A literal the\n * union hasn't seen flips `changed` so `writeSchemes` re-emits the\n * scheme files with the new molecule.\n * @param literals Distinct literal className strings.\n * @returns Whether any literal was new to the union.\n */\n private recordLiterals(literals: readonly string[]): boolean {\n let added = false\n for (const literal of literals) {\n if (this.unionLiterals.has(literal)) continue\n this.unionLiterals.add(literal)\n added = true\n }\n return added\n }\n\n /**\n * Forget one source file's contribution. Idempotent — repeated calls\n * for a file that's already dropped are no-ops. Does NOT remove the\n * atom from the union when another file (or the project scan) still\n * references it.\n * @param file Absolute source file path.\n */\n public dropFile(file: string): void {\n const previous = this.fileAtomSets.get(file)\n if (!previous) return\n for (const name of previous) {\n const count = (this.atomRefCount.get(name) ?? 0) - 1\n if (count <= 0) this.atomRefCount.delete(name)\n else this.atomRefCount.set(name, count)\n }\n this.fileAtomSets.delete(file)\n }\n\n /**\n * Serialize the union into per-scheme files + manifest, writing only\n * files whose source bytes changed. Called after every `recordFile`\n * from the transformer — and once at Metro startup via\n * `ensureFilesExist` to seed disk from the project scan alone.\n * @returns List of scheme keys whose files were rewritten (empty\n * when the union is byte-identical to the last flush).\n */\n public async writeSchemes(): Promise<{ changedSchemes: readonly string[] }> {\n await this.ensureProjectScanned()\n const sortedAtomNames = [...this.unionAtoms.keys()].toSorted((a, b) => a.localeCompare(b))\n const result = buildSchemeSources(sortedAtomNames, this.unionAtoms, this.unionKeyframes, this.serializedCache, this.breakpoints, this.unionGradients, this.unionHaptics, [...this.unionLiterals], this.themeTokens)\n this.serializedMissesCount += result.serializedMisses\n const { schemeSources, manifestSource } = result\n\n const changed: string[] = []\n for (const [scheme, source] of Object.entries(schemeSources)) {\n const signature = signatureOf(source)\n const target = schemeFilePath(this.cacheDir, scheme)\n if (this.canSkipWrite(scheme, signature, target, source)) continue\n if (writeIfChanged(target, source)) changed.push(scheme)\n this.schemeSignatures.set(scheme, signature)\n }\n\n const manifestSignature = signatureOf(manifestSource)\n const manifestTarget = path.join(this.cacheDir, MANIFEST_BASENAME)\n if (this.schemeSignatures.get('__manifest') !== manifestSignature || !existsSync(manifestTarget)) {\n if (writeIfChanged(manifestTarget, manifestSource)) changed.push('__manifest')\n this.schemeSignatures.set('__manifest', manifestSignature)\n }\n\n this.reapOrphanedSchemes(new Set(Object.keys(schemeSources)))\n return { changedSchemes: changed }\n }\n\n /**\n * Whether the current write for one scheme can be skipped. A skip is\n * safe only when the cached signature matches AND the bytes on disk\n * still equal the expected source — an `existsSync` pass alone would\n * keep a truncated or externally-modified file (corrupt content with a\n * stale-but-matching signature). The byte read happens only on a\n * signature match, so the common no-change path stays cheap.\n * @param scheme Scheme registry key.\n * @param signature Signature of the source about to be written.\n * @param target Absolute path of the scheme file.\n * @param source Expected file content.\n * @returns Whether writing this scheme can be skipped.\n */\n private canSkipWrite(scheme: string, signature: string, target: string, source: string): boolean {\n if (this.schemeSignatures.get(scheme) !== signature) return false\n try {\n return readFileSync(target, 'utf8') === source\n } catch {\n // Missing or unreadable on disk — must rewrite.\n return false\n }\n }\n\n /**\n * Delete `<scheme>.style.js` files left behind by a scheme that's no\n * longer part of the build (removed `@variant`, theme swap), and drop\n * their cached signatures so a later re-introduction rewrites cleanly.\n * The `common` file and the manifest are never touched.\n * @param liveSchemes Scheme names the current build wrote.\n */\n private reapOrphanedSchemes(liveSchemes: ReadonlySet<string>): void {\n for (const scheme of findOrphanedSchemes(this.cacheDir, liveSchemes)) {\n rmSync(schemeFilePath(this.cacheDir, scheme), { force: true })\n this.schemeSignatures.delete(scheme)\n }\n }\n\n /**\n * Ensure the manifest + common scheme files exist on disk so Metro's\n * resolver can SHA1 them at boot before the first transform runs.\n */\n public async ensureFilesExist(): Promise<void> {\n if (existsSync(this.manifestPath) && existsSync(schemeFilePath(this.cacheDir, 'common'))) {\n // Still trigger the scan so the in-memory union is complete; file\n // bytes may already be authoritative from a prior Metro run.\n await this.ensureProjectScanned()\n return\n }\n await this.writeSchemes()\n }\n\n /**\n * Apply one file's atom-name diff to the in-memory refcount + union.\n * @param file Source file path.\n * @param newAtoms New atom-name set for the file.\n * @param resolvedAtoms Fresh parser output — carries the resolved\n * styles for every entry in `newAtoms`.\n * @param newKeyframes Keyframes this file's atoms reference.\n */\n private applyDiff(\n file: string,\n newAtoms: ReadonlySet<string>,\n resolvedAtoms: ReadonlyMap<string, SchemedStyle>,\n newKeyframes: ReadonlyMap<string, KeyframeBlock>,\n ): void {\n const previous = this.fileAtomSets.get(file) ?? new Set<string>()\n for (const name of previous) {\n if (newAtoms.has(name)) continue\n const count = (this.atomRefCount.get(name) ?? 0) - 1\n if (count <= 0) this.atomRefCount.delete(name)\n else this.atomRefCount.set(name, count)\n // Do NOT remove `name` from `unionAtoms` — the project scan still\n // references it (orphaned atoms get reaped on the next Metro\n // cold start when the scanner re-walks disk).\n }\n for (const name of newAtoms) {\n if (!previous.has(name)) this.atomRefCount.set(name, (this.atomRefCount.get(name) ?? 0) + 1)\n // Only install the resolved style when the atom is new to the\n // union. Replacing an existing entry with a fresh parser-\n // produced object would swap the identity guard the per-atom\n // serialization cache uses and force a re-stringify for every\n // atom on every FR save. CSS edits rebuild the whole builder\n // (via `getRnwindState`) so stale values aren't possible.\n if (!this.unionAtoms.has(name)) {\n const style = resolvedAtoms.get(name)\n if (style) this.unionAtoms.set(name, style)\n }\n }\n this.fileAtomSets.set(file, new Set(newAtoms))\n for (const [name, kf] of newKeyframes) this.unionKeyframes.set(name, kf)\n }\n}\n\nexport { UnionBuilder }\n"],"names":["existsSync","readFileSync","mkdirSync","randomBytes","writeFileSync","renameSync","rmSync","createHash","readdirSync","buildSchemeSources"],"mappings":"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"}
|
|
@@ -74,6 +74,12 @@ declare class UnionBuilder {
|
|
|
74
74
|
get manifestPath(): string;
|
|
75
75
|
/** Cumulative cache-miss count — exposed for tests to assert cache behaviour. */
|
|
76
76
|
get serializedMisses(): number;
|
|
77
|
+
/**
|
|
78
|
+
* Snapshot of the scheme keys currently tracked in `schemeSignatures` —
|
|
79
|
+
* exposed for tests to assert orphan-signature cleanup.
|
|
80
|
+
* @returns Scheme signature keys (includes the `__manifest` sentinel).
|
|
81
|
+
*/
|
|
82
|
+
schemeSignatureKeys(): readonly string[];
|
|
77
83
|
/**
|
|
78
84
|
* Absolute path of one scheme's style file.
|
|
79
85
|
* @param scheme Registry key.
|
|
@@ -129,6 +135,28 @@ declare class UnionBuilder {
|
|
|
129
135
|
writeSchemes(): Promise<{
|
|
130
136
|
changedSchemes: readonly string[];
|
|
131
137
|
}>;
|
|
138
|
+
/**
|
|
139
|
+
* Whether the current write for one scheme can be skipped. A skip is
|
|
140
|
+
* safe only when the cached signature matches AND the bytes on disk
|
|
141
|
+
* still equal the expected source — an `existsSync` pass alone would
|
|
142
|
+
* keep a truncated or externally-modified file (corrupt content with a
|
|
143
|
+
* stale-but-matching signature). The byte read happens only on a
|
|
144
|
+
* signature match, so the common no-change path stays cheap.
|
|
145
|
+
* @param scheme Scheme registry key.
|
|
146
|
+
* @param signature Signature of the source about to be written.
|
|
147
|
+
* @param target Absolute path of the scheme file.
|
|
148
|
+
* @param source Expected file content.
|
|
149
|
+
* @returns Whether writing this scheme can be skipped.
|
|
150
|
+
*/
|
|
151
|
+
private canSkipWrite;
|
|
152
|
+
/**
|
|
153
|
+
* Delete `<scheme>.style.js` files left behind by a scheme that's no
|
|
154
|
+
* longer part of the build (removed `@variant`, theme swap), and drop
|
|
155
|
+
* their cached signatures so a later re-introduction rewrites cleanly.
|
|
156
|
+
* The `common` file and the manifest are never touched.
|
|
157
|
+
* @param liveSchemes Scheme names the current build wrote.
|
|
158
|
+
*/
|
|
159
|
+
private reapOrphanedSchemes;
|
|
132
160
|
/**
|
|
133
161
|
* Ensure the manifest + common scheme files exist on disk so Metro's
|
|
134
162
|
* resolver can SHA1 them at boot before the first transform runs.
|
package/lib/cjs/metro/state.cjs
CHANGED
|
@@ -57,24 +57,68 @@ const WRAP_MODULES_ENV = 'RNWIND_WRAP_MODULES';
|
|
|
57
57
|
let libraryFingerprint;
|
|
58
58
|
/** Live state shared across one Metro transform worker. */
|
|
59
59
|
let cached = null;
|
|
60
|
+
/**
|
|
61
|
+
* Cached wrap-module map. The env var feeding it only changes on a Metro
|
|
62
|
+
* (re)configure, so rebuilding the 11-entry Map per file transform is pure
|
|
63
|
+
* waste — `configureRnwindState` / `resetRnwindState` clear this so the next
|
|
64
|
+
* call rebuilds from the current env.
|
|
65
|
+
*/
|
|
66
|
+
let cachedWrapModules = null;
|
|
67
|
+
/**
|
|
68
|
+
* Process-scoped memo of resolved theme CSS keyed by entry path. Every file
|
|
69
|
+
* transform reads the theme hash (via `getRnwindState` + `getRnwindCacheKey`)
|
|
70
|
+
* and the rebuild path reads the full CSS again — without this memo each is a
|
|
71
|
+
* full disk read + `@import` inline + SHA-256. Keyed on `statSync` mtime so a
|
|
72
|
+
* real edit busts it while unchanged files reuse the cached result. This is
|
|
73
|
+
* NOT a competing persistent cache: it's in-memory, process-scoped, and only
|
|
74
|
+
* defers the redundant re-reads Metro's own caching can't see.
|
|
75
|
+
*/
|
|
76
|
+
const themeMemo = new Map();
|
|
77
|
+
/**
|
|
78
|
+
* Load (or reuse) the resolved theme CSS + hash for `cssPath`. Re-reads from
|
|
79
|
+
* disk only when the file's mtime changed since the last read; otherwise the
|
|
80
|
+
* cached entry is returned untouched.
|
|
81
|
+
* @param cssPath Absolute CSS entry path.
|
|
82
|
+
* @returns The memo entry, or `null` when the entry can't be read.
|
|
83
|
+
*/
|
|
84
|
+
function loadThemeMemo(cssPath) {
|
|
85
|
+
let stat;
|
|
86
|
+
try {
|
|
87
|
+
stat = node_fs.statSync(cssPath);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
themeMemo.delete(cssPath);
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
const { mtimeMs } = stat;
|
|
94
|
+
const cachedEntry = themeMemo.get(cssPath);
|
|
95
|
+
if (cachedEntry?.mtimeMs === mtimeMs)
|
|
96
|
+
return cachedEntry;
|
|
97
|
+
const css = cssImports.resolveThemeCss(cssPath);
|
|
98
|
+
const entry = { mtimeMs, hash: node_crypto.createHash('sha256').update(css).digest('hex').slice(0, 16), css };
|
|
99
|
+
themeMemo.set(cssPath, entry);
|
|
100
|
+
return entry;
|
|
101
|
+
}
|
|
60
102
|
/**
|
|
61
103
|
* Cheap content-hash readout. SHA-256 prefix of the FULLY-RESOLVED theme
|
|
62
104
|
* CSS — `@import`s flattened — so an edit to a theme file the entry only
|
|
63
105
|
* re-exports (`@import "@acme/ui/theme.css"`) still rotates the hash and
|
|
64
106
|
* invalidates Metro's cache. Returns `'missing'` when the entry can't be
|
|
65
|
-
* read so the cache key stays deterministic.
|
|
107
|
+
* read so the cache key stays deterministic. Memoised by mtime.
|
|
66
108
|
* @param cssPath Absolute CSS path.
|
|
67
109
|
* @returns 16-char hex content hash.
|
|
68
110
|
*/
|
|
69
111
|
function readThemeHashFor(cssPath) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
112
|
+
return loadThemeMemo(cssPath)?.hash ?? 'missing';
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Resolved theme CSS for the entry, served from the same mtime memo so the
|
|
116
|
+
* rebuild path doesn't re-read the file the hash readout already loaded.
|
|
117
|
+
* @param cssPath Absolute CSS path.
|
|
118
|
+
* @returns Fully-inlined theme CSS, or `''` when unreadable.
|
|
119
|
+
*/
|
|
120
|
+
function readThemeCssFor(cssPath) {
|
|
121
|
+
return loadThemeMemo(cssPath)?.css ?? '';
|
|
78
122
|
}
|
|
79
123
|
/**
|
|
80
124
|
* Hash a small set of rnwind library files whose changes affect the
|
|
@@ -153,6 +197,7 @@ function configureRnwindState(cssEntryFile, cacheDir, watchFolders = [], wrapMod
|
|
|
153
197
|
process.env[WRAP_MODULES_ENV] = wrapModules.join(',');
|
|
154
198
|
}
|
|
155
199
|
cached = null;
|
|
200
|
+
cachedWrapModules = null;
|
|
156
201
|
}
|
|
157
202
|
/**
|
|
158
203
|
* Effective module → wrap-policy map: the built-in defaults merged with
|
|
@@ -160,9 +205,12 @@ function configureRnwindState(cssEntryFile, cacheDir, watchFolders = [], wrapMod
|
|
|
160
205
|
* @returns Module → policy map the import-rewrite consults.
|
|
161
206
|
*/
|
|
162
207
|
function getWrapModules() {
|
|
208
|
+
if (cachedWrapModules)
|
|
209
|
+
return cachedWrapModules;
|
|
163
210
|
const raw = process.env[WRAP_MODULES_ENV];
|
|
164
211
|
const extra = raw && raw.length > 0 ? raw.split(',').filter((entry) => entry.length > 0) : undefined;
|
|
165
|
-
|
|
212
|
+
cachedWrapModules = wrapImports.buildWrapModules(extra);
|
|
213
|
+
return cachedWrapModules;
|
|
166
214
|
}
|
|
167
215
|
/**
|
|
168
216
|
* Fetch (or build) the worker-local rnwind state. Re-reads the theme
|
|
@@ -182,15 +230,24 @@ function getRnwindState(projectRoot) {
|
|
|
182
230
|
if (!cacheDir)
|
|
183
231
|
throw new Error('rnwind: RNWIND_CACHE_DIR is not set — did `withRnwindConfig` run?');
|
|
184
232
|
const currentHash = readThemeHashFor(cssEntry);
|
|
185
|
-
|
|
233
|
+
const currentFingerprint = getLibraryFingerprint();
|
|
234
|
+
// Reuse only when the CSS hash, the project root, AND the library
|
|
235
|
+
// fingerprint all match — an in-place rnwind upgrade rotates the
|
|
236
|
+
// fingerprint while the CSS hash is unchanged, and that must still rebuild.
|
|
237
|
+
if (cached?.themeHash === currentHash &&
|
|
238
|
+
cached.libraryFingerprint === currentFingerprint &&
|
|
239
|
+
cached.projectRoot === projectRoot) {
|
|
186
240
|
return cached;
|
|
187
|
-
|
|
241
|
+
}
|
|
242
|
+
// Served from the same mtime memo `readThemeHashFor` just populated — no
|
|
243
|
+
// second disk read on the rebuild path.
|
|
244
|
+
const themeCss = readThemeCssFor(cssEntry);
|
|
188
245
|
const parser = new twParser.TailwindParser({
|
|
189
246
|
themeCss,
|
|
190
247
|
sources: defaultSources(projectRoot, cacheDir, readWatchFolders()),
|
|
191
248
|
});
|
|
192
249
|
const builder = new unionBuilder.UnionBuilder(cacheDir, parser);
|
|
193
|
-
cached = { parser, builder, themeCss, themeHash: currentHash, projectRoot };
|
|
250
|
+
cached = { parser, builder, themeCss, themeHash: currentHash, libraryFingerprint: currentFingerprint, projectRoot };
|
|
194
251
|
return cached;
|
|
195
252
|
}
|
|
196
253
|
/**
|
|
@@ -214,6 +271,7 @@ function getRnwindCacheKey() {
|
|
|
214
271
|
/** Drop the cached state — call after editing the theme CSS. */
|
|
215
272
|
function resetRnwindState() {
|
|
216
273
|
cached = null;
|
|
274
|
+
cachedWrapModules = null;
|
|
217
275
|
}
|
|
218
276
|
/**
|
|
219
277
|
* Drop cached state, rebuild parser/builder with the fresh CSS, rescan
|
|
@@ -225,6 +283,9 @@ function resetRnwindState() {
|
|
|
225
283
|
* @param projectRoot Absolute project root (from `metroConfig.projectRoot`).
|
|
226
284
|
*/
|
|
227
285
|
async function onThemeChange(projectRoot) {
|
|
286
|
+
// The watcher already told us the CSS changed; an atomic save can reuse the
|
|
287
|
+
// same mtime, so bust the memo unconditionally rather than trusting stat.
|
|
288
|
+
themeMemo.clear();
|
|
228
289
|
resetRnwindState();
|
|
229
290
|
const state = getRnwindState(projectRoot);
|
|
230
291
|
await state.builder.writeSchemes();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.cjs","sources":["../../../../src/metro/state.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs'\nimport path from 'node:path'\nimport { createHash } from 'node:crypto'\nimport { UnionBuilder } from '../core/style-builder'\nimport { TailwindParser, type SourceEntry } from '../core/parser'\nimport { resolveThemeCss } from './css-imports'\nimport { buildWrapModules } from './wrap-imports'\n\n/**\n * Default oxide Scanner globs — walk every JS/TS source under the\n * project root AND every monorepo watch folder, excluding\n * `node_modules` and rnwind's own cache dir so we don't rescan\n * generated scheme files.\n *\n * Monorepo layouts (Yarn workspaces, pnpm workspaces, Nx) surface\n * sibling package roots as `metroConfig.watchFolders`. Every folder\n * Metro watches must also be scanned so atoms declared in shared UI\n * packages make it into the union — without this, only the app's\n * own files would be scanned and every UI-package atom would resolve\n * to `undefined` at runtime.\n * @param projectRoot Absolute project root.\n * @param cacheDir Absolute rnwind cache dir (to exclude).\n * @param watchFolders Extra monorepo roots Metro is watching.\n * @returns Scanner sources suitable for `parser.parseProject()`.\n */\nfunction defaultSources(projectRoot: string, cacheDir: string, watchFolders: readonly string[]): readonly SourceEntry[] {\n const cacheBaseName = path.basename(cacheDir)\n const roots = new Set<string>([projectRoot, ...watchFolders])\n const sources: SourceEntry[] = []\n for (const root of roots) {\n sources.push({ base: root, pattern: '**/*.{ts,tsx,js,jsx}', negated: false }, { base: root, pattern: '**/node_modules/**', negated: true }, { base: root, pattern: `**/${cacheBaseName}/**`, negated: true })\n }\n return sources\n}\n\n/**\n * Read monorepo watch-folder paths out of the worker environment.\n * Empty array when the host isn't a monorepo.\n * @returns Absolute paths Metro also watches (sibling packages).\n */\nfunction readWatchFolders(): readonly string[] {\n const raw = process.env[WATCH_FOLDERS_ENV]\n if (!raw || raw.length === 0) return []\n return raw.split('\\0').filter((entry) => entry.length > 0)\n}\n\n/** Env var Metro workers read to locate the theme CSS on disk. */\nconst CSS_ENTRY_ENV = 'RNWIND_CSS_ENTRY_FILE'\n/** Env var Metro workers read to locate the cache directory (`.rnwind`). */\nconst CACHE_DIR_ENV = 'RNWIND_CACHE_DIR'\n/** Env var carrying `watchFolders` from Metro config (NUL-separated). */\nconst WATCH_FOLDERS_ENV = 'RNWIND_WATCH_FOLDERS'\n/** Env var carrying extra modules whose component exports get `wrap()`-ed. Comma-separated. */\nconst WRAP_MODULES_ENV = 'RNWIND_WRAP_MODULES'\n\n/** Memoised library fingerprint — read once per worker process. */\nlet libraryFingerprint: string | undefined\n\n/** Live state shared across one Metro transform worker. */\nlet cached: RnwindState | null = null\n\n/**\n * Cheap content-hash readout. SHA-256 prefix of the FULLY-RESOLVED theme\n * CSS — `@import`s flattened — so an edit to a theme file the entry only\n * re-exports (`@import \"@acme/ui/theme.css\"`) still rotates the hash and\n * invalidates Metro's cache. Returns `'missing'` when the entry can't be\n * read so the cache key stays deterministic.\n * @param cssPath Absolute CSS path.\n * @returns 16-char hex content hash.\n */\nfunction readThemeHashFor(cssPath: string): string {\n if (!existsSync(cssPath)) return 'missing'\n try {\n return createHash('sha256').update(resolveThemeCss(cssPath)).digest('hex').slice(0, 16)\n } catch {\n return 'missing'\n }\n}\n\n/**\n * Hash a small set of rnwind library files whose changes affect the\n * generated transform output. When the library is rebuilt (workspace\n * dev OR npm install of a new version) the file bytes change, the\n * fingerprint rotates, and Metro's transform cache invalidates.\n *\n * Includes the import-rewriter (`wrap-imports`) and runtime resolver\n * alongside the parser / style-builder so a change to the transformer —\n * e.g. renaming the injected wrap helper — invalidates every stale\n * per-file cache entry on the next dev run. Without this, a user upgrading rnwind in-place\n * would keep loading the old transformed bytes; React-refresh would\n * then preserve fiber state across the version bump and the rendered\n * hook list could shift, surfacing as \"change in the order of Hooks\"\n * runtime errors.\n * Memoised — read once per worker process.\n * @returns 16-char hex fingerprint.\n */\nfunction getLibraryFingerprint(): string {\n if (libraryFingerprint !== undefined) return libraryFingerprint\n const here = path.dirname(__filename)\n const candidates = [\n path.resolve(here, '..', 'core', 'style-builder', 'build-style.mjs'),\n path.resolve(here, '..', 'core', 'style-builder', 'build-style.cjs'),\n path.resolve(here, '..', 'core', 'parser', 'tw-parser.mjs'),\n path.resolve(here, '..', 'core', 'parser', 'tw-parser.cjs'),\n path.resolve(here, 'wrap-imports.mjs'),\n path.resolve(here, 'wrap-imports.cjs'),\n path.resolve(here, 'transformer.mjs'),\n path.resolve(here, 'transformer.cjs'),\n // Source-tree fallback for tests + workspace dev (no built lib yet).\n path.resolve(here, '..', '..', 'src', 'core', 'style-builder', 'build-style.ts'),\n path.resolve(here, '..', '..', 'src', 'core', 'parser', 'tw-parser.ts'),\n path.resolve(here, '..', '..', 'src', 'metro', 'wrap-imports.ts'),\n path.resolve(here, '..', '..', 'src', 'metro', 'transformer.ts'),\n ]\n const hash = createHash('sha256')\n let included = 0\n for (const file of candidates) {\n if (!existsSync(file)) continue\n try {\n hash.update(readFileSync(file))\n included += 1\n } catch {\n // Unreadable file — skip; fingerprint still derives from whatever WE could read.\n }\n }\n libraryFingerprint = included > 0 ? hash.digest('hex').slice(0, 16) : '0'.repeat(16)\n return libraryFingerprint\n}\n\n/**\n * Worker-local state. Lazy-initialised on first access so files that\n * bypass the transform don't pay for construction.\n */\nexport interface RnwindState {\n parser: TailwindParser\n builder: UnionBuilder\n themeCss: string\n themeHash: string\n projectRoot: string\n}\n\n/**\n * Publish the theme CSS path + cache dir to the environment so worker\n * subprocesses (spawned by Metro once `babelTransformerPath` is set)\n * can rebuild the same state without re-reading the Metro config.\n * @param cssEntryFile Absolute path to the user's theme CSS.\n * @param cacheDir Absolute path to the cache dir (`.rnwind`).\n * @param watchFolders Monorepo watch folders to scan for atoms.\n * @param wrapModules Extra modules whose component exports get `wrap()`-ed.\n */\nexport function configureRnwindState(\n cssEntryFile: string,\n cacheDir: string,\n watchFolders: readonly string[] = [],\n wrapModules?: readonly string[],\n): void {\n process.env[CSS_ENTRY_ENV] = cssEntryFile\n process.env[CACHE_DIR_ENV] = cacheDir\n if (watchFolders.length === 0) {\n delete process.env[WATCH_FOLDERS_ENV]\n } else {\n process.env[WATCH_FOLDERS_ENV] = watchFolders.join('\\0')\n }\n if (!wrapModules || wrapModules.length === 0) {\n delete process.env[WRAP_MODULES_ENV]\n } else {\n process.env[WRAP_MODULES_ENV] = wrapModules.join(',')\n }\n cached = null\n}\n\n/**\n * Effective module → wrap-policy map: the built-in defaults merged with\n * any extra modules the Metro config supplied.\n * @returns Module → policy map the import-rewrite consults.\n */\nexport function getWrapModules(): ReturnType<typeof buildWrapModules> {\n const raw = process.env[WRAP_MODULES_ENV]\n const extra = raw && raw.length > 0 ? raw.split(',').filter((entry) => entry.length > 0) : undefined\n return buildWrapModules(extra)\n}\n\n/**\n * Fetch (or build) the worker-local rnwind state. Re-reads the theme\n * CSS hash on every call: if the user edited `global.css` while Metro\n * is running, the cached state is dropped and a fresh parser + ledger\n * is built. Combined with the `getCacheKey()` export on the\n * transformer (which folds the same hash into Metro's per-file cache\n * key) every CSS edit produces a full, correct re-bundle.\n * @param projectRoot\n * @returns The live rnwind state.\n */\nexport function getRnwindState(projectRoot: string): RnwindState {\n const cssEntry = process.env[CSS_ENTRY_ENV]\n const cacheDir = process.env[CACHE_DIR_ENV]\n if (!cssEntry) throw new Error('rnwind: RNWIND_CSS_ENTRY_FILE is not set — did `withRnwindConfig` run?')\n if (!cacheDir) throw new Error('rnwind: RNWIND_CACHE_DIR is not set — did `withRnwindConfig` run?')\n const currentHash = readThemeHashFor(cssEntry)\n if (cached?.themeHash === currentHash && cached.projectRoot === projectRoot) return cached\n const themeCss = resolveThemeCss(cssEntry)\n const parser = new TailwindParser({\n themeCss,\n sources: defaultSources(projectRoot, cacheDir, readWatchFolders()),\n })\n const builder = new UnionBuilder(cacheDir, parser)\n cached = { parser, builder, themeCss, themeHash: currentHash, projectRoot }\n return cached\n}\n\n/**\n * Compute the rnwind cache-key suffix Metro mixes into every per-file\n * transform cache entry via the transformer's `getCacheKey()` export.\n * Includes the CSS path + its current content hash + the rnwind\n * library fingerprint, so any edit to `global.css` OR a library\n * upgrade flips the cache key and forces Metro to re-run the\n * transformer.\n * @returns Deterministic string suitable for appending to Metro's cache key.\n */\nexport function getRnwindCacheKey(): string {\n const cssEntry = process.env[CSS_ENTRY_ENV] ?? ''\n // Wrap-module config changes which import sites get `wrap()`-ed, so it\n // MUST flip the cache key — otherwise Metro replays stale transforms\n // (a newly-opted-in module keeps its raw import, a removed one keeps\n // the wrap).\n const wrapModules = process.env[WRAP_MODULES_ENV] ?? ''\n return `rnwind:${cssEntry}:${readThemeHashFor(cssEntry)}|lib:${getLibraryFingerprint()}|wm:${wrapModules}`\n}\n\n/** Drop the cached state — call after editing the theme CSS. */\nexport function resetRnwindState(): void {\n cached = null\n}\n\n/**\n * Drop cached state, rebuild parser/builder with the fresh CSS, rescan\n * the project, and rewrite every scheme file on disk. This is what\n * `withRnwindConfig`'s CSS file-watcher invokes so `global.css` edits\n * propagate to the app via Metro's HMR — without this, the CSS-as-JS\n * module would re-emit `export {}` whose bytes never change, so Metro\n * would never invalidate downstream modules.\n * @param projectRoot Absolute project root (from `metroConfig.projectRoot`).\n */\nexport async function onThemeChange(projectRoot: string): Promise<void> {\n resetRnwindState()\n const state = getRnwindState(projectRoot)\n await state.builder.writeSchemes()\n}\n\n/**\n * Resolve the on-disk path of the scheme manifest module for the\n * resolver. The manifest eager-imports `common.style.js` and\n * lazy-requires each variant scheme; SchemeProvider calls its\n * `ensureSchemeLoaded` export to trigger per-scheme requires.\n * @returns Absolute path to `<cacheDir>/schemes.js`.\n */\nexport function manifestPathFor(): string {\n const cacheDir = process.env[CACHE_DIR_ENV]\n if (!cacheDir) throw new Error('rnwind: RNWIND_CACHE_DIR is not set')\n return path.join(cacheDir, 'schemes.js')\n}\n"],"names":["existsSync","createHash","resolveThemeCss","readFileSync","buildWrapModules","TailwindParser","UnionBuilder"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"state.cjs","sources":["../../../../src/metro/state.ts"],"sourcesContent":["import { existsSync, readFileSync, statSync } from 'node:fs'\nimport path from 'node:path'\nimport { createHash } from 'node:crypto'\nimport { UnionBuilder } from '../core/style-builder'\nimport { TailwindParser, type SourceEntry } from '../core/parser'\nimport { resolveThemeCss } from './css-imports'\nimport { buildWrapModules } from './wrap-imports'\n\n/**\n * Default oxide Scanner globs — walk every JS/TS source under the\n * project root AND every monorepo watch folder, excluding\n * `node_modules` and rnwind's own cache dir so we don't rescan\n * generated scheme files.\n *\n * Monorepo layouts (Yarn workspaces, pnpm workspaces, Nx) surface\n * sibling package roots as `metroConfig.watchFolders`. Every folder\n * Metro watches must also be scanned so atoms declared in shared UI\n * packages make it into the union — without this, only the app's\n * own files would be scanned and every UI-package atom would resolve\n * to `undefined` at runtime.\n * @param projectRoot Absolute project root.\n * @param cacheDir Absolute rnwind cache dir (to exclude).\n * @param watchFolders Extra monorepo roots Metro is watching.\n * @returns Scanner sources suitable for `parser.parseProject()`.\n */\nfunction defaultSources(projectRoot: string, cacheDir: string, watchFolders: readonly string[]): readonly SourceEntry[] {\n const cacheBaseName = path.basename(cacheDir)\n const roots = new Set<string>([projectRoot, ...watchFolders])\n const sources: SourceEntry[] = []\n for (const root of roots) {\n sources.push({ base: root, pattern: '**/*.{ts,tsx,js,jsx}', negated: false }, { base: root, pattern: '**/node_modules/**', negated: true }, { base: root, pattern: `**/${cacheBaseName}/**`, negated: true })\n }\n return sources\n}\n\n/**\n * Read monorepo watch-folder paths out of the worker environment.\n * Empty array when the host isn't a monorepo.\n * @returns Absolute paths Metro also watches (sibling packages).\n */\nfunction readWatchFolders(): readonly string[] {\n const raw = process.env[WATCH_FOLDERS_ENV]\n if (!raw || raw.length === 0) return []\n return raw.split('\\0').filter((entry) => entry.length > 0)\n}\n\n/** Env var Metro workers read to locate the theme CSS on disk. */\nconst CSS_ENTRY_ENV = 'RNWIND_CSS_ENTRY_FILE'\n/** Env var Metro workers read to locate the cache directory (`.rnwind`). */\nconst CACHE_DIR_ENV = 'RNWIND_CACHE_DIR'\n/** Env var carrying `watchFolders` from Metro config (NUL-separated). */\nconst WATCH_FOLDERS_ENV = 'RNWIND_WATCH_FOLDERS'\n/** Env var carrying extra modules whose component exports get `wrap()`-ed. Comma-separated. */\nconst WRAP_MODULES_ENV = 'RNWIND_WRAP_MODULES'\n\n/** Memoised library fingerprint — read once per worker process. */\nlet libraryFingerprint: string | undefined\n\n/** Live state shared across one Metro transform worker. */\nlet cached: RnwindState | null = null\n\n/**\n * Cached wrap-module map. The env var feeding it only changes on a Metro\n * (re)configure, so rebuilding the 11-entry Map per file transform is pure\n * waste — `configureRnwindState` / `resetRnwindState` clear this so the next\n * call rebuilds from the current env.\n */\nlet cachedWrapModules: ReturnType<typeof buildWrapModules> | null = null\n\n/** A memoised theme read: the resolved CSS, its content hash, and its mtime. */\ninterface ThemeMemoEntry {\n mtimeMs: number\n hash: string\n css: string\n}\n\n/**\n * Process-scoped memo of resolved theme CSS keyed by entry path. Every file\n * transform reads the theme hash (via `getRnwindState` + `getRnwindCacheKey`)\n * and the rebuild path reads the full CSS again — without this memo each is a\n * full disk read + `@import` inline + SHA-256. Keyed on `statSync` mtime so a\n * real edit busts it while unchanged files reuse the cached result. This is\n * NOT a competing persistent cache: it's in-memory, process-scoped, and only\n * defers the redundant re-reads Metro's own caching can't see.\n */\nconst themeMemo = new Map<string, ThemeMemoEntry>()\n\n/** Test-only counter — increments on each actual disk read (memo miss). */\nlet themeReadCount = 0\n\n/**\n * Load (or reuse) the resolved theme CSS + hash for `cssPath`. Re-reads from\n * disk only when the file's mtime changed since the last read; otherwise the\n * cached entry is returned untouched.\n * @param cssPath Absolute CSS entry path.\n * @returns The memo entry, or `null` when the entry can't be read.\n */\nfunction loadThemeMemo(cssPath: string): ThemeMemoEntry | null {\n let stat: ReturnType<typeof statSync>\n try {\n stat = statSync(cssPath)\n } catch {\n themeMemo.delete(cssPath)\n return null\n }\n const { mtimeMs } = stat\n const cachedEntry = themeMemo.get(cssPath)\n if (cachedEntry?.mtimeMs === mtimeMs) return cachedEntry\n const css = resolveThemeCss(cssPath)\n themeReadCount += 1\n const entry: ThemeMemoEntry = { mtimeMs, hash: createHash('sha256').update(css).digest('hex').slice(0, 16), css }\n themeMemo.set(cssPath, entry)\n return entry\n}\n\n/**\n * Cheap content-hash readout. SHA-256 prefix of the FULLY-RESOLVED theme\n * CSS — `@import`s flattened — so an edit to a theme file the entry only\n * re-exports (`@import \"@acme/ui/theme.css\"`) still rotates the hash and\n * invalidates Metro's cache. Returns `'missing'` when the entry can't be\n * read so the cache key stays deterministic. Memoised by mtime.\n * @param cssPath Absolute CSS path.\n * @returns 16-char hex content hash.\n */\nfunction readThemeHashFor(cssPath: string): string {\n return loadThemeMemo(cssPath)?.hash ?? 'missing'\n}\n\n/**\n * Resolved theme CSS for the entry, served from the same mtime memo so the\n * rebuild path doesn't re-read the file the hash readout already loaded.\n * @param cssPath Absolute CSS path.\n * @returns Fully-inlined theme CSS, or `''` when unreadable.\n */\nfunction readThemeCssFor(cssPath: string): string {\n return loadThemeMemo(cssPath)?.css ?? ''\n}\n\n/** Test-only — count of actual disk reads of the theme CSS (memo misses). */\nexport function __getThemeReadCount(): number {\n return themeReadCount\n}\n\n/** Test-only — clear the theme memo so the next read hits disk. */\nexport function __resetThemeMemo(): void {\n themeMemo.clear()\n}\n\n/**\n * Hash a small set of rnwind library files whose changes affect the\n * generated transform output. When the library is rebuilt (workspace\n * dev OR npm install of a new version) the file bytes change, the\n * fingerprint rotates, and Metro's transform cache invalidates.\n *\n * Includes the import-rewriter (`wrap-imports`) and runtime resolver\n * alongside the parser / style-builder so a change to the transformer —\n * e.g. renaming the injected wrap helper — invalidates every stale\n * per-file cache entry on the next dev run. Without this, a user upgrading rnwind in-place\n * would keep loading the old transformed bytes; React-refresh would\n * then preserve fiber state across the version bump and the rendered\n * hook list could shift, surfacing as \"change in the order of Hooks\"\n * runtime errors.\n * Memoised — read once per worker process.\n * @returns 16-char hex fingerprint.\n */\nfunction getLibraryFingerprint(): string {\n if (libraryFingerprint !== undefined) return libraryFingerprint\n const here = path.dirname(__filename)\n const candidates = [\n path.resolve(here, '..', 'core', 'style-builder', 'build-style.mjs'),\n path.resolve(here, '..', 'core', 'style-builder', 'build-style.cjs'),\n path.resolve(here, '..', 'core', 'parser', 'tw-parser.mjs'),\n path.resolve(here, '..', 'core', 'parser', 'tw-parser.cjs'),\n path.resolve(here, 'wrap-imports.mjs'),\n path.resolve(here, 'wrap-imports.cjs'),\n path.resolve(here, 'transformer.mjs'),\n path.resolve(here, 'transformer.cjs'),\n // Source-tree fallback for tests + workspace dev (no built lib yet).\n path.resolve(here, '..', '..', 'src', 'core', 'style-builder', 'build-style.ts'),\n path.resolve(here, '..', '..', 'src', 'core', 'parser', 'tw-parser.ts'),\n path.resolve(here, '..', '..', 'src', 'metro', 'wrap-imports.ts'),\n path.resolve(here, '..', '..', 'src', 'metro', 'transformer.ts'),\n ]\n const hash = createHash('sha256')\n let included = 0\n for (const file of candidates) {\n if (!existsSync(file)) continue\n try {\n hash.update(readFileSync(file))\n included += 1\n } catch {\n // Unreadable file — skip; fingerprint still derives from whatever WE could read.\n }\n }\n libraryFingerprint = included > 0 ? hash.digest('hex').slice(0, 16) : '0'.repeat(16)\n return libraryFingerprint\n}\n\n/**\n * Test-only override for the memoised library fingerprint. Production reads\n * the value once from disk; tests use this to simulate a library upgrade\n * (fingerprint rotation) without rebuilding the lib. Passing `undefined`\n * clears the override so the next call re-derives from disk.\n * @param value Forced fingerprint, or omit/`undefined` to clear the override.\n */\nexport function __setLibraryFingerprintForTest(value?: string): void {\n libraryFingerprint = value\n}\n\n/**\n * Worker-local state. Lazy-initialised on first access so files that\n * bypass the transform don't pay for construction.\n */\nexport interface RnwindState {\n parser: TailwindParser\n builder: UnionBuilder\n themeCss: string\n themeHash: string\n /**\n * Library fingerprint captured when this state was built. Folded into the\n * rebuild guard so an in-place rnwind upgrade (unchanged CSS, rotated\n * fingerprint) drops the stale builder + on-disk scheme format.\n */\n libraryFingerprint: string\n projectRoot: string\n}\n\n/**\n * Publish the theme CSS path + cache dir to the environment so worker\n * subprocesses (spawned by Metro once `babelTransformerPath` is set)\n * can rebuild the same state without re-reading the Metro config.\n * @param cssEntryFile Absolute path to the user's theme CSS.\n * @param cacheDir Absolute path to the cache dir (`.rnwind`).\n * @param watchFolders Monorepo watch folders to scan for atoms.\n * @param wrapModules Extra modules whose component exports get `wrap()`-ed.\n */\nexport function configureRnwindState(\n cssEntryFile: string,\n cacheDir: string,\n watchFolders: readonly string[] = [],\n wrapModules?: readonly string[],\n): void {\n process.env[CSS_ENTRY_ENV] = cssEntryFile\n process.env[CACHE_DIR_ENV] = cacheDir\n if (watchFolders.length === 0) {\n delete process.env[WATCH_FOLDERS_ENV]\n } else {\n process.env[WATCH_FOLDERS_ENV] = watchFolders.join('\\0')\n }\n if (!wrapModules || wrapModules.length === 0) {\n delete process.env[WRAP_MODULES_ENV]\n } else {\n process.env[WRAP_MODULES_ENV] = wrapModules.join(',')\n }\n cached = null\n cachedWrapModules = null\n}\n\n/**\n * Effective module → wrap-policy map: the built-in defaults merged with\n * any extra modules the Metro config supplied.\n * @returns Module → policy map the import-rewrite consults.\n */\nexport function getWrapModules(): ReturnType<typeof buildWrapModules> {\n if (cachedWrapModules) return cachedWrapModules\n const raw = process.env[WRAP_MODULES_ENV]\n const extra = raw && raw.length > 0 ? raw.split(',').filter((entry) => entry.length > 0) : undefined\n cachedWrapModules = buildWrapModules(extra)\n return cachedWrapModules\n}\n\n/**\n * Fetch (or build) the worker-local rnwind state. Re-reads the theme\n * CSS hash on every call: if the user edited `global.css` while Metro\n * is running, the cached state is dropped and a fresh parser + ledger\n * is built. Combined with the `getCacheKey()` export on the\n * transformer (which folds the same hash into Metro's per-file cache\n * key) every CSS edit produces a full, correct re-bundle.\n * @param projectRoot\n * @returns The live rnwind state.\n */\nexport function getRnwindState(projectRoot: string): RnwindState {\n const cssEntry = process.env[CSS_ENTRY_ENV]\n const cacheDir = process.env[CACHE_DIR_ENV]\n if (!cssEntry) throw new Error('rnwind: RNWIND_CSS_ENTRY_FILE is not set — did `withRnwindConfig` run?')\n if (!cacheDir) throw new Error('rnwind: RNWIND_CACHE_DIR is not set — did `withRnwindConfig` run?')\n const currentHash = readThemeHashFor(cssEntry)\n const currentFingerprint = getLibraryFingerprint()\n // Reuse only when the CSS hash, the project root, AND the library\n // fingerprint all match — an in-place rnwind upgrade rotates the\n // fingerprint while the CSS hash is unchanged, and that must still rebuild.\n if (\n cached?.themeHash === currentHash &&\n cached.libraryFingerprint === currentFingerprint &&\n cached.projectRoot === projectRoot\n ) {\n return cached\n }\n // Served from the same mtime memo `readThemeHashFor` just populated — no\n // second disk read on the rebuild path.\n const themeCss = readThemeCssFor(cssEntry)\n const parser = new TailwindParser({\n themeCss,\n sources: defaultSources(projectRoot, cacheDir, readWatchFolders()),\n })\n const builder = new UnionBuilder(cacheDir, parser)\n cached = { parser, builder, themeCss, themeHash: currentHash, libraryFingerprint: currentFingerprint, projectRoot }\n return cached\n}\n\n/**\n * Compute the rnwind cache-key suffix Metro mixes into every per-file\n * transform cache entry via the transformer's `getCacheKey()` export.\n * Includes the CSS path + its current content hash + the rnwind\n * library fingerprint, so any edit to `global.css` OR a library\n * upgrade flips the cache key and forces Metro to re-run the\n * transformer.\n * @returns Deterministic string suitable for appending to Metro's cache key.\n */\nexport function getRnwindCacheKey(): string {\n const cssEntry = process.env[CSS_ENTRY_ENV] ?? ''\n // Wrap-module config changes which import sites get `wrap()`-ed, so it\n // MUST flip the cache key — otherwise Metro replays stale transforms\n // (a newly-opted-in module keeps its raw import, a removed one keeps\n // the wrap).\n const wrapModules = process.env[WRAP_MODULES_ENV] ?? ''\n return `rnwind:${cssEntry}:${readThemeHashFor(cssEntry)}|lib:${getLibraryFingerprint()}|wm:${wrapModules}`\n}\n\n/** Drop the cached state — call after editing the theme CSS. */\nexport function resetRnwindState(): void {\n cached = null\n cachedWrapModules = null\n}\n\n/**\n * Drop cached state, rebuild parser/builder with the fresh CSS, rescan\n * the project, and rewrite every scheme file on disk. This is what\n * `withRnwindConfig`'s CSS file-watcher invokes so `global.css` edits\n * propagate to the app via Metro's HMR — without this, the CSS-as-JS\n * module would re-emit `export {}` whose bytes never change, so Metro\n * would never invalidate downstream modules.\n * @param projectRoot Absolute project root (from `metroConfig.projectRoot`).\n */\nexport async function onThemeChange(projectRoot: string): Promise<void> {\n // The watcher already told us the CSS changed; an atomic save can reuse the\n // same mtime, so bust the memo unconditionally rather than trusting stat.\n themeMemo.clear()\n resetRnwindState()\n const state = getRnwindState(projectRoot)\n await state.builder.writeSchemes()\n}\n\n/**\n * Resolve the on-disk path of the scheme manifest module for the\n * resolver. The manifest eager-imports `common.style.js` and\n * lazy-requires each variant scheme; SchemeProvider calls its\n * `ensureSchemeLoaded` export to trigger per-scheme requires.\n * @returns Absolute path to `<cacheDir>/schemes.js`.\n */\nexport function manifestPathFor(): string {\n const cacheDir = process.env[CACHE_DIR_ENV]\n if (!cacheDir) throw new Error('rnwind: RNWIND_CACHE_DIR is not set')\n return path.join(cacheDir, 'schemes.js')\n}\n"],"names":["statSync","resolveThemeCss","createHash","existsSync","readFileSync","buildWrapModules","TailwindParser","UnionBuilder"],"mappings":"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"}
|