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.
Files changed (134) hide show
  1. package/lib/cjs/core/normalize-classname.cjs +25 -0
  2. package/lib/cjs/core/normalize-classname.cjs.map +1 -0
  3. package/lib/cjs/core/normalize-classname.d.ts +10 -0
  4. package/lib/cjs/core/style-builder/build-style.cjs +258 -58
  5. package/lib/cjs/core/style-builder/build-style.cjs.map +1 -1
  6. package/lib/cjs/core/style-builder/build-style.d.ts +6 -1
  7. package/lib/cjs/core/style-builder/union-builder.cjs +37 -3
  8. package/lib/cjs/core/style-builder/union-builder.cjs.map +1 -1
  9. package/lib/cjs/core/style-builder/union-builder.d.ts +21 -1
  10. package/lib/cjs/metro/css-imports.cjs +81 -0
  11. package/lib/cjs/metro/css-imports.cjs.map +1 -0
  12. package/lib/cjs/metro/css-imports.d.ts +8 -0
  13. package/lib/cjs/metro/dts.cjs +7 -16
  14. package/lib/cjs/metro/dts.cjs.map +1 -1
  15. package/lib/cjs/metro/dts.d.ts +2 -4
  16. package/lib/cjs/metro/state.cjs +38 -86
  17. package/lib/cjs/metro/state.cjs.map +1 -1
  18. package/lib/cjs/metro/state.d.ts +8 -25
  19. package/lib/cjs/metro/transformer.cjs +193 -34
  20. package/lib/cjs/metro/transformer.cjs.map +1 -1
  21. package/lib/cjs/metro/with-config.cjs +2 -2
  22. package/lib/cjs/metro/with-config.cjs.map +1 -1
  23. package/lib/cjs/metro/with-config.d.ts +11 -26
  24. package/lib/cjs/metro/wrap-imports.cjs +273 -0
  25. package/lib/cjs/metro/wrap-imports.cjs.map +1 -0
  26. package/lib/cjs/metro/wrap-imports.d.ts +26 -0
  27. package/lib/cjs/runtime/components/rnwind-provider.cjs +0 -17
  28. package/lib/cjs/runtime/components/rnwind-provider.cjs.map +1 -1
  29. package/lib/cjs/runtime/components/rnwind-provider.d.ts +0 -14
  30. package/lib/cjs/runtime/hooks/use-css.cjs +16 -10
  31. package/lib/cjs/runtime/hooks/use-css.cjs.map +1 -1
  32. package/lib/cjs/runtime/hooks/use-css.d.ts +15 -9
  33. package/lib/cjs/runtime/index.cjs +11 -13
  34. package/lib/cjs/runtime/index.cjs.map +1 -1
  35. package/lib/cjs/runtime/index.d.ts +4 -9
  36. package/lib/cjs/runtime/lookup-css.cjs +10 -0
  37. package/lib/cjs/runtime/lookup-css.cjs.map +1 -1
  38. package/lib/cjs/runtime/lookup-css.d.ts +7 -0
  39. package/lib/cjs/runtime/resolve.cjs +348 -0
  40. package/lib/cjs/runtime/resolve.cjs.map +1 -0
  41. package/lib/cjs/runtime/resolve.d.ts +61 -0
  42. package/lib/cjs/runtime/wrap.cjs +254 -0
  43. package/lib/cjs/runtime/wrap.cjs.map +1 -0
  44. package/lib/cjs/runtime/wrap.d.ts +37 -0
  45. package/lib/cjs/testing/index.cjs +81 -50
  46. package/lib/cjs/testing/index.cjs.map +1 -1
  47. package/lib/esm/core/normalize-classname.d.ts +10 -0
  48. package/lib/esm/core/normalize-classname.mjs +23 -0
  49. package/lib/esm/core/normalize-classname.mjs.map +1 -0
  50. package/lib/esm/core/style-builder/build-style.d.ts +6 -1
  51. package/lib/esm/core/style-builder/build-style.mjs +258 -58
  52. package/lib/esm/core/style-builder/build-style.mjs.map +1 -1
  53. package/lib/esm/core/style-builder/union-builder.d.ts +21 -1
  54. package/lib/esm/core/style-builder/union-builder.mjs +37 -3
  55. package/lib/esm/core/style-builder/union-builder.mjs.map +1 -1
  56. package/lib/esm/metro/css-imports.d.ts +8 -0
  57. package/lib/esm/metro/css-imports.mjs +79 -0
  58. package/lib/esm/metro/css-imports.mjs.map +1 -0
  59. package/lib/esm/metro/dts.d.ts +2 -4
  60. package/lib/esm/metro/dts.mjs +7 -16
  61. package/lib/esm/metro/dts.mjs.map +1 -1
  62. package/lib/esm/metro/state.d.ts +8 -25
  63. package/lib/esm/metro/state.mjs +39 -85
  64. package/lib/esm/metro/state.mjs.map +1 -1
  65. package/lib/esm/metro/transformer.mjs +194 -35
  66. package/lib/esm/metro/transformer.mjs.map +1 -1
  67. package/lib/esm/metro/with-config.d.ts +11 -26
  68. package/lib/esm/metro/with-config.mjs +2 -2
  69. package/lib/esm/metro/with-config.mjs.map +1 -1
  70. package/lib/esm/metro/wrap-imports.d.ts +26 -0
  71. package/lib/esm/metro/wrap-imports.mjs +250 -0
  72. package/lib/esm/metro/wrap-imports.mjs.map +1 -0
  73. package/lib/esm/runtime/components/rnwind-provider.d.ts +0 -14
  74. package/lib/esm/runtime/components/rnwind-provider.mjs +1 -17
  75. package/lib/esm/runtime/components/rnwind-provider.mjs.map +1 -1
  76. package/lib/esm/runtime/hooks/use-css.d.ts +15 -9
  77. package/lib/esm/runtime/hooks/use-css.mjs +16 -10
  78. package/lib/esm/runtime/hooks/use-css.mjs.map +1 -1
  79. package/lib/esm/runtime/index.d.ts +4 -9
  80. package/lib/esm/runtime/index.mjs +4 -4
  81. package/lib/esm/runtime/index.mjs.map +1 -1
  82. package/lib/esm/runtime/lookup-css.d.ts +7 -0
  83. package/lib/esm/runtime/lookup-css.mjs +10 -1
  84. package/lib/esm/runtime/lookup-css.mjs.map +1 -1
  85. package/lib/esm/runtime/resolve.d.ts +61 -0
  86. package/lib/esm/runtime/resolve.mjs +341 -0
  87. package/lib/esm/runtime/resolve.mjs.map +1 -0
  88. package/lib/esm/runtime/wrap.d.ts +37 -0
  89. package/lib/esm/runtime/wrap.mjs +251 -0
  90. package/lib/esm/runtime/wrap.mjs.map +1 -0
  91. package/lib/esm/testing/index.mjs +84 -53
  92. package/lib/esm/testing/index.mjs.map +1 -1
  93. package/package.json +2 -1
  94. package/src/core/normalize-classname.ts +19 -0
  95. package/src/core/style-builder/build-style.ts +286 -55
  96. package/src/core/style-builder/union-builder.ts +36 -3
  97. package/src/metro/css-imports.ts +75 -0
  98. package/src/metro/dts.ts +7 -19
  99. package/src/metro/state.ts +38 -83
  100. package/src/metro/transformer.ts +190 -34
  101. package/src/metro/with-config.ts +13 -28
  102. package/src/metro/wrap-imports.ts +260 -0
  103. package/src/runtime/components/rnwind-provider.tsx +0 -17
  104. package/src/runtime/hooks/use-css.ts +17 -11
  105. package/src/runtime/index.ts +3 -26
  106. package/src/runtime/lookup-css.ts +10 -0
  107. package/src/runtime/resolve.ts +381 -0
  108. package/src/runtime/wrap.tsx +267 -0
  109. package/src/testing/index.ts +106 -56
  110. package/lib/cjs/core/parser/text-truncate.cjs +0 -78
  111. package/lib/cjs/core/parser/text-truncate.cjs.map +0 -1
  112. package/lib/cjs/metro/transform-ast.cjs +0 -1472
  113. package/lib/cjs/metro/transform-ast.cjs.map +0 -1
  114. package/lib/cjs/metro/transform-ast.d.ts +0 -88
  115. package/lib/cjs/runtime/haptics.cjs +0 -113
  116. package/lib/cjs/runtime/haptics.cjs.map +0 -1
  117. package/lib/cjs/runtime/haptics.d.ts +0 -48
  118. package/lib/cjs/runtime/interactive-box.cjs +0 -35
  119. package/lib/cjs/runtime/interactive-box.cjs.map +0 -1
  120. package/lib/cjs/runtime/interactive-box.d.ts +0 -40
  121. package/lib/esm/core/parser/text-truncate.mjs +0 -75
  122. package/lib/esm/core/parser/text-truncate.mjs.map +0 -1
  123. package/lib/esm/metro/transform-ast.d.ts +0 -88
  124. package/lib/esm/metro/transform-ast.mjs +0 -1451
  125. package/lib/esm/metro/transform-ast.mjs.map +0 -1
  126. package/lib/esm/runtime/haptics.d.ts +0 -48
  127. package/lib/esm/runtime/haptics.mjs +0 -110
  128. package/lib/esm/runtime/haptics.mjs.map +0 -1
  129. package/lib/esm/runtime/interactive-box.d.ts +0 -40
  130. package/lib/esm/runtime/interactive-box.mjs +0 -33
  131. package/lib/esm/runtime/interactive-box.mjs.map +0 -1
  132. package/src/metro/transform-ast.ts +0 -1729
  133. package/src/runtime/haptics.ts +0 -120
  134. package/src/runtime/interactive-box.tsx +0 -57
@@ -5,6 +5,8 @@ var path = require('node:path');
5
5
  var node_crypto = require('node:crypto');
6
6
  var unionBuilder = require('../core/style-builder/union-builder.cjs');
7
7
  var twParser = require('../core/parser/tw-parser.cjs');
8
+ var cssImports = require('./css-imports.cjs');
9
+ var wrapImports = require('./wrap-imports.cjs');
8
10
 
9
11
  /**
10
12
  * Default oxide Scanner globs — walk every JS/TS source under the
@@ -49,21 +51,18 @@ const CSS_ENTRY_ENV = 'RNWIND_CSS_ENTRY_FILE';
49
51
  const CACHE_DIR_ENV = 'RNWIND_CACHE_DIR';
50
52
  /** Env var carrying `watchFolders` from Metro config (NUL-separated). */
51
53
  const WATCH_FOLDERS_ENV = 'RNWIND_WATCH_FOLDERS';
52
- /** Env var carrying extra className prefixes the Metro config supplied. */
53
- const CLASSNAME_PREFIXES_ENV = 'RNWIND_CLASSNAME_PREFIXES';
54
- /** Env var carrying extra import sources whose JSX exports get className→style rewrites. Comma-separated. */
55
- const HOST_SOURCES_ENV = 'RNWIND_HOST_SOURCES';
56
- /** Env var carrying extra JSX tag names (verbatim, may contain `.`) treated as hosts. Comma-separated. */
57
- const HOST_COMPONENTS_ENV = 'RNWIND_HOST_COMPONENTS';
54
+ /** Env var carrying extra modules whose component exports get `wrap()`-ed. Comma-separated. */
55
+ const WRAP_MODULES_ENV = 'RNWIND_WRAP_MODULES';
58
56
  /** Memoised library fingerprint — read once per worker process. */
59
57
  let libraryFingerprint;
60
58
  /** Live state shared across one Metro transform worker. */
61
59
  let cached = null;
62
60
  /**
63
- * Cheap content-hash readout. SHA-256 prefix of the CSS bytes plus the
64
- * file's mtime nanoseconds (so identical content with different mtime
65
- * atomic rewrites still picks up the change). Returns `'missing'`
66
- * when the file can't be read so the cache key is still deterministic.
61
+ * Cheap content-hash readout. SHA-256 prefix of the FULLY-RESOLVED theme
62
+ * CSS — `@import`s flattened so an edit to a theme file the entry only
63
+ * re-exports (`@import "@acme/ui/theme.css"`) still rotates the hash and
64
+ * invalidates Metro's cache. Returns `'missing'` when the entry can't be
65
+ * read so the cache key stays deterministic.
67
66
  * @param cssPath Absolute CSS path.
68
67
  * @returns 16-char hex content hash.
69
68
  */
@@ -71,9 +70,7 @@ function readThemeHashFor(cssPath) {
71
70
  if (!node_fs.existsSync(cssPath))
72
71
  return 'missing';
73
72
  try {
74
- const bytes = node_fs.readFileSync(cssPath);
75
- const mtime = node_fs.statSync(cssPath).mtimeMs.toString();
76
- return node_crypto.createHash('sha256').update(bytes).update(mtime).digest('hex').slice(0, 16);
73
+ return node_crypto.createHash('sha256').update(cssImports.resolveThemeCss(cssPath)).digest('hex').slice(0, 16);
77
74
  }
78
75
  catch {
79
76
  return 'missing';
@@ -85,10 +82,10 @@ function readThemeHashFor(cssPath) {
85
82
  * dev OR npm install of a new version) the file bytes change, the
86
83
  * fingerprint rotates, and Metro's transform cache invalidates.
87
84
  *
88
- * Includes the JSX rewriter (`transform-ast`) alongside the parser /
89
- * style-builder so a change to the transformer — e.g. renaming the
90
- * injected context hook — invalidates every stale per-file cache entry
91
- * on the next dev run. Without this, a user upgrading rnwind in-place
85
+ * Includes the import-rewriter (`wrap-imports`) and runtime resolver
86
+ * alongside the parser / style-builder so a change to the transformer —
87
+ * e.g. renaming the injected wrap helper — invalidates every stale
88
+ * per-file cache entry on the next dev run. Without this, a user upgrading rnwind in-place
92
89
  * would keep loading the old transformed bytes; React-refresh would
93
90
  * then preserve fiber state across the version bump and the rendered
94
91
  * hook list could shift, surfacing as "change in the order of Hooks"
@@ -105,14 +102,14 @@ function getLibraryFingerprint() {
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, 'transform-ast.mjs'),
109
- path.resolve(here, 'transform-ast.cjs'),
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', 'transform-ast.ts'),
112
+ path.resolve(here, '..', '..', 'src', 'metro', 'wrap-imports.ts'),
116
113
  path.resolve(here, '..', '..', 'src', 'metro', 'transformer.ts'),
117
114
  ];
118
115
  const hash = node_crypto.createHash('sha256');
@@ -137,12 +134,10 @@ function getLibraryFingerprint() {
137
134
  * can rebuild the same state without re-reading the Metro config.
138
135
  * @param cssEntryFile Absolute path to the user's theme CSS.
139
136
  * @param cacheDir Absolute path to the cache dir (`.rnwind`).
140
- * @param watchFolders
141
- * @param classNamePrefixes Extra JSX prop-name prefixes to rewrite.
142
- * @param hostSources
143
- * @param hostComponents
137
+ * @param watchFolders Monorepo watch folders to scan for atoms.
138
+ * @param wrapModules Extra modules whose component exports get `wrap()`-ed.
144
139
  */
145
- function configureRnwindState(cssEntryFile, cacheDir, watchFolders = [], classNamePrefixes, hostSources, hostComponents) {
140
+ function configureRnwindState(cssEntryFile, cacheDir, watchFolders = [], wrapModules) {
146
141
  process.env[CSS_ENTRY_ENV] = cssEntryFile;
147
142
  process.env[CACHE_DIR_ENV] = cacheDir;
148
143
  if (watchFolders.length === 0) {
@@ -151,62 +146,23 @@ function configureRnwindState(cssEntryFile, cacheDir, watchFolders = [], classNa
151
146
  else {
152
147
  process.env[WATCH_FOLDERS_ENV] = watchFolders.join('\0');
153
148
  }
154
- if (!classNamePrefixes || classNamePrefixes.length === 0) {
155
- delete process.env[CLASSNAME_PREFIXES_ENV];
149
+ if (!wrapModules || wrapModules.length === 0) {
150
+ delete process.env[WRAP_MODULES_ENV];
156
151
  }
157
152
  else {
158
- process.env[CLASSNAME_PREFIXES_ENV] = classNamePrefixes.join(',');
159
- }
160
- if (!hostSources || hostSources.length === 0) {
161
- delete process.env[HOST_SOURCES_ENV];
162
- }
163
- else {
164
- process.env[HOST_SOURCES_ENV] = hostSources.join(',');
165
- }
166
- if (!hostComponents || hostComponents.length === 0) {
167
- delete process.env[HOST_COMPONENTS_ENV];
168
- }
169
- else {
170
- process.env[HOST_COMPONENTS_ENV] = hostComponents.join(',');
153
+ process.env[WRAP_MODULES_ENV] = wrapModules.join(',');
171
154
  }
172
155
  cached = null;
173
156
  }
174
157
  /**
175
- * Read the caller-configured extra className prefixes out of the
176
- * worker environment. Returns an empty array when unset — the
177
- * transformer applies the built-in `contentContainer` default on top
178
- * either way.
179
- * @returns User-supplied extra prefixes.
180
- */
181
- function getClassNamePrefixes() {
182
- const raw = process.env[CLASSNAME_PREFIXES_ENV];
183
- if (!raw || raw.length === 0)
184
- return [];
185
- return raw.split(',').filter((entry) => entry.length > 0);
186
- }
187
- /**
188
- * Read the caller-configured extra host module sources out of the
189
- * worker environment. Empty array when unset — the transformer applies
190
- * its built-in default list on top either way.
191
- * @returns User-supplied extra host sources.
158
+ * Effective module → wrap-policy map: the built-in defaults merged with
159
+ * any extra modules the Metro config supplied.
160
+ * @returns Module policy map the import-rewrite consults.
192
161
  */
193
- function getHostSources() {
194
- const raw = process.env[HOST_SOURCES_ENV];
195
- if (!raw || raw.length === 0)
196
- return [];
197
- return raw.split(',').filter((entry) => entry.length > 0);
198
- }
199
- /**
200
- * Read the caller-configured extra host JSX tag names out of the worker
201
- * environment. Verbatim names — may include `.` for member expressions
202
- * like `'Animated.View'`.
203
- * @returns User-supplied extra host component names.
204
- */
205
- function getHostComponents() {
206
- const raw = process.env[HOST_COMPONENTS_ENV];
207
- if (!raw || raw.length === 0)
208
- return [];
209
- return raw.split(',').filter((entry) => entry.length > 0);
162
+ function getWrapModules() {
163
+ const raw = process.env[WRAP_MODULES_ENV];
164
+ const extra = raw && raw.length > 0 ? raw.split(',').filter((entry) => entry.length > 0) : undefined;
165
+ return wrapImports.buildWrapModules(extra);
210
166
  }
211
167
  /**
212
168
  * Fetch (or build) the worker-local rnwind state. Re-reads the theme
@@ -228,7 +184,7 @@ function getRnwindState(projectRoot) {
228
184
  const currentHash = readThemeHashFor(cssEntry);
229
185
  if (cached?.themeHash === currentHash && cached.projectRoot === projectRoot)
230
186
  return cached;
231
- const themeCss = node_fs.readFileSync(cssEntry, 'utf8');
187
+ const themeCss = cssImports.resolveThemeCss(cssEntry);
232
188
  const parser = new twParser.TailwindParser({
233
189
  themeCss,
234
190
  sources: defaultSources(projectRoot, cacheDir, readWatchFolders()),
@@ -248,14 +204,12 @@ function getRnwindState(projectRoot) {
248
204
  */
249
205
  function getRnwindCacheKey() {
250
206
  const cssEntry = process.env[CSS_ENTRY_ENV] ?? '';
251
- const prefixes = process.env[CLASSNAME_PREFIXES_ENV] ?? '';
252
- // Host source / component config changes which JSX tags get rewritten,
253
- // so it MUST flip the cache key otherwise Metro replays stale
254
- // transforms (a newly-opted-in host keeps its raw className, a removed
255
- // one keeps the rewrite).
256
- const hostSources = process.env[HOST_SOURCES_ENV] ?? '';
257
- const hostComponents = process.env[HOST_COMPONENTS_ENV] ?? '';
258
- return `rnwind:${cssEntry}:${readThemeHashFor(cssEntry)}|lib:${getLibraryFingerprint()}|pfx:${prefixes}|hs:${hostSources}|hc:${hostComponents}`;
207
+ // Wrap-module config changes which import sites get `wrap()`-ed, so it
208
+ // MUST flip the cache key otherwise Metro replays stale transforms
209
+ // (a newly-opted-in module keeps its raw import, a removed one keeps
210
+ // the wrap).
211
+ const wrapModules = process.env[WRAP_MODULES_ENV] ?? '';
212
+ return `rnwind:${cssEntry}:${readThemeHashFor(cssEntry)}|lib:${getLibraryFingerprint()}|wm:${wrapModules}`;
259
213
  }
260
214
  /** Drop the cached state — call after editing the theme CSS. */
261
215
  function resetRnwindState() {
@@ -290,11 +244,9 @@ function manifestPathFor() {
290
244
  }
291
245
 
292
246
  exports.configureRnwindState = configureRnwindState;
293
- exports.getClassNamePrefixes = getClassNamePrefixes;
294
- exports.getHostComponents = getHostComponents;
295
- exports.getHostSources = getHostSources;
296
247
  exports.getRnwindCacheKey = getRnwindCacheKey;
297
248
  exports.getRnwindState = getRnwindState;
249
+ exports.getWrapModules = getWrapModules;
298
250
  exports.manifestPathFor = manifestPathFor;
299
251
  exports.onThemeChange = onThemeChange;
300
252
  exports.resetRnwindState = resetRnwindState;
@@ -1 +1 @@
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'\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 className prefixes the Metro config supplied. */\nconst CLASSNAME_PREFIXES_ENV = 'RNWIND_CLASSNAME_PREFIXES'\n/** Env var carrying extra import sources whose JSX exports get className→style rewrites. Comma-separated. */\nconst HOST_SOURCES_ENV = 'RNWIND_HOST_SOURCES'\n/** Env var carrying extra JSX tag names (verbatim, may contain `.`) treated as hosts. Comma-separated. */\nconst HOST_COMPONENTS_ENV = 'RNWIND_HOST_COMPONENTS'\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 CSS bytes plus the\n * file's mtime nanoseconds (so identical content with different mtime\n * — atomic rewrites — still picks up the change). Returns `'missing'`\n * when the file can't be read so the cache key is still 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 const bytes = readFileSync(cssPath)\n const mtime = statSync(cssPath).mtimeMs.toString()\n return createHash('sha256').update(bytes).update(mtime).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 JSX rewriter (`transform-ast`) alongside the parser /\n * style-builder so a change to the transformer — e.g. renaming the\n * injected context hook — invalidates every stale per-file cache entry\n * 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, 'transform-ast.mjs'),\n path.resolve(here, 'transform-ast.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', 'transform-ast.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\n * @param classNamePrefixes Extra JSX prop-name prefixes to rewrite.\n * @param hostSources\n * @param hostComponents\n */\nexport function configureRnwindState(\n cssEntryFile: string,\n cacheDir: string,\n watchFolders: readonly string[] = [],\n classNamePrefixes?: readonly string[],\n hostSources?: readonly string[],\n hostComponents?: 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 (!classNamePrefixes || classNamePrefixes.length === 0) {\n delete process.env[CLASSNAME_PREFIXES_ENV]\n } else {\n process.env[CLASSNAME_PREFIXES_ENV] = classNamePrefixes.join(',')\n }\n if (!hostSources || hostSources.length === 0) {\n delete process.env[HOST_SOURCES_ENV]\n } else {\n process.env[HOST_SOURCES_ENV] = hostSources.join(',')\n }\n if (!hostComponents || hostComponents.length === 0) {\n delete process.env[HOST_COMPONENTS_ENV]\n } else {\n process.env[HOST_COMPONENTS_ENV] = hostComponents.join(',')\n }\n cached = null\n}\n\n/**\n * Read the caller-configured extra className prefixes out of the\n * worker environment. Returns an empty array when unset — the\n * transformer applies the built-in `contentContainer` default on top\n * either way.\n * @returns User-supplied extra prefixes.\n */\nexport function getClassNamePrefixes(): readonly string[] {\n const raw = process.env[CLASSNAME_PREFIXES_ENV]\n if (!raw || raw.length === 0) return []\n return raw.split(',').filter((entry) => entry.length > 0)\n}\n\n/**\n * Read the caller-configured extra host module sources out of the\n * worker environment. Empty array when unset — the transformer applies\n * its built-in default list on top either way.\n * @returns User-supplied extra host sources.\n */\nexport function getHostSources(): readonly string[] {\n const raw = process.env[HOST_SOURCES_ENV]\n if (!raw || raw.length === 0) return []\n return raw.split(',').filter((entry) => entry.length > 0)\n}\n\n/**\n * Read the caller-configured extra host JSX tag names out of the worker\n * environment. Verbatim names — may include `.` for member expressions\n * like `'Animated.View'`.\n * @returns User-supplied extra host component names.\n */\nexport function getHostComponents(): readonly string[] {\n const raw = process.env[HOST_COMPONENTS_ENV]\n if (!raw || raw.length === 0) return []\n return raw.split(',').filter((entry) => entry.length > 0)\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 = readFileSync(cssEntry, 'utf8')\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 const prefixes = process.env[CLASSNAME_PREFIXES_ENV] ?? ''\n // Host source / component config changes which JSX tags get rewritten,\n // so it MUST flip the cache key — otherwise Metro replays stale\n // transforms (a newly-opted-in host keeps its raw className, a removed\n // one keeps the rewrite).\n const hostSources = process.env[HOST_SOURCES_ENV] ?? ''\n const hostComponents = process.env[HOST_COMPONENTS_ENV] ?? ''\n return `rnwind:${cssEntry}:${readThemeHashFor(cssEntry)}|lib:${getLibraryFingerprint()}|pfx:${prefixes}|hs:${hostSources}|hc:${hostComponents}`\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","readFileSync","statSync","createHash","TailwindParser","UnionBuilder"],"mappings":"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"}
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,5 +1,6 @@
1
1
  import { UnionBuilder } from '../core/style-builder';
2
2
  import { TailwindParser } from '../core/parser';
3
+ import { buildWrapModules } from './wrap-imports';
3
4
  /**
4
5
  * Worker-local state. Lazy-initialised on first access so files that
5
6
  * bypass the transform don't pay for construction.
@@ -17,34 +18,16 @@ export interface RnwindState {
17
18
  * can rebuild the same state without re-reading the Metro config.
18
19
  * @param cssEntryFile Absolute path to the user's theme CSS.
19
20
  * @param cacheDir Absolute path to the cache dir (`.rnwind`).
20
- * @param watchFolders
21
- * @param classNamePrefixes Extra JSX prop-name prefixes to rewrite.
22
- * @param hostSources
23
- * @param hostComponents
21
+ * @param watchFolders Monorepo watch folders to scan for atoms.
22
+ * @param wrapModules Extra modules whose component exports get `wrap()`-ed.
24
23
  */
25
- export declare function configureRnwindState(cssEntryFile: string, cacheDir: string, watchFolders?: readonly string[], classNamePrefixes?: readonly string[], hostSources?: readonly string[], hostComponents?: readonly string[]): void;
24
+ export declare function configureRnwindState(cssEntryFile: string, cacheDir: string, watchFolders?: readonly string[], wrapModules?: readonly string[]): void;
26
25
  /**
27
- * Read the caller-configured extra className prefixes out of the
28
- * worker environment. Returns an empty array when unset — the
29
- * transformer applies the built-in `contentContainer` default on top
30
- * either way.
31
- * @returns User-supplied extra prefixes.
26
+ * Effective module → wrap-policy map: the built-in defaults merged with
27
+ * any extra modules the Metro config supplied.
28
+ * @returns Module policy map the import-rewrite consults.
32
29
  */
33
- export declare function getClassNamePrefixes(): readonly string[];
34
- /**
35
- * Read the caller-configured extra host module sources out of the
36
- * worker environment. Empty array when unset — the transformer applies
37
- * its built-in default list on top either way.
38
- * @returns User-supplied extra host sources.
39
- */
40
- export declare function getHostSources(): readonly string[];
41
- /**
42
- * Read the caller-configured extra host JSX tag names out of the worker
43
- * environment. Verbatim names — may include `.` for member expressions
44
- * like `'Animated.View'`.
45
- * @returns User-supplied extra host component names.
46
- */
47
- export declare function getHostComponents(): readonly string[];
30
+ export declare function getWrapModules(): ReturnType<typeof buildWrapModules>;
48
31
  /**
49
32
  * Fetch (or build) the worker-local rnwind state. Re-reads the theme
50
33
  * CSS hash on every call: if the user edited `global.css` while Metro
@@ -5,8 +5,8 @@ var parser = require('@babel/parser');
5
5
  var generate = require('@babel/generator');
6
6
  var node_crypto = require('node:crypto');
7
7
  var node_fs = require('node:fs');
8
- var transformAst = require('./transform-ast.cjs');
9
8
  var state = require('./state.cjs');
9
+ var wrapImports = require('./wrap-imports.cjs');
10
10
  var resolver = require('./resolver.cjs');
11
11
  var warnUnknownClasses$1 = require('./warn-unknown-classes.cjs');
12
12
 
@@ -75,10 +75,19 @@ function parseUserSource(source) {
75
75
  * @param candidates Every candidate oxide surfaced from the source.
76
76
  * @param atoms Successfully resolved atoms (keys are class names).
77
77
  * @param filename Source path, prefixed onto the warning.
78
+ * @param features Feature-atom maps (gradient / haptic) — their names are
79
+ * known classes even though they carry no RN style, so they're excluded
80
+ * from the unknown-class warning.
78
81
  */
79
- function warnUnknownClasses(source, candidates, atoms, filename) {
80
- const atomNames = new Set(atoms.keys());
81
- const unknown = warnUnknownClasses$1.filterUnknownClassCandidates(source, candidates, atomNames);
82
+ function warnUnknownClasses(source, candidates, atoms, filename, features = []) {
83
+ // Feature atoms (gradient / haptic) resolve to no RN style, so they're
84
+ // absent from `atoms` — but they're NOT unknown. Fold their names into
85
+ // the known set so `active:haptic-rigid` etc. don't warn at build time.
86
+ const known = new Set(atoms.keys());
87
+ for (const map of features)
88
+ for (const name of map.keys())
89
+ known.add(name);
90
+ const unknown = warnUnknownClasses$1.filterUnknownClassCandidates(source, candidates, known);
82
91
  if (unknown.length === 0)
83
92
  return;
84
93
  // eslint-disable-next-line no-console
@@ -121,52 +130,181 @@ function isThemeCssEntry(filename) {
121
130
  return typeof cssEntry === 'string' && cssEntry.length > 0 && cssEntry === filename;
122
131
  }
123
132
  /**
124
- * Parse + run rnwind's JSX rewrite + regenerate source code. When
125
- * parsing or transformation fails, fall back to the original source —
126
- * we don't want a transient parse error to crash Metro for a file the
127
- * upstream might handle fine.
133
+ * Wrap host imports + compile any className literals, then regenerate
134
+ * source. Two paths:
135
+ * - **className present**: oxide-scan the file, record its atoms into
136
+ * the union, and inject the generated-style + theme-signature
137
+ * side-effect imports so the runtime registries populate.
138
+ * - **import-only** (a `{...rest}` forwarder or a leaf with no literal
139
+ * `className=`): just wrap the host imports so a forwarded className
140
+ * still resolves at render — no oxide scan, no injected imports.
141
+ *
142
+ * On parse failure, fall back to the original source — a transient parse
143
+ * error shouldn't crash Metro for a file the upstream might handle fine.
128
144
  * @param args Metro args; `src` is the original source text.
129
- * @returns Rewritten source text (with `className=` rewrites applied).
145
+ * @returns Rewritten source text.
130
146
  */
131
147
  async function rewriteSource(args) {
132
148
  const ast = parseUserSource(args.src);
133
149
  if (!ast)
134
150
  return args.src;
151
+ // Wrap host component imports so `<View className=…>` resolves at render
152
+ // through the runtime `wrap` (works for literal, spread, and forwarded
153
+ // classNames alike). No JSX is rewritten here.
154
+ const wrapped = wrapImports.rewriteWrapImports(ast, state.getWrapModules());
155
+ if (!/classname=/i.test(args.src)) {
156
+ // Import-only file: nothing to compile. Drop any stale atom
157
+ // contribution (className may have just been removed) and emit the
158
+ // wrapped imports — or the untouched source when nothing wrapped.
159
+ dropFileSafely(args.filename, projectRootOf(args));
160
+ return wrapped ? generateModule(ast).code : args.src;
161
+ }
135
162
  const state$1 = state.getRnwindState(projectRootOf(args));
136
163
  const extension = extensionOf(args.filename);
137
164
  const parsed = await state$1.parser.parseAtoms({ content: args.src, extension });
138
- warnUnknownClasses(args.src, parsed.candidates, parsed.atoms, args.filename);
139
- const classNamePrefixes = state.getClassNamePrefixes();
140
- const hostSources = state.getHostSources();
141
- const hostComponents = state.getHostComponents();
165
+ warnUnknownClasses(args.src, parsed.candidates, parsed.atoms, args.filename, [parsed.gradientAtoms, parsed.hapticAtoms]);
142
166
  if (parsed.atoms.size === 0) {
143
167
  state$1.builder.dropFile(args.filename);
144
168
  await state$1.builder.writeSchemes();
145
- transformAst.transformAst(ast, {
146
- styleSpecifiers: [],
147
- gradientAtoms: parsed.gradientAtoms,
148
- hapticAtoms: parsed.hapticAtoms,
149
- classNamePrefixes,
150
- hostSources,
151
- hostComponents,
152
- });
153
169
  injectThemeSignatureImport(ast);
154
170
  return generateModule(ast).code;
155
171
  }
156
- const { changed } = await state$1.builder.recordFile(args.filename, parsed.atoms, parsed.keyframes);
172
+ const literals = collectClassNameLiterals(ast);
173
+ const { changed } = await state$1.builder.recordFile(args.filename, parsed.atoms, parsed.keyframes, literals);
157
174
  if (changed)
158
175
  await state$1.builder.writeSchemes();
159
- transformAst.transformAst(ast, {
160
- styleSpecifiers: resolver.STYLE_SPECIFIERS,
161
- gradientAtoms: parsed.gradientAtoms,
162
- hapticAtoms: parsed.hapticAtoms,
163
- classNamePrefixes,
164
- hostSources,
165
- hostComponents,
166
- });
176
+ injectSideEffectImports(ast, resolver.STYLE_SPECIFIERS);
167
177
  injectThemeSignatureImport(ast);
168
178
  return generateModule(ast).code;
169
179
  }
180
+ /**
181
+ * Drop a file's union contribution, swallowing the "state not configured"
182
+ * error unit tests hit when they call the transformer without
183
+ * `configureRnwindState`.
184
+ * @param filename Absolute source path.
185
+ * @param projectRoot Project root for state lookup.
186
+ */
187
+ function dropFileSafely(filename, projectRoot) {
188
+ try {
189
+ state.getRnwindState(projectRoot).builder.dropFile(filename);
190
+ }
191
+ catch {
192
+ // State not configured (standalone/unit test). Nothing to drop.
193
+ }
194
+ }
195
+ /**
196
+ * Whether a JSX attribute names a className-style prop (`className` or
197
+ * any `<prefix>ClassName`).
198
+ * @param node JSX attribute node.
199
+ * @returns True when the attribute is a className prop.
200
+ */
201
+ function isClassNameAttribute(node) {
202
+ if (!t__namespace.isJSXIdentifier(node.name))
203
+ return false;
204
+ const { name } = node.name;
205
+ return name === 'className' || name.endsWith('ClassName');
206
+ }
207
+ /**
208
+ * Pull static string literals out of a className expression. Handles a
209
+ * bare string, a no-substitution template, and the branches of a
210
+ * ternary / `&&` (so `cond ? 'a' : 'b'` and `flag && 'x'` both register
211
+ * their literals). Dynamic interpolations are skipped — they resolve via
212
+ * the runtime atom path.
213
+ * @param expr Expression inside a `className={...}` container.
214
+ * @param out Accumulator for discovered literals.
215
+ */
216
+ function collectLiteralsFromExpression(expr, out) {
217
+ if (!expr)
218
+ return;
219
+ if (t__namespace.isStringLiteral(expr)) {
220
+ out.push(expr.value);
221
+ return;
222
+ }
223
+ if (t__namespace.isTemplateLiteral(expr) && expr.expressions.length === 0 && expr.quasis.length === 1) {
224
+ const cooked = expr.quasis[0]?.value.cooked;
225
+ if (typeof cooked === 'string')
226
+ out.push(cooked);
227
+ return;
228
+ }
229
+ if (t__namespace.isConditionalExpression(expr)) {
230
+ collectLiteralsFromExpression(expr.consequent, out);
231
+ collectLiteralsFromExpression(expr.alternate, out);
232
+ return;
233
+ }
234
+ if (t__namespace.isLogicalExpression(expr)) {
235
+ collectLiteralsFromExpression(expr.right, out);
236
+ }
237
+ }
238
+ /** AST node keys the literal walk skips — position / comment metadata. */
239
+ const SKIP_WALK_KEYS = new Set(['type', 'loc', 'start', 'end', 'range', 'leadingComments', 'trailingComments', 'innerComments']);
240
+ /**
241
+ * Collect the static literals from one className JSX attribute into the
242
+ * dedup accumulator.
243
+ * @param attribute The (already className-matched) JSX attribute.
244
+ * @param seen Dedup set of literals already collected.
245
+ * @param out Ordered accumulator.
246
+ */
247
+ function collectAttributeLiterals(attribute, seen, out) {
248
+ const { value } = attribute;
249
+ const found = [];
250
+ if (t__namespace.isStringLiteral(value))
251
+ found.push(value.value);
252
+ else if (t__namespace.isJSXExpressionContainer(value))
253
+ collectLiteralsFromExpression(value.expression, found);
254
+ for (const literal of found) {
255
+ if (seen.has(literal))
256
+ continue;
257
+ seen.add(literal);
258
+ out.push(literal);
259
+ }
260
+ }
261
+ /**
262
+ * Walk the AST for every `className=` / `<prefix>ClassName=` literal so
263
+ * the builder can pre-merge each into a per-scheme molecule. A generic
264
+ * node walk (no scope build) keeps it cheap; only JSX attribute nodes do
265
+ * any work.
266
+ * @param ast Parsed Babel file.
267
+ * @returns Distinct literal className strings, in first-seen order.
268
+ */
269
+ function collectClassNameLiterals(ast) {
270
+ const out = [];
271
+ const seen = new Set();
272
+ const visit = (node) => {
273
+ if (!node || typeof node !== 'object')
274
+ return;
275
+ if (Array.isArray(node)) {
276
+ for (const child of node)
277
+ visit(child);
278
+ return;
279
+ }
280
+ const typed = node;
281
+ if (typeof typed.type !== 'string')
282
+ return;
283
+ if (typed.type === 'JSXAttribute' && isClassNameAttribute(node)) {
284
+ collectAttributeLiterals(node, seen, out);
285
+ }
286
+ for (const key in typed) {
287
+ if (SKIP_WALK_KEYS.has(key))
288
+ continue;
289
+ visit(typed[key]);
290
+ }
291
+ };
292
+ visit(ast.program);
293
+ return out;
294
+ }
295
+ /**
296
+ * Prepend side-effect imports (`import '<spec>'`) so the generated
297
+ * per-scheme style + manifest modules load — registering this file's
298
+ * atoms / molecules / features into the runtime registries the wrapper's
299
+ * `resolve` reads.
300
+ * @param ast Babel File AST to mutate in place.
301
+ * @param specifiers Module specifiers to side-effect-import.
302
+ */
303
+ function injectSideEffectImports(ast, specifiers) {
304
+ for (const specifier of specifiers) {
305
+ ast.program.body.unshift(t__namespace.importDeclaration([], t__namespace.stringLiteral(specifier)));
306
+ }
307
+ }
170
308
  /**
171
309
  * Prepend `import 'rnwind/__generated/theme-signature'` to every
172
310
  * rnwind-transformed file. The resolver maps that specifier to the
@@ -262,10 +400,16 @@ function loadUpstream() {
262
400
  function isRewriteCandidate(args) {
263
401
  if (!/\.(?:tsx|ts|jsx|js)$/i.test(args.filename))
264
402
  return false;
265
- // Case-insensitive so `<prefix>ClassName=` (e.g. `contentContainerClassName=`)
266
- // which has a capital `C` and so doesn't contain the lowercase
267
- // `className=` still routes the file through the rewrite pass.
268
- if (!/classname=/i.test(args.src))
403
+ // Process the file when it either:
404
+ // - carries a `className=` / `<prefix>ClassName=` literal (case-
405
+ // insensitive `contentContainerClassName=` has a capital C), or
406
+ // - spreads props (`{...rest}`) onto a host from a wrap-module, where a
407
+ // forwarded className must still get its import wrapped (no literal
408
+ // appears in this file). A style-less `<View/>` with neither is left
409
+ // alone so it never pays for an unused wrapper.
410
+ const hasClassName = /classname=/i.test(args.src);
411
+ const isForwarder = /\{\s*\.\.\./.test(args.src) && mentionsWrapModule(args.src);
412
+ if (!hasClassName && !isForwarder)
269
413
  return false;
270
414
  if (!args.filename.includes('/node_modules/'))
271
415
  return true;
@@ -278,6 +422,21 @@ function isRewriteCandidate(args) {
278
422
  return false;
279
423
  }
280
424
  }
425
+ /**
426
+ * Cheap pre-parse check: does the source import from any configured
427
+ * wrap-module? A quoted specifier match is enough — `rewriteWrapImports`
428
+ * re-verifies precisely on the AST, so a false positive only costs a
429
+ * no-op parse.
430
+ * @param source Source text.
431
+ * @returns True when a wrap-module specifier appears in the source.
432
+ */
433
+ function mentionsWrapModule(source) {
434
+ for (const moduleName of state.getWrapModules().keys()) {
435
+ if (source.includes(`'${moduleName}'`) || source.includes(`"${moduleName}"`))
436
+ return true;
437
+ }
438
+ return false;
439
+ }
281
440
  /**
282
441
  * Fallback parse when no upstream is configured AND Metro didn't hand
283
442
  * us an AST. Used by unit tests and standalone setups.