@wix/zero-config-implementation 1.71.0 → 1.73.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/dist/{index-C4cP1dwy.js → index-K3lIO4FC.js} +21613 -19661
- package/dist/{index-DT1jTZEG.js → index-NbI-_ozJ.js} +1 -1
- package/dist/index.d.ts +23 -8
- package/dist/index.js +1 -1
- package/package.json +4 -3
- package/src/__fixtures__/cjs-ref-element-entry.cjs +1 -0
- package/src/__fixtures__/cjs-ref-element-lazy-entry.cjs +3 -0
- package/src/__fixtures__/cjs-ref-element-order-entry.cjs +8 -0
- package/src/__fixtures__/cjs-ref-element-order-target.cjs +7 -0
- package/src/__fixtures__/cjs-ref-element-target.cjs +5 -0
- package/src/__fixtures__/esm-ref-element-entry.mjs +3 -0
- package/src/component-renderer.ts +12 -9
- package/src/converters/to-editor-component.test.ts +88 -0
- package/src/converters/to-editor-component.ts +35 -6
- package/src/index.ts +121 -54
- package/src/information-extractors/css/parse.test.ts +49 -1
- package/src/information-extractors/css/parse.ts +167 -3
- package/src/information-extractors/css/selector-matcher.ts +3 -1
- package/src/information-extractors/css/types.ts +11 -0
- package/src/information-extractors/react/extractors/core/tree-builder.ts +3 -3
- package/src/information-extractors/react/extractors/core/types.ts +4 -5
- package/src/information-extractors/react/extractors/css-properties.test.ts +294 -2
- package/src/information-extractors/react/extractors/css-properties.ts +248 -98
- package/src/information-extractors/react/extractors/index.ts +1 -0
- package/src/information-extractors/react/extractors/prop-tracker.test.ts +251 -0
- package/src/information-extractors/react/extractors/prop-tracker.ts +78 -14
- package/src/information-extractors/react/index.ts +1 -0
- package/src/information-extractors/react/types.ts +1 -1
- package/src/information-extractors/react/utils/mock-generator.test.ts +131 -0
- package/src/information-extractors/react/utils/mock-generator.ts +38 -13
- package/src/manifest-pipeline.ts +22 -15
- package/src/module-loader.test.ts +150 -0
- package/src/module-loader.ts +48 -8
- package/src/react-runtime-interceptor.ts +64 -0
- package/src/react-runtime-loader.ts +656 -0
- package/src/ref-elements/component-tag.ts +105 -0
- package/src/ref-elements/context.test.ts +179 -0
- package/src/ref-elements/context.ts +280 -0
- package/src/ref-elements/eligible-paths.ts +45 -0
- package/src/ref-elements/module-resolution.test.ts +50 -0
- package/src/ref-elements/module-resolution.ts +21 -0
- package/src/ref-elements/module-specifier.ts +17 -0
- package/src/ref-elements/path-utils.test.ts +14 -0
- package/src/ref-elements/path-utils.ts +55 -0
- package/src/ref-elements/types.ts +17 -0
- package/src/utils/css-class.ts +15 -0
- package/src/jsx-runtime-interceptor.ts +0 -245
|
@@ -0,0 +1,656 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import { createRequire, register } from 'node:module'
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
4
|
+
import { MessageChannel, MessagePort } from 'node:worker_threads'
|
|
5
|
+
import { buildRefElementTaggerSource, tagRefElementComponentForCleanup } from './ref-elements/component-tag'
|
|
6
|
+
import type { RefElementModule } from './ref-elements/types'
|
|
7
|
+
|
|
8
|
+
const JSX_INTERCEPTOR_STATE_KEY = 'zero-config:jsx-interceptor'
|
|
9
|
+
const JSX_ORIGINALS_KEY = 'zero-config:jsx-originals'
|
|
10
|
+
const REACT_ORIGINALS_KEY = 'zero-config:react-originals'
|
|
11
|
+
const LOADER_REGISTERED_KEY = Symbol.for('zero-config:jsx-loader-registered')
|
|
12
|
+
const RUNTIME_STATE_KEY = Symbol.for('zero-config:ref-element-runtime-state')
|
|
13
|
+
const REF_ELEMENT_LOADER_REQUEST_TIMEOUT_MS = 1_000
|
|
14
|
+
const REF_ELEMENT_ENTRY_SIGNATURE_QUERY_PARAM = 'zero-config-ref-entry'
|
|
15
|
+
|
|
16
|
+
interface RegisteredRefElementModuleState {
|
|
17
|
+
exportNames: string[]
|
|
18
|
+
resolvedModuleUrl: string
|
|
19
|
+
refComponentType: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface PreparedRefElementEsmImport {
|
|
23
|
+
cleanup: () => Promise<void>
|
|
24
|
+
importUrl: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface RefElementLoaderRequest {
|
|
28
|
+
modulesByResolvedUrl: Record<string, RegisteredRefElementModuleState>
|
|
29
|
+
requestId: number
|
|
30
|
+
signature: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface RefElementLoaderResponse {
|
|
34
|
+
error?: string
|
|
35
|
+
requestId: number
|
|
36
|
+
success: boolean
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface PendingLoaderRequest {
|
|
40
|
+
reject: (error: Error) => void
|
|
41
|
+
resolve: () => void
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface ActiveCjsRefElementExtraction {
|
|
45
|
+
cleanupHandlers: Array<() => void>
|
|
46
|
+
id: number
|
|
47
|
+
runtimeModulesByResolvedPath: Map<string, RegisteredRefElementModuleState[]>
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface InternalModuleApi {
|
|
51
|
+
_load: (request: string, parent: NodeJS.Module | null | undefined, isMain: boolean) => unknown
|
|
52
|
+
_resolveFilename: (
|
|
53
|
+
request: string,
|
|
54
|
+
parent: NodeJS.Module | null | undefined,
|
|
55
|
+
isMain: boolean,
|
|
56
|
+
options?: unknown,
|
|
57
|
+
) => string
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface RefElementRuntimeState {
|
|
61
|
+
activeCjsExtractions: Map<number, ActiveCjsRefElementExtraction>
|
|
62
|
+
loaderPort: MessagePort | null
|
|
63
|
+
nextCjsExtractionId: number
|
|
64
|
+
nextLoaderRequestId: number
|
|
65
|
+
pendingLoaderRequests: Map<number, PendingLoaderRequest>
|
|
66
|
+
registeredEsmModulesByResolvedUrl: Map<string, RegisteredRefElementModuleState>
|
|
67
|
+
originalModuleLoad: InternalModuleApi['_load'] | null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function prepareRefElementEsmImport(
|
|
71
|
+
entryPath: string,
|
|
72
|
+
runtimeModules: RefElementModule[] = [],
|
|
73
|
+
): Promise<PreparedRefElementEsmImport> {
|
|
74
|
+
ensureReactRuntimeLoader()
|
|
75
|
+
|
|
76
|
+
if (runtimeModules.length === 0) {
|
|
77
|
+
return {
|
|
78
|
+
cleanup: async () => {},
|
|
79
|
+
importUrl: pathToFileURL(entryPath).href,
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const registeredModules = mergeRefElementModules(runtimeModules)
|
|
84
|
+
const refElementEntrySignature = buildRefElementEntrySignature(registeredModules)
|
|
85
|
+
await registerEsmRefElementModules(refElementEntrySignature, registeredModules)
|
|
86
|
+
const entryImportUrl = pathToFileURL(entryPath)
|
|
87
|
+
entryImportUrl.searchParams.set(REF_ELEMENT_ENTRY_SIGNATURE_QUERY_PARAM, refElementEntrySignature)
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
cleanup: async () => {},
|
|
91
|
+
importUrl: entryImportUrl.href,
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function ensureReactRuntimeLoader(): void {
|
|
96
|
+
const globalRecord = globalThis as Record<symbol, unknown>
|
|
97
|
+
if (globalRecord[LOADER_REGISTERED_KEY]) {
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
const runtimeState = readRefElementRuntimeState()
|
|
101
|
+
const { port1: runtimePort, port2: loaderPort } = new MessageChannel()
|
|
102
|
+
try {
|
|
103
|
+
runtimeState.loaderPort = runtimePort
|
|
104
|
+
runtimePort.on('message', (message: RefElementLoaderResponse) => {
|
|
105
|
+
const pendingLoaderRequest = runtimeState.pendingLoaderRequests.get(message.requestId)
|
|
106
|
+
if (!pendingLoaderRequest) {
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
runtimeState.pendingLoaderRequests.delete(message.requestId)
|
|
111
|
+
if (message.success) {
|
|
112
|
+
pendingLoaderRequest.resolve()
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
pendingLoaderRequest.reject(new Error(message.error ?? 'Failed to register ref-element modules in the loader'))
|
|
117
|
+
})
|
|
118
|
+
runtimePort.unref()
|
|
119
|
+
loaderPort.unref()
|
|
120
|
+
|
|
121
|
+
const require = createRequire(import.meta.url)
|
|
122
|
+
const reactRuntime = require('react/jsx-runtime') as typeof import('react/jsx-runtime')
|
|
123
|
+
const reactDevRuntime = require('react/jsx-dev-runtime') as typeof import('react/jsx-dev-runtime')
|
|
124
|
+
const reactModule = require('react') as Record<string, unknown>
|
|
125
|
+
;(globalThis as Record<PropertyKey, unknown>)[Symbol.for(JSX_ORIGINALS_KEY)] = {
|
|
126
|
+
Fragment: reactRuntime.Fragment,
|
|
127
|
+
jsx: reactRuntime.jsx,
|
|
128
|
+
jsxDEV: reactDevRuntime.jsxDEV,
|
|
129
|
+
jsxs: reactRuntime.jsxs,
|
|
130
|
+
}
|
|
131
|
+
;(globalThis as Record<PropertyKey, unknown>)[Symbol.for(REACT_ORIGINALS_KEY)] = reactModule
|
|
132
|
+
|
|
133
|
+
const reactModuleUrl = `data:text/javascript,${encodeURIComponent(buildReactModuleShimSource(Object.keys(reactModule)))}`
|
|
134
|
+
const interceptorModuleUrl = `data:text/javascript,${encodeURIComponent(buildJsxRuntimeInterceptorSource())}`
|
|
135
|
+
const loaderSource = buildReactRuntimeLoaderSource({
|
|
136
|
+
interceptorModuleUrl,
|
|
137
|
+
reactModuleUrl,
|
|
138
|
+
taggerSource: buildRefElementTaggerSource(),
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
register(`data:text/javascript,${encodeURIComponent(loaderSource)}`, {
|
|
142
|
+
data: { port: loaderPort },
|
|
143
|
+
transferList: [loaderPort],
|
|
144
|
+
})
|
|
145
|
+
globalRecord[LOADER_REGISTERED_KEY] = true
|
|
146
|
+
} catch (error) {
|
|
147
|
+
runtimeState.loaderPort = null
|
|
148
|
+
runtimePort.close()
|
|
149
|
+
loaderPort.close()
|
|
150
|
+
throw error
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function readRegisteredRefElementModuleUrlsForTests(): string[] {
|
|
155
|
+
return [...readRefElementRuntimeState().registeredEsmModulesByResolvedUrl.keys()]
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function readLoaderPortRefStateForTests(): boolean | null {
|
|
159
|
+
const loaderPort = readRefElementRuntimeState().loaderPort
|
|
160
|
+
if (!(loaderPort instanceof MessagePort)) {
|
|
161
|
+
return null
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return (loaderPort as MessagePort & { hasRef: () => boolean }).hasRef()
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function loadTaggedCjsModule(
|
|
168
|
+
entryPath: string,
|
|
169
|
+
runtimeModules: RefElementModule[] = [],
|
|
170
|
+
): { cleanup: () => Promise<void>; moduleExports: Record<string, unknown> } {
|
|
171
|
+
const runtimeState = readRefElementRuntimeState()
|
|
172
|
+
const entryRequire = createRequire(entryPath)
|
|
173
|
+
const activeExtraction = createActiveCjsRefElementExtraction(runtimeState, runtimeModules)
|
|
174
|
+
installCjsRefElementInterception(runtimeState)
|
|
175
|
+
|
|
176
|
+
tagCachedCjsRefElementModules(entryRequire, activeExtraction)
|
|
177
|
+
|
|
178
|
+
let didCleanup = false
|
|
179
|
+
try {
|
|
180
|
+
const moduleExports = entryRequire(entryPath) as Record<string, unknown>
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
cleanup: async () => {
|
|
184
|
+
if (didCleanup) {
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
didCleanup = true
|
|
189
|
+
cleanupActiveCjsRefElementExtraction(runtimeState, activeExtraction.id)
|
|
190
|
+
},
|
|
191
|
+
moduleExports,
|
|
192
|
+
}
|
|
193
|
+
} catch (error) {
|
|
194
|
+
cleanupActiveCjsRefElementExtraction(runtimeState, activeExtraction.id)
|
|
195
|
+
throw error
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function readRefElementRuntimeState(): RefElementRuntimeState {
|
|
200
|
+
const globalRecord = globalThis as Record<symbol, RefElementRuntimeState | undefined>
|
|
201
|
+
const runtimeState = globalRecord[RUNTIME_STATE_KEY]
|
|
202
|
+
if (runtimeState) {
|
|
203
|
+
return runtimeState
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const nextRuntimeState: RefElementRuntimeState = {
|
|
207
|
+
activeCjsExtractions: new Map(),
|
|
208
|
+
loaderPort: null,
|
|
209
|
+
nextCjsExtractionId: 0,
|
|
210
|
+
nextLoaderRequestId: 0,
|
|
211
|
+
pendingLoaderRequests: new Map(),
|
|
212
|
+
registeredEsmModulesByResolvedUrl: new Map(),
|
|
213
|
+
originalModuleLoad: null,
|
|
214
|
+
}
|
|
215
|
+
globalRecord[RUNTIME_STATE_KEY] = nextRuntimeState
|
|
216
|
+
return nextRuntimeState
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function registerEsmRefElementModules(
|
|
220
|
+
refElementEntrySignature: string,
|
|
221
|
+
runtimeModules: RegisteredRefElementModuleState[],
|
|
222
|
+
): Promise<void> {
|
|
223
|
+
const runtimeState = readRefElementRuntimeState()
|
|
224
|
+
const modulesByResolvedUrl: Record<string, RegisteredRefElementModuleState> = {}
|
|
225
|
+
|
|
226
|
+
for (const runtimeModule of runtimeModules) {
|
|
227
|
+
const registeredModule = upsertRefElementModule(runtimeState.registeredEsmModulesByResolvedUrl, runtimeModule)
|
|
228
|
+
modulesByResolvedUrl[registeredModule.resolvedModuleUrl] = registeredModule
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
await postRefElementLoaderRequest({
|
|
232
|
+
modulesByResolvedUrl,
|
|
233
|
+
signature: refElementEntrySignature,
|
|
234
|
+
})
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function postRefElementLoaderRequest(request: Omit<RefElementLoaderRequest, 'requestId'>): Promise<void> {
|
|
238
|
+
const runtimeState = readRefElementRuntimeState()
|
|
239
|
+
const loaderPort = runtimeState.loaderPort
|
|
240
|
+
if (!(loaderPort instanceof MessagePort)) {
|
|
241
|
+
return Promise.reject(new Error('Ref-element loader has not been initialized'))
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
runtimeState.nextLoaderRequestId += 1
|
|
245
|
+
const requestId = runtimeState.nextLoaderRequestId
|
|
246
|
+
|
|
247
|
+
return new Promise<void>((resolve, reject) => {
|
|
248
|
+
const requestTimeout = setTimeout(() => {
|
|
249
|
+
runtimeState.pendingLoaderRequests.delete(requestId)
|
|
250
|
+
reject(new Error('Timed out waiting for ref-element loader registration'))
|
|
251
|
+
}, REF_ELEMENT_LOADER_REQUEST_TIMEOUT_MS)
|
|
252
|
+
|
|
253
|
+
runtimeState.pendingLoaderRequests.set(requestId, {
|
|
254
|
+
reject: (error) => {
|
|
255
|
+
clearTimeout(requestTimeout)
|
|
256
|
+
reject(error)
|
|
257
|
+
},
|
|
258
|
+
resolve: () => {
|
|
259
|
+
clearTimeout(requestTimeout)
|
|
260
|
+
resolve()
|
|
261
|
+
},
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
loaderPort.postMessage({
|
|
265
|
+
...request,
|
|
266
|
+
requestId,
|
|
267
|
+
} satisfies RefElementLoaderRequest)
|
|
268
|
+
})
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function createActiveCjsRefElementExtraction(
|
|
272
|
+
runtimeState: RefElementRuntimeState,
|
|
273
|
+
runtimeModules: RefElementModule[],
|
|
274
|
+
): ActiveCjsRefElementExtraction {
|
|
275
|
+
runtimeState.nextCjsExtractionId += 1
|
|
276
|
+
|
|
277
|
+
const activeExtraction: ActiveCjsRefElementExtraction = {
|
|
278
|
+
cleanupHandlers: [],
|
|
279
|
+
id: runtimeState.nextCjsExtractionId,
|
|
280
|
+
runtimeModulesByResolvedPath: new Map(),
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
for (const runtimeModule of mergeRefElementModules(runtimeModules)) {
|
|
284
|
+
if (!runtimeModule.resolvedModuleUrl.startsWith('file:')) {
|
|
285
|
+
continue
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const resolvedModulePath = fileURLToPath(runtimeModule.resolvedModuleUrl)
|
|
289
|
+
const existingRuntimeModules = activeExtraction.runtimeModulesByResolvedPath.get(resolvedModulePath) ?? []
|
|
290
|
+
existingRuntimeModules.push(runtimeModule)
|
|
291
|
+
activeExtraction.runtimeModulesByResolvedPath.set(resolvedModulePath, existingRuntimeModules)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
runtimeState.activeCjsExtractions.set(activeExtraction.id, activeExtraction)
|
|
295
|
+
return activeExtraction
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function installCjsRefElementInterception(runtimeState: RefElementRuntimeState): void {
|
|
299
|
+
if (runtimeState.originalModuleLoad) {
|
|
300
|
+
return
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const internalModule = readInternalModuleApi()
|
|
304
|
+
runtimeState.originalModuleLoad = internalModule._load
|
|
305
|
+
internalModule._load = function (
|
|
306
|
+
request: string,
|
|
307
|
+
parent: NodeJS.Module | null | undefined,
|
|
308
|
+
isMain: boolean,
|
|
309
|
+
): unknown {
|
|
310
|
+
const moduleExports = runtimeState.originalModuleLoad?.call(this, request, parent, isMain)
|
|
311
|
+
const resolvedModulePath = resolveRequestedCjsModulePath(request, parent, isMain)
|
|
312
|
+
|
|
313
|
+
if (resolvedModulePath) {
|
|
314
|
+
tagActiveCjsRefElementModules(runtimeState, resolvedModulePath, moduleExports)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return moduleExports
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function cleanupActiveCjsRefElementExtraction(runtimeState: RefElementRuntimeState, extractionId: number): void {
|
|
322
|
+
const activeExtraction = runtimeState.activeCjsExtractions.get(extractionId)
|
|
323
|
+
if (!activeExtraction) {
|
|
324
|
+
return
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
runtimeState.activeCjsExtractions.delete(extractionId)
|
|
328
|
+
|
|
329
|
+
for (const cleanupHandler of activeExtraction.cleanupHandlers.reverse()) {
|
|
330
|
+
cleanupHandler()
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (runtimeState.activeCjsExtractions.size !== 0) {
|
|
334
|
+
return
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const internalModule = readInternalModuleApi()
|
|
338
|
+
if (runtimeState.originalModuleLoad) {
|
|
339
|
+
internalModule._load = runtimeState.originalModuleLoad
|
|
340
|
+
runtimeState.originalModuleLoad = null
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function tagCachedCjsRefElementModules(
|
|
345
|
+
entryRequire: NodeJS.Require,
|
|
346
|
+
activeExtraction: ActiveCjsRefElementExtraction,
|
|
347
|
+
): void {
|
|
348
|
+
for (const [resolvedModulePath, runtimeModules] of activeExtraction.runtimeModulesByResolvedPath) {
|
|
349
|
+
const cachedModule = entryRequire.cache[resolvedModulePath]
|
|
350
|
+
if (!cachedModule) {
|
|
351
|
+
continue
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
for (const runtimeModule of runtimeModules) {
|
|
355
|
+
activeExtraction.cleanupHandlers.push(...tagCjsRefElementModuleExports(cachedModule.exports, runtimeModule))
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function tagActiveCjsRefElementModules(
|
|
361
|
+
runtimeState: RefElementRuntimeState,
|
|
362
|
+
resolvedModulePath: string,
|
|
363
|
+
moduleExports: unknown,
|
|
364
|
+
): void {
|
|
365
|
+
for (const activeExtraction of runtimeState.activeCjsExtractions.values()) {
|
|
366
|
+
const runtimeModules = activeExtraction.runtimeModulesByResolvedPath.get(resolvedModulePath)
|
|
367
|
+
if (!runtimeModules) {
|
|
368
|
+
continue
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
for (const runtimeModule of runtimeModules) {
|
|
372
|
+
activeExtraction.cleanupHandlers.push(...tagCjsRefElementModuleExports(moduleExports, runtimeModule))
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function readInternalModuleApi(): InternalModuleApi {
|
|
378
|
+
const require = createRequire(import.meta.url)
|
|
379
|
+
return require('node:module') as InternalModuleApi
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function resolveRequestedCjsModulePath(
|
|
383
|
+
request: string,
|
|
384
|
+
parent: NodeJS.Module | null | undefined,
|
|
385
|
+
isMain: boolean,
|
|
386
|
+
): string | undefined {
|
|
387
|
+
try {
|
|
388
|
+
return readInternalModuleApi()._resolveFilename(request, parent, isMain)
|
|
389
|
+
} catch {
|
|
390
|
+
return undefined
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function tagCjsRefElementModuleExports(
|
|
395
|
+
moduleExports: unknown,
|
|
396
|
+
runtimeModule: RegisteredRefElementModuleState,
|
|
397
|
+
): Array<() => void> {
|
|
398
|
+
const cleanupHandlers: Array<() => void> = []
|
|
399
|
+
|
|
400
|
+
for (const exportName of runtimeModule.exportNames) {
|
|
401
|
+
const exportValue = readModuleExportValue(moduleExports, exportName)
|
|
402
|
+
const cleanupHandler = tagRefElementComponentForCleanup(exportValue, runtimeModule.refComponentType)
|
|
403
|
+
if (cleanupHandler) {
|
|
404
|
+
cleanupHandlers.push(cleanupHandler)
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return cleanupHandlers
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function mergeRefElementModules(runtimeModules: RefElementModule[]): RegisteredRefElementModuleState[] {
|
|
412
|
+
const registeredModulesByResolvedUrl = new Map<string, RegisteredRefElementModuleState>()
|
|
413
|
+
|
|
414
|
+
for (const runtimeModule of runtimeModules) {
|
|
415
|
+
upsertRefElementModule(registeredModulesByResolvedUrl, {
|
|
416
|
+
exportNames: [...runtimeModule.exportNames],
|
|
417
|
+
resolvedModuleUrl: runtimeModule.resolvedModuleUrl,
|
|
418
|
+
refComponentType: runtimeModule.refComponentType,
|
|
419
|
+
})
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return [...registeredModulesByResolvedUrl.values()]
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function upsertRefElementModule(
|
|
426
|
+
registeredModulesByResolvedUrl: Map<string, RegisteredRefElementModuleState>,
|
|
427
|
+
nextModule: RegisteredRefElementModuleState,
|
|
428
|
+
): RegisteredRefElementModuleState {
|
|
429
|
+
const existingModule = registeredModulesByResolvedUrl.get(nextModule.resolvedModuleUrl)
|
|
430
|
+
if (!existingModule) {
|
|
431
|
+
registeredModulesByResolvedUrl.set(nextModule.resolvedModuleUrl, nextModule)
|
|
432
|
+
return nextModule
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (existingModule.refComponentType !== nextModule.refComponentType) {
|
|
436
|
+
throw new Error(
|
|
437
|
+
`Conflicting ref-element types for ${nextModule.resolvedModuleUrl}: ` +
|
|
438
|
+
`${existingModule.refComponentType} vs ${nextModule.refComponentType}`,
|
|
439
|
+
)
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
existingModule.exportNames = [...new Set([...existingModule.exportNames, ...nextModule.exportNames])]
|
|
443
|
+
return existingModule
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function buildRefElementEntrySignature(registeredModules: RegisteredRefElementModuleState[]): string {
|
|
447
|
+
const stableSignatureInput = registeredModules
|
|
448
|
+
.map((registeredModule) => ({
|
|
449
|
+
exportNames: [...registeredModule.exportNames].sort(),
|
|
450
|
+
refComponentType: registeredModule.refComponentType,
|
|
451
|
+
resolvedModuleUrl: registeredModule.resolvedModuleUrl,
|
|
452
|
+
}))
|
|
453
|
+
.sort((leftModule, rightModule) => JSON.stringify(leftModule).localeCompare(JSON.stringify(rightModule)))
|
|
454
|
+
|
|
455
|
+
return createHash('sha1').update(JSON.stringify(stableSignatureInput)).digest('hex')
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function readModuleExportValue(moduleExports: unknown, exportName: string): unknown {
|
|
459
|
+
if (exportName === 'default') {
|
|
460
|
+
if (isComponent(moduleExports)) {
|
|
461
|
+
return moduleExports
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (typeof moduleExports === 'object' && moduleExports !== null) {
|
|
465
|
+
return (moduleExports as Record<string, unknown>).default
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return undefined
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (typeof moduleExports !== 'object' || moduleExports === null) {
|
|
472
|
+
return undefined
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return (moduleExports as Record<string, unknown>)[exportName]
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function isComponent(value: unknown): boolean {
|
|
479
|
+
if (typeof value === 'function') {
|
|
480
|
+
return true
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return typeof value === 'object' && value !== null && '$$typeof' in value
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function buildReactModuleShimSource(reactExportNames: string[]): string {
|
|
487
|
+
const reactExportLines = reactExportNames
|
|
488
|
+
.map((exportName) => {
|
|
489
|
+
if (exportName === 'createElement') {
|
|
490
|
+
return `export function createElement() {
|
|
491
|
+
var state = globalThis[STATE_KEY];
|
|
492
|
+
if (state && state.currentCreateElement) return state.currentCreateElement.apply(null, arguments);
|
|
493
|
+
return reactModule.createElement.apply(null, arguments);
|
|
494
|
+
}`
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return `export var ${exportName} = reactModule.${exportName};`
|
|
498
|
+
})
|
|
499
|
+
.join('\n')
|
|
500
|
+
|
|
501
|
+
return `
|
|
502
|
+
var REACT_KEY = Symbol.for(${JSON.stringify(REACT_ORIGINALS_KEY)});
|
|
503
|
+
var STATE_KEY = Symbol.for(${JSON.stringify(JSX_INTERCEPTOR_STATE_KEY)});
|
|
504
|
+
var reactModule = globalThis[REACT_KEY];
|
|
505
|
+
export default reactModule;
|
|
506
|
+
${reactExportLines}
|
|
507
|
+
`
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function buildJsxRuntimeInterceptorSource(): string {
|
|
511
|
+
return `
|
|
512
|
+
const ORIGINALS_KEY = Symbol.for(${JSON.stringify(JSX_ORIGINALS_KEY)});
|
|
513
|
+
const STATE_KEY = Symbol.for(${JSON.stringify(JSX_INTERCEPTOR_STATE_KEY)});
|
|
514
|
+
const originals = globalThis[ORIGINALS_KEY];
|
|
515
|
+
function getState() {
|
|
516
|
+
if (!globalThis[STATE_KEY]) {
|
|
517
|
+
globalThis[STATE_KEY] = {
|
|
518
|
+
currentJsx: originals.jsx, currentJsxs: originals.jsxs,
|
|
519
|
+
currentJsxDEV: originals.jsxDEV, isInsideOriginal: false,
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
return globalThis[STATE_KEY];
|
|
523
|
+
}
|
|
524
|
+
export const Fragment = originals.Fragment;
|
|
525
|
+
export function jsx(type, props, key) {
|
|
526
|
+
const state = getState();
|
|
527
|
+
if (state.isInsideOriginal) return originals.jsx(type, props, key);
|
|
528
|
+
state.isInsideOriginal = true;
|
|
529
|
+
try { return state.currentJsx(type, props, key); }
|
|
530
|
+
finally { state.isInsideOriginal = false; }
|
|
531
|
+
}
|
|
532
|
+
export function jsxs(type, props, key) {
|
|
533
|
+
const state = getState();
|
|
534
|
+
if (state.isInsideOriginal) return originals.jsxs(type, props, key);
|
|
535
|
+
state.isInsideOriginal = true;
|
|
536
|
+
try { return state.currentJsxs(type, props, key); }
|
|
537
|
+
finally { state.isInsideOriginal = false; }
|
|
538
|
+
}
|
|
539
|
+
export function jsxDEV(type, props, key, isStaticChildren, source, self) {
|
|
540
|
+
const state = getState();
|
|
541
|
+
if (state.isInsideOriginal) return originals.jsxDEV(type, props, key, isStaticChildren, source, self);
|
|
542
|
+
state.isInsideOriginal = true;
|
|
543
|
+
try { return state.currentJsxDEV(type, props, key, isStaticChildren, source, self); }
|
|
544
|
+
finally { state.isInsideOriginal = false; }
|
|
545
|
+
}
|
|
546
|
+
`
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function buildReactRuntimeLoaderSource(options: {
|
|
550
|
+
interceptorModuleUrl: string
|
|
551
|
+
reactModuleUrl: string
|
|
552
|
+
taggerSource: string
|
|
553
|
+
}): string {
|
|
554
|
+
return `
|
|
555
|
+
const INTERCEPTOR_URL = ${JSON.stringify(options.interceptorModuleUrl)};
|
|
556
|
+
const REACT_MODULE_URL = ${JSON.stringify(options.reactModuleUrl)};
|
|
557
|
+
const TAGGER_SOURCE = ${JSON.stringify(options.taggerSource)};
|
|
558
|
+
const REF_ELEMENT_ENTRY_SIGNATURE_QUERY_PARAM = ${JSON.stringify(REF_ELEMENT_ENTRY_SIGNATURE_QUERY_PARAM)};
|
|
559
|
+
const refElementModulesBySignature = Object.create(null);
|
|
560
|
+
export function initialize(data) {
|
|
561
|
+
data.port.on('message', (message) => {
|
|
562
|
+
try {
|
|
563
|
+
const registeredModulesByResolvedUrl = refElementModulesBySignature[message.signature] ?? Object.create(null);
|
|
564
|
+
Object.assign(registeredModulesByResolvedUrl, message.modulesByResolvedUrl);
|
|
565
|
+
refElementModulesBySignature[message.signature] = registeredModulesByResolvedUrl;
|
|
566
|
+
|
|
567
|
+
data.port.postMessage({ requestId: message.requestId, success: true });
|
|
568
|
+
} catch (error) {
|
|
569
|
+
data.port.postMessage({
|
|
570
|
+
error: error instanceof Error ? error.message : String(error),
|
|
571
|
+
requestId: message.requestId,
|
|
572
|
+
success: false,
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
function createTaggedRefElementModuleUrl(realUrl, moduleState) {
|
|
578
|
+
const realModuleImportUrl = new URL(realUrl);
|
|
579
|
+
realModuleImportUrl.searchParams.set('zero-config-ref-real', moduleState.refComponentType);
|
|
580
|
+
const namedExportLines = moduleState.exportNames
|
|
581
|
+
.filter((exportName) => exportName !== 'default')
|
|
582
|
+
.map((exportName) => \`export const \${exportName} = safelyTagRefElementComponent(realModule.\${exportName}, REF_COMPONENT_TYPE);\`)
|
|
583
|
+
.join('\\n');
|
|
584
|
+
const taggedModuleSource = \`
|
|
585
|
+
import * as realModule from \${JSON.stringify(realModuleImportUrl.href)};
|
|
586
|
+
const REF_COMPONENT_TYPE = \${JSON.stringify(moduleState.refComponentType)};
|
|
587
|
+
\${TAGGER_SOURCE}
|
|
588
|
+
export default safelyTagRefElementComponent(realModule.default ?? realModule, REF_COMPONENT_TYPE);
|
|
589
|
+
\${namedExportLines}
|
|
590
|
+
export * from \${JSON.stringify(realModuleImportUrl.href)};
|
|
591
|
+
\`;
|
|
592
|
+
|
|
593
|
+
return \`data:text/javascript,\${encodeURIComponent(taggedModuleSource)}\`;
|
|
594
|
+
}
|
|
595
|
+
function readRefElementEntrySignature(urlString) {
|
|
596
|
+
if (!urlString) {
|
|
597
|
+
return undefined;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
try {
|
|
601
|
+
const url = new URL(urlString);
|
|
602
|
+
return url.searchParams.get(REF_ELEMENT_ENTRY_SIGNATURE_QUERY_PARAM) ?? undefined;
|
|
603
|
+
} catch {
|
|
604
|
+
return undefined;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
function shouldPropagateRefElementEntrySignature(resolvedUrl) {
|
|
608
|
+
return resolvedUrl.startsWith('file:') && !resolvedUrl.includes('/node_modules/');
|
|
609
|
+
}
|
|
610
|
+
function addRefElementEntrySignature(resolvedUrl, refElementEntrySignature) {
|
|
611
|
+
const signedUrl = new URL(resolvedUrl);
|
|
612
|
+
if (!signedUrl.searchParams.has(REF_ELEMENT_ENTRY_SIGNATURE_QUERY_PARAM)) {
|
|
613
|
+
signedUrl.searchParams.set(REF_ELEMENT_ENTRY_SIGNATURE_QUERY_PARAM, refElementEntrySignature);
|
|
614
|
+
}
|
|
615
|
+
return signedUrl.href;
|
|
616
|
+
}
|
|
617
|
+
export async function resolve(specifier, context, nextResolve) {
|
|
618
|
+
if (specifier === 'react/jsx-runtime' || specifier === 'react/jsx-dev-runtime') {
|
|
619
|
+
return { shortCircuit: true, url: INTERCEPTOR_URL };
|
|
620
|
+
}
|
|
621
|
+
if (specifier === 'react') {
|
|
622
|
+
return { shortCircuit: true, url: REACT_MODULE_URL };
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const resolvedModule = await nextResolve(specifier, context);
|
|
626
|
+
if (new URL(resolvedModule.url).searchParams.has('zero-config-ref-real')) {
|
|
627
|
+
return resolvedModule;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
const refElementEntrySignature = readRefElementEntrySignature(context.parentURL);
|
|
631
|
+
if (!refElementEntrySignature) {
|
|
632
|
+
return resolvedModule;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const refElementModulesByResolvedUrl = refElementModulesBySignature[refElementEntrySignature] ?? Object.create(null);
|
|
636
|
+
const moduleState = refElementModulesByResolvedUrl[resolvedModule.url];
|
|
637
|
+
if (moduleState) {
|
|
638
|
+
return {
|
|
639
|
+
...resolvedModule,
|
|
640
|
+
shortCircuit: true,
|
|
641
|
+
url: createTaggedRefElementModuleUrl(resolvedModule.url, moduleState),
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
if (shouldPropagateRefElementEntrySignature(resolvedModule.url)) {
|
|
646
|
+
return {
|
|
647
|
+
...resolvedModule,
|
|
648
|
+
shortCircuit: true,
|
|
649
|
+
url: addRefElementEntrySignature(resolvedModule.url, refElementEntrySignature),
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
return resolvedModule;
|
|
654
|
+
}
|
|
655
|
+
`
|
|
656
|
+
}
|