@domscribe/react 0.1.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/adapter/react-adapter.d.ts +121 -0
- package/adapter/react-adapter.d.ts.map +1 -0
- package/adapter/react-adapter.js +486 -0
- package/adapter/types.d.ts +131 -0
- package/adapter/types.d.ts.map +1 -0
- package/adapter/types.js +17 -0
- package/auto-init.d.ts +2 -0
- package/auto-init.d.ts.map +1 -0
- package/auto-init.js +40 -0
- package/component/component-name-resolver.d.ts +118 -0
- package/component/component-name-resolver.d.ts.map +1 -0
- package/component/component-name-resolver.js +361 -0
- package/component/props-extractor.d.ts +51 -0
- package/component/props-extractor.d.ts.map +1 -0
- package/component/props-extractor.js +122 -0
- package/component/state-extractor.d.ts +60 -0
- package/component/state-extractor.d.ts.map +1 -0
- package/component/state-extractor.js +162 -0
- package/component/types.d.ts +256 -0
- package/component/types.d.ts.map +1 -0
- package/component/types.js +5 -0
- package/errors/index.d.ts +36 -0
- package/errors/index.d.ts.map +1 -0
- package/errors/index.js +75 -0
- package/fiber/fiber-walker.d.ts +58 -0
- package/fiber/fiber-walker.d.ts.map +1 -0
- package/fiber/fiber-walker.js +118 -0
- package/fiber/types.d.ts +162 -0
- package/fiber/types.d.ts.map +1 -0
- package/fiber/types.js +32 -0
- package/index.d.ts +14 -0
- package/index.d.ts.map +1 -0
- package/index.js +15 -0
- package/package.json +59 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/utils/constants.d.ts +94 -0
- package/utils/constants.d.ts.map +1 -0
- package/utils/constants.js +123 -0
- package/utils/type-guards.d.ts +31 -0
- package/utils/type-guards.d.ts.map +1 -0
- package/utils/type-guards.js +89 -0
- package/utils/types.d.ts +6 -0
- package/utils/types.d.ts.map +1 -0
- package/utils/types.js +5 -0
- package/vite/index.d.ts +7 -0
- package/vite/index.d.ts.map +1 -0
- package/vite/index.js +5 -0
- package/vite/types.d.ts +56 -0
- package/vite/types.d.ts.map +1 -0
- package/vite/types.js +10 -0
- package/vite/vite-plugin.d.ts +29 -0
- package/vite/vite-plugin.d.ts.map +1 -0
- package/vite/vite-plugin.js +104 -0
- package/webpack/index.d.ts +7 -0
- package/webpack/index.d.ts.map +1 -0
- package/webpack/index.js +5 -0
- package/webpack/webpack-plugin.d.ts +50 -0
- package/webpack/webpack-plugin.d.ts.map +1 -0
- package/webpack/webpack-plugin.js +63 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React adapter type definitions
|
|
3
|
+
* @module @domscribe/react/adapter/types
|
|
4
|
+
*/
|
|
5
|
+
import type { FrameworkAdapter } from '@domscribe/runtime';
|
|
6
|
+
/**
|
|
7
|
+
* React DevTools global hook shape (window.__REACT_DEVTOOLS_GLOBAL_HOOK__)
|
|
8
|
+
*/
|
|
9
|
+
export interface ReactDevToolsHook {
|
|
10
|
+
renderers: Map<number, ReactDevToolsRenderer>;
|
|
11
|
+
rendererInterfaces?: Map<number, ReactDevToolsRendererInterface>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Renderer entry exposed by the DevTools hook
|
|
15
|
+
*/
|
|
16
|
+
export interface ReactDevToolsRenderer {
|
|
17
|
+
findFiberByHostInstance?: (instance: Element) => unknown;
|
|
18
|
+
version?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Renderer interface entry exposed by the DevTools hook
|
|
22
|
+
*/
|
|
23
|
+
export interface ReactDevToolsRendererInterface {
|
|
24
|
+
version?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Capture strategy for React component data
|
|
28
|
+
*
|
|
29
|
+
* - `devtools`: Use React DevTools hook (most reliable, requires DevTools)
|
|
30
|
+
* - `fiber`: Direct Fiber tree access (fast, but React internal API)
|
|
31
|
+
* - `best-effort`: Try multiple strategies in order of reliability
|
|
32
|
+
*/
|
|
33
|
+
export declare enum CaptureStrategy {
|
|
34
|
+
DEVTOOLS = "devtools",
|
|
35
|
+
FIBER = "fiber",
|
|
36
|
+
BEST_EFFORT = "best-effort"
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Configuration options for ReactAdapter
|
|
40
|
+
*/
|
|
41
|
+
export interface ReactAdapterOptions {
|
|
42
|
+
/**
|
|
43
|
+
* Capture strategy to use
|
|
44
|
+
* @default CaptureStrategy.BEST_EFFORT
|
|
45
|
+
*/
|
|
46
|
+
strategy?: CaptureStrategy;
|
|
47
|
+
/**
|
|
48
|
+
* Maximum depth to traverse the Fiber tree
|
|
49
|
+
* @default 50
|
|
50
|
+
*/
|
|
51
|
+
maxTreeDepth?: number;
|
|
52
|
+
/**
|
|
53
|
+
* Enable debug logging
|
|
54
|
+
* @default false
|
|
55
|
+
*/
|
|
56
|
+
debug?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Include wrappers (HOC, memo, forwardRef) in component names
|
|
59
|
+
* @default true
|
|
60
|
+
*/
|
|
61
|
+
includeWrappers?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Custom hook name resolvers for better debugging
|
|
64
|
+
* Maps hook index to semantic name (e.g., { 0: 'count', 1: 'setCount' })
|
|
65
|
+
*/
|
|
66
|
+
hookNameResolvers?: Map<string, Map<number, string>>;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Internal state for ReactAdapter
|
|
70
|
+
*/
|
|
71
|
+
export interface ReactAdapterState {
|
|
72
|
+
/**
|
|
73
|
+
* Whether the adapter is initialized
|
|
74
|
+
*/
|
|
75
|
+
initialized: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Detected React version
|
|
78
|
+
*/
|
|
79
|
+
version?: string;
|
|
80
|
+
/**
|
|
81
|
+
* Whether React DevTools hook is available
|
|
82
|
+
*/
|
|
83
|
+
hasDevTools: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Whether Fiber internals are accessible
|
|
86
|
+
*/
|
|
87
|
+
hasFiberAccess: boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Active capture strategy
|
|
90
|
+
*/
|
|
91
|
+
activeStrategy: CaptureStrategy;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Result of a component resolution operation
|
|
95
|
+
*/
|
|
96
|
+
export interface ComponentResolutionResult {
|
|
97
|
+
/**
|
|
98
|
+
* Whether the resolution was successful
|
|
99
|
+
*/
|
|
100
|
+
success: boolean;
|
|
101
|
+
/**
|
|
102
|
+
* The component instance (Fiber node)
|
|
103
|
+
*/
|
|
104
|
+
component?: unknown;
|
|
105
|
+
/**
|
|
106
|
+
* The capture strategy that succeeded
|
|
107
|
+
*/
|
|
108
|
+
strategy?: CaptureStrategy;
|
|
109
|
+
/**
|
|
110
|
+
* Error if resolution failed
|
|
111
|
+
*/
|
|
112
|
+
error?: Error;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Extended FrameworkAdapter with React-specific methods
|
|
116
|
+
*/
|
|
117
|
+
export interface ReactFrameworkAdapter extends FrameworkAdapter {
|
|
118
|
+
/**
|
|
119
|
+
* Get the active capture strategy
|
|
120
|
+
*/
|
|
121
|
+
getActiveStrategy(): CaptureStrategy;
|
|
122
|
+
/**
|
|
123
|
+
* Get the React version
|
|
124
|
+
*/
|
|
125
|
+
getReactVersion(): string | null;
|
|
126
|
+
/**
|
|
127
|
+
* Check if React DevTools is available
|
|
128
|
+
*/
|
|
129
|
+
hasDevToolsAccess(): boolean;
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../packages/domscribe-react/src/adapter/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE3D;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;IAC9C,kBAAkB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,8BAA8B,CAAC,CAAC;CAClE;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,uBAAuB,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,OAAO,CAAC;IACzD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,oBAAY,eAAe;IACzB,QAAQ,aAAa;IACrB,KAAK,UAAU;IACf,WAAW,gBAAgB;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,QAAQ,CAAC,EAAE,eAAe,CAAC;IAE3B;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAE1B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACtD;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;OAEG;IACH,WAAW,EAAE,OAAO,CAAC;IAErB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,WAAW,EAAE,OAAO,CAAC;IAErB;;OAEG;IACH,cAAc,EAAE,OAAO,CAAC;IAExB;;OAEG;IACH,cAAc,EAAE,eAAe,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;IAEjB;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;OAEG;IACH,QAAQ,CAAC,EAAE,eAAe,CAAC;IAE3B;;OAEG;IACH,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AAED;;GAEG;AACH,MAAM,WAAW,qBAAsB,SAAQ,gBAAgB;IAC7D;;OAEG;IACH,iBAAiB,IAAI,eAAe,CAAC;IAErC;;OAEG;IACH,eAAe,IAAI,MAAM,GAAG,IAAI,CAAC;IAEjC;;OAEG;IACH,iBAAiB,IAAI,OAAO,CAAC;CAC9B"}
|
package/adapter/types.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React adapter type definitions
|
|
3
|
+
* @module @domscribe/react/adapter/types
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Capture strategy for React component data
|
|
7
|
+
*
|
|
8
|
+
* - `devtools`: Use React DevTools hook (most reliable, requires DevTools)
|
|
9
|
+
* - `fiber`: Direct Fiber tree access (fast, but React internal API)
|
|
10
|
+
* - `best-effort`: Try multiple strategies in order of reliability
|
|
11
|
+
*/
|
|
12
|
+
export var CaptureStrategy;
|
|
13
|
+
(function (CaptureStrategy) {
|
|
14
|
+
CaptureStrategy["DEVTOOLS"] = "devtools";
|
|
15
|
+
CaptureStrategy["FIBER"] = "fiber";
|
|
16
|
+
CaptureStrategy["BEST_EFFORT"] = "best-effort";
|
|
17
|
+
})(CaptureStrategy || (CaptureStrategy = {}));
|
package/auto-init.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-init.d.ts","sourceRoot":"","sources":["../../../packages/domscribe-react/src/auto-init.ts"],"names":[],"mappings":""}
|
package/auto-init.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-initializing entry point for React adapter.
|
|
3
|
+
*
|
|
4
|
+
* Importing this module automatically initializes RuntimeManager with ReactAdapter.
|
|
5
|
+
* Used by the webpack plugin which adds this as an entry point.
|
|
6
|
+
*
|
|
7
|
+
* When used with the webpack plugin, `__DOMSCRIBE_RUNTIME_OPTIONS__` and
|
|
8
|
+
* `__DOMSCRIBE_ADAPTER_OPTIONS__` are injected via DefinePlugin. Falls back
|
|
9
|
+
* to empty objects when not defined (e.g. direct import without the plugin).
|
|
10
|
+
*
|
|
11
|
+
* @module @domscribe/react/auto-init
|
|
12
|
+
*/
|
|
13
|
+
import { RuntimeManager } from '@domscribe/runtime';
|
|
14
|
+
import { createReactAdapter } from './adapter/react-adapter.js';
|
|
15
|
+
try {
|
|
16
|
+
const runtimeOpts = typeof __DOMSCRIBE_RUNTIME_OPTIONS__ !== 'undefined'
|
|
17
|
+
? __DOMSCRIBE_RUNTIME_OPTIONS__
|
|
18
|
+
: {};
|
|
19
|
+
const adapterOpts = typeof __DOMSCRIBE_ADAPTER_OPTIONS__ !== 'undefined'
|
|
20
|
+
? __DOMSCRIBE_ADAPTER_OPTIONS__
|
|
21
|
+
: {};
|
|
22
|
+
// Reconstruct hookNameResolvers from plain object to Map<string, Map<number, string>>
|
|
23
|
+
const rawResolvers = adapterOpts.hookNameResolvers;
|
|
24
|
+
const hookNameResolvers = rawResolvers
|
|
25
|
+
? new Map(Object.entries(rawResolvers).map(([k, v]) => [
|
|
26
|
+
k,
|
|
27
|
+
new Map(Object.entries(v).map(([i, n]) => [Number(i), n])),
|
|
28
|
+
]))
|
|
29
|
+
: undefined;
|
|
30
|
+
RuntimeManager.getInstance().initialize({
|
|
31
|
+
...runtimeOpts,
|
|
32
|
+
adapter: createReactAdapter({
|
|
33
|
+
...adapterOpts,
|
|
34
|
+
hookNameResolvers,
|
|
35
|
+
}),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
console.warn('[domscribe] Failed to auto-init React runtime:', e instanceof Error ? e.message : String(e));
|
|
40
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ComponentNameResolver - Resolve component names from React Fiber nodes
|
|
3
|
+
*
|
|
4
|
+
* Handles name resolution with wrapper detection (HOC, memo, forwardRef),
|
|
5
|
+
* fallback strategies, and display name formatting.
|
|
6
|
+
*
|
|
7
|
+
* @module @domscribe/react/component/component-name-resolver
|
|
8
|
+
*/
|
|
9
|
+
import type { ExtendedReactFiber } from '../fiber/types.js';
|
|
10
|
+
import type { NameResolutionResult, NameResolutionOptions } from './types.js';
|
|
11
|
+
/**
|
|
12
|
+
* ComponentNameResolver class for resolving component names from Fiber nodes
|
|
13
|
+
*/
|
|
14
|
+
export declare class ComponentNameResolver {
|
|
15
|
+
/**
|
|
16
|
+
* Resolve the name of a component from its Fiber node
|
|
17
|
+
*
|
|
18
|
+
* @param fiber - Fiber node to resolve name from
|
|
19
|
+
* @param options - Resolution options
|
|
20
|
+
* @returns Name resolution result
|
|
21
|
+
* @throws {NameResolutionError} If fiber is invalid
|
|
22
|
+
*/
|
|
23
|
+
resolve(fiber: ExtendedReactFiber, options?: NameResolutionOptions): NameResolutionResult;
|
|
24
|
+
/**
|
|
25
|
+
* Resolve host component (DOM element) name
|
|
26
|
+
*
|
|
27
|
+
* @param fiber - Host component Fiber node
|
|
28
|
+
* @returns Name resolution result
|
|
29
|
+
*/
|
|
30
|
+
private resolveHostComponent;
|
|
31
|
+
/**
|
|
32
|
+
* Resolve text node name
|
|
33
|
+
*
|
|
34
|
+
* @returns Name resolution result for text nodes
|
|
35
|
+
*/
|
|
36
|
+
private resolveTextNode;
|
|
37
|
+
/**
|
|
38
|
+
* Resolve component name without wrapper analysis
|
|
39
|
+
*
|
|
40
|
+
* @param fiber - Fiber node
|
|
41
|
+
* @param options - Resolution options
|
|
42
|
+
* @returns Name resolution result
|
|
43
|
+
*/
|
|
44
|
+
private resolveSimple;
|
|
45
|
+
/**
|
|
46
|
+
* Resolve component name with wrapper analysis
|
|
47
|
+
*
|
|
48
|
+
* @param fiber - Fiber node
|
|
49
|
+
* @param options - Resolution options
|
|
50
|
+
* @returns Name resolution result with wrapper chain
|
|
51
|
+
*/
|
|
52
|
+
private resolveWithWrappers;
|
|
53
|
+
/**
|
|
54
|
+
* Extract displayName from Fiber type
|
|
55
|
+
*
|
|
56
|
+
* @param fiber - Fiber node
|
|
57
|
+
* @returns Display name or null
|
|
58
|
+
*/
|
|
59
|
+
private extractDisplayName;
|
|
60
|
+
/**
|
|
61
|
+
* Extract name from Fiber type
|
|
62
|
+
*
|
|
63
|
+
* @param fiber - Fiber node
|
|
64
|
+
* @returns Type name or null
|
|
65
|
+
*/
|
|
66
|
+
private extractTypeName;
|
|
67
|
+
/**
|
|
68
|
+
* Extract name from Fiber elementType
|
|
69
|
+
*
|
|
70
|
+
* @param fiber - Fiber node
|
|
71
|
+
* @returns Element type name or null
|
|
72
|
+
*/
|
|
73
|
+
private extractElementTypeName;
|
|
74
|
+
/**
|
|
75
|
+
* Detect HOC pattern from component name
|
|
76
|
+
*
|
|
77
|
+
* @param fiber - Fiber node
|
|
78
|
+
* @returns HOC name or null
|
|
79
|
+
*/
|
|
80
|
+
private detectHOCPattern;
|
|
81
|
+
/**
|
|
82
|
+
* Unwrap a memo component to get the inner component
|
|
83
|
+
*
|
|
84
|
+
* @param fiber - Memo Fiber node
|
|
85
|
+
* @returns Unwrapped Fiber
|
|
86
|
+
*/
|
|
87
|
+
private unwrapMemo;
|
|
88
|
+
/**
|
|
89
|
+
* Unwrap a forwardRef component to get the inner component
|
|
90
|
+
*
|
|
91
|
+
* @param fiber - ForwardRef Fiber node
|
|
92
|
+
* @returns Unwrapped Fiber
|
|
93
|
+
*/
|
|
94
|
+
private unwrapForwardRef;
|
|
95
|
+
/**
|
|
96
|
+
* Unwrap an HOC to get the inner component
|
|
97
|
+
*
|
|
98
|
+
* @param fiber - HOC Fiber node
|
|
99
|
+
* @returns Unwrapped Fiber (or original if can't unwrap)
|
|
100
|
+
*/
|
|
101
|
+
private unwrapHOC;
|
|
102
|
+
/**
|
|
103
|
+
* Format a component name with its wrapper chain
|
|
104
|
+
*
|
|
105
|
+
* @param name - Component name
|
|
106
|
+
* @param wrappers - Wrapper names
|
|
107
|
+
* @returns Formatted name
|
|
108
|
+
*/
|
|
109
|
+
private formatWrappedName;
|
|
110
|
+
/**
|
|
111
|
+
* Normalize resolution options with defaults
|
|
112
|
+
*
|
|
113
|
+
* @param options - User-provided options
|
|
114
|
+
* @returns Normalized options
|
|
115
|
+
*/
|
|
116
|
+
private normalizeOptions;
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=component-name-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"component-name-resolver.d.ts","sourceRoot":"","sources":["../../../../packages/domscribe-react/src/component/component-name-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,KAAK,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAQ9E;;GAEG;AACH,qBAAa,qBAAqB;IAChC;;;;;;;OAOG;IACH,OAAO,CACL,KAAK,EAAE,kBAAkB,EACzB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,oBAAoB;IA0BvB;;;;;OAKG;IACH,OAAO,CAAC,oBAAoB;IAe5B;;;;OAIG;IACH,OAAO,CAAC,eAAe;IAUvB;;;;;;OAMG;IACH,OAAO,CAAC,aAAa;IAoDrB;;;;;;OAMG;IACH,OAAO,CAAC,mBAAmB;IA6D3B;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;IA2B1B;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IAoBvB;;;;;OAKG;IACH,OAAO,CAAC,sBAAsB;IAgC9B;;;;;OAKG;IACH,OAAO,CAAC,gBAAgB;IAYxB;;;;;OAKG;IACH,OAAO,CAAC,UAAU;IAsBlB;;;;;OAKG;IACH,OAAO,CAAC,gBAAgB;IAsBxB;;;;;OAKG;IACH,OAAO,CAAC,SAAS;IAUjB;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IASzB;;;;;OAKG;IACH,OAAO,CAAC,gBAAgB;CAWzB"}
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ComponentNameResolver - Resolve component names from React Fiber nodes
|
|
3
|
+
*
|
|
4
|
+
* Handles name resolution with wrapper detection (HOC, memo, forwardRef),
|
|
5
|
+
* fallback strategies, and display name formatting.
|
|
6
|
+
*
|
|
7
|
+
* @module @domscribe/react/component/component-name-resolver
|
|
8
|
+
*/
|
|
9
|
+
import { REACT_FIBER_TAGS, COMMON_HOC_PATTERNS, DEFAULT_OPTIONS, } from '../utils/constants.js';
|
|
10
|
+
import { NameResolutionError } from '../errors/index.js';
|
|
11
|
+
/**
|
|
12
|
+
* ComponentNameResolver class for resolving component names from Fiber nodes
|
|
13
|
+
*/
|
|
14
|
+
export class ComponentNameResolver {
|
|
15
|
+
/**
|
|
16
|
+
* Resolve the name of a component from its Fiber node
|
|
17
|
+
*
|
|
18
|
+
* @param fiber - Fiber node to resolve name from
|
|
19
|
+
* @param options - Resolution options
|
|
20
|
+
* @returns Name resolution result
|
|
21
|
+
* @throws {NameResolutionError} If fiber is invalid
|
|
22
|
+
*/
|
|
23
|
+
resolve(fiber, options) {
|
|
24
|
+
if (!fiber) {
|
|
25
|
+
throw new NameResolutionError('Fiber node is required');
|
|
26
|
+
}
|
|
27
|
+
const opts = this.normalizeOptions(options);
|
|
28
|
+
// Handle host components (DOM elements)
|
|
29
|
+
if (fiber.tag === REACT_FIBER_TAGS.HostComponent) {
|
|
30
|
+
return this.resolveHostComponent(fiber);
|
|
31
|
+
}
|
|
32
|
+
// Handle text nodes
|
|
33
|
+
if (fiber.tag === REACT_FIBER_TAGS.HostText) {
|
|
34
|
+
return this.resolveTextNode();
|
|
35
|
+
}
|
|
36
|
+
// Resolve with wrapper analysis if enabled
|
|
37
|
+
if (opts.includeWrappers) {
|
|
38
|
+
return this.resolveWithWrappers(fiber, opts);
|
|
39
|
+
}
|
|
40
|
+
// Simple resolution
|
|
41
|
+
return this.resolveSimple(fiber, opts);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Resolve host component (DOM element) name
|
|
45
|
+
*
|
|
46
|
+
* @param fiber - Host component Fiber node
|
|
47
|
+
* @returns Name resolution result
|
|
48
|
+
*/
|
|
49
|
+
resolveHostComponent(fiber) {
|
|
50
|
+
const tagName = typeof fiber.type === 'string' ? fiber.type : 'UnknownElement';
|
|
51
|
+
return {
|
|
52
|
+
name: tagName,
|
|
53
|
+
displayName: tagName,
|
|
54
|
+
method: 'type-name',
|
|
55
|
+
confidence: 1.0,
|
|
56
|
+
wrappers: [],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Resolve text node name
|
|
61
|
+
*
|
|
62
|
+
* @returns Name resolution result for text nodes
|
|
63
|
+
*/
|
|
64
|
+
resolveTextNode() {
|
|
65
|
+
return {
|
|
66
|
+
name: 'Text',
|
|
67
|
+
displayName: 'Text',
|
|
68
|
+
method: 'type-name',
|
|
69
|
+
confidence: 1.0,
|
|
70
|
+
wrappers: [],
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Resolve component name without wrapper analysis
|
|
75
|
+
*
|
|
76
|
+
* @param fiber - Fiber node
|
|
77
|
+
* @param options - Resolution options
|
|
78
|
+
* @returns Name resolution result
|
|
79
|
+
*/
|
|
80
|
+
resolveSimple(fiber, options) {
|
|
81
|
+
// Try type.displayName
|
|
82
|
+
const displayName = this.extractDisplayName(fiber);
|
|
83
|
+
if (displayName) {
|
|
84
|
+
return {
|
|
85
|
+
name: displayName,
|
|
86
|
+
displayName,
|
|
87
|
+
method: 'displayName',
|
|
88
|
+
confidence: 1.0,
|
|
89
|
+
wrappers: [],
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
// Try type.name
|
|
93
|
+
const typeName = this.extractTypeName(fiber);
|
|
94
|
+
if (typeName) {
|
|
95
|
+
// Determine if function or class
|
|
96
|
+
const isFunction = typeof fiber.type === 'function';
|
|
97
|
+
return {
|
|
98
|
+
name: typeName,
|
|
99
|
+
displayName: typeName,
|
|
100
|
+
method: isFunction ? 'function-name' : 'type-name',
|
|
101
|
+
confidence: 0.8,
|
|
102
|
+
wrappers: [],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
// Try elementType
|
|
106
|
+
const elementTypeName = this.extractElementTypeName(fiber);
|
|
107
|
+
if (elementTypeName) {
|
|
108
|
+
return {
|
|
109
|
+
name: elementTypeName,
|
|
110
|
+
displayName: elementTypeName,
|
|
111
|
+
method: 'type-name',
|
|
112
|
+
confidence: 0.5,
|
|
113
|
+
wrappers: [],
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
// Fallback
|
|
117
|
+
return {
|
|
118
|
+
name: options.fallbackName,
|
|
119
|
+
displayName: options.fallbackName,
|
|
120
|
+
method: 'fallback',
|
|
121
|
+
confidence: 0.0,
|
|
122
|
+
wrappers: [],
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Resolve component name with wrapper analysis
|
|
127
|
+
*
|
|
128
|
+
* @param fiber - Fiber node
|
|
129
|
+
* @param options - Resolution options
|
|
130
|
+
* @returns Name resolution result with wrapper chain
|
|
131
|
+
*/
|
|
132
|
+
resolveWithWrappers(fiber, options) {
|
|
133
|
+
const wrappers = [];
|
|
134
|
+
let currentFiber = fiber;
|
|
135
|
+
let depth = 0;
|
|
136
|
+
// Unwrap layers
|
|
137
|
+
while (depth < options.maxWrapperDepth) {
|
|
138
|
+
// Check for memo
|
|
139
|
+
if (currentFiber.tag === REACT_FIBER_TAGS.MemoComponent ||
|
|
140
|
+
currentFiber.tag === REACT_FIBER_TAGS.SimpleMemoComponent) {
|
|
141
|
+
wrappers.push('memo');
|
|
142
|
+
const unwrapped = this.unwrapMemo(currentFiber);
|
|
143
|
+
if (unwrapped === currentFiber)
|
|
144
|
+
break;
|
|
145
|
+
currentFiber = unwrapped;
|
|
146
|
+
}
|
|
147
|
+
// Check for forwardRef
|
|
148
|
+
else if (currentFiber.tag === REACT_FIBER_TAGS.ForwardRef) {
|
|
149
|
+
wrappers.push('forwardRef');
|
|
150
|
+
const unwrapped = this.unwrapForwardRef(currentFiber);
|
|
151
|
+
if (unwrapped === currentFiber)
|
|
152
|
+
break;
|
|
153
|
+
currentFiber = unwrapped;
|
|
154
|
+
}
|
|
155
|
+
// Check for HOC patterns
|
|
156
|
+
else {
|
|
157
|
+
const hocName = this.detectHOCPattern(currentFiber);
|
|
158
|
+
if (hocName) {
|
|
159
|
+
wrappers.push(hocName);
|
|
160
|
+
const unwrapped = this.unwrapHOC(currentFiber);
|
|
161
|
+
if (unwrapped === currentFiber)
|
|
162
|
+
break;
|
|
163
|
+
currentFiber = unwrapped;
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
break; // No more wrappers
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
depth++;
|
|
170
|
+
}
|
|
171
|
+
// Get the innermost component name
|
|
172
|
+
const innerResult = this.resolveSimple(currentFiber, options);
|
|
173
|
+
// Build display name with wrappers
|
|
174
|
+
const displayName = wrappers.length > 0
|
|
175
|
+
? this.formatWrappedName(innerResult.name, wrappers)
|
|
176
|
+
: innerResult.name;
|
|
177
|
+
return {
|
|
178
|
+
name: innerResult.name,
|
|
179
|
+
displayName,
|
|
180
|
+
method: innerResult.method,
|
|
181
|
+
confidence: innerResult.confidence,
|
|
182
|
+
wrappers,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Extract displayName from Fiber type
|
|
187
|
+
*
|
|
188
|
+
* @param fiber - Fiber node
|
|
189
|
+
* @returns Display name or null
|
|
190
|
+
*/
|
|
191
|
+
extractDisplayName(fiber) {
|
|
192
|
+
const type = fiber.type;
|
|
193
|
+
// Check function components (e.g., MyComponent.displayName = 'Pretty')
|
|
194
|
+
if (typeof type === 'function' &&
|
|
195
|
+
'displayName' in type &&
|
|
196
|
+
typeof type.displayName === 'string' &&
|
|
197
|
+
type.displayName.length > 0) {
|
|
198
|
+
return type.displayName;
|
|
199
|
+
}
|
|
200
|
+
// Check object types (e.g., memo/forwardRef wrappers with displayName)
|
|
201
|
+
if (type &&
|
|
202
|
+
typeof type === 'object' &&
|
|
203
|
+
'displayName' in type &&
|
|
204
|
+
typeof type.displayName === 'string' &&
|
|
205
|
+
type.displayName.length > 0) {
|
|
206
|
+
return type.displayName;
|
|
207
|
+
}
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Extract name from Fiber type
|
|
212
|
+
*
|
|
213
|
+
* @param fiber - Fiber node
|
|
214
|
+
* @returns Type name or null
|
|
215
|
+
*/
|
|
216
|
+
extractTypeName(fiber) {
|
|
217
|
+
const type = fiber.type;
|
|
218
|
+
if (typeof type === 'function' && type.name) {
|
|
219
|
+
return type.name;
|
|
220
|
+
}
|
|
221
|
+
if (type &&
|
|
222
|
+
typeof type === 'object' &&
|
|
223
|
+
'name' in type &&
|
|
224
|
+
typeof type.name === 'string' &&
|
|
225
|
+
type.name.length > 0) {
|
|
226
|
+
return type.name;
|
|
227
|
+
}
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Extract name from Fiber elementType
|
|
232
|
+
*
|
|
233
|
+
* @param fiber - Fiber node
|
|
234
|
+
* @returns Element type name or null
|
|
235
|
+
*/
|
|
236
|
+
extractElementTypeName(fiber) {
|
|
237
|
+
const elementType = fiber.elementType;
|
|
238
|
+
if (typeof elementType === 'string') {
|
|
239
|
+
return elementType;
|
|
240
|
+
}
|
|
241
|
+
if (typeof elementType === 'function' && elementType.name) {
|
|
242
|
+
return elementType.name;
|
|
243
|
+
}
|
|
244
|
+
if (elementType &&
|
|
245
|
+
typeof elementType === 'object' &&
|
|
246
|
+
'displayName' in elementType &&
|
|
247
|
+
typeof elementType.displayName === 'string') {
|
|
248
|
+
return elementType.displayName;
|
|
249
|
+
}
|
|
250
|
+
if (elementType &&
|
|
251
|
+
typeof elementType === 'object' &&
|
|
252
|
+
'name' in elementType &&
|
|
253
|
+
typeof elementType.name === 'string') {
|
|
254
|
+
return elementType.name;
|
|
255
|
+
}
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Detect HOC pattern from component name
|
|
260
|
+
*
|
|
261
|
+
* @param fiber - Fiber node
|
|
262
|
+
* @returns HOC name or null
|
|
263
|
+
*/
|
|
264
|
+
detectHOCPattern(fiber) {
|
|
265
|
+
const name = this.extractTypeName(fiber) || this.extractDisplayName(fiber);
|
|
266
|
+
if (!name) {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
const matchedPattern = COMMON_HOC_PATTERNS.find((pattern) => name.startsWith(pattern));
|
|
270
|
+
return matchedPattern || null;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Unwrap a memo component to get the inner component
|
|
274
|
+
*
|
|
275
|
+
* @param fiber - Memo Fiber node
|
|
276
|
+
* @returns Unwrapped Fiber
|
|
277
|
+
*/
|
|
278
|
+
unwrapMemo(fiber) {
|
|
279
|
+
// Try to get the inner type from elementType
|
|
280
|
+
if (fiber.elementType &&
|
|
281
|
+
typeof fiber.elementType === 'object' &&
|
|
282
|
+
fiber.elementType !== null &&
|
|
283
|
+
'type' in fiber.elementType) {
|
|
284
|
+
const innerType = fiber.elementType.type;
|
|
285
|
+
// Create a synthetic fiber with the inner type and reset tag
|
|
286
|
+
// so the unwrap loop doesn't re-match this as a memo wrapper
|
|
287
|
+
return {
|
|
288
|
+
...fiber,
|
|
289
|
+
tag: REACT_FIBER_TAGS.FunctionComponent,
|
|
290
|
+
type: innerType,
|
|
291
|
+
elementType: innerType,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
return fiber;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Unwrap a forwardRef component to get the inner component
|
|
298
|
+
*
|
|
299
|
+
* @param fiber - ForwardRef Fiber node
|
|
300
|
+
* @returns Unwrapped Fiber
|
|
301
|
+
*/
|
|
302
|
+
unwrapForwardRef(fiber) {
|
|
303
|
+
// Try to get the render function from elementType
|
|
304
|
+
if (fiber.elementType &&
|
|
305
|
+
typeof fiber.elementType === 'object' &&
|
|
306
|
+
fiber.elementType !== null &&
|
|
307
|
+
'render' in fiber.elementType) {
|
|
308
|
+
const renderFn = fiber.elementType.render;
|
|
309
|
+
// Create a synthetic fiber with the render function as type and reset tag
|
|
310
|
+
// so the unwrap loop doesn't re-match this as a forwardRef wrapper
|
|
311
|
+
return {
|
|
312
|
+
...fiber,
|
|
313
|
+
tag: REACT_FIBER_TAGS.FunctionComponent,
|
|
314
|
+
type: renderFn,
|
|
315
|
+
elementType: renderFn,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
return fiber;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Unwrap an HOC to get the inner component
|
|
322
|
+
*
|
|
323
|
+
* @param fiber - HOC Fiber node
|
|
324
|
+
* @returns Unwrapped Fiber (or original if can't unwrap)
|
|
325
|
+
*/
|
|
326
|
+
unwrapHOC(fiber) {
|
|
327
|
+
// For HOCs, we can't reliably unwrap without runtime inspection
|
|
328
|
+
// This is a best-effort attempt using child
|
|
329
|
+
if (fiber.child) {
|
|
330
|
+
return fiber.child;
|
|
331
|
+
}
|
|
332
|
+
return fiber;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Format a component name with its wrapper chain
|
|
336
|
+
*
|
|
337
|
+
* @param name - Component name
|
|
338
|
+
* @param wrappers - Wrapper names
|
|
339
|
+
* @returns Formatted name
|
|
340
|
+
*/
|
|
341
|
+
formatWrappedName(name, wrappers) {
|
|
342
|
+
if (wrappers.length === 0) {
|
|
343
|
+
return name;
|
|
344
|
+
}
|
|
345
|
+
// Format: wrapper1(wrapper2(Component))
|
|
346
|
+
return `${wrappers.join('(')}(${name}${')'.repeat(wrappers.length)}`;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Normalize resolution options with defaults
|
|
350
|
+
*
|
|
351
|
+
* @param options - User-provided options
|
|
352
|
+
* @returns Normalized options
|
|
353
|
+
*/
|
|
354
|
+
normalizeOptions(options) {
|
|
355
|
+
return {
|
|
356
|
+
includeWrappers: options?.includeWrappers ?? false,
|
|
357
|
+
maxWrapperDepth: options?.maxWrapperDepth ?? DEFAULT_OPTIONS.MAX_WRAPPER_DEPTH,
|
|
358
|
+
fallbackName: options?.fallbackName ?? DEFAULT_OPTIONS.FALLBACK_COMPONENT_NAME,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
}
|