@creact-labs/creact 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/LICENSE +212 -0
- package/README.md +379 -0
- package/dist/cli/commands/BuildCommand.d.ts +40 -0
- package/dist/cli/commands/BuildCommand.js +151 -0
- package/dist/cli/commands/DeployCommand.d.ts +38 -0
- package/dist/cli/commands/DeployCommand.js +194 -0
- package/dist/cli/commands/DevCommand.d.ts +52 -0
- package/dist/cli/commands/DevCommand.js +385 -0
- package/dist/cli/commands/PlanCommand.d.ts +39 -0
- package/dist/cli/commands/PlanCommand.js +164 -0
- package/dist/cli/commands/index.d.ts +36 -0
- package/dist/cli/commands/index.js +43 -0
- package/dist/cli/core/ArgumentParser.d.ts +46 -0
- package/dist/cli/core/ArgumentParser.js +127 -0
- package/dist/cli/core/BaseCommand.d.ts +75 -0
- package/dist/cli/core/BaseCommand.js +95 -0
- package/dist/cli/core/CLIContext.d.ts +68 -0
- package/dist/cli/core/CLIContext.js +183 -0
- package/dist/cli/core/CommandRegistry.d.ts +64 -0
- package/dist/cli/core/CommandRegistry.js +89 -0
- package/dist/cli/core/index.d.ts +36 -0
- package/dist/cli/core/index.js +43 -0
- package/dist/cli/index.d.ts +35 -0
- package/dist/cli/index.js +100 -0
- package/dist/cli/output.d.ts +204 -0
- package/dist/cli/output.js +437 -0
- package/dist/cli/utils.d.ts +59 -0
- package/dist/cli/utils.js +76 -0
- package/dist/context/createContext.d.ts +90 -0
- package/dist/context/createContext.js +113 -0
- package/dist/context/index.d.ts +30 -0
- package/dist/context/index.js +35 -0
- package/dist/core/CReact.d.ts +409 -0
- package/dist/core/CReact.js +1127 -0
- package/dist/core/CloudDOMBuilder.d.ts +429 -0
- package/dist/core/CloudDOMBuilder.js +1198 -0
- package/dist/core/ContextDependencyTracker.d.ts +165 -0
- package/dist/core/ContextDependencyTracker.js +448 -0
- package/dist/core/ErrorRecoveryManager.d.ts +145 -0
- package/dist/core/ErrorRecoveryManager.js +443 -0
- package/dist/core/EventBus.d.ts +91 -0
- package/dist/core/EventBus.js +185 -0
- package/dist/core/ProviderOutputTracker.d.ts +211 -0
- package/dist/core/ProviderOutputTracker.js +476 -0
- package/dist/core/ReactiveUpdateQueue.d.ts +76 -0
- package/dist/core/ReactiveUpdateQueue.js +121 -0
- package/dist/core/Reconciler.d.ts +415 -0
- package/dist/core/Reconciler.js +1037 -0
- package/dist/core/RenderScheduler.d.ts +153 -0
- package/dist/core/RenderScheduler.js +519 -0
- package/dist/core/Renderer.d.ts +276 -0
- package/dist/core/Renderer.js +791 -0
- package/dist/core/Runtime.d.ts +246 -0
- package/dist/core/Runtime.js +640 -0
- package/dist/core/StateBindingManager.d.ts +121 -0
- package/dist/core/StateBindingManager.js +309 -0
- package/dist/core/StateMachine.d.ts +424 -0
- package/dist/core/StateMachine.js +787 -0
- package/dist/core/StructuralChangeDetector.d.ts +140 -0
- package/dist/core/StructuralChangeDetector.js +363 -0
- package/dist/core/Validator.d.ts +127 -0
- package/dist/core/Validator.js +279 -0
- package/dist/core/errors.d.ts +153 -0
- package/dist/core/errors.js +202 -0
- package/dist/core/index.d.ts +38 -0
- package/dist/core/index.js +64 -0
- package/dist/core/types.d.ts +263 -0
- package/dist/core/types.js +48 -0
- package/dist/hooks/context.d.ts +147 -0
- package/dist/hooks/context.js +334 -0
- package/dist/hooks/useContext.d.ts +113 -0
- package/dist/hooks/useContext.js +169 -0
- package/dist/hooks/useEffect.d.ts +105 -0
- package/dist/hooks/useEffect.js +540 -0
- package/dist/hooks/useInstance.d.ts +139 -0
- package/dist/hooks/useInstance.js +441 -0
- package/dist/hooks/useState.d.ts +120 -0
- package/dist/hooks/useState.js +298 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +70 -0
- package/dist/jsx.d.ts +64 -0
- package/dist/jsx.js +76 -0
- package/dist/providers/DummyBackendProvider.d.ts +193 -0
- package/dist/providers/DummyBackendProvider.js +189 -0
- package/dist/providers/DummyCloudProvider.d.ts +128 -0
- package/dist/providers/DummyCloudProvider.js +157 -0
- package/dist/providers/IBackendProvider.d.ts +177 -0
- package/dist/providers/IBackendProvider.js +31 -0
- package/dist/providers/ICloudProvider.d.ts +146 -0
- package/dist/providers/ICloudProvider.js +31 -0
- package/dist/providers/index.d.ts +31 -0
- package/dist/providers/index.js +31 -0
- package/dist/test-event-callbacks.d.ts +0 -0
- package/dist/test-event-callbacks.js +1 -0
- package/dist/utils/Logger.d.ts +144 -0
- package/dist/utils/Logger.js +220 -0
- package/dist/utils/Output.d.ts +161 -0
- package/dist/utils/Output.js +401 -0
- package/dist/utils/deepEqual.d.ts +71 -0
- package/dist/utils/deepEqual.js +276 -0
- package/dist/utils/naming.d.ts +241 -0
- package/dist/utils/naming.js +376 -0
- package/package.json +87 -0
|
@@ -0,0 +1,791 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
|
|
10
|
+
*
|
|
11
|
+
|
|
12
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
13
|
+
|
|
14
|
+
*
|
|
15
|
+
|
|
16
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
17
|
+
|
|
18
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
19
|
+
|
|
20
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
21
|
+
|
|
22
|
+
* See the License for the specific language governing permissions and
|
|
23
|
+
|
|
24
|
+
* limitations under the License.
|
|
25
|
+
|
|
26
|
+
*
|
|
27
|
+
|
|
28
|
+
* Copyright 2025 Daniel Coutinho Ribeiro
|
|
29
|
+
|
|
30
|
+
*/
|
|
31
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
32
|
+
exports.Renderer = void 0;
|
|
33
|
+
const useInstance_1 = require("../hooks/useInstance");
|
|
34
|
+
const context_1 = require("../hooks/context");
|
|
35
|
+
const naming_1 = require("../utils/naming");
|
|
36
|
+
const Logger_1 = require("../utils/Logger");
|
|
37
|
+
const logger = Logger_1.LoggerFactory.getLogger('renderer');
|
|
38
|
+
/**
|
|
39
|
+
* Renderer transforms JSX into a Fiber tree
|
|
40
|
+
*
|
|
41
|
+
* The Fiber tree is an intermediate representation that includes:
|
|
42
|
+
* - All components (containers + resources)
|
|
43
|
+
* - Hierarchical paths for identity tracking
|
|
44
|
+
* - Props and children resolved recursively
|
|
45
|
+
*
|
|
46
|
+
* Enhanced with context reactivity:
|
|
47
|
+
* - Tracks context provider value changes
|
|
48
|
+
* - Integrates with ContextDependencyTracker for selective re-rendering
|
|
49
|
+
* - Detects when context values change and triggers appropriate re-renders
|
|
50
|
+
*
|
|
51
|
+
* No dependencies injected - pure transformation logic
|
|
52
|
+
*/
|
|
53
|
+
class Renderer {
|
|
54
|
+
constructor() {
|
|
55
|
+
this.currentFiber = null;
|
|
56
|
+
this.currentPath = [];
|
|
57
|
+
// Structural change detection
|
|
58
|
+
this.previousStructures = new WeakMap();
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Render JSX to Fiber tree
|
|
62
|
+
*
|
|
63
|
+
* @param jsx - JSX element to render
|
|
64
|
+
* @returns Root Fiber node
|
|
65
|
+
*/
|
|
66
|
+
render(jsx) {
|
|
67
|
+
// Run rendering within AsyncLocalStorage context for thread safety
|
|
68
|
+
return (0, context_1.runWithHookContext)(() => {
|
|
69
|
+
try {
|
|
70
|
+
this.currentPath = [];
|
|
71
|
+
this.currentFiber = this.renderElement(jsx, [], 0);
|
|
72
|
+
return this.currentFiber;
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
// Clear context stacks to prevent memory leaks
|
|
76
|
+
// This ensures context stacks are always cleared after each render cycle
|
|
77
|
+
(0, context_1.clearContextStacks)();
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Recursively render a JSX element to a Fiber node
|
|
83
|
+
*
|
|
84
|
+
* @param element - JSX element
|
|
85
|
+
* @param parentPath - Path from parent
|
|
86
|
+
* @param siblingIndex - Sibling index for automatic key generation (default 0)
|
|
87
|
+
* @returns Fiber node
|
|
88
|
+
*/
|
|
89
|
+
renderElement(element, parentPath, siblingIndex = 0) {
|
|
90
|
+
if (!element || typeof element !== 'object') {
|
|
91
|
+
throw new Error('Invalid JSX element');
|
|
92
|
+
}
|
|
93
|
+
const { type, props, key } = element;
|
|
94
|
+
if (!type) {
|
|
95
|
+
throw new Error('JSX element missing type');
|
|
96
|
+
}
|
|
97
|
+
// Ensure props is always an object (handle null/undefined)
|
|
98
|
+
const safeProps = props || {};
|
|
99
|
+
// Generate path for this node with sibling index support
|
|
100
|
+
const nodeName = (0, naming_1.getNodeName)(type, safeProps, key, siblingIndex);
|
|
101
|
+
const path = [...parentPath, nodeName];
|
|
102
|
+
// Create Fiber node for component execution
|
|
103
|
+
const fiber = {
|
|
104
|
+
type,
|
|
105
|
+
props: { ...safeProps },
|
|
106
|
+
children: [],
|
|
107
|
+
path,
|
|
108
|
+
key: key !== undefined ? (typeof key === 'symbol' ? nodeName : key.toString()) : undefined,
|
|
109
|
+
cloudDOMNodes: [], // Will be populated by useInstance
|
|
110
|
+
};
|
|
111
|
+
// Check if this is a Context Provider and handle value changes
|
|
112
|
+
const isProvider = typeof type === 'function' && type._isContextProvider;
|
|
113
|
+
if (isProvider) {
|
|
114
|
+
const contextId = type._contextId;
|
|
115
|
+
const newValue = safeProps.value;
|
|
116
|
+
// Detect context value changes and schedule re-renders if needed
|
|
117
|
+
this.handleContextProviderValueChange(contextId, newValue);
|
|
118
|
+
// Push value onto stack for child components
|
|
119
|
+
(0, context_1.pushContextValue)(contextId, newValue);
|
|
120
|
+
}
|
|
121
|
+
// Set context and execute component function to get children
|
|
122
|
+
(0, context_1.setRenderContext)(fiber, path);
|
|
123
|
+
(0, useInstance_1.resetConstructCounts)(fiber); // Reset construct call counts for this component
|
|
124
|
+
const children = this.executeComponent(type, safeProps, path);
|
|
125
|
+
(0, context_1.clearRenderContext)();
|
|
126
|
+
// Recursively render children
|
|
127
|
+
if (children) {
|
|
128
|
+
fiber.children = this.renderChildren(children, path);
|
|
129
|
+
}
|
|
130
|
+
// Check for structural changes after component execution
|
|
131
|
+
this.handleStructuralChanges(fiber);
|
|
132
|
+
// Pop context value from stack after rendering children
|
|
133
|
+
if (isProvider) {
|
|
134
|
+
const contextId = type._contextId;
|
|
135
|
+
(0, context_1.popContextValue)(contextId);
|
|
136
|
+
}
|
|
137
|
+
return fiber;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Execute a component function to resolve its children
|
|
141
|
+
*
|
|
142
|
+
* Note: setRenderContext should already be called by renderComponent before this
|
|
143
|
+
*
|
|
144
|
+
* @param type - Component type (function or class)
|
|
145
|
+
* @param props - Props to pass
|
|
146
|
+
* @param path - Current path (for context)
|
|
147
|
+
* @returns Children elements
|
|
148
|
+
*/
|
|
149
|
+
executeComponent(type, props, path) {
|
|
150
|
+
// Store current path for hooks to access
|
|
151
|
+
const previousPath = this.currentPath;
|
|
152
|
+
this.currentPath = path;
|
|
153
|
+
try {
|
|
154
|
+
// Handle Fragment (symbol type)
|
|
155
|
+
if (typeof type === 'symbol') {
|
|
156
|
+
// Fragments just pass through their children
|
|
157
|
+
return props.children;
|
|
158
|
+
}
|
|
159
|
+
// Handle function components
|
|
160
|
+
if (typeof type === 'function') {
|
|
161
|
+
// Check if it's a class component
|
|
162
|
+
if (type.prototype && type.prototype.isReactComponent) {
|
|
163
|
+
const instance = new type(props);
|
|
164
|
+
return instance.render();
|
|
165
|
+
}
|
|
166
|
+
// Function component
|
|
167
|
+
return type(props);
|
|
168
|
+
}
|
|
169
|
+
// Handle intrinsic elements (strings like 'div')
|
|
170
|
+
if (typeof type === 'string') {
|
|
171
|
+
return props.children;
|
|
172
|
+
}
|
|
173
|
+
throw new Error(`Unknown component type: ${typeof type === 'symbol' ? 'Symbol' : type}`);
|
|
174
|
+
}
|
|
175
|
+
finally {
|
|
176
|
+
// Clear rendering context for hooks
|
|
177
|
+
(0, context_1.clearRenderContext)();
|
|
178
|
+
// Restore previous path
|
|
179
|
+
this.currentPath = previousPath;
|
|
180
|
+
// Copy CloudDOM nodes from temp fiber to actual fiber (will be done in renderElement)
|
|
181
|
+
// This is handled by returning the tempFiber's cloudDOMNodes
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Render children elements
|
|
186
|
+
*
|
|
187
|
+
* @param children - Children to render (can be array, single element, or null)
|
|
188
|
+
* @param parentPath - Path from parent
|
|
189
|
+
* @returns Array of Fiber nodes
|
|
190
|
+
*/
|
|
191
|
+
renderChildren(children, parentPath) {
|
|
192
|
+
if (!children) {
|
|
193
|
+
return [];
|
|
194
|
+
}
|
|
195
|
+
// Debug logging for array handling
|
|
196
|
+
if (Array.isArray(children) && children.length > 0) {
|
|
197
|
+
const hasNestedArrays = children.some(child => Array.isArray(child));
|
|
198
|
+
if (hasNestedArrays) {
|
|
199
|
+
logger.debug(`[renderChildren] Nested arrays detected at ${parentPath.join('.')}`);
|
|
200
|
+
logger.debug(` Before flat: ${children.length} items`);
|
|
201
|
+
logger.debug(` After flat: ${children.flat().length} items`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// Normalize to array and flatten nested arrays (handles .map() results)
|
|
205
|
+
const childArray = Array.isArray(children) ? children.flat() : [children];
|
|
206
|
+
// Filter out null/undefined/boolean values
|
|
207
|
+
const validChildren = childArray.filter((child) => child !== null && child !== undefined && typeof child !== 'boolean');
|
|
208
|
+
// Track sibling counts by component type for automatic key generation
|
|
209
|
+
const siblingCounts = new Map();
|
|
210
|
+
// Track which types have been warned about (only warn once per type per render)
|
|
211
|
+
const warnedTypes = new Set();
|
|
212
|
+
// Render each child
|
|
213
|
+
return validChildren
|
|
214
|
+
.map((child) => {
|
|
215
|
+
// Handle text nodes (strings/numbers)
|
|
216
|
+
if (typeof child === 'string' || typeof child === 'number') {
|
|
217
|
+
// Skip text nodes in infrastructure (they don't make sense)
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
// Render JSX element
|
|
221
|
+
if (typeof child === 'object' && child.type) {
|
|
222
|
+
// Track sibling index for this type
|
|
223
|
+
const currentIndex = siblingCounts.get(child.type) || 0;
|
|
224
|
+
siblingCounts.set(child.type, currentIndex + 1);
|
|
225
|
+
// Emit warning when multiple siblings of same type without explicit keys
|
|
226
|
+
// This detects potential .map() usage without keys
|
|
227
|
+
if (currentIndex > 0 && !child.key && !warnedTypes.has(child.type)) {
|
|
228
|
+
warnedTypes.add(child.type);
|
|
229
|
+
const typeName = this.getTypeName(child.type);
|
|
230
|
+
const pathStr = parentPath.join('.');
|
|
231
|
+
logger.warn(`Warning: Multiple children of type "${typeName}" ` +
|
|
232
|
+
`without explicit keys at path "${pathStr}". ` +
|
|
233
|
+
`Consider adding a "key" prop for better reconciliation.`);
|
|
234
|
+
}
|
|
235
|
+
// Pass sibling index to renderElement
|
|
236
|
+
return this.renderElement(child, parentPath, currentIndex);
|
|
237
|
+
}
|
|
238
|
+
return null;
|
|
239
|
+
})
|
|
240
|
+
.filter((node) => node !== null);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Get a human-readable type name for warning messages
|
|
244
|
+
*
|
|
245
|
+
* @param type - Component type
|
|
246
|
+
* @returns Type name string
|
|
247
|
+
*/
|
|
248
|
+
getTypeName(type) {
|
|
249
|
+
if (typeof type === 'function') {
|
|
250
|
+
return type.displayName || type.name || 'anonymous';
|
|
251
|
+
}
|
|
252
|
+
if (typeof type === 'string') {
|
|
253
|
+
return type;
|
|
254
|
+
}
|
|
255
|
+
if (typeof type === 'symbol') {
|
|
256
|
+
return 'Fragment';
|
|
257
|
+
}
|
|
258
|
+
return 'unknown';
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Get current path (for hooks to access)
|
|
262
|
+
*/
|
|
263
|
+
getCurrentPath() {
|
|
264
|
+
return [...this.currentPath];
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Get current Fiber for post-deployment effects
|
|
268
|
+
*
|
|
269
|
+
* @returns Current Fiber node or null
|
|
270
|
+
*/
|
|
271
|
+
getCurrentFiber() {
|
|
272
|
+
return this.currentFiber;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Set the RenderScheduler for selective re-rendering integration
|
|
276
|
+
*
|
|
277
|
+
* @param scheduler - RenderScheduler instance
|
|
278
|
+
*/
|
|
279
|
+
setRenderScheduler(scheduler) {
|
|
280
|
+
this.renderScheduler = scheduler;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Set the ContextDependencyTracker for context reactivity
|
|
284
|
+
*
|
|
285
|
+
* @param tracker - ContextDependencyTracker instance
|
|
286
|
+
*/
|
|
287
|
+
setContextDependencyTracker(tracker) {
|
|
288
|
+
this.contextDependencyTracker = tracker;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Set structural change detector for topology change detection
|
|
292
|
+
* @param detector - StructuralChangeDetector instance
|
|
293
|
+
*/
|
|
294
|
+
setStructuralChangeDetector(detector) {
|
|
295
|
+
this.structuralChangeDetector = detector;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Handle context provider value changes
|
|
299
|
+
* Called when a context provider's value prop changes
|
|
300
|
+
*
|
|
301
|
+
* @param contextId - Context identifier
|
|
302
|
+
* @param newValue - New context value
|
|
303
|
+
*/
|
|
304
|
+
handleContextProviderValueChange(contextId, newValue) {
|
|
305
|
+
if (this.contextDependencyTracker) {
|
|
306
|
+
try {
|
|
307
|
+
// Update context value and get affected fibers
|
|
308
|
+
const affectedFibers = this.contextDependencyTracker.updateContextValue(contextId, newValue);
|
|
309
|
+
// Schedule re-renders for affected components
|
|
310
|
+
if (this.renderScheduler && affectedFibers.length > 0) {
|
|
311
|
+
affectedFibers.forEach((fiber) => {
|
|
312
|
+
this.renderScheduler.schedule(fiber, 'context-change');
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
catch (error) {
|
|
317
|
+
logger.warn('Context provider value change handling failed:', error);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Detect structural changes in a component
|
|
323
|
+
* Compares the current structure with the previous render
|
|
324
|
+
*
|
|
325
|
+
* @param fiber - Fiber node to check for structural changes
|
|
326
|
+
* @returns True if structural changes were detected
|
|
327
|
+
*/
|
|
328
|
+
detectStructuralChanges(fiber) {
|
|
329
|
+
// Generate structural signature for current render
|
|
330
|
+
const currentStructure = this.generateStructuralSignature(fiber);
|
|
331
|
+
// Get previous structure
|
|
332
|
+
const previousStructure = this.previousStructures.get(fiber);
|
|
333
|
+
// Store current structure for next comparison
|
|
334
|
+
this.previousStructures.set(fiber, currentStructure);
|
|
335
|
+
// If no previous structure, this is the first render (not a change)
|
|
336
|
+
if (!previousStructure) {
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
// Compare structures
|
|
340
|
+
const hasChanged = previousStructure !== currentStructure;
|
|
341
|
+
if (hasChanged) {
|
|
342
|
+
logger.debug(`Structural change detected in ${fiber.path.join('.')}:`);
|
|
343
|
+
logger.debug(` Previous: ${previousStructure}`);
|
|
344
|
+
logger.debug(` Current: ${currentStructure}`);
|
|
345
|
+
}
|
|
346
|
+
return hasChanged;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Generate a structural signature for a fiber node
|
|
350
|
+
* This captures the essential structure that affects CloudDOM generation
|
|
351
|
+
*
|
|
352
|
+
* @param fiber - Fiber node to generate signature for
|
|
353
|
+
* @returns Structural signature string
|
|
354
|
+
*/
|
|
355
|
+
generateStructuralSignature(fiber) {
|
|
356
|
+
const parts = [];
|
|
357
|
+
// Include component type
|
|
358
|
+
const typeName = fiber.type?.name || (typeof fiber.type === 'symbol' ? 'fragment' : fiber.type);
|
|
359
|
+
parts.push(`type:${typeName}`);
|
|
360
|
+
// Include number and types of CloudDOM nodes (useInstance calls)
|
|
361
|
+
if (fiber.cloudDOMNodes && fiber.cloudDOMNodes.length > 0) {
|
|
362
|
+
const nodeSignatures = fiber.cloudDOMNodes
|
|
363
|
+
.map((node) => `${node.construct?.name || 'unknown'}:${node.id}`)
|
|
364
|
+
.sort(); // Sort for consistent ordering
|
|
365
|
+
parts.push(`nodes:[${nodeSignatures.join(',')}]`);
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
parts.push('nodes:[]');
|
|
369
|
+
}
|
|
370
|
+
// Include number of children and their types
|
|
371
|
+
if (fiber.children && fiber.children.length > 0) {
|
|
372
|
+
const childTypes = fiber.children
|
|
373
|
+
.map((child) => child.type?.name ||
|
|
374
|
+
(typeof child.type === 'symbol' ? 'fragment' : child.type) ||
|
|
375
|
+
'unknown')
|
|
376
|
+
.sort(); // Sort for consistent ordering
|
|
377
|
+
parts.push(`children:[${childTypes.join(',')}]`);
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
parts.push('children:[]');
|
|
381
|
+
}
|
|
382
|
+
// Include key if present (affects identity)
|
|
383
|
+
if (fiber.key) {
|
|
384
|
+
const keyValue = typeof fiber.key === 'symbol' ? 'fragment' : fiber.key;
|
|
385
|
+
parts.push(`key:${keyValue}`);
|
|
386
|
+
}
|
|
387
|
+
return parts.join('|');
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Check for structural changes and schedule re-renders if needed
|
|
391
|
+
* Called during component re-execution
|
|
392
|
+
*
|
|
393
|
+
* @param fiber - Fiber node to check
|
|
394
|
+
*/
|
|
395
|
+
handleStructuralChanges(fiber) {
|
|
396
|
+
if (this.detectStructuralChanges(fiber)) {
|
|
397
|
+
// Schedule re-render for structural change
|
|
398
|
+
if (this.renderScheduler) {
|
|
399
|
+
this.renderScheduler.schedule(fiber, 'structural-change');
|
|
400
|
+
}
|
|
401
|
+
// Also check if any dependent components need re-rendering
|
|
402
|
+
if (fiber.dependents) {
|
|
403
|
+
Array.from(fiber.dependents).forEach((dependent) => {
|
|
404
|
+
if (this.renderScheduler) {
|
|
405
|
+
this.renderScheduler.schedule(dependent, 'structural-change');
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Handle structural changes between old and new fiber trees
|
|
413
|
+
* Called during re-rendering to detect topology changes
|
|
414
|
+
*
|
|
415
|
+
* @param oldFiber - Previous fiber tree
|
|
416
|
+
* @param newFiber - New fiber tree after re-render
|
|
417
|
+
*/
|
|
418
|
+
handleStructuralChangesComparison(oldFiber, newFiber) {
|
|
419
|
+
if (!this.structuralChangeDetector) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
try {
|
|
423
|
+
// Extract CloudDOM from both fiber trees for comparison
|
|
424
|
+
const oldCloudDOM = this.extractCloudDOMFromFiber(oldFiber);
|
|
425
|
+
const newCloudDOM = this.extractCloudDOMFromFiber(newFiber);
|
|
426
|
+
// Detect structural changes
|
|
427
|
+
const changes = this.structuralChangeDetector.detectStructuralChanges(oldCloudDOM, newCloudDOM, newFiber);
|
|
428
|
+
if (changes.length > 0) {
|
|
429
|
+
// Trigger re-renders for affected components
|
|
430
|
+
this.structuralChangeDetector.triggerStructuralReRenders(changes, this.renderScheduler);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
catch (error) {
|
|
434
|
+
logger.error('Error handling structural changes:', error);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Extract CloudDOM nodes from a fiber tree for structural comparison
|
|
439
|
+
*
|
|
440
|
+
* @param fiber - Fiber tree to extract CloudDOM from
|
|
441
|
+
* @returns Array of CloudDOM nodes
|
|
442
|
+
*/
|
|
443
|
+
extractCloudDOMFromFiber(fiber) {
|
|
444
|
+
const cloudDOMNodes = [];
|
|
445
|
+
const walkFiber = (currentFiber) => {
|
|
446
|
+
// Check for cloudDOMNodes array
|
|
447
|
+
if (currentFiber.cloudDOMNodes &&
|
|
448
|
+
Array.isArray(currentFiber.cloudDOMNodes)) {
|
|
449
|
+
cloudDOMNodes.push(...currentFiber.cloudDOMNodes);
|
|
450
|
+
}
|
|
451
|
+
// Legacy cloudDOMNode removed - only use cloudDOMNodes array
|
|
452
|
+
// Recursively walk children
|
|
453
|
+
if (currentFiber.children && currentFiber.children.length > 0) {
|
|
454
|
+
for (const child of currentFiber.children) {
|
|
455
|
+
walkFiber(child);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
walkFiber(fiber);
|
|
460
|
+
return cloudDOMNodes;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Re-render specific components with selective updates
|
|
464
|
+
* This method re-executes only the specified components and their children
|
|
465
|
+
*
|
|
466
|
+
* @param components - Array of fiber nodes to re-render
|
|
467
|
+
* @param reason - Reason for the re-render
|
|
468
|
+
* @returns Updated root fiber node
|
|
469
|
+
*/
|
|
470
|
+
reRenderComponents(components, reason) {
|
|
471
|
+
if (components.length === 0) {
|
|
472
|
+
throw new Error('[Renderer] No components provided for re-rendering');
|
|
473
|
+
}
|
|
474
|
+
return (0, context_1.runWithHookContext)(() => {
|
|
475
|
+
try {
|
|
476
|
+
// Find the root component to determine the full tree structure
|
|
477
|
+
const rootComponent = this.findRootComponent(components);
|
|
478
|
+
if (!rootComponent) {
|
|
479
|
+
throw new Error('[Renderer] Could not determine root component for re-rendering');
|
|
480
|
+
}
|
|
481
|
+
// Track dependencies during re-render
|
|
482
|
+
this.trackDependenciesDuringRender(components);
|
|
483
|
+
// Selectively re-render the components
|
|
484
|
+
const updatedRoot = this.selectiveReRender(rootComponent, new Set(components), reason);
|
|
485
|
+
// Detect structural changes if this is a structural re-render
|
|
486
|
+
if (reason === 'structural-change' && this.structuralChangeDetector) {
|
|
487
|
+
this.handleStructuralChangesComparison(rootComponent, updatedRoot);
|
|
488
|
+
}
|
|
489
|
+
// Update current fiber reference
|
|
490
|
+
this.currentFiber = updatedRoot;
|
|
491
|
+
return updatedRoot;
|
|
492
|
+
}
|
|
493
|
+
finally {
|
|
494
|
+
// Clear context stacks to prevent memory leaks
|
|
495
|
+
(0, context_1.clearContextStacks)();
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Find components that depend on changed state/outputs
|
|
501
|
+
* This method traverses the fiber tree to find dependent components
|
|
502
|
+
*
|
|
503
|
+
* @param changedFiber - Fiber node that changed
|
|
504
|
+
* @returns Set of fiber nodes that depend on the changed fiber
|
|
505
|
+
*/
|
|
506
|
+
findDependentComponents(changedFiber) {
|
|
507
|
+
const dependents = new Set();
|
|
508
|
+
// If the fiber has explicit dependents, add them
|
|
509
|
+
if (changedFiber.dependents) {
|
|
510
|
+
Array.from(changedFiber.dependents).forEach((dependent) => dependents.add(dependent));
|
|
511
|
+
}
|
|
512
|
+
// Traverse the tree to find implicit dependencies
|
|
513
|
+
if (this.currentFiber) {
|
|
514
|
+
this.findDependentsRecursive(this.currentFiber, changedFiber, dependents);
|
|
515
|
+
}
|
|
516
|
+
return dependents;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Track component dependencies during render
|
|
520
|
+
* This builds the dependency graph for selective re-rendering
|
|
521
|
+
*
|
|
522
|
+
* @param components - Components being rendered
|
|
523
|
+
*/
|
|
524
|
+
trackDependenciesDuringRender(components) {
|
|
525
|
+
for (const component of components) {
|
|
526
|
+
// Initialize dependency tracking if not present
|
|
527
|
+
if (!component.dependencies) {
|
|
528
|
+
component.dependencies = new Set();
|
|
529
|
+
}
|
|
530
|
+
if (!component.dependents) {
|
|
531
|
+
component.dependents = new Set();
|
|
532
|
+
}
|
|
533
|
+
// Track dependencies based on context usage, state bindings, etc.
|
|
534
|
+
this.buildDependencyGraph(component);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Build dependency graph for a component
|
|
539
|
+
* This analyzes the component's usage patterns to determine dependencies
|
|
540
|
+
*
|
|
541
|
+
* @param component - Component to analyze
|
|
542
|
+
*/
|
|
543
|
+
buildDependencyGraph(component) {
|
|
544
|
+
// Track context dependencies
|
|
545
|
+
const contextDependencies = this.contextDependencyTracker.getFiberContexts(component);
|
|
546
|
+
contextDependencies.forEach((contextId) => {
|
|
547
|
+
// Find context provider components and add as dependencies
|
|
548
|
+
const providerFiber = this.findContextProvider(contextId);
|
|
549
|
+
if (providerFiber && providerFiber !== component) {
|
|
550
|
+
if (!component.dependencies) {
|
|
551
|
+
component.dependencies = new Set();
|
|
552
|
+
}
|
|
553
|
+
component.dependencies.add(providerFiber);
|
|
554
|
+
if (!providerFiber.dependents) {
|
|
555
|
+
providerFiber.dependents = new Set();
|
|
556
|
+
}
|
|
557
|
+
providerFiber.dependents.add(component);
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
// Track state binding dependencies
|
|
561
|
+
// (This would integrate with StateBindingManager)
|
|
562
|
+
// Track instance output dependencies
|
|
563
|
+
// (This would integrate with ProviderOutputTracker)
|
|
564
|
+
// Basic parent-child dependencies
|
|
565
|
+
if (component.children) {
|
|
566
|
+
component.children.forEach((child) => {
|
|
567
|
+
// Child depends on parent
|
|
568
|
+
if (!child.dependencies) {
|
|
569
|
+
child.dependencies = new Set();
|
|
570
|
+
}
|
|
571
|
+
child.dependencies.add(component);
|
|
572
|
+
// Parent has child as dependent
|
|
573
|
+
if (!component.dependents) {
|
|
574
|
+
component.dependents = new Set();
|
|
575
|
+
}
|
|
576
|
+
component.dependents.add(child);
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Find the context provider fiber for a given context ID
|
|
582
|
+
* This traverses up the fiber tree to find the provider
|
|
583
|
+
*
|
|
584
|
+
* @param contextId - Context identifier to find provider for
|
|
585
|
+
* @returns Provider fiber node or null if not found
|
|
586
|
+
*/
|
|
587
|
+
findContextProvider(contextId) {
|
|
588
|
+
// This is a simplified implementation
|
|
589
|
+
// In a full implementation, we would traverse the fiber tree
|
|
590
|
+
// to find the actual provider component
|
|
591
|
+
if (!this.currentFiber) {
|
|
592
|
+
return null;
|
|
593
|
+
}
|
|
594
|
+
// For now, we'll do a simple search through the current fiber tree
|
|
595
|
+
return this.searchForProvider(this.currentFiber, contextId);
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Recursively search for a context provider in the fiber tree
|
|
599
|
+
*
|
|
600
|
+
* @param fiber - Current fiber to search
|
|
601
|
+
* @param contextId - Context ID to find
|
|
602
|
+
* @returns Provider fiber or null
|
|
603
|
+
*/
|
|
604
|
+
searchForProvider(fiber, contextId) {
|
|
605
|
+
// Check if this fiber is a provider for the context
|
|
606
|
+
const isProvider = typeof fiber.type === 'function' &&
|
|
607
|
+
fiber.type._isContextProvider &&
|
|
608
|
+
fiber.type._contextId === contextId;
|
|
609
|
+
if (isProvider) {
|
|
610
|
+
return fiber;
|
|
611
|
+
}
|
|
612
|
+
// Search children
|
|
613
|
+
if (fiber.children) {
|
|
614
|
+
for (const child of fiber.children) {
|
|
615
|
+
const result = this.searchForProvider(child, contextId);
|
|
616
|
+
if (result) {
|
|
617
|
+
return result;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
return null;
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Perform selective re-render of specific components
|
|
625
|
+
* Only re-executes components that need updates
|
|
626
|
+
*
|
|
627
|
+
* @param rootFiber - Root fiber node
|
|
628
|
+
* @param componentsToReRender - Set of components that need re-rendering
|
|
629
|
+
* @param reason - Reason for re-render
|
|
630
|
+
* @returns Updated fiber tree
|
|
631
|
+
*/
|
|
632
|
+
selectiveReRender(rootFiber, componentsToReRender, reason) {
|
|
633
|
+
// Create a copy of the root fiber for selective updates
|
|
634
|
+
const updatedFiber = this.cloneFiberForUpdate(rootFiber);
|
|
635
|
+
// Recursively update only the components that need re-rendering
|
|
636
|
+
this.selectiveReRenderRecursive(updatedFiber, componentsToReRender, reason, []);
|
|
637
|
+
return updatedFiber;
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Recursively perform selective re-rendering
|
|
641
|
+
*
|
|
642
|
+
* @param fiber - Current fiber node
|
|
643
|
+
* @param componentsToReRender - Set of components to re-render
|
|
644
|
+
* @param reason - Reason for re-render
|
|
645
|
+
* @param currentPath - Current path in the tree
|
|
646
|
+
*/
|
|
647
|
+
selectiveReRenderRecursive(fiber, componentsToReRender, reason, currentPath) {
|
|
648
|
+
// Check if this component needs re-rendering
|
|
649
|
+
const needsReRender = this.shouldReRenderComponent(fiber, componentsToReRender);
|
|
650
|
+
if (needsReRender) {
|
|
651
|
+
// Update reactive state
|
|
652
|
+
if (!fiber.reactiveState) {
|
|
653
|
+
fiber.reactiveState = {
|
|
654
|
+
renderCount: 0,
|
|
655
|
+
isDirty: false,
|
|
656
|
+
updatePending: false,
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
fiber.reactiveState.lastRenderReason = reason;
|
|
660
|
+
fiber.reactiveState.lastRenderTime = Date.now();
|
|
661
|
+
fiber.reactiveState.renderCount++;
|
|
662
|
+
fiber.reactiveState.isDirty = false;
|
|
663
|
+
fiber.reactiveState.updatePending = false;
|
|
664
|
+
// Re-execute the component
|
|
665
|
+
this.reExecuteComponent(fiber, currentPath);
|
|
666
|
+
}
|
|
667
|
+
// Recursively process children
|
|
668
|
+
if (fiber.children) {
|
|
669
|
+
fiber.children.forEach((child) => {
|
|
670
|
+
const childPath = [...currentPath, child.path[child.path.length - 1]];
|
|
671
|
+
this.selectiveReRenderRecursive(child, componentsToReRender, reason, childPath);
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Determine if a component should be re-rendered
|
|
677
|
+
*
|
|
678
|
+
* @param fiber - Fiber node to check
|
|
679
|
+
* @param componentsToReRender - Set of components marked for re-rendering
|
|
680
|
+
* @returns True if component should be re-rendered
|
|
681
|
+
*/
|
|
682
|
+
shouldReRenderComponent(fiber, componentsToReRender) {
|
|
683
|
+
// Direct match
|
|
684
|
+
if (componentsToReRender.has(fiber)) {
|
|
685
|
+
return true;
|
|
686
|
+
}
|
|
687
|
+
// Check if any dependencies need re-rendering
|
|
688
|
+
if (fiber.dependencies) {
|
|
689
|
+
const dependencyArray = Array.from(fiber.dependencies);
|
|
690
|
+
for (const dependency of dependencyArray) {
|
|
691
|
+
if (componentsToReRender.has(dependency)) {
|
|
692
|
+
return true;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
// Check reactive state
|
|
697
|
+
if (fiber.reactiveState?.isDirty) {
|
|
698
|
+
return true;
|
|
699
|
+
}
|
|
700
|
+
return false;
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Re-execute a component function
|
|
704
|
+
*
|
|
705
|
+
* @param fiber - Fiber node to re-execute
|
|
706
|
+
* @param currentPath - Current path in the tree
|
|
707
|
+
*/
|
|
708
|
+
reExecuteComponent(fiber, currentPath) {
|
|
709
|
+
// Set up rendering context
|
|
710
|
+
(0, context_1.setRenderContext)(fiber, fiber.path);
|
|
711
|
+
(0, useInstance_1.resetConstructCounts)(fiber);
|
|
712
|
+
try {
|
|
713
|
+
// Re-execute the component
|
|
714
|
+
const children = this.executeComponent(fiber.type, fiber.props, fiber.path);
|
|
715
|
+
// Update children if they changed
|
|
716
|
+
if (children) {
|
|
717
|
+
fiber.children = this.renderChildren(children, fiber.path);
|
|
718
|
+
}
|
|
719
|
+
// Check for structural changes after re-execution
|
|
720
|
+
this.handleStructuralChanges(fiber);
|
|
721
|
+
}
|
|
722
|
+
finally {
|
|
723
|
+
// Clean up context
|
|
724
|
+
(0, context_1.clearRenderContext)();
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Find the root component from a set of components
|
|
729
|
+
*
|
|
730
|
+
* @param components - Array of components
|
|
731
|
+
* @returns Root component or null
|
|
732
|
+
*/
|
|
733
|
+
findRootComponent(components) {
|
|
734
|
+
// Find the component with the shortest path (closest to root)
|
|
735
|
+
let rootComponent = null;
|
|
736
|
+
let shortestPathLength = Infinity;
|
|
737
|
+
for (const component of components) {
|
|
738
|
+
if (component.path.length < shortestPathLength) {
|
|
739
|
+
shortestPathLength = component.path.length;
|
|
740
|
+
rootComponent = component;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
return rootComponent;
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Recursively find dependent components
|
|
747
|
+
*
|
|
748
|
+
* @param currentFiber - Current fiber being examined
|
|
749
|
+
* @param changedFiber - Fiber that changed
|
|
750
|
+
* @param dependents - Set to collect dependents
|
|
751
|
+
*/
|
|
752
|
+
findDependentsRecursive(currentFiber, changedFiber, dependents) {
|
|
753
|
+
// Check if current fiber depends on changed fiber
|
|
754
|
+
if (currentFiber.dependencies?.has(changedFiber)) {
|
|
755
|
+
dependents.add(currentFiber);
|
|
756
|
+
}
|
|
757
|
+
// Recursively check children
|
|
758
|
+
if (currentFiber.children) {
|
|
759
|
+
currentFiber.children.forEach((child) => {
|
|
760
|
+
this.findDependentsRecursive(child, changedFiber, dependents);
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* Set StateBindingManager for context dependency tracker integration
|
|
766
|
+
*/
|
|
767
|
+
setStateBindingManager(stateBindingManager) {
|
|
768
|
+
this.contextDependencyTracker.setStateBindingManager(stateBindingManager);
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Clone a fiber node for selective updates
|
|
772
|
+
*
|
|
773
|
+
* @param fiber - Fiber to clone
|
|
774
|
+
* @returns Cloned fiber
|
|
775
|
+
*/
|
|
776
|
+
cloneFiberForUpdate(fiber) {
|
|
777
|
+
return {
|
|
778
|
+
...fiber,
|
|
779
|
+
props: { ...fiber.props },
|
|
780
|
+
children: fiber.children
|
|
781
|
+
? fiber.children.map((child) => this.cloneFiberForUpdate(child))
|
|
782
|
+
: [],
|
|
783
|
+
hooks: fiber.hooks ? [...fiber.hooks] : undefined,
|
|
784
|
+
state: fiber.state ? { ...fiber.state } : undefined,
|
|
785
|
+
reactiveState: fiber.reactiveState ? { ...fiber.reactiveState } : undefined,
|
|
786
|
+
dependencies: fiber.dependencies ? new Set(fiber.dependencies) : undefined,
|
|
787
|
+
dependents: fiber.dependents ? new Set(fiber.dependents) : undefined,
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
exports.Renderer = Renderer;
|