@wix/zero-config-implementation 1.6.0 → 1.8.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/converters/utils.d.ts +1 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.js +25891 -37144
- package/dist/information-extractors/react/extractors/core/index.d.ts +1 -1
- package/dist/information-extractors/react/extractors/core/runner.d.ts +5 -1
- package/dist/information-extractors/react/extractors/index.d.ts +1 -1
- package/dist/information-extractors/react/index.d.ts +1 -1
- package/dist/manifest-pipeline.d.ts +2 -2
- package/dist/module-loader.d.ts +20 -0
- package/dist/schema.d.ts +1 -0
- package/package.json +2 -5
- package/src/converters/data-item-builder.ts +4 -5
- package/src/converters/utils.test.ts +16 -0
- package/src/converters/utils.ts +8 -0
- package/src/index.ts +9 -5
- package/src/information-extractors/react/extractors/core/index.ts +1 -1
- package/src/information-extractors/react/extractors/core/runner.ts +10 -1
- package/src/information-extractors/react/extractors/index.ts +1 -0
- package/src/information-extractors/react/index.ts +1 -0
- package/src/information-extractors/ts/components.ts +13 -1
- package/src/information-extractors/ts/utils/semantic-type-resolver.ts +2 -4
- package/src/manifest-pipeline.ts +3 -1
- package/src/module-loader.ts +76 -0
- package/src/schema.ts +33 -1
- package/vite.config.ts +6 -0
- package/dist/component-loader.d.ts +0 -42
- package/src/component-loader.test.ts +0 -277
- package/src/component-loader.ts +0 -256
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { ExtractorStore } from './store';
|
|
2
2
|
export { runExtractors } from './runner';
|
|
3
|
-
export type { ExtractionResult } from './runner';
|
|
3
|
+
export type { ExtractionResult, RunExtractorsOptions } from './runner';
|
|
4
4
|
export { buildElementTree } from './tree-builder';
|
|
5
5
|
export type { ExtractedElement } from './tree-builder';
|
|
6
6
|
export type { ReactExtractor, RenderContext, CreateElementEvent, RenderCompleteEvent } from './types';
|
|
@@ -8,6 +8,10 @@ export interface ExtractionResult {
|
|
|
8
8
|
store: ExtractorStore;
|
|
9
9
|
elements: ExtractedElement[];
|
|
10
10
|
}
|
|
11
|
+
export interface RunExtractorsOptions {
|
|
12
|
+
/** Optional HOC to wrap the component before rendering (e.g. a context provider). */
|
|
13
|
+
wrapper?: (component: ComponentType<unknown>) => ComponentType<unknown>;
|
|
14
|
+
}
|
|
11
15
|
/**
|
|
12
16
|
* Runs extractors through the full lifecycle and returns extraction results.
|
|
13
17
|
*
|
|
@@ -16,4 +20,4 @@ export interface ExtractionResult {
|
|
|
16
20
|
* @param extractors - Array of extractors to run
|
|
17
21
|
* @returns Extraction results including HTML, store, and element tree
|
|
18
22
|
*/
|
|
19
|
-
export declare function runExtractors(componentInfo: ComponentInfo, component: ComponentType<unknown>, extractors: ReactExtractor[]): ExtractionResult;
|
|
23
|
+
export declare function runExtractors(componentInfo: ComponentInfo, component: ComponentType<unknown>, extractors: ReactExtractor[], options?: RunExtractorsOptions): ExtractionResult;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Core infrastructure and pluggable extractors for the React information extraction system.
|
|
5
5
|
*/
|
|
6
6
|
export { ExtractorStore, runExtractors, buildElementTree, } from './core';
|
|
7
|
-
export type { ExtractionResult, ExtractedElement, ReactExtractor, RenderContext, CreateElementEvent, RenderCompleteEvent, } from './core';
|
|
7
|
+
export type { ExtractionResult, RunExtractorsOptions, ExtractedElement, ReactExtractor, RenderContext, CreateElementEvent, RenderCompleteEvent, } from './core';
|
|
8
8
|
export { createPropTrackerExtractor } from './prop-tracker';
|
|
9
9
|
export type { PropTrackerData, PropTrackerExtractorState } from './prop-tracker';
|
|
10
10
|
export { createCssPropertiesExtractor } from './css-properties';
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
* API: runExtractors() with pluggable extractors
|
|
5
5
|
*/
|
|
6
6
|
export { ExtractorStore, runExtractors, buildElementTree, createPropTrackerExtractor, createCssPropertiesExtractor, } from './extractors';
|
|
7
|
-
export type { ExtractionResult, ExtractedElement, ReactExtractor, RenderContext, CreateElementEvent, RenderCompleteEvent, PropTrackerData, PropTrackerExtractorState, CssPropertiesData, } from './extractors';
|
|
7
|
+
export type { ExtractionResult, RunExtractorsOptions, ExtractedElement, ReactExtractor, RenderContext, CreateElementEvent, RenderCompleteEvent, PropTrackerData, PropTrackerExtractorState, CssPropertiesData, } from './extractors';
|
|
8
8
|
export type { CoupledComponentInfo, CoupledProp, TrackingStores, DOMBinding, PropReadInfo, PropWriteInfo, PropSpyMeta, } from './types';
|
|
9
9
|
export type { PropSpyContext } from './utils/prop-spy';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ComponentInfo } from './information-extractors/ts';
|
|
2
|
-
import { CoupledComponentInfo } from './information-extractors/react';
|
|
2
|
+
import { RunExtractorsOptions, CoupledComponentInfo } from './information-extractors/react';
|
|
3
3
|
import { CSSParserAPI } from './information-extractors/css';
|
|
4
4
|
import { ComponentType } from 'react';
|
|
5
5
|
/** A non-fatal issue encountered during component processing. */
|
|
@@ -30,4 +30,4 @@ export interface ProcessComponentResult {
|
|
|
30
30
|
* encountered during processing. Always returns a component, falling back
|
|
31
31
|
* to minimal info (without DOM coupling) when rendering fails.
|
|
32
32
|
*/
|
|
33
|
-
export declare function processComponent(componentInfo: ComponentInfo, loadComponent: (componentName: string) => ComponentType<unknown> | null, cssImportPaths: string[], loaderHasError?: boolean): ProcessComponentResult;
|
|
33
|
+
export declare function processComponent(componentInfo: ComponentInfo, loadComponent: (componentName: string) => ComponentType<unknown> | null, cssImportPaths: string[], loaderHasError?: boolean, options?: RunExtractorsOptions): ProcessComponentResult;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ResultAsync } from 'neverthrow';
|
|
2
|
+
import { ComponentType } from 'react';
|
|
3
|
+
import { IoError } from './errors';
|
|
4
|
+
type IoErrorInstance = InstanceType<typeof IoError>;
|
|
5
|
+
/**
|
|
6
|
+
* Attempts to load a module, first via ESM `import()`, then via CJS `require`.
|
|
7
|
+
*
|
|
8
|
+
* ESM `import()` is preferred because it participates in the ESM loader hook
|
|
9
|
+
* pipeline (registered via `module.register()` in the CLI). This is required
|
|
10
|
+
* for JSX interception to work — the loader hook redirects `react/jsx-runtime`
|
|
11
|
+
* to the interceptable version. CJS `require()` bypasses ESM hooks even when
|
|
12
|
+
* loading ESM modules (Node 22+), so it's only used as a fallback.
|
|
13
|
+
*
|
|
14
|
+
* @param entryPath - Absolute path to the module entry point.
|
|
15
|
+
* @returns A `ResultAsync` containing the module exports on success.
|
|
16
|
+
* @errors {IoError} When both ESM import and CJS require fail.
|
|
17
|
+
*/
|
|
18
|
+
export declare function loadModule(entryPath: string): ResultAsync<Record<string, unknown>, IoErrorInstance>;
|
|
19
|
+
export declare function findComponent(moduleExports: Record<string, unknown>, name: string): ComponentType<unknown> | null;
|
|
20
|
+
export {};
|
package/dist/schema.d.ts
CHANGED
|
@@ -77,6 +77,7 @@ export declare const DATA: {
|
|
|
77
77
|
readonly edgeAnchorLinks: "edgeAnchorLinks";
|
|
78
78
|
readonly loginToWixLink: "loginToWixLink";
|
|
79
79
|
};
|
|
80
|
+
WIX_TYPE_TO_DATA_TYPE: Record<string, "number" | "function" | "UNKNOWN_DataType" | "text" | "textEnum" | "booleanValue" | "a11y" | "link" | "image" | "video" | "vectorArt" | "audio" | "schema" | "localDate" | "localTime" | "localDateTime" | "webUrl" | "email" | "phone" | "hostname" | "regex" | "guid" | "richText" | "container" | "arrayItems" | "direction" | "menuItems" | "data" | "onClick" | "onChange" | "onKeyPress" | "onKeyUp" | "onSubmit">;
|
|
80
81
|
};
|
|
81
82
|
export declare const MEDIA: {
|
|
82
83
|
VIDEO_CATEGORY: {
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"registry": "https://registry.npmjs.org/",
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
7
|
-
"version": "1.
|
|
7
|
+
"version": "1.8.0",
|
|
8
8
|
"description": "Core library for extracting component manifests from JS and CSS files",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"main": "dist/index.js",
|
|
@@ -50,14 +50,11 @@
|
|
|
50
50
|
"modern-errors-cli": "^6.0.0",
|
|
51
51
|
"neverthrow": "^8.2.0",
|
|
52
52
|
"parse5": "^7.1.2",
|
|
53
|
-
"pkg-entry-points": "^1.1.1",
|
|
54
53
|
"react-docgen-typescript": "^2.4.0",
|
|
55
|
-
"read-package-up": "^11.0.0",
|
|
56
54
|
"tsconfck": "^3.1.6",
|
|
57
55
|
"typedoc": "^0.28.16",
|
|
58
56
|
"typedoc-plugin-markdown": "^4.9.0",
|
|
59
57
|
"vite": "^7.3.1",
|
|
60
|
-
"vite-node": "^5.3.0",
|
|
61
58
|
"vite-plugin-dts": "^4.5.4",
|
|
62
59
|
"vitest": "^4.0.17"
|
|
63
60
|
},
|
|
@@ -77,5 +74,5 @@
|
|
|
77
74
|
]
|
|
78
75
|
}
|
|
79
76
|
},
|
|
80
|
-
"falconPackageHash": "
|
|
77
|
+
"falconPackageHash": "8ca973dc7cc82c6e8d0a333de6717b866e33337ee211bd335f3fa800"
|
|
81
78
|
}
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
* resolved type metadata is inconsistent (kind === 'array' but no elementType).
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import { camelCase } from 'case-anything'
|
|
16
15
|
import type { Result } from 'neverthrow'
|
|
17
16
|
import { err, ok } from 'neverthrow'
|
|
18
17
|
import { ParseError } from '../errors'
|
|
@@ -21,7 +20,7 @@ import type { DataItem } from '../schema'
|
|
|
21
20
|
import { DATA, MEDIA } from '../schema'
|
|
22
21
|
import { formatDisplayName } from './utils'
|
|
23
22
|
|
|
24
|
-
const { DATA_TYPE } = DATA
|
|
23
|
+
const { DATA_TYPE, WIX_TYPE_TO_DATA_TYPE } = DATA
|
|
25
24
|
|
|
26
25
|
type ParseErrorInstance = InstanceType<typeof ParseError>
|
|
27
26
|
|
|
@@ -279,10 +278,10 @@ function handleSemanticType(dataItem: DataItem, resolvedType: ResolvedType): voi
|
|
|
279
278
|
return
|
|
280
279
|
}
|
|
281
280
|
|
|
282
|
-
// Wix public-schemas (Builder) types - map directly to DATA_TYPE via
|
|
281
|
+
// Wix public-schemas (Builder) types - map directly to DATA_TYPE via explicit lookup
|
|
283
282
|
if (source === '@wix/public-schemas') {
|
|
284
|
-
const dataTypeKey =
|
|
285
|
-
if (dataTypeKey
|
|
283
|
+
const dataTypeKey = WIX_TYPE_TO_DATA_TYPE[semanticValue]
|
|
284
|
+
if (dataTypeKey) {
|
|
286
285
|
dataItem.dataType = DATA_TYPE[dataTypeKey]
|
|
287
286
|
applyDataToBuilderType(dataItem, dataTypeKey)
|
|
288
287
|
} else {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { formatDisplayName } from './utils'
|
|
3
|
+
|
|
4
|
+
describe('formatDisplayName', () => {
|
|
5
|
+
it('maps a11y to Accessibility', () => {
|
|
6
|
+
expect(formatDisplayName('a11y')).toBe('Accessibility')
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
it('formats kebab-case', () => {
|
|
10
|
+
expect(formatDisplayName('input-field-weight')).toBe('Input Field Weight')
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('formats camelCase', () => {
|
|
14
|
+
expect(formatDisplayName('camelCaseExample')).toBe('Camel Case Example')
|
|
15
|
+
})
|
|
16
|
+
})
|
package/src/converters/utils.ts
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
import { capitalCase } from 'case-anything'
|
|
6
6
|
|
|
7
|
+
const DISPLAY_NAME_OVERRIDES = {
|
|
8
|
+
a11y: 'Accessibility',
|
|
9
|
+
} as const
|
|
10
|
+
|
|
7
11
|
/**
|
|
8
12
|
* Formats any string format to Title Case display name
|
|
9
13
|
* Handles: kebab-case, camelCase, PascalCase, snake_case, SCREAMING_SNAKE_CASE, and mixed formats
|
|
@@ -15,7 +19,11 @@ import { capitalCase } from 'case-anything'
|
|
|
15
19
|
* "snake_case_example" -> "Snake Case Example"
|
|
16
20
|
* "SCREAMING_SNAKE_CASE" -> "Screaming Snake Case"
|
|
17
21
|
* "mixed-format_example" -> "Mixed Format Example"
|
|
22
|
+
* "a11y" -> "Accessibility"
|
|
18
23
|
*/
|
|
19
24
|
export function formatDisplayName(input: string): string {
|
|
25
|
+
if (input in DISPLAY_NAME_OVERRIDES) {
|
|
26
|
+
return DISPLAY_NAME_OVERRIDES[input as keyof typeof DISPLAY_NAME_OVERRIDES]
|
|
27
|
+
}
|
|
20
28
|
return capitalCase(input, { keepSpecialCharacters: false })
|
|
21
29
|
}
|
package/src/index.ts
CHANGED
|
@@ -11,8 +11,9 @@ import { compileTsFile } from './ts-compiler'
|
|
|
11
11
|
import { type NotFoundError, ParseError } from './errors'
|
|
12
12
|
|
|
13
13
|
// Component loader helpers
|
|
14
|
-
import { findComponent, loadModule } from './
|
|
14
|
+
import { findComponent, loadModule } from './module-loader'
|
|
15
15
|
|
|
16
|
+
import type { RunExtractorsOptions } from './information-extractors/react'
|
|
16
17
|
// Pipeline orchestration
|
|
17
18
|
import { processComponent } from './manifest-pipeline'
|
|
18
19
|
export type {
|
|
@@ -36,6 +37,8 @@ export interface ManifestResult {
|
|
|
36
37
|
errors: ExtractionError[]
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
export type { RunExtractorsOptions } from './information-extractors/react'
|
|
41
|
+
|
|
39
42
|
export interface ExtractionError {
|
|
40
43
|
componentName: string
|
|
41
44
|
phase: 'render' | 'coupling' | 'css' | 'loader' | 'conversion'
|
|
@@ -59,6 +62,7 @@ export interface ExtractionError {
|
|
|
59
62
|
export function extractComponentManifest(
|
|
60
63
|
componentPath: string,
|
|
61
64
|
compiledEntryPath: string,
|
|
65
|
+
options?: RunExtractorsOptions,
|
|
62
66
|
): ResultAsync<ManifestResult, InstanceType<typeof NotFoundError> | InstanceType<typeof ParseError>> {
|
|
63
67
|
// Step 1: Load the compiled package module (non-fatal)
|
|
64
68
|
return loadModule(compiledEntryPath)
|
|
@@ -104,7 +108,7 @@ export function extractComponentManifest(
|
|
|
104
108
|
// Surface loader error as a non-fatal error
|
|
105
109
|
if (loaderError) {
|
|
106
110
|
errors.push({
|
|
107
|
-
componentName:
|
|
111
|
+
componentName: componentInfo.componentName,
|
|
108
112
|
phase: 'loader',
|
|
109
113
|
error: loaderError,
|
|
110
114
|
})
|
|
@@ -126,7 +130,7 @@ export function extractComponentManifest(
|
|
|
126
130
|
}
|
|
127
131
|
|
|
128
132
|
// Step 5: Process the default-exported component (non-fatal)
|
|
129
|
-
const processResult = processComponent(componentInfo, loadComponent, cssImportPaths, !!loaderError)
|
|
133
|
+
const processResult = processComponent(componentInfo, loadComponent, cssImportPaths, !!loaderError, options)
|
|
130
134
|
errors.push(...processResult.warnings)
|
|
131
135
|
const component = toEditorReactComponent(processResult.component)
|
|
132
136
|
|
|
@@ -216,8 +220,8 @@ export {
|
|
|
216
220
|
withDefectBoundary,
|
|
217
221
|
} from './errors'
|
|
218
222
|
|
|
219
|
-
/**
|
|
220
|
-
export {
|
|
223
|
+
/** Module loader primitives */
|
|
224
|
+
export { loadModule, findComponent } from './module-loader'
|
|
221
225
|
|
|
222
226
|
// ── Tier 3: Low-Level Renderer ──────────────────────────────────────────────
|
|
223
227
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { ExtractorStore } from './store'
|
|
2
2
|
export { runExtractors } from './runner'
|
|
3
|
-
export type { ExtractionResult } from './runner'
|
|
3
|
+
export type { ExtractionResult, RunExtractorsOptions } from './runner'
|
|
4
4
|
export { buildElementTree } from './tree-builder'
|
|
5
5
|
export type { ExtractedElement } from './tree-builder'
|
|
6
6
|
export type { ReactExtractor, RenderContext, CreateElementEvent, RenderCompleteEvent } from './types'
|
|
@@ -27,6 +27,11 @@ export interface ExtractionResult {
|
|
|
27
27
|
elements: ExtractedElement[]
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
export interface RunExtractorsOptions {
|
|
31
|
+
/** Optional HOC to wrap the component before rendering (e.g. a context provider). */
|
|
32
|
+
wrapper?: (component: ComponentType<unknown>) => ComponentType<unknown>
|
|
33
|
+
}
|
|
34
|
+
|
|
30
35
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
31
36
|
// Runner
|
|
32
37
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -43,6 +48,7 @@ export function runExtractors(
|
|
|
43
48
|
componentInfo: ComponentInfo,
|
|
44
49
|
component: ComponentType<unknown>,
|
|
45
50
|
extractors: ReactExtractor[],
|
|
51
|
+
options?: RunExtractorsOptions,
|
|
46
52
|
): ExtractionResult {
|
|
47
53
|
// Reset mock counter for reproducible results
|
|
48
54
|
resetMockCounter()
|
|
@@ -74,8 +80,11 @@ export function runExtractors(
|
|
|
74
80
|
},
|
|
75
81
|
]
|
|
76
82
|
|
|
83
|
+
// Apply optional HOC wrapper before rendering
|
|
84
|
+
const renderComponent = options?.wrapper ? options.wrapper(context.component) : context.component
|
|
85
|
+
|
|
77
86
|
// Phase 2: Render with element creation interception
|
|
78
|
-
const html = renderWithExtractors(
|
|
87
|
+
const html = renderWithExtractors(renderComponent, context.props, listeners, store)
|
|
79
88
|
|
|
80
89
|
// Phase 3: renderComplete - extractors can post-process
|
|
81
90
|
for (const ext of extractors) {
|
|
@@ -158,7 +158,19 @@ function findPropsType(
|
|
|
158
158
|
if (!moduleSymbol) return undefined
|
|
159
159
|
|
|
160
160
|
const exports = checker.getExportsOfModule(moduleSymbol)
|
|
161
|
-
|
|
161
|
+
let componentSymbol = exports.find((s) => s.getName() === componentName)
|
|
162
|
+
|
|
163
|
+
// If not found by name, check whether the default export resolves to the component
|
|
164
|
+
if (!componentSymbol) {
|
|
165
|
+
const defaultSymbol = exports.find((s) => s.getName() === 'default')
|
|
166
|
+
if (defaultSymbol && (defaultSymbol.getFlags() & ts.SymbolFlags.Alias) !== 0) {
|
|
167
|
+
const resolved = checker.getAliasedSymbol(defaultSymbol)
|
|
168
|
+
if (resolved.getName() === componentName) {
|
|
169
|
+
componentSymbol = resolved
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
162
174
|
if (!componentSymbol) return undefined
|
|
163
175
|
|
|
164
176
|
const componentType = checker.getTypeOfSymbol(componentSymbol)
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import * as fs from 'node:fs'
|
|
2
2
|
import * as path from 'node:path'
|
|
3
|
-
import { camelCase } from 'case-anything'
|
|
4
3
|
import ts from 'typescript'
|
|
5
4
|
import { DATA } from '../../../schema'
|
|
6
5
|
import type { PropInfo, ResolvedType } from '../types'
|
|
7
6
|
|
|
8
7
|
const MAX_RESOLVE_DEPTH = 30
|
|
9
8
|
|
|
10
|
-
const {
|
|
9
|
+
const { WIX_TYPE_TO_DATA_TYPE } = DATA
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
12
|
* Cache for package name lookups
|
|
@@ -50,8 +49,7 @@ const VALID_REACT_SEMANTIC_TYPES = new Set(['ReactNode', 'ReactElement'])
|
|
|
50
49
|
* Checks if a Wix type name maps to a valid DATA_TYPE key
|
|
51
50
|
*/
|
|
52
51
|
function isValidWixSemanticType(symbolName: string): boolean {
|
|
53
|
-
|
|
54
|
-
return camelCaseKey in DATA_TYPE
|
|
52
|
+
return symbolName in WIX_TYPE_TO_DATA_TYPE
|
|
55
53
|
}
|
|
56
54
|
|
|
57
55
|
/**
|
package/src/manifest-pipeline.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { PropInfo } from './information-extractors/ts/types'
|
|
|
6
6
|
import {
|
|
7
7
|
type ExtractedElement,
|
|
8
8
|
type PropTrackerData,
|
|
9
|
+
type RunExtractorsOptions,
|
|
9
10
|
createCssPropertiesExtractor,
|
|
10
11
|
createPropTrackerExtractor,
|
|
11
12
|
runExtractors,
|
|
@@ -62,6 +63,7 @@ export function processComponent(
|
|
|
62
63
|
loadComponent: (componentName: string) => ComponentType<unknown> | null,
|
|
63
64
|
cssImportPaths: string[],
|
|
64
65
|
loaderHasError?: boolean,
|
|
66
|
+
options?: RunExtractorsOptions,
|
|
65
67
|
): ProcessComponentResult {
|
|
66
68
|
const warnings: ExtractionWarning[] = []
|
|
67
69
|
|
|
@@ -97,7 +99,7 @@ export function processComponent(
|
|
|
97
99
|
const { extractor: propTracker, state } = createPropTrackerExtractor()
|
|
98
100
|
const cssExtractor = createCssPropertiesExtractor()
|
|
99
101
|
|
|
100
|
-
const result = runExtractors(componentInfo, Component, [propTracker, cssExtractor])
|
|
102
|
+
const result = runExtractors(componentInfo, Component, [propTracker, cssExtractor], options)
|
|
101
103
|
html = result.html
|
|
102
104
|
extractedElements = result.elements
|
|
103
105
|
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { createRequire } from 'node:module'
|
|
2
|
+
import { pascalCase } from 'case-anything'
|
|
3
|
+
import { ResultAsync, errAsync, okAsync } from 'neverthrow'
|
|
4
|
+
import type { ComponentType } from 'react'
|
|
5
|
+
import { IoError } from './errors'
|
|
6
|
+
|
|
7
|
+
type IoErrorInstance = InstanceType<typeof IoError>
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Attempts to load a module, first via ESM `import()`, then via CJS `require`.
|
|
11
|
+
*
|
|
12
|
+
* ESM `import()` is preferred because it participates in the ESM loader hook
|
|
13
|
+
* pipeline (registered via `module.register()` in the CLI). This is required
|
|
14
|
+
* for JSX interception to work — the loader hook redirects `react/jsx-runtime`
|
|
15
|
+
* to the interceptable version. CJS `require()` bypasses ESM hooks even when
|
|
16
|
+
* loading ESM modules (Node 22+), so it's only used as a fallback.
|
|
17
|
+
*
|
|
18
|
+
* @param entryPath - Absolute path to the module entry point.
|
|
19
|
+
* @returns A `ResultAsync` containing the module exports on success.
|
|
20
|
+
* @errors {IoError} When both ESM import and CJS require fail.
|
|
21
|
+
*/
|
|
22
|
+
export function loadModule(entryPath: string): ResultAsync<Record<string, unknown>, IoErrorInstance> {
|
|
23
|
+
return ResultAsync.fromPromise(import(entryPath) as Promise<Record<string, unknown>>, () => null).orElse(() => {
|
|
24
|
+
try {
|
|
25
|
+
const require = createRequire(import.meta.url)
|
|
26
|
+
const exports = require(entryPath)
|
|
27
|
+
return okAsync(exports as Record<string, unknown>)
|
|
28
|
+
} catch (requireErr) {
|
|
29
|
+
const cause = requireErr instanceof Error ? requireErr : new Error(String(requireErr))
|
|
30
|
+
return errAsync(
|
|
31
|
+
new IoError(`Failed to load ${entryPath}: ${cause.message}`, {
|
|
32
|
+
cause,
|
|
33
|
+
props: { phase: 'load' },
|
|
34
|
+
}),
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isPascalCase(name: string): boolean {
|
|
41
|
+
return name.length > 0 && pascalCase(name) === name
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isComponent(value: unknown): value is ComponentType<unknown> {
|
|
45
|
+
if (typeof value === 'function') return isPascalCase(value.name)
|
|
46
|
+
// React.memo() and React.forwardRef() return objects, not functions
|
|
47
|
+
if (typeof value === 'object' && value !== null && '$$typeof' in value) return true
|
|
48
|
+
return false
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function findComponent(moduleExports: Record<string, unknown>, name: string): ComponentType<unknown> | null {
|
|
52
|
+
// Direct named export
|
|
53
|
+
const direct = moduleExports[name]
|
|
54
|
+
if ((typeof direct === 'function' && isPascalCase(name)) || isComponent(direct)) {
|
|
55
|
+
return direct as ComponentType<unknown>
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Check default export
|
|
59
|
+
const defaultExport = moduleExports.default
|
|
60
|
+
if (defaultExport) {
|
|
61
|
+
// default is the component itself (matches by function name)
|
|
62
|
+
if (typeof defaultExport === 'function' && defaultExport.name === name) {
|
|
63
|
+
return defaultExport as ComponentType<unknown>
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// CJS interop: default is an object with named exports
|
|
67
|
+
if (typeof defaultExport === 'object' && defaultExport !== null) {
|
|
68
|
+
const nested = (defaultExport as Record<string, unknown>)[name]
|
|
69
|
+
if (isComponent(nested)) {
|
|
70
|
+
return nested as ComponentType<unknown>
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return null
|
|
76
|
+
}
|
package/src/schema.ts
CHANGED
|
@@ -116,7 +116,39 @@ const ELEMENT_TYPE = {
|
|
|
116
116
|
refElement: 'refElement',
|
|
117
117
|
} as const
|
|
118
118
|
|
|
119
|
-
|
|
119
|
+
/**
|
|
120
|
+
* Maps @wix/public-schemas type names (PascalCase) to their DATA_TYPE keys.
|
|
121
|
+
* Derived from the exports of @wix/public-schemas.
|
|
122
|
+
*/
|
|
123
|
+
const WIX_TYPE_TO_DATA_TYPE: Record<string, keyof typeof DATA_TYPE> = {
|
|
124
|
+
Link: 'link',
|
|
125
|
+
Image: 'image',
|
|
126
|
+
Video: 'video',
|
|
127
|
+
VectorArt: 'vectorArt',
|
|
128
|
+
A11y: 'a11y',
|
|
129
|
+
Audio: 'audio',
|
|
130
|
+
MenuItems: 'menuItems',
|
|
131
|
+
Schema: 'schema',
|
|
132
|
+
Text: 'text',
|
|
133
|
+
TextEnum: 'textEnum',
|
|
134
|
+
NumberType: 'number',
|
|
135
|
+
BooleanValue: 'booleanValue',
|
|
136
|
+
LocalDate: 'localDate',
|
|
137
|
+
LocalTime: 'localTime',
|
|
138
|
+
LocalDateTime: 'localDateTime',
|
|
139
|
+
WebUrl: 'webUrl',
|
|
140
|
+
Email: 'email',
|
|
141
|
+
Phone: 'phone',
|
|
142
|
+
Hostname: 'hostname',
|
|
143
|
+
Regex: 'regex',
|
|
144
|
+
Guid: 'guid',
|
|
145
|
+
RichText: 'richText',
|
|
146
|
+
Container: 'container',
|
|
147
|
+
ArrayItems: 'arrayItems',
|
|
148
|
+
Direction: 'direction',
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export const DATA = { DATA_TYPE, A11Y_ATTRIBUTES, LINK_TYPE, WIX_TYPE_TO_DATA_TYPE }
|
|
120
152
|
export const MEDIA = { VIDEO_CATEGORY, VECTOR_ART_CATEGORY, IMAGE_CATEGORY }
|
|
121
153
|
export const ELEMENTS = { ELEMENT_TYPE }
|
|
122
154
|
|
package/vite.config.ts
CHANGED
|
@@ -25,6 +25,12 @@ export default defineConfig({
|
|
|
25
25
|
if (id.startsWith('node:') || builtinModules.includes(id) || ['typescript', 'lightningcss'].includes(id)) {
|
|
26
26
|
return true
|
|
27
27
|
}
|
|
28
|
+
// Externalize React so the host project's React instance is used at runtime.
|
|
29
|
+
// Bundling React creates a second instance that causes hook failures when
|
|
30
|
+
// user components resolve a different React from their own node_modules.
|
|
31
|
+
if (id === 'react' || id === 'react-dom' || id.startsWith('react/') || id.startsWith('react-dom/')) {
|
|
32
|
+
return true
|
|
33
|
+
}
|
|
28
34
|
return false
|
|
29
35
|
},
|
|
30
36
|
},
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { ResultAsync } from 'neverthrow';
|
|
2
|
-
import { ComponentType } from 'react';
|
|
3
|
-
import { IoError, NotFoundError } from './errors';
|
|
4
|
-
type NotFoundErrorInstance = InstanceType<typeof NotFoundError>;
|
|
5
|
-
type IoErrorInstance = InstanceType<typeof IoError>;
|
|
6
|
-
export type LoaderError = NotFoundErrorInstance | IoErrorInstance;
|
|
7
|
-
export interface ComponentLoaderResult {
|
|
8
|
-
loadComponent: (name: string) => ComponentType<unknown> | null;
|
|
9
|
-
packageName: string;
|
|
10
|
-
entryPath: string | undefined;
|
|
11
|
-
exportNames: string[];
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Creates a component loader that dynamically loads components from the
|
|
15
|
-
* user's package. Finds the nearest package.json, resolves its entry point,
|
|
16
|
-
* and loads the module.
|
|
17
|
-
*
|
|
18
|
-
* The returned `loadComponent` is synchronous (required by processComponent).
|
|
19
|
-
* ESM modules are loaded via async `import()` during creation, so this
|
|
20
|
-
* function itself is async.
|
|
21
|
-
*
|
|
22
|
-
* @returns A `ResultAsync` containing a `ComponentLoaderResult` on success.
|
|
23
|
-
* @errors {NotFoundError} When no package.json is found or the package has no resolvable entry points.
|
|
24
|
-
* @errors {IoError} When module loading fails (CJS require or ESM import).
|
|
25
|
-
*/
|
|
26
|
-
export declare function createComponentLoader(componentPath: string): ResultAsync<ComponentLoaderResult, LoaderError>;
|
|
27
|
-
/**
|
|
28
|
-
* Attempts to load a module, first via ESM `import()`, then via CJS `require`.
|
|
29
|
-
*
|
|
30
|
-
* ESM `import()` is preferred because it participates in the ESM loader hook
|
|
31
|
-
* pipeline (registered via `module.register()` in the CLI). This is required
|
|
32
|
-
* for JSX interception to work — the loader hook redirects `react/jsx-runtime`
|
|
33
|
-
* to the interceptable version. CJS `require()` bypasses ESM hooks even when
|
|
34
|
-
* loading ESM modules (Node 22+), so it's only used as a fallback.
|
|
35
|
-
*
|
|
36
|
-
* @param entryPath - Absolute path to the module entry point.
|
|
37
|
-
* @returns A `ResultAsync` containing the module exports on success.
|
|
38
|
-
* @errors {IoError} When both ESM import and CJS require fail.
|
|
39
|
-
*/
|
|
40
|
-
export declare function loadModule(entryPath: string): ResultAsync<Record<string, unknown>, IoErrorInstance>;
|
|
41
|
-
export declare function findComponent(moduleExports: Record<string, unknown>, name: string): ComponentType<unknown> | null;
|
|
42
|
-
export {};
|