@wix/zero-config-implementation 1.78.0 → 1.79.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 +4 -0
- package/dist/{index--6I20lGB.js → index-CoU1WRYf.js} +25036 -24768
- package/dist/{index-DAAmOM1c.js → index-DCJue8M7.js} +1 -1
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/src/converters/to-editor-component.ts +2 -2
- package/src/extensions/context-providers/context.test.ts +181 -0
- package/src/extensions/context-providers/context.ts +83 -0
- package/src/extensions/context-providers/mock-provider.test.ts +44 -0
- package/src/extensions/context-providers/mock-provider.ts +210 -0
- package/src/extensions/context-providers/types.ts +22 -0
- package/src/{ref-elements → extensions/ref-elements}/context.test.ts +4 -4
- package/src/extensions/ref-elements/context.ts +168 -0
- package/src/{ref-elements → extensions/ref-elements}/eligible-paths.ts +1 -1
- package/src/extensions/shared/ast-scanner.ts +85 -0
- package/src/extensions/shared/catalog-loader.ts +64 -0
- package/src/{ref-elements → extensions/shared}/module-resolution.test.ts +2 -2
- package/src/index.ts +108 -67
- package/src/information-extractors/react/extractors/prop-tracker.test.ts +2 -2
- package/src/information-extractors/react/extractors/prop-tracker.ts +3 -3
- package/src/information-extractors/react/utils/mock-generator.test.ts +107 -3
- package/src/information-extractors/react/utils/mock-generator.ts +104 -1
- package/src/manifest-pipeline.ts +4 -1
- package/src/module-loader.test.ts +1 -1
- package/src/module-loader.ts +1 -1
- package/src/react-runtime-loader.ts +28 -2
- package/src/ref-elements/context.ts +0 -280
- /package/src/{ref-elements → extensions/ref-elements}/component-tag.ts +0 -0
- /package/src/{ref-elements → extensions/ref-elements}/path-utils.test.ts +0 -0
- /package/src/{ref-elements → extensions/ref-elements}/path-utils.ts +0 -0
- /package/src/{ref-elements → extensions/ref-elements}/types.ts +0 -0
- /package/src/{ref-elements → extensions/shared}/module-resolution.ts +0 -0
- /package/src/{ref-elements/module-specifier.ts → extensions/shared/package-specifier.ts} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { createRefElementMarker, parseRefElementMarker } from '../../../ref-elements/path-utils'
|
|
1
|
+
import { beforeEach, describe, expect, it } from 'vitest'
|
|
2
|
+
import { createRefElementMarker, parseRefElementMarker } from '../../../extensions/ref-elements/path-utils'
|
|
3
3
|
import type { ComponentInfo } from '../../ts/types'
|
|
4
|
-
import { generateMockProps, resetMockCounter } from './mock-generator'
|
|
4
|
+
import { generateMockContextValues, generateMockProps, resetMockCounter } from './mock-generator'
|
|
5
5
|
|
|
6
6
|
describe('generateMockProps', () => {
|
|
7
7
|
it('injects a dedicated ref-element id marker into each eligible elementProps branch', () => {
|
|
@@ -129,3 +129,107 @@ describe('generateMockProps', () => {
|
|
|
129
129
|
expect(parseRefElementMarker(ctaProps.id)).toBe('card.elementProps.cta')
|
|
130
130
|
})
|
|
131
131
|
})
|
|
132
|
+
|
|
133
|
+
describe('generateMockContextValues', () => {
|
|
134
|
+
beforeEach(() => {
|
|
135
|
+
resetMockCounter()
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('returns an empty object for an empty schema', () => {
|
|
139
|
+
expect(generateMockContextValues({ items: {} })).toEqual({})
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('maps text to a string', () => {
|
|
143
|
+
const result = generateMockContextValues({ items: { title: { dataType: 'text' } } })
|
|
144
|
+
expect(typeof result.title).toBe('string')
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('maps number to a number', () => {
|
|
148
|
+
const result = generateMockContextValues({ items: { count: { dataType: 'number' } } })
|
|
149
|
+
expect(typeof result.count).toBe('number')
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it('maps boolean to a boolean', () => {
|
|
153
|
+
const result = generateMockContextValues({ items: { active: { dataType: 'boolean' } } })
|
|
154
|
+
expect(typeof result.active).toBe('boolean')
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('maps function to a function', () => {
|
|
158
|
+
const result = generateMockContextValues({ items: { onAction: { dataType: 'function' } } })
|
|
159
|
+
expect(typeof result.onAction).toBe('function')
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it('maps richText to an HTML paragraph string', () => {
|
|
163
|
+
const result = generateMockContextValues({ items: { body: { dataType: 'richText' } } })
|
|
164
|
+
expect(result.body as string).toMatch(/^<p>/)
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it('maps image to an image-shaped object', () => {
|
|
168
|
+
const result = generateMockContextValues({ items: { photo: { dataType: 'image' } } })
|
|
169
|
+
expect(result.photo).toMatchObject({
|
|
170
|
+
uri: expect.any(String),
|
|
171
|
+
url: expect.any(String),
|
|
172
|
+
width: expect.any(Number),
|
|
173
|
+
height: expect.any(Number),
|
|
174
|
+
})
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
it('maps link to a link-shaped object', () => {
|
|
178
|
+
const result = generateMockContextValues({ items: { destination: { dataType: 'link' } } })
|
|
179
|
+
expect(result.destination).toMatchObject({ href: expect.any(String), target: expect.any(String) })
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
it('maps data with a nested schema to a nested object', () => {
|
|
183
|
+
const result = generateMockContextValues({
|
|
184
|
+
items: {
|
|
185
|
+
event: {
|
|
186
|
+
dataType: 'data',
|
|
187
|
+
data: { items: { name: { dataType: 'text' } } },
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
})
|
|
191
|
+
expect(result.event).toMatchObject({ name: expect.any(String) })
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it('maps data with an empty schema to a permissive mock that handles Object.keys safely', () => {
|
|
195
|
+
const result = generateMockContextValues({
|
|
196
|
+
items: { event: { dataType: 'data', data: { items: {} } } },
|
|
197
|
+
})
|
|
198
|
+
expect(() => Object.keys(result.event as object)).not.toThrow()
|
|
199
|
+
expect(Object.keys(result.event as object)).toHaveLength(0)
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
it('returns a permissive fallback for undeclared context fields so deep access chains do not crash', () => {
|
|
203
|
+
const result = generateMockContextValues({ items: {} })
|
|
204
|
+
const undeclared = (result as Record<string, unknown>).anything
|
|
205
|
+
expect(() => Object.keys(undeclared as object)).not.toThrow()
|
|
206
|
+
expect(Object.keys(undeclared as object)).toHaveLength(0)
|
|
207
|
+
const deeplyNested = (undeclared as Record<string, unknown>).deeply
|
|
208
|
+
expect(() => (deeplyNested as Record<string, unknown>)?.nested).not.toThrow()
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
it('maps arrayItems with a nested schema to a single-element array containing the element object', () => {
|
|
212
|
+
const result = generateMockContextValues({
|
|
213
|
+
items: {
|
|
214
|
+
tickets: {
|
|
215
|
+
dataType: 'arrayItems',
|
|
216
|
+
arrayItems: { item: { dataType: 'data', data: { items: { price: { dataType: 'number' } } } } },
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
})
|
|
220
|
+
expect(result.tickets).toHaveLength(1)
|
|
221
|
+
expect((result.tickets as Record<string, unknown>[])[0]).toMatchObject({ price: expect.any(Number) })
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
it('maps arrayItems with no item schema to an empty array', () => {
|
|
225
|
+
const result = generateMockContextValues({
|
|
226
|
+
items: { tickets: { dataType: 'arrayItems', arrayItems: {} } },
|
|
227
|
+
})
|
|
228
|
+
expect(result.tickets).toEqual([])
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
it('falls back to a string for unknown dataTypes', () => {
|
|
232
|
+
const result = generateMockContextValues({ items: { something: { dataType: 'unknown-type' } } })
|
|
233
|
+
expect(typeof result.something).toBe('string')
|
|
234
|
+
})
|
|
235
|
+
})
|
|
@@ -4,7 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { faker } from '@faker-js/faker'
|
|
7
|
-
import {
|
|
7
|
+
import type { ContextSchema } from '../../../extensions/context-providers/types'
|
|
8
|
+
import {
|
|
9
|
+
createRefElementMarker,
|
|
10
|
+
extractElementPropsPathFromPropPath,
|
|
11
|
+
} from '../../../extensions/ref-elements/path-utils'
|
|
8
12
|
import type { ComponentInfo, DefaultValue, PropInfo, ResolvedType } from '../../ts/types'
|
|
9
13
|
|
|
10
14
|
export const PRESETS_WRAPPER_CLASS_NAME = 'mock-presets-wrapper-probe'
|
|
@@ -484,3 +488,102 @@ function generateMockMenuItems(): unknown[] {
|
|
|
484
488
|
link: generateMockLink(),
|
|
485
489
|
}))
|
|
486
490
|
}
|
|
491
|
+
|
|
492
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
493
|
+
// Context Schema Mock Generator
|
|
494
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* A recursive Proxy that safely handles any property access, function call, or string coercion.
|
|
498
|
+
* Used as a fallback for context fields not declared in the schema.
|
|
499
|
+
*/
|
|
500
|
+
function buildPermissiveMock(): unknown {
|
|
501
|
+
const mock: unknown = new Proxy((() => {}) as (...args: unknown[]) => unknown, {
|
|
502
|
+
get(_target, prop) {
|
|
503
|
+
if (prop === 'then') return undefined // prevent thenable detection
|
|
504
|
+
if (prop === Symbol.toPrimitive) return (_hint: string) => 'mock'
|
|
505
|
+
if (prop === Symbol.iterator) return function* () {}
|
|
506
|
+
if (prop === Symbol.asyncIterator) return async function* () {}
|
|
507
|
+
if (typeof prop === 'symbol') return undefined
|
|
508
|
+
return mock
|
|
509
|
+
},
|
|
510
|
+
ownKeys() {
|
|
511
|
+
return []
|
|
512
|
+
},
|
|
513
|
+
getOwnPropertyDescriptor() {
|
|
514
|
+
return undefined
|
|
515
|
+
},
|
|
516
|
+
apply() {
|
|
517
|
+
return mock
|
|
518
|
+
},
|
|
519
|
+
})
|
|
520
|
+
return mock
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Wraps schema-derived values in a Proxy so that any property NOT declared in the schema
|
|
525
|
+
* returns a permissive mock instead of undefined, preventing crashes on undeclared field access.
|
|
526
|
+
*/
|
|
527
|
+
function buildSchemaSeededProxy(schemaValues: Record<string, unknown>): Record<string, unknown> {
|
|
528
|
+
return new Proxy(schemaValues, {
|
|
529
|
+
get(target, prop) {
|
|
530
|
+
if (prop === 'then') return undefined // prevent thenable/Promise detection
|
|
531
|
+
if (typeof prop === 'symbol') return Reflect.get(target, prop)
|
|
532
|
+
if (Object.prototype.hasOwnProperty.call(target, prop)) return target[prop as string]
|
|
533
|
+
return buildPermissiveMock()
|
|
534
|
+
},
|
|
535
|
+
})
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/** Generate mock context values from a context extension schema. */
|
|
539
|
+
export function generateMockContextValues(schema: ContextSchema): Record<string, unknown> {
|
|
540
|
+
const result: Record<string, unknown> = {}
|
|
541
|
+
for (const [key, item] of Object.entries(schema.items)) {
|
|
542
|
+
result[key] = generateValueFromResolvedType(contextDataTypeToResolvedType(item), key, `context.${key}`)
|
|
543
|
+
}
|
|
544
|
+
return buildSchemaSeededProxy(result)
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function contextDataTypeToResolvedType(item: ContextSchema['items'][string]): ResolvedType {
|
|
548
|
+
switch (item.dataType) {
|
|
549
|
+
case 'text':
|
|
550
|
+
return { kind: 'primitive', value: 'string' }
|
|
551
|
+
case 'richText':
|
|
552
|
+
return { kind: 'semantic', value: 'RichText' }
|
|
553
|
+
case 'image':
|
|
554
|
+
return { kind: 'semantic', value: 'Image' }
|
|
555
|
+
case 'video':
|
|
556
|
+
return { kind: 'semantic', value: 'Video' }
|
|
557
|
+
case 'vectorArt':
|
|
558
|
+
return { kind: 'semantic', value: 'VectorArt' }
|
|
559
|
+
case 'link':
|
|
560
|
+
return { kind: 'semantic', value: 'Link' }
|
|
561
|
+
case 'function':
|
|
562
|
+
return { kind: 'function' }
|
|
563
|
+
case 'number':
|
|
564
|
+
return { kind: 'primitive', value: 'number' }
|
|
565
|
+
case 'boolean':
|
|
566
|
+
return { kind: 'primitive', value: 'boolean' }
|
|
567
|
+
case 'arrayItems': {
|
|
568
|
+
const itemEntry = item.arrayItems?.item
|
|
569
|
+
if (itemEntry) {
|
|
570
|
+
const itemValue = generateValueFromResolvedType(
|
|
571
|
+
contextDataTypeToResolvedType(itemEntry),
|
|
572
|
+
'item',
|
|
573
|
+
'context.arrayItem',
|
|
574
|
+
)
|
|
575
|
+
return { kind: 'literal', value: [itemValue] }
|
|
576
|
+
}
|
|
577
|
+
return { kind: 'literal', value: [] }
|
|
578
|
+
}
|
|
579
|
+
case 'data': {
|
|
580
|
+
const nestedSchema = item.data
|
|
581
|
+
if (nestedSchema?.items && Object.keys(nestedSchema.items).length > 0) {
|
|
582
|
+
return { kind: 'literal', value: generateMockContextValues(nestedSchema) }
|
|
583
|
+
}
|
|
584
|
+
return { kind: 'literal', value: buildPermissiveMock() }
|
|
585
|
+
}
|
|
586
|
+
default:
|
|
587
|
+
return { kind: 'primitive', value: 'string' }
|
|
588
|
+
}
|
|
589
|
+
}
|
package/src/manifest-pipeline.ts
CHANGED
|
@@ -15,10 +15,13 @@ import {
|
|
|
15
15
|
} from './information-extractors/react'
|
|
16
16
|
import type { CoupledComponentInfo, CoupledProp, DOMBinding, TrackingStores } from './information-extractors/react'
|
|
17
17
|
|
|
18
|
+
import {
|
|
19
|
+
extractElementPropsPathFromPropPath,
|
|
20
|
+
extractParentElementPropsPath,
|
|
21
|
+
} from './extensions/ref-elements/path-utils'
|
|
18
22
|
import { matchCssSelectors, parseCss } from './information-extractors/css'
|
|
19
23
|
import type { CSSParserAPI } from './information-extractors/css'
|
|
20
24
|
import { compileSass } from './information-extractors/css/sass-adapter'
|
|
21
|
-
import { extractElementPropsPathFromPropPath, extractParentElementPropsPath } from './ref-elements/path-utils'
|
|
22
25
|
|
|
23
26
|
import type { ComponentType } from 'react'
|
|
24
27
|
|
|
@@ -2,9 +2,9 @@ import { createRequire } from 'node:module'
|
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import { pathToFileURL } from 'node:url'
|
|
4
4
|
import { describe, expect, it } from 'vitest'
|
|
5
|
+
import { readRefElementComponentType } from './extensions/ref-elements/component-tag'
|
|
5
6
|
import { loadCjsModule, loadModuleForExtraction } from './module-loader'
|
|
6
7
|
import { readLoaderPortRefStateForTests, readRegisteredRefElementModuleUrlsForTests } from './react-runtime-loader'
|
|
7
|
-
import { readRefElementComponentType } from './ref-elements/component-tag'
|
|
8
8
|
|
|
9
9
|
const require = createRequire(import.meta.url)
|
|
10
10
|
|
package/src/module-loader.ts
CHANGED
|
@@ -4,8 +4,8 @@ import React from 'react'
|
|
|
4
4
|
import type { ComponentType } from 'react'
|
|
5
5
|
import ReactDOM from 'react-dom'
|
|
6
6
|
|
|
7
|
+
import type { RefElementModule } from './extensions/ref-elements/types'
|
|
7
8
|
import { ensureReactRuntimeLoader, loadTaggedCjsModule, prepareRefElementEsmImport } from './react-runtime-loader'
|
|
8
|
-
import type { RefElementModule } from './ref-elements/types'
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Structured failure from `loadModule` when both ESM import and CJS require fail.
|
|
@@ -2,8 +2,8 @@ import { createHash } from 'node:crypto'
|
|
|
2
2
|
import { createRequire, register } from 'node:module'
|
|
3
3
|
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
4
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'
|
|
5
|
+
import { buildRefElementTaggerSource, tagRefElementComponentForCleanup } from './extensions/ref-elements/component-tag'
|
|
6
|
+
import type { RefElementModule } from './extensions/ref-elements/types'
|
|
7
7
|
|
|
8
8
|
const JSX_INTERCEPTOR_STATE_KEY = 'zero-config:jsx-interceptor'
|
|
9
9
|
const JSX_ORIGINALS_KEY = 'zero-config:jsx-originals'
|
|
@@ -68,6 +68,32 @@ interface RefElementRuntimeState {
|
|
|
68
68
|
originalModuleLoad: InternalModuleApi['_load'] | null
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Returns the URL that the ESM loader will assign to a context provider module
|
|
73
|
+
* when it is imported transitively from a ref-entry-signed statics bundle.
|
|
74
|
+
*
|
|
75
|
+
* When runtimeModules is non-empty, the statics bundle entry URL carries a
|
|
76
|
+
* `?zero-config-ref-entry=HASH` signature, and `shouldPropagateRefElementEntrySignature`
|
|
77
|
+
* propagates that hash to every non-node_modules file: import. `captureHookContext`
|
|
78
|
+
* must import the provider at the same signed URL so both paths hit the same Node.js
|
|
79
|
+
* ESM cache entry (and therefore the same `createContext` return value).
|
|
80
|
+
*
|
|
81
|
+
* When runtimeModules is empty, no hash is ever appended, so the plain URL is correct.
|
|
82
|
+
*/
|
|
83
|
+
export function computeSignedContextProviderUrl(resolvedModuleUrl: string, runtimeModules: RefElementModule[]): string {
|
|
84
|
+
if (runtimeModules.length === 0) {
|
|
85
|
+
return resolvedModuleUrl
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const registeredModules = mergeRefElementModules(runtimeModules)
|
|
89
|
+
const signature = buildRefElementEntrySignature(registeredModules)
|
|
90
|
+
const signedUrl = new URL(resolvedModuleUrl)
|
|
91
|
+
if (!signedUrl.searchParams.has(REF_ELEMENT_ENTRY_SIGNATURE_QUERY_PARAM)) {
|
|
92
|
+
signedUrl.searchParams.set(REF_ELEMENT_ENTRY_SIGNATURE_QUERY_PARAM, signature)
|
|
93
|
+
}
|
|
94
|
+
return signedUrl.href
|
|
95
|
+
}
|
|
96
|
+
|
|
71
97
|
export async function prepareRefElementEsmImport(
|
|
72
98
|
entryPath: string,
|
|
73
99
|
runtimeModules: RefElementModule[] = [],
|
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
import ts from 'typescript'
|
|
2
|
-
import type { ComponentInfo } from '../information-extractors/ts/types'
|
|
3
|
-
import { extractEligibleRefElementPaths } from './eligible-paths'
|
|
4
|
-
import { resolveModuleUrlFromEntryPath } from './module-resolution'
|
|
5
|
-
import { extractBasePackageName, extractModuleLeafExportName } from './module-specifier'
|
|
6
|
-
import type { RefElementContext, RefElementModule } from './types'
|
|
7
|
-
|
|
8
|
-
interface RawRefElementExtensionEntry {
|
|
9
|
-
options?: {
|
|
10
|
-
type?: string
|
|
11
|
-
resources?: {
|
|
12
|
-
client?: {
|
|
13
|
-
moduleSpecifier?: string
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
type?: string
|
|
18
|
-
resources?: {
|
|
19
|
-
client?: {
|
|
20
|
-
moduleSpecifier?: string
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
interface NormalizedRefElementCatalogEntry {
|
|
26
|
-
moduleSpecifier: string
|
|
27
|
-
refComponentType: string
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export async function buildRefElementContext(
|
|
31
|
-
program: ts.Program,
|
|
32
|
-
sourceFilePath: string,
|
|
33
|
-
componentInfo: ComponentInfo,
|
|
34
|
-
compiledEntryPath: string,
|
|
35
|
-
): Promise<RefElementContext> {
|
|
36
|
-
const importedModuleSpecifiers = collectImportedRefElementModuleSpecifiers(program, sourceFilePath)
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
eligiblePaths: extractEligibleRefElementPaths(componentInfo),
|
|
40
|
-
runtimeModules: await resolveRuntimeModules(importedModuleSpecifiers, compiledEntryPath),
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async function resolveRuntimeModules(
|
|
45
|
-
importedModuleSpecifiers: string[],
|
|
46
|
-
compiledEntryPath: string,
|
|
47
|
-
): Promise<RefElementModule[]> {
|
|
48
|
-
if (importedModuleSpecifiers.length === 0) {
|
|
49
|
-
return []
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const importedModuleSpecifierSet = new Set(importedModuleSpecifiers)
|
|
53
|
-
const importedPackageNames = [
|
|
54
|
-
...new Set(importedModuleSpecifiers.filter(isPackageModuleSpecifier).map(extractBasePackageName)),
|
|
55
|
-
]
|
|
56
|
-
const catalogEntries = (
|
|
57
|
-
await Promise.all(importedPackageNames.map((packageName) => loadCatalogEntries(packageName, compiledEntryPath)))
|
|
58
|
-
).flat()
|
|
59
|
-
|
|
60
|
-
const runtimeModulesBySpecifier = new Map<string, Set<string>>()
|
|
61
|
-
|
|
62
|
-
for (const catalogEntry of catalogEntries) {
|
|
63
|
-
if (!importedModuleSpecifierSet.has(catalogEntry.moduleSpecifier)) {
|
|
64
|
-
continue
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const refComponentTypes = runtimeModulesBySpecifier.get(catalogEntry.moduleSpecifier) ?? new Set<string>()
|
|
68
|
-
refComponentTypes.add(catalogEntry.refComponentType)
|
|
69
|
-
runtimeModulesBySpecifier.set(catalogEntry.moduleSpecifier, refComponentTypes)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const runtimeModules: RefElementModule[] = []
|
|
73
|
-
|
|
74
|
-
for (const [moduleSpecifier, refComponentTypes] of runtimeModulesBySpecifier.entries()) {
|
|
75
|
-
if (refComponentTypes.size !== 1) {
|
|
76
|
-
continue
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const refComponentType = [...refComponentTypes][0]
|
|
80
|
-
if (!refComponentType) {
|
|
81
|
-
continue
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const resolvedModuleUrl = resolveModuleUrlFromEntryPath(compiledEntryPath, moduleSpecifier)
|
|
85
|
-
if (!resolvedModuleUrl) {
|
|
86
|
-
continue
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
runtimeModules.push({
|
|
90
|
-
exportNames: buildRefElementExportNames(moduleSpecifier),
|
|
91
|
-
moduleSpecifier,
|
|
92
|
-
resolvedModuleUrl,
|
|
93
|
-
refComponentType,
|
|
94
|
-
})
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return runtimeModules
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function isPackageModuleSpecifier(moduleSpecifier: string): boolean {
|
|
101
|
-
return !moduleSpecifier.startsWith('.') && !moduleSpecifier.startsWith('/') && !moduleSpecifier.startsWith('file:')
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function collectImportedRefElementModuleSpecifiers(program: ts.Program, sourceFilePath: string): string[] {
|
|
105
|
-
const entrySourceFile = findSourceFile(program, sourceFilePath)
|
|
106
|
-
if (!entrySourceFile) {
|
|
107
|
-
return []
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const importedModuleSpecifiers = new Set<string>()
|
|
111
|
-
const visitedSourceFiles = new Set<string>()
|
|
112
|
-
|
|
113
|
-
function visitSourceFile(sourceFile: ts.SourceFile): void {
|
|
114
|
-
if (visitedSourceFiles.has(sourceFile.fileName)) {
|
|
115
|
-
return
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
visitedSourceFiles.add(sourceFile.fileName)
|
|
119
|
-
|
|
120
|
-
for (const statement of sourceFile.statements) {
|
|
121
|
-
const moduleSpecifier = readReferencedModuleSpecifier(statement)
|
|
122
|
-
if (!moduleSpecifier) {
|
|
123
|
-
continue
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (referencesRefElementModule(statement, moduleSpecifier)) {
|
|
127
|
-
importedModuleSpecifiers.add(moduleSpecifier)
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const importedSourceFile = resolveImportedSourceFile(program, sourceFile.fileName, moduleSpecifier)
|
|
131
|
-
if (importedSourceFile) {
|
|
132
|
-
visitSourceFile(importedSourceFile)
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
visitSourceFile(entrySourceFile)
|
|
138
|
-
return [...importedModuleSpecifiers]
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
async function loadCatalogEntries(
|
|
142
|
-
packageName: string,
|
|
143
|
-
compiledEntryPath: string,
|
|
144
|
-
): Promise<NormalizedRefElementCatalogEntry[]> {
|
|
145
|
-
const resolvedExtensionsUrl = resolveModuleUrlFromEntryPath(compiledEntryPath, `${packageName}/extensions`)
|
|
146
|
-
if (!resolvedExtensionsUrl) {
|
|
147
|
-
return []
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const extensionModule = (await import(resolvedExtensionsUrl)) as Record<string, unknown>
|
|
151
|
-
|
|
152
|
-
return extractRawExtensionEntries(extensionModule)
|
|
153
|
-
.map(normalizeExtensionEntry)
|
|
154
|
-
.flatMap((catalogEntry) => (catalogEntry ? [catalogEntry] : []))
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function referencesRefElementModule(statement: ts.Statement, moduleSpecifier: string): boolean {
|
|
158
|
-
const moduleLeafExportName = extractModuleLeafExportName(moduleSpecifier)
|
|
159
|
-
if (!moduleLeafExportName) {
|
|
160
|
-
return false
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
if (ts.isImportDeclaration(statement)) {
|
|
164
|
-
return importsRefElementModule(statement, moduleLeafExportName)
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (ts.isExportDeclaration(statement)) {
|
|
168
|
-
return reExportsRefElementModule(statement, moduleLeafExportName)
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
return false
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function importsRefElementModule(importDeclaration: ts.ImportDeclaration, moduleLeafExportName: string): boolean {
|
|
175
|
-
const importClause = importDeclaration.importClause
|
|
176
|
-
if (!importClause) {
|
|
177
|
-
return false
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
if (importClause.name) {
|
|
181
|
-
return true
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (!importClause.namedBindings || !ts.isNamedImports(importClause.namedBindings)) {
|
|
185
|
-
return false
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
return importClause.namedBindings.elements.some((importSpecifier) => {
|
|
189
|
-
const importedName = importSpecifier.propertyName?.text ?? importSpecifier.name.text
|
|
190
|
-
return importedName === moduleLeafExportName
|
|
191
|
-
})
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function reExportsRefElementModule(exportDeclaration: ts.ExportDeclaration, moduleLeafExportName: string): boolean {
|
|
195
|
-
if (!exportDeclaration.exportClause || !ts.isNamedExports(exportDeclaration.exportClause)) {
|
|
196
|
-
return false
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
return exportDeclaration.exportClause.elements.some((exportSpecifier) => {
|
|
200
|
-
const exportedSourceName = exportSpecifier.propertyName?.text ?? exportSpecifier.name.text
|
|
201
|
-
return exportedSourceName === 'default' || exportedSourceName === moduleLeafExportName
|
|
202
|
-
})
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function extractRawExtensionEntries(extensionModule: Record<string, unknown>): RawRefElementExtensionEntry[] {
|
|
206
|
-
const defaultExport = extensionModule.default
|
|
207
|
-
const extensionEntries = Array.isArray(defaultExport)
|
|
208
|
-
? defaultExport
|
|
209
|
-
: ((defaultExport as { extensions?: unknown[] } | undefined)?.extensions ?? extensionModule.extensions)
|
|
210
|
-
|
|
211
|
-
return Array.isArray(extensionEntries) ? (extensionEntries as RawRefElementExtensionEntry[]) : []
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function normalizeExtensionEntry(rawEntry: RawRefElementExtensionEntry): NormalizedRefElementCatalogEntry | undefined {
|
|
215
|
-
const normalizedEntry = rawEntry.options ?? rawEntry
|
|
216
|
-
const moduleSpecifier = normalizedEntry.resources?.client?.moduleSpecifier
|
|
217
|
-
const refComponentType = normalizedEntry.type
|
|
218
|
-
|
|
219
|
-
if (!moduleSpecifier || !refComponentType) {
|
|
220
|
-
return undefined
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
return {
|
|
224
|
-
moduleSpecifier,
|
|
225
|
-
refComponentType,
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function buildRefElementExportNames(moduleSpecifier: string): string[] {
|
|
230
|
-
const exportNames = new Set<string>(['default'])
|
|
231
|
-
if (extractBasePackageName(moduleSpecifier) === moduleSpecifier) {
|
|
232
|
-
return [...exportNames]
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
const moduleLeafName = extractModuleLeafExportName(moduleSpecifier)
|
|
236
|
-
if (moduleLeafName) {
|
|
237
|
-
exportNames.add(moduleLeafName)
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
return [...exportNames]
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
function findSourceFile(program: ts.Program, sourceFilePath: string): ts.SourceFile | undefined {
|
|
244
|
-
return (
|
|
245
|
-
program.getSourceFile(sourceFilePath) ??
|
|
246
|
-
program.getSourceFiles().find((sourceFile) => sourceFile.fileName === sourceFilePath)
|
|
247
|
-
)
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function resolveImportedSourceFile(
|
|
251
|
-
program: ts.Program,
|
|
252
|
-
importingFilePath: string,
|
|
253
|
-
moduleSpecifier: string,
|
|
254
|
-
): ts.SourceFile | undefined {
|
|
255
|
-
const resolvedModule = ts.resolveModuleName(moduleSpecifier, importingFilePath, program.getCompilerOptions(), ts.sys)
|
|
256
|
-
const resolvedFileName = resolvedModule.resolvedModule?.resolvedFileName
|
|
257
|
-
|
|
258
|
-
if (!resolvedFileName) {
|
|
259
|
-
return undefined
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const resolvedSourceFile = findSourceFile(program, resolvedFileName)
|
|
263
|
-
if (!resolvedSourceFile || resolvedSourceFile.isDeclarationFile) {
|
|
264
|
-
return undefined
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
return resolvedSourceFile
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
function readReferencedModuleSpecifier(statement: ts.Statement): string | undefined {
|
|
271
|
-
if (ts.isImportDeclaration(statement) && ts.isStringLiteral(statement.moduleSpecifier)) {
|
|
272
|
-
return statement.moduleSpecifier.text
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
if (ts.isExportDeclaration(statement) && statement.moduleSpecifier && ts.isStringLiteral(statement.moduleSpecifier)) {
|
|
276
|
-
return statement.moduleSpecifier.text
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
return undefined
|
|
280
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|