@wix/zero-config-implementation 1.26.0 → 1.28.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/extraction-types.d.ts +7 -0
- package/dist/index.d.ts +12 -13
- package/dist/index.js +7975 -7958
- package/dist/manifest-pipeline.d.ts +2 -9
- package/dist/module-loader.d.ts +12 -5
- package/package.json +2 -2
- package/src/extraction-types.ts +8 -0
- package/src/index.ts +83 -101
- package/src/information-extractors/react/extractors/core/tree-builder.ts +32 -4
- package/src/manifest-pipeline.ts +51 -46
- package/src/module-loader.ts +22 -12
|
@@ -2,13 +2,7 @@ import { ComponentInfo } from './information-extractors/ts';
|
|
|
2
2
|
import { RunExtractorsOptions, CoupledComponentInfo } from './information-extractors/react';
|
|
3
3
|
import { CSSParserAPI } from './information-extractors/css';
|
|
4
4
|
import { ComponentType } from 'react';
|
|
5
|
-
|
|
6
|
-
export interface ExtractionWarning {
|
|
7
|
-
componentName: string;
|
|
8
|
-
phase: 'render' | 'coupling' | 'css' | 'loader' | 'conversion';
|
|
9
|
-
error: string;
|
|
10
|
-
stack?: string;
|
|
11
|
-
}
|
|
5
|
+
import { ExtractionError } from './extraction-types';
|
|
12
6
|
export interface ExtractedCssInfo {
|
|
13
7
|
filePath: string;
|
|
14
8
|
api: CSSParserAPI;
|
|
@@ -23,7 +17,6 @@ export interface ComponentInfoWithCss extends CoupledComponentInfo {
|
|
|
23
17
|
/** The result of processing a single component through the manifest pipeline. */
|
|
24
18
|
export interface ProcessComponentResult {
|
|
25
19
|
component: ComponentInfoWithCss;
|
|
26
|
-
warnings: ExtractionWarning[];
|
|
27
20
|
}
|
|
28
21
|
/**
|
|
29
22
|
* Processes a single component through the full manifest pipeline:
|
|
@@ -33,4 +26,4 @@ export interface ProcessComponentResult {
|
|
|
33
26
|
* encountered during processing. Always returns a component, falling back
|
|
34
27
|
* to minimal info (without DOM coupling) when rendering fails.
|
|
35
28
|
*/
|
|
36
|
-
export declare function processComponent(componentInfo: ComponentInfo, loadComponent: (componentName: string) => ComponentType<unknown> | null, cssImportPaths: string[], loaderHasError
|
|
29
|
+
export declare function processComponent(componentInfo: ComponentInfo, loadComponent: (componentName: string) => ComponentType<unknown> | null, cssImportPaths: string[], loaderHasError: boolean, report: (error: ExtractionError) => void, options?: RunExtractorsOptions): ProcessComponentResult;
|
package/dist/module-loader.d.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { ResultAsync } from 'neverthrow';
|
|
2
2
|
import { ComponentType } from 'react';
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Structured failure from `loadModule` when both ESM import and CJS require fail.
|
|
5
|
+
* Carries each error separately so callers can report them independently.
|
|
6
|
+
*/
|
|
7
|
+
export interface LoadModuleFailure {
|
|
8
|
+
/** The ESM import error, or null when no ESM was attempted (e.g. empty path). */
|
|
9
|
+
esmError: Error | null;
|
|
10
|
+
/** The CJS require error, or a generic error for the empty-path case. */
|
|
11
|
+
cjsError: Error;
|
|
12
|
+
}
|
|
5
13
|
/**
|
|
6
14
|
* Attempts to load a module, first via ESM `import()`, then via CJS `require`.
|
|
7
15
|
*
|
|
@@ -13,8 +21,7 @@ type IoErrorInstance = InstanceType<typeof IoError>;
|
|
|
13
21
|
*
|
|
14
22
|
* @param entryPath - Absolute path to the module entry point.
|
|
15
23
|
* @returns A `ResultAsync` containing the module exports on success.
|
|
16
|
-
* @errors {
|
|
24
|
+
* @errors {LoadModuleFailure} When both ESM import and CJS require fail.
|
|
17
25
|
*/
|
|
18
|
-
export declare function loadModule(entryPath: string): ResultAsync<Record<string, unknown>,
|
|
26
|
+
export declare function loadModule(entryPath: string): ResultAsync<Record<string, unknown>, LoadModuleFailure>;
|
|
19
27
|
export declare function findComponent(moduleExports: Record<string, unknown>, name: string): ComponentType<unknown> | null;
|
|
20
|
-
export {};
|
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.28.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",
|
|
@@ -83,5 +83,5 @@
|
|
|
83
83
|
]
|
|
84
84
|
}
|
|
85
85
|
},
|
|
86
|
-
"falconPackageHash": "
|
|
86
|
+
"falconPackageHash": "b9b7aa47b7618d7b031628208f9fb26661e34ddb5fe300679306084e"
|
|
87
87
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { BaseError } from './errors'
|
|
2
|
+
|
|
3
|
+
/** A non-fatal issue encountered during component extraction. */
|
|
4
|
+
export interface ExtractionError {
|
|
5
|
+
componentName: string
|
|
6
|
+
phase: 'render' | 'coupling' | 'css' | 'loader' | 'conversion'
|
|
7
|
+
error: InstanceType<typeof BaseError>
|
|
8
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,32 +1,16 @@
|
|
|
1
1
|
import type { EditorReactComponent } from '@wix/zero-config-schema'
|
|
2
|
-
import { Result, ResultAsync } from 'neverthrow'
|
|
2
|
+
import { Result, type ResultAsync, okAsync } from 'neverthrow'
|
|
3
3
|
import type { ComponentType } from 'react'
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import type
|
|
7
|
-
|
|
8
|
-
import { compileTsFile } from './ts-compiler'
|
|
9
|
-
|
|
10
|
-
// Error classes
|
|
11
|
-
import { type NotFoundError, ParseError } from './errors'
|
|
12
|
-
|
|
13
|
-
// Component loader helpers
|
|
14
|
-
import { findComponent, loadModule } from './module-loader'
|
|
15
|
-
|
|
5
|
+
import { toEditorReactComponent } from './converters'
|
|
6
|
+
import { IoError, type NotFoundError, ParseError } from './errors'
|
|
7
|
+
import type { ExtractionError } from './extraction-types'
|
|
16
8
|
import type { RunExtractorsOptions } from './information-extractors/react'
|
|
17
|
-
|
|
9
|
+
import { extractCssImports, extractDefaultComponentInfo } from './information-extractors/ts'
|
|
18
10
|
import { processComponent } from './manifest-pipeline'
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
ExtractionWarning,
|
|
23
|
-
ProcessComponentResult,
|
|
24
|
-
} from './manifest-pipeline'
|
|
25
|
-
|
|
26
|
-
export type { EditorReactComponent } from '@wix/zero-config-schema'
|
|
27
|
-
|
|
28
|
-
// Converter
|
|
29
|
-
import { toEditorReactComponent } from './converters'
|
|
11
|
+
import { findComponent, loadModule } from './module-loader'
|
|
12
|
+
import type { LoadModuleFailure } from './module-loader'
|
|
13
|
+
import { compileTsFile } from './ts-compiler'
|
|
30
14
|
|
|
31
15
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
32
16
|
// Types
|
|
@@ -37,15 +21,6 @@ export interface ManifestResult {
|
|
|
37
21
|
errors: ExtractionError[]
|
|
38
22
|
}
|
|
39
23
|
|
|
40
|
-
export type { RunExtractorsOptions } from './information-extractors/react'
|
|
41
|
-
|
|
42
|
-
export interface ExtractionError {
|
|
43
|
-
componentName: string
|
|
44
|
-
phase: 'render' | 'coupling' | 'css' | 'loader' | 'conversion'
|
|
45
|
-
error: string
|
|
46
|
-
stack?: string
|
|
47
|
-
}
|
|
48
|
-
|
|
49
24
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
50
25
|
// Main API
|
|
51
26
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -55,96 +30,100 @@ export interface ExtractionError {
|
|
|
55
30
|
*
|
|
56
31
|
* @param componentPath - Path to the TypeScript source file
|
|
57
32
|
* @param compiledEntryPath - Path to the built JS entry file of the component's package (e.g. dist/index.js)
|
|
58
|
-
* @
|
|
33
|
+
* @param options.onError - Called for each non-fatal extraction error as it occurs
|
|
59
34
|
* @errors
|
|
60
35
|
* - {@link NotFoundError} — Source file does not exist (phase: `compile`)
|
|
61
36
|
* - {@link ParseError} — TypeScript config or component types could not be parsed (phase: `compile` | `extract`)
|
|
62
37
|
*/
|
|
38
|
+
export interface ExtractComponentManifestOptions extends RunExtractorsOptions {
|
|
39
|
+
onError?: (error: ExtractionError) => void
|
|
40
|
+
}
|
|
41
|
+
|
|
63
42
|
export function extractComponentManifest(
|
|
64
43
|
componentPath: string,
|
|
65
44
|
compiledEntryPath: string,
|
|
66
|
-
options?:
|
|
45
|
+
options?: ExtractComponentManifestOptions,
|
|
67
46
|
): ResultAsync<ManifestResult, InstanceType<typeof NotFoundError> | InstanceType<typeof ParseError>> {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
// Surface loader error as a non-fatal error
|
|
118
|
-
if (loaderError) {
|
|
119
|
-
errors.push({
|
|
120
|
-
componentName: componentInfo.componentName,
|
|
47
|
+
const errors: ExtractionError[] = []
|
|
48
|
+
const report = (error: ExtractionError): void => {
|
|
49
|
+
errors.push(error)
|
|
50
|
+
options?.onError?.(error)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Step 1: Compile TypeScript (fatal)
|
|
54
|
+
return compileTsFile(componentPath)
|
|
55
|
+
.andThen((program) => {
|
|
56
|
+
// Step 2: Extract default-exported component types (fatal)
|
|
57
|
+
const safeExtract = Result.fromThrowable(
|
|
58
|
+
(prog: typeof program) => {
|
|
59
|
+
const componentInfo = extractDefaultComponentInfo(prog, componentPath)
|
|
60
|
+
if (!componentInfo) {
|
|
61
|
+
throw new Error(`No default export found in "${componentPath}"`)
|
|
62
|
+
}
|
|
63
|
+
return componentInfo
|
|
64
|
+
},
|
|
65
|
+
(thrown) =>
|
|
66
|
+
new ParseError(
|
|
67
|
+
`Failed to extract component types from "${componentPath}": ${thrown instanceof Error ? thrown.message : String(thrown)}`,
|
|
68
|
+
{ cause: thrown as Error, props: { phase: 'extract' } },
|
|
69
|
+
),
|
|
70
|
+
)
|
|
71
|
+
return safeExtract(program).map((componentInfo) => ({ program, componentInfo }))
|
|
72
|
+
})
|
|
73
|
+
.andThen(({ program, componentInfo }) => {
|
|
74
|
+
const { componentName } = componentInfo
|
|
75
|
+
|
|
76
|
+
// Step 3: Load the compiled package module (non-fatal) — done after TS extraction
|
|
77
|
+
// so componentName is known when reporting failures
|
|
78
|
+
return loadModule(compiledEntryPath)
|
|
79
|
+
.map((moduleExports) => ({
|
|
80
|
+
loadComponent: (name: string) => findComponent(moduleExports, name),
|
|
81
|
+
failure: null as LoadModuleFailure | null,
|
|
82
|
+
}))
|
|
83
|
+
.orElse((failure) => okAsync({ loadComponent: () => null as ComponentType<unknown> | null, failure }))
|
|
84
|
+
.map(({ loadComponent, failure }) => {
|
|
85
|
+
if (failure) {
|
|
86
|
+
if (failure.esmError) {
|
|
87
|
+
report({
|
|
88
|
+
componentName,
|
|
89
|
+
phase: 'loader',
|
|
90
|
+
error: new IoError('ESM import failed', { cause: failure.esmError, props: { phase: 'loader' } }),
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
report({
|
|
94
|
+
componentName,
|
|
121
95
|
phase: 'loader',
|
|
122
|
-
error:
|
|
123
|
-
stack: loaderStack,
|
|
96
|
+
error: new IoError('CJS require failed', { cause: failure.cjsError, props: { phase: 'loader' } }),
|
|
124
97
|
})
|
|
125
98
|
}
|
|
126
99
|
|
|
127
100
|
// Step 4: Extract CSS imports (non-fatal)
|
|
128
101
|
let cssImportPaths: string[] = []
|
|
129
|
-
const
|
|
130
|
-
const cssResult = safeCssImports(program)
|
|
102
|
+
const cssResult = Result.fromThrowable(extractCssImports, (thrown) => thrown)(program)
|
|
131
103
|
if (cssResult.isOk()) {
|
|
132
104
|
cssImportPaths = cssResult.value
|
|
133
105
|
} else {
|
|
134
106
|
const thrown = cssResult.error
|
|
135
|
-
|
|
136
|
-
componentName
|
|
107
|
+
report({
|
|
108
|
+
componentName,
|
|
137
109
|
phase: 'css',
|
|
138
|
-
error:
|
|
139
|
-
|
|
110
|
+
error: new ParseError(
|
|
111
|
+
`Failed to extract CSS imports: ${thrown instanceof Error ? thrown.message : String(thrown)}`,
|
|
112
|
+
{ cause: thrown instanceof Error ? thrown : undefined, props: { phase: 'css' } },
|
|
113
|
+
),
|
|
140
114
|
})
|
|
141
115
|
}
|
|
142
116
|
|
|
143
117
|
// Step 5: Process the default-exported component (non-fatal)
|
|
144
|
-
const processResult = processComponent(
|
|
145
|
-
|
|
118
|
+
const processResult = processComponent(
|
|
119
|
+
componentInfo,
|
|
120
|
+
loadComponent,
|
|
121
|
+
cssImportPaths,
|
|
122
|
+
!!failure,
|
|
123
|
+
report,
|
|
124
|
+
options,
|
|
125
|
+
)
|
|
146
126
|
const component = toEditorReactComponent(processResult.component)
|
|
147
|
-
|
|
148
127
|
return { component, errors }
|
|
149
128
|
})
|
|
150
129
|
})
|
|
@@ -171,6 +150,9 @@ export function extractComponentManifest(
|
|
|
171
150
|
|
|
172
151
|
// ── Tier 1: High-Level API ──────────────────────────────────────────────────
|
|
173
152
|
// extractComponentManifest() is exported above as a named function declaration.
|
|
153
|
+
export type { ExtractionError } from './extraction-types'
|
|
154
|
+
export type { EditorReactComponent } from '@wix/zero-config-schema'
|
|
155
|
+
export type { ComponentInfoWithCss, ExtractedCssInfo, ProcessComponentResult } from './manifest-pipeline'
|
|
174
156
|
|
|
175
157
|
// ── Tier 2: Pipeline Building Blocks ────────────────────────────────────────
|
|
176
158
|
|
|
@@ -187,6 +169,7 @@ export type {
|
|
|
187
169
|
} from './information-extractors/ts/types'
|
|
188
170
|
|
|
189
171
|
/** React render-time extraction */
|
|
172
|
+
export type { RunExtractorsOptions } from './information-extractors/react'
|
|
190
173
|
export {
|
|
191
174
|
runExtractors,
|
|
192
175
|
ExtractorStore,
|
|
@@ -204,8 +187,6 @@ export type {
|
|
|
204
187
|
PropTrackerData,
|
|
205
188
|
PropTrackerExtractorState,
|
|
206
189
|
CssPropertiesData,
|
|
207
|
-
} from './information-extractors/react'
|
|
208
|
-
export type {
|
|
209
190
|
CoupledComponentInfo,
|
|
210
191
|
CoupledProp,
|
|
211
192
|
DOMBinding,
|
|
@@ -231,6 +212,7 @@ export {
|
|
|
231
212
|
|
|
232
213
|
/** Module loader primitives */
|
|
233
214
|
export { loadModule, findComponent } from './module-loader'
|
|
215
|
+
export type { LoadModuleFailure } from './module-loader'
|
|
234
216
|
|
|
235
217
|
// ── Tier 3: Low-Level Renderer ──────────────────────────────────────────────
|
|
236
218
|
|
|
@@ -12,6 +12,7 @@ import { findPreferredSemanticClass } from '../../../../utils/css-class'
|
|
|
12
12
|
import { PRESETS_WRAPPER_CLASS_NAME } from '../../utils/mock-generator'
|
|
13
13
|
import type { CssPropertiesData } from '../css-properties'
|
|
14
14
|
import { addTextProperties } from '../css-properties'
|
|
15
|
+
import type { PropTrackerData } from '../prop-tracker'
|
|
15
16
|
import type { ExtractorStore } from './store'
|
|
16
17
|
|
|
17
18
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -150,7 +151,7 @@ function getElementNamePart(element: Element, getElementById: (id: string) => El
|
|
|
150
151
|
const id = getAttribute(element, 'id')
|
|
151
152
|
// Skip spy-instrumented ids (mock_propName_XXXXXX) — they are runtime mock values,
|
|
152
153
|
// not semantic identifiers, and would produce garbage element names.
|
|
153
|
-
if (id && !id.
|
|
154
|
+
if (id && !id.includes('mock_')) {
|
|
154
155
|
return pascalCase(id)
|
|
155
156
|
}
|
|
156
157
|
|
|
@@ -179,6 +180,28 @@ function getElementNamePart(element: Element, getElementById: (id: string) => El
|
|
|
179
180
|
return normalizeTagName(element.tagName)
|
|
180
181
|
}
|
|
181
182
|
|
|
183
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
184
|
+
// Element Filtering
|
|
185
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Returns true if the element should be included in the tree:
|
|
189
|
+
* - has a BEM-semantic CSS class, OR
|
|
190
|
+
* - has a className prop propagated from the component's props
|
|
191
|
+
*/
|
|
192
|
+
function hasClassNameCriteria(element: Element, extractorData: Map<string, unknown>): boolean {
|
|
193
|
+
const classAttr = getAttribute(element, 'class')
|
|
194
|
+
if (classAttr) {
|
|
195
|
+
const semanticClass = findPreferredSemanticClass(classAttr.split(' '))
|
|
196
|
+
if (semanticClass) return true
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const propTrackerData = extractorData.get('prop-tracker') as PropTrackerData | undefined
|
|
200
|
+
if (propTrackerData?.boundProps.includes('className')) return true
|
|
201
|
+
|
|
202
|
+
return false
|
|
203
|
+
}
|
|
204
|
+
|
|
182
205
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
183
206
|
// Tree Building
|
|
184
207
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -234,15 +257,20 @@ export function buildElementTree(html: string, store: ExtractorStore): Extracted
|
|
|
234
257
|
|
|
235
258
|
// If this element has a traceId, it's a traced element
|
|
236
259
|
if (traceId) {
|
|
260
|
+
// Get extractor data from store
|
|
261
|
+
const extractorData = store.getAll(traceId) ?? new Map()
|
|
262
|
+
|
|
263
|
+
// Filter: only include non-root elements with semantic or propagated className
|
|
264
|
+
if (!isRoot && !hasClassNameCriteria(node, extractorData)) {
|
|
265
|
+
return node.childNodes.flatMap((child) => walkTree(child, ancestorPath, false))
|
|
266
|
+
}
|
|
267
|
+
|
|
237
268
|
// Compute this element's name part
|
|
238
269
|
const namePart = isRoot ? 'root' : getElementNamePart(node, getElementById)
|
|
239
270
|
|
|
240
271
|
// Full name is ancestor path + this element's name (no separator)
|
|
241
272
|
const name = isRoot ? 'root' : ancestorPath + namePart
|
|
242
273
|
|
|
243
|
-
// Get extractor data from store
|
|
244
|
-
const extractorData = store.getAll(traceId) ?? new Map()
|
|
245
|
-
|
|
246
274
|
// Check for text content
|
|
247
275
|
const hasText = hasDirectTextContent(node)
|
|
248
276
|
|
package/src/manifest-pipeline.ts
CHANGED
|
@@ -19,18 +19,13 @@ import { compileSass } from './information-extractors/css/sass-adapter'
|
|
|
19
19
|
|
|
20
20
|
import type { ComponentType } from 'react'
|
|
21
21
|
|
|
22
|
+
import { IoError, NotFoundError, ParseError } from './errors'
|
|
23
|
+
import type { ExtractionError } from './extraction-types'
|
|
24
|
+
|
|
22
25
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
23
26
|
// Types
|
|
24
27
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
25
28
|
|
|
26
|
-
/** A non-fatal issue encountered during component processing. */
|
|
27
|
-
export interface ExtractionWarning {
|
|
28
|
-
componentName: string
|
|
29
|
-
phase: 'render' | 'coupling' | 'css' | 'loader' | 'conversion'
|
|
30
|
-
error: string
|
|
31
|
-
stack?: string
|
|
32
|
-
}
|
|
33
|
-
|
|
34
29
|
export interface ExtractedCssInfo {
|
|
35
30
|
filePath: string
|
|
36
31
|
api: CSSParserAPI
|
|
@@ -47,7 +42,6 @@ export interface ComponentInfoWithCss extends CoupledComponentInfo {
|
|
|
47
42
|
/** The result of processing a single component through the manifest pipeline. */
|
|
48
43
|
export interface ProcessComponentResult {
|
|
49
44
|
component: ComponentInfoWithCss
|
|
50
|
-
warnings: ExtractionWarning[]
|
|
51
45
|
}
|
|
52
46
|
|
|
53
47
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -66,29 +60,34 @@ export function processComponent(
|
|
|
66
60
|
componentInfo: ComponentInfo,
|
|
67
61
|
loadComponent: (componentName: string) => ComponentType<unknown> | null,
|
|
68
62
|
cssImportPaths: string[],
|
|
69
|
-
loaderHasError
|
|
63
|
+
loaderHasError: boolean,
|
|
64
|
+
report: (error: ExtractionError) => void,
|
|
70
65
|
options?: RunExtractorsOptions,
|
|
71
66
|
): ProcessComponentResult {
|
|
72
|
-
const
|
|
67
|
+
const { componentName } = componentInfo
|
|
73
68
|
|
|
74
69
|
// Load the actual component
|
|
75
70
|
let Component: ComponentType<unknown> | null = null
|
|
76
71
|
try {
|
|
77
|
-
Component = loadComponent(
|
|
72
|
+
Component = loadComponent(componentName)
|
|
78
73
|
if (!Component && !loaderHasError) {
|
|
79
|
-
|
|
80
|
-
componentName
|
|
74
|
+
report({
|
|
75
|
+
componentName,
|
|
81
76
|
phase: 'loader',
|
|
82
|
-
error: `Component "${
|
|
77
|
+
error: new NotFoundError(`Component "${componentName}" not found in package exports`, {
|
|
78
|
+
props: { phase: 'loader' },
|
|
79
|
+
}),
|
|
83
80
|
})
|
|
84
81
|
}
|
|
85
|
-
} catch (
|
|
82
|
+
} catch (thrownError) {
|
|
86
83
|
if (!loaderHasError) {
|
|
87
|
-
|
|
88
|
-
componentName
|
|
84
|
+
report({
|
|
85
|
+
componentName,
|
|
89
86
|
phase: 'loader',
|
|
90
|
-
error:
|
|
91
|
-
|
|
87
|
+
error: new IoError(
|
|
88
|
+
`Failed to load "${componentName}": ${thrownError instanceof Error ? thrownError.message : String(thrownError)}`,
|
|
89
|
+
{ cause: thrownError instanceof Error ? thrownError : undefined, props: { phase: 'loader' } },
|
|
90
|
+
),
|
|
92
91
|
})
|
|
93
92
|
}
|
|
94
93
|
}
|
|
@@ -110,18 +109,20 @@ export function processComponent(
|
|
|
110
109
|
|
|
111
110
|
const { props: coupledProps, innerElementProps } = buildCoupledProps(componentInfo, state.stores)
|
|
112
111
|
coupledInfo = {
|
|
113
|
-
componentName
|
|
112
|
+
componentName,
|
|
114
113
|
props: coupledProps,
|
|
115
114
|
elements: convertElements(extractedElements),
|
|
116
115
|
innerElementProps: innerElementProps.size > 0 ? innerElementProps : undefined,
|
|
117
116
|
propUsages: state.stores.propUsages,
|
|
118
117
|
}
|
|
119
|
-
} catch (
|
|
120
|
-
|
|
121
|
-
componentName
|
|
118
|
+
} catch (thrownError) {
|
|
119
|
+
report({
|
|
120
|
+
componentName,
|
|
122
121
|
phase: 'render',
|
|
123
|
-
error:
|
|
124
|
-
|
|
122
|
+
error: new IoError(thrownError instanceof Error ? thrownError.message : String(thrownError), {
|
|
123
|
+
cause: thrownError instanceof Error ? thrownError : undefined,
|
|
124
|
+
props: { phase: 'render' },
|
|
125
|
+
}),
|
|
125
126
|
})
|
|
126
127
|
}
|
|
127
128
|
}
|
|
@@ -133,7 +134,7 @@ export function processComponent(
|
|
|
133
134
|
} else {
|
|
134
135
|
// Fallback: create minimal info without DOM coupling
|
|
135
136
|
enhancedInfo = {
|
|
136
|
-
componentName
|
|
137
|
+
componentName,
|
|
137
138
|
props: Object.fromEntries(
|
|
138
139
|
Object.entries(componentInfo.props).map(([name, info]) => [
|
|
139
140
|
name,
|
|
@@ -146,8 +147,7 @@ export function processComponent(
|
|
|
146
147
|
}
|
|
147
148
|
|
|
148
149
|
// Read and parse CSS imports
|
|
149
|
-
const
|
|
150
|
-
warnings.push(...cssWarnings)
|
|
150
|
+
const css = extractCssInfo(cssImportPaths, componentName, report)
|
|
151
151
|
|
|
152
152
|
// Match CSS selectors to elements
|
|
153
153
|
let varUsedByTraceId = new Map<string, Set<string>>()
|
|
@@ -159,12 +159,14 @@ export function processComponent(
|
|
|
159
159
|
elements: convertElements(matchResult.elements),
|
|
160
160
|
}
|
|
161
161
|
varUsedByTraceId = matchResult.varUsedByTraceId
|
|
162
|
-
} catch (
|
|
163
|
-
|
|
164
|
-
componentName
|
|
162
|
+
} catch (thrownError) {
|
|
163
|
+
report({
|
|
164
|
+
componentName,
|
|
165
165
|
phase: 'css',
|
|
166
|
-
error:
|
|
167
|
-
|
|
166
|
+
error: new IoError(
|
|
167
|
+
`CSS selector matching failed: ${thrownError instanceof Error ? thrownError.message : String(thrownError)}`,
|
|
168
|
+
{ cause: thrownError instanceof Error ? thrownError : undefined, props: { phase: 'css' } },
|
|
169
|
+
),
|
|
168
170
|
})
|
|
169
171
|
}
|
|
170
172
|
}
|
|
@@ -175,7 +177,6 @@ export function processComponent(
|
|
|
175
177
|
css,
|
|
176
178
|
varUsedByTraceId,
|
|
177
179
|
},
|
|
178
|
-
warnings,
|
|
179
180
|
}
|
|
180
181
|
}
|
|
181
182
|
|
|
@@ -327,14 +328,14 @@ function convertElements(elements: ExtractedElement[]): CoupledComponentInfo['el
|
|
|
327
328
|
|
|
328
329
|
/**
|
|
329
330
|
* Reads and parses CSS files, extracting standard and custom properties.
|
|
330
|
-
*
|
|
331
|
+
* Non-fatal parse failures are reported via `reportError`.
|
|
331
332
|
*/
|
|
332
333
|
function extractCssInfo(
|
|
333
334
|
cssImportPaths: string[],
|
|
334
335
|
componentName: string,
|
|
335
|
-
|
|
336
|
+
report: (error: ExtractionError) => void,
|
|
337
|
+
): ExtractedCssInfo[] {
|
|
336
338
|
const cssInfos: ExtractedCssInfo[] = []
|
|
337
|
-
const warnings: ExtractionWarning[] = []
|
|
338
339
|
|
|
339
340
|
for (const cssPath of cssImportPaths) {
|
|
340
341
|
try {
|
|
@@ -343,11 +344,13 @@ function extractCssInfo(
|
|
|
343
344
|
if (cssPath.endsWith('.scss') || cssPath.endsWith('.sass')) {
|
|
344
345
|
const compiledCssResult = compileSass(cssPath)
|
|
345
346
|
if (compiledCssResult.isErr()) {
|
|
346
|
-
|
|
347
|
+
report({
|
|
347
348
|
componentName,
|
|
348
349
|
phase: 'css',
|
|
349
|
-
error: `Failed to
|
|
350
|
-
|
|
350
|
+
error: new ParseError(`Failed to compile ${cssPath}`, {
|
|
351
|
+
cause: compiledCssResult.error,
|
|
352
|
+
props: { phase: 'css' },
|
|
353
|
+
}),
|
|
351
354
|
})
|
|
352
355
|
continue
|
|
353
356
|
}
|
|
@@ -381,15 +384,17 @@ function extractCssInfo(
|
|
|
381
384
|
customProperties,
|
|
382
385
|
isCssModule: /\.module\.(css|scss|sass)$/.test(cssPath),
|
|
383
386
|
})
|
|
384
|
-
} catch (
|
|
385
|
-
|
|
387
|
+
} catch (thrownError) {
|
|
388
|
+
report({
|
|
386
389
|
componentName,
|
|
387
390
|
phase: 'css',
|
|
388
|
-
error:
|
|
389
|
-
|
|
391
|
+
error: new ParseError(
|
|
392
|
+
`Failed to parse ${cssPath}: ${thrownError instanceof Error ? thrownError.message : String(thrownError)}`,
|
|
393
|
+
{ cause: thrownError instanceof Error ? thrownError : undefined, props: { phase: 'css' } },
|
|
394
|
+
),
|
|
390
395
|
})
|
|
391
396
|
}
|
|
392
397
|
}
|
|
393
398
|
|
|
394
|
-
return
|
|
399
|
+
return cssInfos
|
|
395
400
|
}
|