@wix/zero-config-implementation 1.5.0

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 (78) hide show
  1. package/README.md +72 -0
  2. package/dist/component-loader.d.ts +42 -0
  3. package/dist/component-renderer.d.ts +31 -0
  4. package/dist/converters/data-item-builder.d.ts +15 -0
  5. package/dist/converters/index.d.ts +1 -0
  6. package/dist/converters/to-editor-component.d.ts +3 -0
  7. package/dist/converters/utils.d.ts +16 -0
  8. package/dist/errors.d.ts +230 -0
  9. package/dist/index.d.ts +42 -0
  10. package/dist/index.js +51978 -0
  11. package/dist/information-extractors/css/index.d.ts +3 -0
  12. package/dist/information-extractors/css/parse.d.ts +7 -0
  13. package/dist/information-extractors/css/selector-matcher.d.ts +3 -0
  14. package/dist/information-extractors/css/types.d.ts +49 -0
  15. package/dist/information-extractors/react/extractors/core/index.d.ts +6 -0
  16. package/dist/information-extractors/react/extractors/core/runner.d.ts +19 -0
  17. package/dist/information-extractors/react/extractors/core/store.d.ts +17 -0
  18. package/dist/information-extractors/react/extractors/core/tree-builder.d.ts +15 -0
  19. package/dist/information-extractors/react/extractors/core/types.d.ts +40 -0
  20. package/dist/information-extractors/react/extractors/css-properties.d.ts +20 -0
  21. package/dist/information-extractors/react/extractors/index.d.ts +11 -0
  22. package/dist/information-extractors/react/extractors/prop-tracker.d.ts +24 -0
  23. package/dist/information-extractors/react/index.d.ts +9 -0
  24. package/dist/information-extractors/react/types.d.ts +51 -0
  25. package/dist/information-extractors/react/utils/mock-generator.d.ts +9 -0
  26. package/dist/information-extractors/react/utils/prop-spy.d.ts +10 -0
  27. package/dist/information-extractors/ts/components.d.ts +9 -0
  28. package/dist/information-extractors/ts/css-imports.d.ts +2 -0
  29. package/dist/information-extractors/ts/index.d.ts +3 -0
  30. package/dist/information-extractors/ts/types.d.ts +47 -0
  31. package/dist/information-extractors/ts/utils/semantic-type-resolver.d.ts +3 -0
  32. package/dist/jsx-runtime-interceptor.d.ts +42 -0
  33. package/dist/jsx-runtime-interceptor.js +63 -0
  34. package/dist/jsx-runtime-loader.d.ts +23 -0
  35. package/dist/jsx-runtime-loader.js +7 -0
  36. package/dist/manifest-pipeline.d.ts +33 -0
  37. package/dist/schema.d.ts +167 -0
  38. package/dist/ts-compiler.d.ts +13 -0
  39. package/package.json +81 -0
  40. package/src/component-loader.test.ts +277 -0
  41. package/src/component-loader.ts +256 -0
  42. package/src/component-renderer.ts +192 -0
  43. package/src/converters/data-item-builder.ts +354 -0
  44. package/src/converters/index.ts +1 -0
  45. package/src/converters/to-editor-component.ts +167 -0
  46. package/src/converters/utils.ts +21 -0
  47. package/src/errors.ts +103 -0
  48. package/src/index.ts +223 -0
  49. package/src/information-extractors/css/README.md +3 -0
  50. package/src/information-extractors/css/index.ts +3 -0
  51. package/src/information-extractors/css/parse.ts +450 -0
  52. package/src/information-extractors/css/selector-matcher.ts +88 -0
  53. package/src/information-extractors/css/types.ts +56 -0
  54. package/src/information-extractors/react/extractors/core/index.ts +6 -0
  55. package/src/information-extractors/react/extractors/core/runner.ts +89 -0
  56. package/src/information-extractors/react/extractors/core/store.ts +36 -0
  57. package/src/information-extractors/react/extractors/core/tree-builder.ts +273 -0
  58. package/src/information-extractors/react/extractors/core/types.ts +48 -0
  59. package/src/information-extractors/react/extractors/css-properties.ts +214 -0
  60. package/src/information-extractors/react/extractors/index.ts +27 -0
  61. package/src/information-extractors/react/extractors/prop-tracker.ts +132 -0
  62. package/src/information-extractors/react/index.ts +53 -0
  63. package/src/information-extractors/react/types.ts +70 -0
  64. package/src/information-extractors/react/utils/mock-generator.ts +331 -0
  65. package/src/information-extractors/react/utils/prop-spy.ts +168 -0
  66. package/src/information-extractors/ts/components.ts +300 -0
  67. package/src/information-extractors/ts/css-imports.ts +26 -0
  68. package/src/information-extractors/ts/index.ts +3 -0
  69. package/src/information-extractors/ts/types.ts +56 -0
  70. package/src/information-extractors/ts/utils/semantic-type-resolver.ts +377 -0
  71. package/src/jsx-runtime-interceptor.ts +146 -0
  72. package/src/jsx-runtime-loader.ts +38 -0
  73. package/src/manifest-pipeline.ts +362 -0
  74. package/src/schema.ts +174 -0
  75. package/src/ts-compiler.ts +41 -0
  76. package/tsconfig.json +17 -0
  77. package/typedoc.json +18 -0
  78. package/vite.config.ts +45 -0
@@ -0,0 +1,377 @@
1
+ import * as fs from 'node:fs'
2
+ import * as path from 'node:path'
3
+ import { camelCase } from 'case-anything'
4
+ import ts from 'typescript'
5
+ import { DATA } from '../../../schema'
6
+ import type { PropInfo, ResolvedType } from '../types'
7
+
8
+ const MAX_RESOLVE_DEPTH = 30
9
+
10
+ const { DATA_TYPE } = DATA
11
+
12
+ /**
13
+ * Cache for package name lookups
14
+ */
15
+ const packageNameCache = new Map<string, string | undefined>()
16
+
17
+ /**
18
+ * Gets the package name that a symbol originates from
19
+ */
20
+ function getSymbolPackage(symbol: ts.Symbol, checker: ts.TypeChecker): string | undefined {
21
+ // If the symbol is an alias (e.g., from an import), resolve it first
22
+ let resolvedSymbol = symbol
23
+ if ((symbol.flags & ts.SymbolFlags.Alias) !== 0) {
24
+ resolvedSymbol = checker.getAliasedSymbol(symbol)
25
+ }
26
+
27
+ const decl = resolvedSymbol.getDeclarations()?.[0]
28
+ if (!decl) return undefined
29
+
30
+ // Get the file path containing the declaration
31
+ const filePath = decl.getSourceFile().fileName
32
+
33
+ // Check cache first
34
+ if (packageNameCache.has(filePath)) {
35
+ return packageNameCache.get(filePath)
36
+ }
37
+
38
+ // Check for known package patterns in the file path
39
+ // This handles both node_modules paths and type augmentation files
40
+ const packageName = findPackageName(filePath)
41
+
42
+ packageNameCache.set(filePath, packageName)
43
+ return packageName
44
+ }
45
+
46
+ // Valid React semantic types (maps to container)
47
+ const VALID_REACT_SEMANTIC_TYPES = new Set(['ReactNode', 'ReactElement'])
48
+
49
+ /**
50
+ * Checks if a Wix type name maps to a valid DATA_TYPE key
51
+ */
52
+ function isValidWixSemanticType(symbolName: string): boolean {
53
+ const camelCaseKey = camelCase(symbolName)
54
+ return camelCaseKey in DATA_TYPE
55
+ }
56
+
57
+ /**
58
+ * Checks if a type is a valid semantic type from React or Wix
59
+ * Only returns semantic for types we can actually handle
60
+ */
61
+ function checkForSemanticType(
62
+ type: ts.Type,
63
+ checker: ts.TypeChecker,
64
+ ): { kind: 'semantic'; value: string; source: string } | undefined {
65
+ // Get the type's symbol
66
+ const symbol = type.aliasSymbol ?? type.getSymbol()
67
+ if (!symbol) return undefined
68
+
69
+ const symbolName = symbol.getName()
70
+ const packageName = getSymbolPackage(symbol, checker)
71
+
72
+ // Check React types - only valid ones
73
+ if (packageName === '@types/react' || packageName === 'react') {
74
+ if (VALID_REACT_SEMANTIC_TYPES.has(symbolName)) {
75
+ return { kind: 'semantic', value: symbolName, source: packageName }
76
+ }
77
+ return undefined
78
+ }
79
+
80
+ // Check Wix types - only ones that map to DATA_TYPE keys
81
+ if (packageName === '@wix/public-schemas') {
82
+ if (isValidWixSemanticType(symbolName)) {
83
+ return { kind: 'semantic', value: symbolName, source: packageName }
84
+ }
85
+ return undefined
86
+ }
87
+
88
+ return undefined
89
+ }
90
+
91
+ /**
92
+ * Special handling for ReactNode which is a union type
93
+ */
94
+ function checkForReactNode(type: ts.Type, checker: ts.TypeChecker): boolean {
95
+ if (!(type.flags & ts.TypeFlags.Union)) return false
96
+
97
+ const unionType = type as ts.UnionType
98
+ let hasReactElement = false
99
+ let hasString = false
100
+
101
+ for (const memberType of unionType.types) {
102
+ const memberSymbol = memberType.aliasSymbol ?? memberType.getSymbol()
103
+ if (memberSymbol) {
104
+ const symbolName = memberSymbol.getName()
105
+ const packageName = getSymbolPackage(memberSymbol, checker)
106
+ if ((packageName === '@types/react' || packageName === 'react') && symbolName === 'ReactElement') {
107
+ hasReactElement = true
108
+ }
109
+ }
110
+ if (memberType.flags & ts.TypeFlags.String) {
111
+ hasString = true
112
+ }
113
+ }
114
+
115
+ // ReactNode is typically a union containing at least ReactElement and string
116
+ if (hasReactElement && hasString) {
117
+ const aliasName = type.aliasSymbol?.getName()
118
+ return aliasName === 'ReactNode' || !type.aliasSymbol
119
+ }
120
+
121
+ return false
122
+ }
123
+
124
+ export function resolveType(
125
+ type: ts.Type,
126
+ checker: ts.TypeChecker,
127
+ depth = 0,
128
+ typeString?: string,
129
+ declaredSymbol?: ts.Symbol,
130
+ ): ResolvedType {
131
+ // Prevent infinite recursion
132
+ if (depth > MAX_RESOLVE_DEPTH) {
133
+ throw new Error(`Max resolve depth reached for type: ${checker.typeToString(type)}`)
134
+ }
135
+
136
+ // Check for semantic types from React or Wix packages
137
+ const semanticType = checkForSemanticType(type, checker)
138
+ if (semanticType) {
139
+ return {
140
+ kind: 'semantic',
141
+ value: semanticType.value,
142
+ source: semanticType.source,
143
+ }
144
+ }
145
+
146
+ // Special handling for ReactNode (union type)
147
+ if (checkForReactNode(type, checker)) {
148
+ return {
149
+ kind: 'semantic',
150
+ value: 'ReactNode',
151
+ source: '@types/react',
152
+ }
153
+ }
154
+
155
+ // Fallback: For branded primitives where the resolved type loses alias info,
156
+ // use the declared symbol from the type reference to check the package
157
+ if (declaredSymbol && typeString) {
158
+ const packageName = getSymbolPackage(declaredSymbol, checker)
159
+
160
+ // Check React types - only valid ones
161
+ if (packageName === '@types/react' || packageName === 'react') {
162
+ if (VALID_REACT_SEMANTIC_TYPES.has(typeString)) {
163
+ return {
164
+ kind: 'semantic',
165
+ value: typeString,
166
+ source: packageName,
167
+ }
168
+ }
169
+ }
170
+
171
+ // Check Wix types - only ones that map to DATA_TYPE keys
172
+ if (packageName === '@wix/public-schemas') {
173
+ if (isValidWixSemanticType(typeString)) {
174
+ return {
175
+ kind: 'semantic',
176
+ value: typeString,
177
+ source: packageName,
178
+ }
179
+ }
180
+ }
181
+ }
182
+
183
+ // Try each resolver in order
184
+ const resolvers = [
185
+ () => resolvePrimitiveType(type),
186
+ () => resolveLiteralType(type, checker),
187
+ () => resolveUnionType(type, checker, depth),
188
+ () => resolveIntersectionType(type, checker, depth),
189
+ () => resolveArrayType(type, checker, depth),
190
+ () => resolveFunctionType(type, checker),
191
+ () => resolveObjectType(type, checker, depth),
192
+ ]
193
+
194
+ for (const resolver of resolvers) {
195
+ const result = resolver()
196
+ if (result) {
197
+ return result
198
+ }
199
+ }
200
+
201
+ // Fallback to primitive with the type string
202
+ return {
203
+ kind: 'primitive',
204
+ value: checker.typeToString(type),
205
+ }
206
+ }
207
+
208
+ function resolvePrimitiveType(type: ts.Type): ResolvedType | undefined {
209
+ if (type.flags & ts.TypeFlags.String) {
210
+ return { kind: 'primitive', value: 'string' }
211
+ }
212
+ if (type.flags & ts.TypeFlags.Number) {
213
+ return { kind: 'primitive', value: 'number' }
214
+ }
215
+ if (type.flags & ts.TypeFlags.Boolean) {
216
+ return { kind: 'primitive', value: 'boolean' }
217
+ }
218
+ if (type.flags & ts.TypeFlags.Null) {
219
+ return { kind: 'primitive', value: 'null' }
220
+ }
221
+ if (type.flags & ts.TypeFlags.Undefined) {
222
+ return { kind: 'primitive', value: 'undefined' }
223
+ }
224
+ if (type.flags & ts.TypeFlags.Any) {
225
+ return { kind: 'primitive', value: 'any' }
226
+ }
227
+ return undefined
228
+ }
229
+
230
+ function resolveLiteralType(type: ts.Type, checker: ts.TypeChecker): ResolvedType | undefined {
231
+ if (type.flags & ts.TypeFlags.StringLiteral) {
232
+ const literalType = type as ts.StringLiteralType
233
+ return {
234
+ kind: 'literal',
235
+ value: literalType.value,
236
+ }
237
+ }
238
+
239
+ if (type.flags & ts.TypeFlags.NumberLiteral) {
240
+ const literalType = type as ts.NumberLiteralType
241
+ return {
242
+ kind: 'literal',
243
+ value: literalType.value,
244
+ }
245
+ }
246
+
247
+ if (type.flags & ts.TypeFlags.BooleanLiteral) {
248
+ const typeString = checker.typeToString(type)
249
+ return {
250
+ kind: 'literal',
251
+ value: typeString === 'true',
252
+ }
253
+ }
254
+
255
+ return undefined
256
+ }
257
+
258
+ function resolveUnionType(type: ts.Type, checker: ts.TypeChecker, depth: number): ResolvedType | undefined {
259
+ if (type.flags & ts.TypeFlags.Union) {
260
+ const unionType = type as ts.UnionType
261
+ return {
262
+ kind: 'union',
263
+ types: unionType.types.map((t) => resolveType(t, checker, depth + 1)),
264
+ }
265
+ }
266
+ return undefined
267
+ }
268
+
269
+ function resolveIntersectionType(type: ts.Type, checker: ts.TypeChecker, depth: number): ResolvedType | undefined {
270
+ if (type.flags & ts.TypeFlags.Intersection) {
271
+ const intersectionType = type as ts.IntersectionType
272
+ return {
273
+ kind: 'intersection',
274
+ types: intersectionType.types.map((t) => resolveType(t, checker, depth + 1)),
275
+ }
276
+ }
277
+ return undefined
278
+ }
279
+
280
+ function resolveArrayType(type: ts.Type, checker: ts.TypeChecker, depth: number): ResolvedType | undefined {
281
+ if (checker.isArrayType(type)) {
282
+ const arrayType = type as ts.TypeReference
283
+ const typeArgs = checker.getTypeArguments(arrayType)
284
+ if (typeArgs.length > 0) {
285
+ return {
286
+ kind: 'array',
287
+ elementType: resolveType(typeArgs[0], checker, depth + 1),
288
+ }
289
+ }
290
+ return { kind: 'array', elementType: { kind: 'primitive', value: 'any' } }
291
+ }
292
+ return undefined
293
+ }
294
+
295
+ function resolveFunctionType(type: ts.Type, checker: ts.TypeChecker): ResolvedType | undefined {
296
+ const callSignatures = type.getCallSignatures()
297
+ if (callSignatures.length > 0) {
298
+ const signature = callSignatures[0]
299
+ const parameters = signature.getParameters().map((param) => {
300
+ const paramType = checker.getTypeOfSymbol(param)
301
+ return {
302
+ name: param.getName(),
303
+ type: checker.typeToString(paramType),
304
+ }
305
+ })
306
+ const returnType = signature.getReturnType()
307
+
308
+ return {
309
+ kind: 'function',
310
+ value: {
311
+ parameters,
312
+ returnType: checker.typeToString(returnType),
313
+ },
314
+ }
315
+ }
316
+ return undefined
317
+ }
318
+
319
+ function resolveObjectType(type: ts.Type, checker: ts.TypeChecker, depth: number): ResolvedType | undefined {
320
+ const properties = type.getProperties()
321
+ if (properties.length === 0) {
322
+ return undefined
323
+ }
324
+
325
+ const resolvedProperties: Record<string, PropInfo> = {}
326
+
327
+ // Filter out built-in properties
328
+ const userDefinedProps = properties.filter((prop) => {
329
+ const propName = prop.getName()
330
+ return !['toString', 'valueOf', 'hasOwnProperty', 'constructor', 'toLocaleString'].includes(propName)
331
+ })
332
+
333
+ for (const prop of userDefinedProps) {
334
+ const propName = prop.getName()
335
+ const propType = checker.getTypeOfSymbol(prop)
336
+ const isOptional = (prop.flags & ts.SymbolFlags.Optional) !== 0
337
+
338
+ resolvedProperties[propName] = {
339
+ name: propName,
340
+ type: checker.typeToString(propType),
341
+ required: !isOptional,
342
+ resolvedType: resolveType(propType, checker, depth + 1),
343
+ }
344
+ }
345
+
346
+ if (Object.keys(resolvedProperties).length > 0) {
347
+ return {
348
+ kind: 'object',
349
+ properties: resolvedProperties,
350
+ }
351
+ }
352
+
353
+ return undefined
354
+ }
355
+
356
+ function findPackageName(filePath: string): string | undefined {
357
+ let currentDir = path.dirname(filePath)
358
+ const root = path.parse(currentDir).root
359
+
360
+ while (currentDir !== root) {
361
+ const packageJsonPath = path.join(currentDir, 'package.json')
362
+
363
+ if (fs.existsSync(packageJsonPath)) {
364
+ try {
365
+ const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf-8')
366
+ const packageJson = JSON.parse(packageJsonContent)
367
+ return packageJson.name
368
+ } catch (error) {
369
+ throw new Error(`Failed to read package.json at ${packageJsonPath}: ${error}`)
370
+ }
371
+ }
372
+
373
+ currentDir = path.dirname(currentDir)
374
+ }
375
+
376
+ return undefined
377
+ }
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Interceptable JSX Runtime
3
+ *
4
+ * This module wraps React's jsx-runtime and allows dynamic interception
5
+ * of jsx/jsxs/jsxDEV calls. This is necessary because ESM imports are immutable,
6
+ * so we can't monkey-patch react/jsx-runtime directly.
7
+ *
8
+ * ## Usage contexts
9
+ *
10
+ * This module runs in two contexts that must share mutable state:
11
+ *
12
+ * 1. **Vite alias (tests)** — aliased to `react/jsx-runtime` in vite.config.ts
13
+ * 2. **Node.js ESM loader hook (CLI)** — redirected via `module.register()` in cli.ts
14
+ *
15
+ * In both cases the interceptor may be loaded as a separate module instance from
16
+ * the copy bundled into the main dist files. To ensure `setJsxInterceptors()`
17
+ * (called from component-renderer.ts) affects the `jsx`/`jsxs` that user
18
+ * components call, mutable state lives on `globalThis` via `Symbol.for()`.
19
+ */
20
+
21
+ import { createRequire } from 'node:module'
22
+
23
+ // Use require() to load the real React modules, bypassing Vite alias / ESM loader hook
24
+ const require = createRequire(import.meta.url)
25
+ const originalRuntime = require('react/jsx-runtime') as typeof import('react/jsx-runtime')
26
+ const originalDevRuntime = require('react/jsx-dev-runtime') as typeof import('react/jsx-dev-runtime')
27
+
28
+ // Re-export Fragment unchanged
29
+ export const Fragment = originalRuntime.Fragment
30
+
31
+ // Type for jsxDEV function
32
+ type JsxDevFn = typeof originalDevRuntime.jsxDEV
33
+
34
+ // ─────────────────────────────────────────────────────────────────────────────
35
+ // Shared state via globalThis
36
+ //
37
+ // The bundled copy (inside cli.js / index.js) and the standalone copy (loaded
38
+ // by the ESM loader hook or Vite alias) must share the same mutable state.
39
+ // Symbol.for() guarantees a process-wide singleton key.
40
+ // ─────────────────────────────────────────────────────────────────────────────
41
+
42
+ const STATE_KEY = Symbol.for('zero-config:jsx-interceptor')
43
+
44
+ interface JsxInterceptorState {
45
+ currentJsx: typeof originalRuntime.jsx
46
+ currentJsxs: typeof originalRuntime.jsxs
47
+ currentJsxDEV: JsxDevFn
48
+ isInsideOriginal: boolean
49
+ }
50
+
51
+ function getState(): JsxInterceptorState {
52
+ const g = globalThis as unknown as Record<symbol, JsxInterceptorState | undefined>
53
+ if (!g[STATE_KEY]) {
54
+ g[STATE_KEY] = {
55
+ currentJsx: originalRuntime.jsx,
56
+ currentJsxs: originalRuntime.jsxs,
57
+ currentJsxDEV: originalDevRuntime.jsxDEV,
58
+ isInsideOriginal: false,
59
+ }
60
+ }
61
+ return g[STATE_KEY]!
62
+ }
63
+
64
+ /**
65
+ * Sets custom jsx/jsxs/jsxDEV implementations for interception.
66
+ * Call with no arguments to restore originals.
67
+ */
68
+ export function setJsxInterceptors(
69
+ jsx?: typeof originalRuntime.jsx,
70
+ jsxs?: typeof originalRuntime.jsxs,
71
+ jsxDEV?: JsxDevFn,
72
+ ): void {
73
+ const state = getState()
74
+ state.currentJsx = jsx ?? originalRuntime.jsx
75
+ state.currentJsxs = jsxs ?? originalRuntime.jsxs
76
+ state.currentJsxDEV = jsxDEV ?? originalDevRuntime.jsxDEV
77
+ }
78
+
79
+ /**
80
+ * Gets the original jsx/jsxs/jsxDEV functions.
81
+ */
82
+ export function getOriginals() {
83
+ return {
84
+ jsx: originalRuntime.jsx,
85
+ jsxs: originalRuntime.jsxs,
86
+ jsxDEV: originalDevRuntime.jsxDEV,
87
+ }
88
+ }
89
+
90
+ // Export interceptable jsx/jsxs - these delegate to the shared state
91
+ export function jsx(
92
+ type: Parameters<typeof originalRuntime.jsx>[0],
93
+ props: Parameters<typeof originalRuntime.jsx>[1],
94
+ key?: Parameters<typeof originalRuntime.jsx>[2],
95
+ ) {
96
+ const state = getState()
97
+ if (state.isInsideOriginal) {
98
+ return originalRuntime.jsx(type, props, key)
99
+ }
100
+ state.isInsideOriginal = true
101
+ try {
102
+ return state.currentJsx(type, props, key)
103
+ } finally {
104
+ state.isInsideOriginal = false
105
+ }
106
+ }
107
+
108
+ export function jsxs(
109
+ type: Parameters<typeof originalRuntime.jsxs>[0],
110
+ props: Parameters<typeof originalRuntime.jsxs>[1],
111
+ key?: Parameters<typeof originalRuntime.jsxs>[2],
112
+ ) {
113
+ const state = getState()
114
+ if (state.isInsideOriginal) {
115
+ return originalRuntime.jsxs(type, props, key)
116
+ }
117
+ state.isInsideOriginal = true
118
+ try {
119
+ return state.currentJsxs(type, props, key)
120
+ } finally {
121
+ state.isInsideOriginal = false
122
+ }
123
+ }
124
+
125
+ // Export interceptable jsxDEV for development mode
126
+ export function jsxDEV(
127
+ type: Parameters<JsxDevFn>[0],
128
+ props: Parameters<JsxDevFn>[1],
129
+ key: Parameters<JsxDevFn>[2],
130
+ isStaticChildren: Parameters<JsxDevFn>[3],
131
+ source: Parameters<JsxDevFn>[4],
132
+ self: Parameters<JsxDevFn>[5],
133
+ ) {
134
+ const state = getState()
135
+ // Prevent recursion when React's internal jsxDEV calls other jsx functions
136
+ if (state.isInsideOriginal) {
137
+ return originalDevRuntime.jsxDEV(type, props, key, isStaticChildren, source, self)
138
+ }
139
+
140
+ state.isInsideOriginal = true
141
+ try {
142
+ return state.currentJsxDEV(type, props, key, isStaticChildren, source, self)
143
+ } finally {
144
+ state.isInsideOriginal = false
145
+ }
146
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Node.js ESM Customization Hook
3
+ *
4
+ * Redirects ESM `import 'react/jsx-runtime'` and `import 'react/jsx-dev-runtime'`
5
+ * to the interceptable jsx-runtime-interceptor module.
6
+ *
7
+ * Registered via `module.register()` in the CLI before loading user components.
8
+ * This ensures that dynamically imported user components use the interceptable
9
+ * jsx-runtime, enabling prop tracking and element tracing.
10
+ *
11
+ * CJS `require()` calls are not affected by this hook — the interceptor uses
12
+ * `createRequire` internally, so it always loads the real React runtime.
13
+ */
14
+
15
+ // Compute sibling file URL at runtime — avoids Vite's static `new URL()` asset transform
16
+ const INTERCEPTOR_URL = import.meta.url.replace(/jsx-runtime-loader\.js$/, 'jsx-runtime-interceptor.js')
17
+
18
+ interface ResolveResult {
19
+ url: string
20
+ shortCircuit?: boolean
21
+ }
22
+
23
+ interface ResolveContext {
24
+ parentURL?: string
25
+ }
26
+
27
+ type NextResolve = (specifier: string, context: ResolveContext) => Promise<ResolveResult>
28
+
29
+ export async function resolve(
30
+ specifier: string,
31
+ context: ResolveContext,
32
+ nextResolve: NextResolve,
33
+ ): Promise<ResolveResult> {
34
+ if (specifier === 'react/jsx-runtime' || specifier === 'react/jsx-dev-runtime') {
35
+ return { shortCircuit: true, url: INTERCEPTOR_URL }
36
+ }
37
+ return nextResolve(specifier, context)
38
+ }