@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.
- package/README.md +72 -0
- package/dist/component-loader.d.ts +42 -0
- package/dist/component-renderer.d.ts +31 -0
- package/dist/converters/data-item-builder.d.ts +15 -0
- package/dist/converters/index.d.ts +1 -0
- package/dist/converters/to-editor-component.d.ts +3 -0
- package/dist/converters/utils.d.ts +16 -0
- package/dist/errors.d.ts +230 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +51978 -0
- package/dist/information-extractors/css/index.d.ts +3 -0
- package/dist/information-extractors/css/parse.d.ts +7 -0
- package/dist/information-extractors/css/selector-matcher.d.ts +3 -0
- package/dist/information-extractors/css/types.d.ts +49 -0
- package/dist/information-extractors/react/extractors/core/index.d.ts +6 -0
- package/dist/information-extractors/react/extractors/core/runner.d.ts +19 -0
- package/dist/information-extractors/react/extractors/core/store.d.ts +17 -0
- package/dist/information-extractors/react/extractors/core/tree-builder.d.ts +15 -0
- package/dist/information-extractors/react/extractors/core/types.d.ts +40 -0
- package/dist/information-extractors/react/extractors/css-properties.d.ts +20 -0
- package/dist/information-extractors/react/extractors/index.d.ts +11 -0
- package/dist/information-extractors/react/extractors/prop-tracker.d.ts +24 -0
- package/dist/information-extractors/react/index.d.ts +9 -0
- package/dist/information-extractors/react/types.d.ts +51 -0
- package/dist/information-extractors/react/utils/mock-generator.d.ts +9 -0
- package/dist/information-extractors/react/utils/prop-spy.d.ts +10 -0
- package/dist/information-extractors/ts/components.d.ts +9 -0
- package/dist/information-extractors/ts/css-imports.d.ts +2 -0
- package/dist/information-extractors/ts/index.d.ts +3 -0
- package/dist/information-extractors/ts/types.d.ts +47 -0
- package/dist/information-extractors/ts/utils/semantic-type-resolver.d.ts +3 -0
- package/dist/jsx-runtime-interceptor.d.ts +42 -0
- package/dist/jsx-runtime-interceptor.js +63 -0
- package/dist/jsx-runtime-loader.d.ts +23 -0
- package/dist/jsx-runtime-loader.js +7 -0
- package/dist/manifest-pipeline.d.ts +33 -0
- package/dist/schema.d.ts +167 -0
- package/dist/ts-compiler.d.ts +13 -0
- package/package.json +81 -0
- package/src/component-loader.test.ts +277 -0
- package/src/component-loader.ts +256 -0
- package/src/component-renderer.ts +192 -0
- package/src/converters/data-item-builder.ts +354 -0
- package/src/converters/index.ts +1 -0
- package/src/converters/to-editor-component.ts +167 -0
- package/src/converters/utils.ts +21 -0
- package/src/errors.ts +103 -0
- package/src/index.ts +223 -0
- package/src/information-extractors/css/README.md +3 -0
- package/src/information-extractors/css/index.ts +3 -0
- package/src/information-extractors/css/parse.ts +450 -0
- package/src/information-extractors/css/selector-matcher.ts +88 -0
- package/src/information-extractors/css/types.ts +56 -0
- package/src/information-extractors/react/extractors/core/index.ts +6 -0
- package/src/information-extractors/react/extractors/core/runner.ts +89 -0
- package/src/information-extractors/react/extractors/core/store.ts +36 -0
- package/src/information-extractors/react/extractors/core/tree-builder.ts +273 -0
- package/src/information-extractors/react/extractors/core/types.ts +48 -0
- package/src/information-extractors/react/extractors/css-properties.ts +214 -0
- package/src/information-extractors/react/extractors/index.ts +27 -0
- package/src/information-extractors/react/extractors/prop-tracker.ts +132 -0
- package/src/information-extractors/react/index.ts +53 -0
- package/src/information-extractors/react/types.ts +70 -0
- package/src/information-extractors/react/utils/mock-generator.ts +331 -0
- package/src/information-extractors/react/utils/prop-spy.ts +168 -0
- package/src/information-extractors/ts/components.ts +300 -0
- package/src/information-extractors/ts/css-imports.ts +26 -0
- package/src/information-extractors/ts/index.ts +3 -0
- package/src/information-extractors/ts/types.ts +56 -0
- package/src/information-extractors/ts/utils/semantic-type-resolver.ts +377 -0
- package/src/jsx-runtime-interceptor.ts +146 -0
- package/src/jsx-runtime-loader.ts +38 -0
- package/src/manifest-pipeline.ts +362 -0
- package/src/schema.ts +174 -0
- package/src/ts-compiler.ts +41 -0
- package/tsconfig.json +17 -0
- package/typedoc.json +18 -0
- 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
|
+
}
|