@coherent.js/devtools 1.1.0 → 2.0.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -12
- package/dist/hybrid-integration-tools.js +1 -1
- package/dist/hybrid-integration-tools.js.map +2 -2
- package/dist/index.js +330 -208
- package/dist/index.js.map +3 -3
- package/dist/profiler.js +120 -41
- package/dist/profiler.js.map +2 -2
- package/package.json +2 -2
- package/types/index.d.ts +74 -12
package/dist/index.js
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
-
}) : x)(function(x) {
|
|
4
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
-
});
|
|
7
|
-
|
|
8
1
|
// src/inspector.js
|
|
9
2
|
var ComponentInspector = class {
|
|
10
3
|
constructor(options = {}) {
|
|
@@ -104,20 +97,20 @@ var ComponentInspector = class {
|
|
|
104
97
|
const warnings = [];
|
|
105
98
|
const info = [];
|
|
106
99
|
const seen = /* @__PURE__ */ new WeakSet();
|
|
107
|
-
const checkCircular = (obj,
|
|
100
|
+
const checkCircular = (obj, path = []) => {
|
|
108
101
|
if (obj === null || typeof obj !== "object") {
|
|
109
102
|
return;
|
|
110
103
|
}
|
|
111
104
|
if (seen.has(obj)) {
|
|
112
|
-
warnings.push(`circular reference detected at ${
|
|
105
|
+
warnings.push(`circular reference detected at ${path.join(".")}`);
|
|
113
106
|
return;
|
|
114
107
|
}
|
|
115
108
|
seen.add(obj);
|
|
116
109
|
if (Array.isArray(obj)) {
|
|
117
|
-
obj.forEach((item, index) => checkCircular(item, [...
|
|
110
|
+
obj.forEach((item, index) => checkCircular(item, [...path, `[${index}]`]));
|
|
118
111
|
} else {
|
|
119
112
|
Object.keys(obj).forEach((key) => {
|
|
120
|
-
checkCircular(obj[key], [...
|
|
113
|
+
checkCircular(obj[key], [...path, key]);
|
|
121
114
|
});
|
|
122
115
|
}
|
|
123
116
|
};
|
|
@@ -390,10 +383,34 @@ function validateComponent(component) {
|
|
|
390
383
|
}
|
|
391
384
|
|
|
392
385
|
// src/profiler.js
|
|
386
|
+
var hasPerformanceTimeline = () => typeof performance !== "undefined" && typeof performance.mark === "function";
|
|
387
|
+
function now() {
|
|
388
|
+
return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
|
|
389
|
+
}
|
|
390
|
+
function clearTimelineEntries(marks, measureName) {
|
|
391
|
+
if (!hasPerformanceTimeline()) return;
|
|
392
|
+
for (const mark of marks) {
|
|
393
|
+
performance.clearMarks?.(mark);
|
|
394
|
+
}
|
|
395
|
+
if (measureName) {
|
|
396
|
+
performance.clearMeasures?.(measureName);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
function recordTimelineMeasure(name, startMark, endMark) {
|
|
400
|
+
if (!hasPerformanceTimeline()) return;
|
|
401
|
+
try {
|
|
402
|
+
performance.mark(endMark);
|
|
403
|
+
performance.measure?.(name, startMark, endMark);
|
|
404
|
+
} catch {
|
|
405
|
+
} finally {
|
|
406
|
+
clearTimelineEntries([startMark, endMark], name);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
393
409
|
var PerformanceProfiler = class {
|
|
394
410
|
constructor(options = {}) {
|
|
395
411
|
this.options = {
|
|
396
|
-
|
|
412
|
+
// Opt-in: a profiler that is merely constructed must cost nothing.
|
|
413
|
+
enabled: false,
|
|
397
414
|
sampleRate: 1,
|
|
398
415
|
// 1.0 = 100% sampling
|
|
399
416
|
slowThreshold: 16,
|
|
@@ -420,14 +437,14 @@ var PerformanceProfiler = class {
|
|
|
420
437
|
const session = {
|
|
421
438
|
id: this.generateId(),
|
|
422
439
|
name,
|
|
423
|
-
startTime:
|
|
440
|
+
startTime: now(),
|
|
424
441
|
measurements: [],
|
|
425
442
|
marks: [],
|
|
426
443
|
active: true
|
|
427
444
|
};
|
|
428
445
|
this.sessions.set(session.id, session);
|
|
429
446
|
this.currentSession = session;
|
|
430
|
-
if (
|
|
447
|
+
if (hasPerformanceTimeline()) {
|
|
431
448
|
performance.mark(`coherent-session-start-${session.id}`);
|
|
432
449
|
}
|
|
433
450
|
return session.id;
|
|
@@ -443,19 +460,18 @@ var PerformanceProfiler = class {
|
|
|
443
460
|
if (!session) {
|
|
444
461
|
return null;
|
|
445
462
|
}
|
|
446
|
-
session.endTime =
|
|
463
|
+
session.endTime = now();
|
|
447
464
|
session.duration = session.endTime - session.startTime;
|
|
448
465
|
session.active = false;
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
466
|
+
recordTimelineMeasure(
|
|
467
|
+
`coherent-session-${session.id}`,
|
|
468
|
+
`coherent-session-start-${session.id}`,
|
|
469
|
+
`coherent-session-end-${session.id}`
|
|
470
|
+
);
|
|
452
471
|
if (this.currentSession === session) {
|
|
453
472
|
this.currentSession = null;
|
|
454
473
|
}
|
|
455
|
-
this.
|
|
456
|
-
if (this.measurements.length > this.options.maxSamples) {
|
|
457
|
-
this.measurements.shift();
|
|
458
|
-
}
|
|
474
|
+
this.addMeasurement(session);
|
|
459
475
|
return this.analyzeSession(session);
|
|
460
476
|
}
|
|
461
477
|
/**
|
|
@@ -469,12 +485,12 @@ var PerformanceProfiler = class {
|
|
|
469
485
|
id: measurementId,
|
|
470
486
|
componentName,
|
|
471
487
|
props,
|
|
472
|
-
startTime:
|
|
488
|
+
startTime: now(),
|
|
473
489
|
startMemory: this.getMemoryUsage(),
|
|
474
490
|
phase: "render"
|
|
475
491
|
};
|
|
476
492
|
this.marks.set(measurementId, measurement);
|
|
477
|
-
if (
|
|
493
|
+
if (hasPerformanceTimeline()) {
|
|
478
494
|
performance.mark(`coherent-render-start-${measurementId}`);
|
|
479
495
|
}
|
|
480
496
|
return measurementId;
|
|
@@ -485,28 +501,21 @@ var PerformanceProfiler = class {
|
|
|
485
501
|
endRender(measurementId, result = {}) {
|
|
486
502
|
if (!measurementId || !this.marks.has(measurementId)) return null;
|
|
487
503
|
const measurement = this.marks.get(measurementId);
|
|
488
|
-
measurement.endTime =
|
|
504
|
+
measurement.endTime = now();
|
|
489
505
|
measurement.duration = measurement.endTime - measurement.startTime;
|
|
490
506
|
measurement.endMemory = this.getMemoryUsage();
|
|
491
|
-
measurement.memoryDelta = measurement.endMemory - measurement.startMemory;
|
|
507
|
+
measurement.memoryDelta = measurement.startMemory && measurement.endMemory ? measurement.endMemory.used - measurement.startMemory.used : null;
|
|
492
508
|
measurement.result = result;
|
|
493
509
|
measurement.slow = measurement.duration > this.options.slowThreshold;
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
`coherent-render-start-${measurementId}`,
|
|
501
|
-
`coherent-render-end-${measurementId}`
|
|
502
|
-
);
|
|
503
|
-
} catch {
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
this.measurements.push(measurement);
|
|
510
|
+
recordTimelineMeasure(
|
|
511
|
+
`coherent-render-${measurementId}`,
|
|
512
|
+
`coherent-render-start-${measurementId}`,
|
|
513
|
+
`coherent-render-end-${measurementId}`
|
|
514
|
+
);
|
|
515
|
+
this.addMeasurement(measurement);
|
|
508
516
|
if (this.currentSession) {
|
|
509
517
|
this.currentSession.measurements.push(measurement);
|
|
518
|
+
this.trim(this.currentSession.measurements);
|
|
510
519
|
}
|
|
511
520
|
this.marks.delete(measurementId);
|
|
512
521
|
return measurement;
|
|
@@ -517,15 +526,13 @@ var PerformanceProfiler = class {
|
|
|
517
526
|
mark(name, data = {}) {
|
|
518
527
|
const mark = {
|
|
519
528
|
name,
|
|
520
|
-
timestamp:
|
|
529
|
+
timestamp: now(),
|
|
521
530
|
data,
|
|
522
531
|
memory: this.getMemoryUsage()
|
|
523
532
|
};
|
|
524
533
|
if (this.currentSession) {
|
|
525
534
|
this.currentSession.marks.push(mark);
|
|
526
|
-
|
|
527
|
-
if (typeof performance !== "undefined" && performance.mark) {
|
|
528
|
-
performance.mark(`coherent-mark-${name}`);
|
|
535
|
+
this.trim(this.currentSession.marks);
|
|
529
536
|
}
|
|
530
537
|
return mark;
|
|
531
538
|
}
|
|
@@ -544,6 +551,22 @@ var PerformanceProfiler = class {
|
|
|
544
551
|
endMark: end.name
|
|
545
552
|
};
|
|
546
553
|
}
|
|
554
|
+
/**
|
|
555
|
+
* Append a measurement, dropping the oldest beyond `maxSamples`.
|
|
556
|
+
*/
|
|
557
|
+
addMeasurement(measurement) {
|
|
558
|
+
this.measurements.push(measurement);
|
|
559
|
+
this.trim(this.measurements);
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Drop the oldest entries of `list` beyond `maxSamples`.
|
|
563
|
+
*/
|
|
564
|
+
trim(list) {
|
|
565
|
+
const max = Math.max(1, Number(this.options.maxSamples) || 1e3);
|
|
566
|
+
if (list.length > max) {
|
|
567
|
+
list.splice(0, list.length - max);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
547
570
|
/**
|
|
548
571
|
* Get memory usage
|
|
549
572
|
*/
|
|
@@ -838,6 +861,10 @@ var PerformanceProfiler = class {
|
|
|
838
861
|
* Clear all data
|
|
839
862
|
*/
|
|
840
863
|
clear() {
|
|
864
|
+
clearTimelineEntries([
|
|
865
|
+
...[...this.marks.keys()].map((id) => `coherent-render-start-${id}`),
|
|
866
|
+
...[...this.sessions.values()].filter((session) => session.active).map((session) => `coherent-session-start-${session.id}`)
|
|
867
|
+
]);
|
|
841
868
|
this.measurements = [];
|
|
842
869
|
this.sessions.clear();
|
|
843
870
|
this.currentSession = null;
|
|
@@ -856,17 +883,28 @@ var PerformanceProfiler = class {
|
|
|
856
883
|
this.options.enabled = false;
|
|
857
884
|
}
|
|
858
885
|
/**
|
|
859
|
-
* Generate unique ID
|
|
886
|
+
* Generate unique ID.
|
|
887
|
+
*
|
|
888
|
+
* Uses crypto.getRandomValues rather than Math.random: the ids key the
|
|
889
|
+
* session and measurement maps, so a predictable suffix lets one caller
|
|
890
|
+
* guess or collide with another's entry. getRandomValues is available in
|
|
891
|
+
* Node 19+ and in browsers without requiring a secure context, unlike
|
|
892
|
+
* randomUUID.
|
|
893
|
+
*
|
|
894
|
+
* @returns {string} A unique profiling id
|
|
860
895
|
*/
|
|
861
896
|
generateId() {
|
|
862
|
-
|
|
897
|
+
const bytes = new Uint8Array(8);
|
|
898
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
899
|
+
const suffix = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
900
|
+
return `prof-${Date.now()}-${suffix}`;
|
|
863
901
|
}
|
|
864
902
|
};
|
|
865
903
|
function createProfiler(options = {}) {
|
|
866
904
|
return new PerformanceProfiler(options);
|
|
867
905
|
}
|
|
868
906
|
async function measure(name, fn, profiler = null) {
|
|
869
|
-
const prof = profiler || new PerformanceProfiler();
|
|
907
|
+
const prof = profiler || new PerformanceProfiler({ enabled: true });
|
|
870
908
|
const sessionId = prof.start(name);
|
|
871
909
|
try {
|
|
872
910
|
const value = await fn();
|
|
@@ -874,14 +912,48 @@ async function measure(name, fn, profiler = null) {
|
|
|
874
912
|
return { value, duration: result?.duration || 0 };
|
|
875
913
|
} catch (error) {
|
|
876
914
|
const result = prof.stop(sessionId);
|
|
877
|
-
|
|
915
|
+
const failure = error instanceof Error ? error : new Error(`${name} failed: ${String(error)}`, { cause: error });
|
|
916
|
+
try {
|
|
917
|
+
failure.duration = result?.duration || 0;
|
|
918
|
+
} catch {
|
|
919
|
+
}
|
|
920
|
+
throw failure;
|
|
878
921
|
}
|
|
879
922
|
}
|
|
880
|
-
function profile(fn) {
|
|
881
|
-
|
|
882
|
-
|
|
923
|
+
function profile(fn, options = {}) {
|
|
924
|
+
if (typeof fn !== "function") {
|
|
925
|
+
throw new TypeError("profile() expects a function");
|
|
926
|
+
}
|
|
927
|
+
const opts = options instanceof PerformanceProfiler ? { profiler: options } : options;
|
|
928
|
+
const profiler = opts.profiler || new PerformanceProfiler({ enabled: true });
|
|
929
|
+
const name = opts.name || fn.name || "anonymous";
|
|
930
|
+
function profiled(...args) {
|
|
931
|
+
const id = profiler.startRender(name);
|
|
932
|
+
let result;
|
|
933
|
+
try {
|
|
934
|
+
result = fn.apply(this, args);
|
|
935
|
+
} catch (error) {
|
|
936
|
+
profiler.endRender(id, { error: true });
|
|
937
|
+
throw error;
|
|
938
|
+
}
|
|
939
|
+
if (result && typeof result.then === "function") {
|
|
940
|
+
return Promise.resolve(result).then(
|
|
941
|
+
(value) => {
|
|
942
|
+
profiler.endRender(id);
|
|
943
|
+
return value;
|
|
944
|
+
},
|
|
945
|
+
(error) => {
|
|
946
|
+
profiler.endRender(id, { error: true });
|
|
947
|
+
throw error;
|
|
948
|
+
}
|
|
949
|
+
);
|
|
950
|
+
}
|
|
951
|
+
profiler.endRender(id);
|
|
883
952
|
return result;
|
|
884
|
-
}
|
|
953
|
+
}
|
|
954
|
+
Object.defineProperty(profiled, "name", { value: name });
|
|
955
|
+
profiled.profiler = profiler;
|
|
956
|
+
return profiled;
|
|
885
957
|
}
|
|
886
958
|
|
|
887
959
|
// src/logger.js
|
|
@@ -1340,28 +1412,53 @@ function createConsoleLogger(prefix = "") {
|
|
|
1340
1412
|
|
|
1341
1413
|
// src/dev-tools.js
|
|
1342
1414
|
import { performanceMonitor, validateComponent as validateComponent2, isCoherentObject } from "@coherent.js/core";
|
|
1415
|
+
var LOCAL_HOSTNAMES = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]", "::1"]);
|
|
1343
1416
|
var DevTools = class {
|
|
1344
|
-
|
|
1417
|
+
/**
|
|
1418
|
+
* @param {Object} [coherentInstance] - Object exposing `render` (and optionally
|
|
1419
|
+
* `createComponent` / `cache`), e.g. `import * as coherent from '@coherent.js/core'`.
|
|
1420
|
+
* @param {Object} [options]
|
|
1421
|
+
* @param {boolean} [options.enabled] - Force on or off; defaults to {@link DevTools#shouldEnable}.
|
|
1422
|
+
* @param {number} [options.maxEntries=100] - Cap on retained warnings and errors each.
|
|
1423
|
+
* @param {boolean} [options.captureConsoleErrors=true] - Record console.error calls.
|
|
1424
|
+
* @param {boolean} [options.trackUnhandledRejections=false] - Record unhandled promise
|
|
1425
|
+
* rejections (Node). They still crash the process as they would without DevTools.
|
|
1426
|
+
* @param {string} [options.hotReloadUrl] - Browser only: WebSocket URL of a dev server
|
|
1427
|
+
* sending `component-updated` / `full-reload` messages. No connection is made without it.
|
|
1428
|
+
* @param {boolean} [options.globalHelpers=true] - Expose `$inspect`, `$history`, … globally.
|
|
1429
|
+
*/
|
|
1430
|
+
constructor(coherentInstance = null, options = {}) {
|
|
1345
1431
|
this.coherent = coherentInstance;
|
|
1346
|
-
this.
|
|
1432
|
+
this.options = {
|
|
1433
|
+
maxEntries: 100,
|
|
1434
|
+
captureConsoleErrors: true,
|
|
1435
|
+
trackUnhandledRejections: false,
|
|
1436
|
+
hotReloadUrl: null,
|
|
1437
|
+
globalHelpers: true,
|
|
1438
|
+
...options
|
|
1439
|
+
};
|
|
1440
|
+
this.isEnabled = typeof options.enabled === "boolean" ? options.enabled : this.shouldEnable();
|
|
1347
1441
|
this.renderHistory = [];
|
|
1348
1442
|
this.componentRegistry = /* @__PURE__ */ new Map();
|
|
1349
1443
|
this.warnings = [];
|
|
1350
1444
|
this.errors = [];
|
|
1351
1445
|
this.hotReloadEnabled = false;
|
|
1446
|
+
this._teardown = [];
|
|
1352
1447
|
if (this.isEnabled) {
|
|
1353
1448
|
this.initialize();
|
|
1354
1449
|
}
|
|
1355
1450
|
}
|
|
1356
1451
|
/**
|
|
1357
|
-
* Check if dev tools should be enabled
|
|
1452
|
+
* Check if dev tools should be enabled: NODE_ENV=development in Node, a
|
|
1453
|
+
* localhost page in the browser. Anything else (a query parameter on a
|
|
1454
|
+
* production host, say) needs the explicit `enabled: true` option.
|
|
1358
1455
|
*/
|
|
1359
1456
|
shouldEnable() {
|
|
1360
|
-
if (typeof process !== "undefined") {
|
|
1457
|
+
if (typeof process !== "undefined" && process?.env) {
|
|
1361
1458
|
return process.env.NODE_ENV === "development";
|
|
1362
1459
|
}
|
|
1363
|
-
if (typeof window !== "undefined") {
|
|
1364
|
-
return
|
|
1460
|
+
if (typeof window !== "undefined" && window.location) {
|
|
1461
|
+
return LOCAL_HOSTNAMES.has(window.location.hostname);
|
|
1365
1462
|
}
|
|
1366
1463
|
return false;
|
|
1367
1464
|
}
|
|
@@ -1370,18 +1467,86 @@ var DevTools = class {
|
|
|
1370
1467
|
*/
|
|
1371
1468
|
initialize() {
|
|
1372
1469
|
console.log("\u{1F6E0}\uFE0F Coherent.js Dev Tools Enabled");
|
|
1373
|
-
this.
|
|
1374
|
-
|
|
1470
|
+
if (this.options.globalHelpers) {
|
|
1471
|
+
this.setupGlobalHelpers();
|
|
1472
|
+
}
|
|
1375
1473
|
this.setupErrorHandling();
|
|
1376
1474
|
this.setupHotReload();
|
|
1377
|
-
|
|
1378
|
-
if (typeof window !== "undefined") {
|
|
1475
|
+
if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
1379
1476
|
this.setupBrowserDevTools();
|
|
1380
1477
|
}
|
|
1381
|
-
|
|
1382
|
-
|
|
1478
|
+
}
|
|
1479
|
+
/**
|
|
1480
|
+
* Undo everything initialize() installed: global helpers, the
|
|
1481
|
+
* console.error hook, the unhandled-rejection listener, the hot-reload
|
|
1482
|
+
* socket and the browser panel.
|
|
1483
|
+
*/
|
|
1484
|
+
destroy() {
|
|
1485
|
+
while (this._teardown.length) {
|
|
1486
|
+
try {
|
|
1487
|
+
this._teardown.pop()();
|
|
1488
|
+
} catch {
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
/**
|
|
1493
|
+
* Append to a bounded list (warnings, errors), dropping the oldest.
|
|
1494
|
+
*/
|
|
1495
|
+
record(list, entry) {
|
|
1496
|
+
list.push(entry);
|
|
1497
|
+
const max = Math.max(1, this.options.maxEntries);
|
|
1498
|
+
if (list.length > max) {
|
|
1499
|
+
list.splice(0, list.length - max);
|
|
1500
|
+
}
|
|
1501
|
+
return entry;
|
|
1502
|
+
}
|
|
1503
|
+
/**
|
|
1504
|
+
* Render through the wrapped Coherent instance, recording timing,
|
|
1505
|
+
* validation warnings and errors.
|
|
1506
|
+
*/
|
|
1507
|
+
render(component, context = {}, options = {}) {
|
|
1508
|
+
const render = this.coherent?.render;
|
|
1509
|
+
if (typeof render !== "function") {
|
|
1510
|
+
throw new TypeError("DevTools.render() needs a Coherent instance with a render() function: createDevTools(coherent)");
|
|
1511
|
+
}
|
|
1512
|
+
if (!this.isEnabled) {
|
|
1513
|
+
return render.call(this.coherent, component, context, options);
|
|
1514
|
+
}
|
|
1515
|
+
const renderStart = performance.now();
|
|
1516
|
+
const renderId = this.generateRenderId();
|
|
1517
|
+
try {
|
|
1518
|
+
this.preRenderAnalysis(component, context, renderId);
|
|
1519
|
+
const result = render.call(this.coherent, component, context, {
|
|
1520
|
+
...options,
|
|
1521
|
+
_devRenderId: renderId
|
|
1522
|
+
});
|
|
1523
|
+
const renderTime = performance.now() - renderStart;
|
|
1524
|
+
this.postRenderAnalysis(component, result, renderTime, renderId);
|
|
1525
|
+
return result;
|
|
1526
|
+
} catch (_error) {
|
|
1527
|
+
this.handleRenderError(_error, component, context, renderId);
|
|
1528
|
+
throw _error;
|
|
1383
1529
|
}
|
|
1384
1530
|
}
|
|
1531
|
+
/**
|
|
1532
|
+
* createComponent() of the wrapped instance, registering the result for
|
|
1533
|
+
* inspection.
|
|
1534
|
+
*/
|
|
1535
|
+
createComponent(config) {
|
|
1536
|
+
const create = this.coherent?.createComponent;
|
|
1537
|
+
if (typeof create !== "function") {
|
|
1538
|
+
throw new TypeError("DevTools.createComponent() needs a Coherent instance with createComponent()");
|
|
1539
|
+
}
|
|
1540
|
+
const component = create.call(this.coherent, config);
|
|
1541
|
+
if (this.isEnabled) {
|
|
1542
|
+
this.componentRegistry.set(config?.name || "anonymous", {
|
|
1543
|
+
config,
|
|
1544
|
+
component,
|
|
1545
|
+
registeredAt: Date.now()
|
|
1546
|
+
});
|
|
1547
|
+
}
|
|
1548
|
+
return component;
|
|
1549
|
+
}
|
|
1385
1550
|
/**
|
|
1386
1551
|
* Set up global helper functions
|
|
1387
1552
|
*/
|
|
@@ -1404,34 +1569,15 @@ var DevTools = class {
|
|
|
1404
1569
|
// Get warnings and errors
|
|
1405
1570
|
$issues: () => ({ warnings: this.warnings, errors: this.errors })
|
|
1406
1571
|
};
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
* Intercept render calls for debugging
|
|
1415
|
-
*/
|
|
1416
|
-
setupRenderInterception() {
|
|
1417
|
-
const originalRender = this.coherent.render;
|
|
1418
|
-
this.coherent.render = (component, context = {}, options = {}) => {
|
|
1419
|
-
const renderStart = performance.now();
|
|
1420
|
-
const renderId = this.generateRenderId();
|
|
1421
|
-
try {
|
|
1422
|
-
this.preRenderAnalysis(component, context, renderId);
|
|
1423
|
-
const result = originalRender.call(this.coherent, component, context, {
|
|
1424
|
-
...options,
|
|
1425
|
-
_devRenderId: renderId
|
|
1426
|
-
});
|
|
1427
|
-
const renderTime = performance.now() - renderStart;
|
|
1428
|
-
this.postRenderAnalysis(component, result, renderTime, renderId);
|
|
1429
|
-
return result;
|
|
1430
|
-
} catch (_error) {
|
|
1431
|
-
this.handleRenderError(_error, component, context, renderId);
|
|
1432
|
-
throw _error;
|
|
1572
|
+
const target = globalThis;
|
|
1573
|
+
const previous = Object.fromEntries(Object.keys(helpers).map((key) => [key, target[key]]));
|
|
1574
|
+
Object.assign(target, helpers);
|
|
1575
|
+
this._teardown.push(() => {
|
|
1576
|
+
for (const [key, value] of Object.entries(previous)) {
|
|
1577
|
+
if (value === void 0) delete target[key];
|
|
1578
|
+
else target[key] = value;
|
|
1433
1579
|
}
|
|
1434
|
-
};
|
|
1580
|
+
});
|
|
1435
1581
|
}
|
|
1436
1582
|
/**
|
|
1437
1583
|
* Pre-render analysis and validation
|
|
@@ -1439,7 +1585,7 @@ var DevTools = class {
|
|
|
1439
1585
|
preRenderAnalysis(component, context, renderId) {
|
|
1440
1586
|
const validation = this.deepValidateComponent(component);
|
|
1441
1587
|
if (!validation.isValid) {
|
|
1442
|
-
this.warnings
|
|
1588
|
+
this.record(this.warnings, {
|
|
1443
1589
|
type: "validation",
|
|
1444
1590
|
message: validation.message,
|
|
1445
1591
|
component: this.serializeComponent(component),
|
|
@@ -1449,14 +1595,14 @@ var DevTools = class {
|
|
|
1449
1595
|
}
|
|
1450
1596
|
const complexity = this.analyzeComplexity(component);
|
|
1451
1597
|
if (complexity > 1e3) {
|
|
1452
|
-
this.warnings
|
|
1598
|
+
this.record(this.warnings, {
|
|
1453
1599
|
type: "performance",
|
|
1454
1600
|
message: `High complexity component detected (${complexity} nodes)`,
|
|
1455
1601
|
renderId,
|
|
1456
1602
|
timestamp: Date.now()
|
|
1457
1603
|
});
|
|
1458
1604
|
}
|
|
1459
|
-
this.analyzeContext(context, renderId);
|
|
1605
|
+
this.analyzeContext(context ?? {}, renderId);
|
|
1460
1606
|
}
|
|
1461
1607
|
/**
|
|
1462
1608
|
* Post-render analysis
|
|
@@ -1467,7 +1613,7 @@ var DevTools = class {
|
|
|
1467
1613
|
timestamp: Date.now(),
|
|
1468
1614
|
component: this.serializeComponent(component),
|
|
1469
1615
|
renderTime,
|
|
1470
|
-
outputSize: result.length,
|
|
1616
|
+
outputSize: typeof result === "string" ? result.length : 0,
|
|
1471
1617
|
complexity: this.analyzeComplexity(component)
|
|
1472
1618
|
};
|
|
1473
1619
|
this.renderHistory.push(renderRecord);
|
|
@@ -1475,7 +1621,7 @@ var DevTools = class {
|
|
|
1475
1621
|
this.renderHistory.shift();
|
|
1476
1622
|
}
|
|
1477
1623
|
if (renderTime > 10) {
|
|
1478
|
-
this.warnings
|
|
1624
|
+
this.record(this.warnings, {
|
|
1479
1625
|
type: "performance",
|
|
1480
1626
|
message: `Slow render detected: ${renderTime.toFixed(2)}ms`,
|
|
1481
1627
|
renderId,
|
|
@@ -1489,11 +1635,11 @@ var DevTools = class {
|
|
|
1489
1635
|
/**
|
|
1490
1636
|
* Deep component validation
|
|
1491
1637
|
*/
|
|
1492
|
-
deepValidateComponent(component,
|
|
1638
|
+
deepValidateComponent(component, path = "root", depth = 0) {
|
|
1493
1639
|
if (depth > 100) {
|
|
1494
1640
|
return {
|
|
1495
1641
|
isValid: false,
|
|
1496
|
-
message: `Component nesting too deep at ${
|
|
1642
|
+
message: `Component nesting too deep at ${path}`
|
|
1497
1643
|
};
|
|
1498
1644
|
}
|
|
1499
1645
|
try {
|
|
@@ -1501,14 +1647,14 @@ var DevTools = class {
|
|
|
1501
1647
|
} catch (_error) {
|
|
1502
1648
|
return {
|
|
1503
1649
|
isValid: false,
|
|
1504
|
-
message: `Invalid component at ${
|
|
1650
|
+
message: `Invalid component at ${path}: ${_error.message}`
|
|
1505
1651
|
};
|
|
1506
1652
|
}
|
|
1507
1653
|
if (Array.isArray(component)) {
|
|
1508
1654
|
for (let i = 0; i < component.length; i++) {
|
|
1509
1655
|
const childValidation = this.deepValidateComponent(
|
|
1510
1656
|
component[i],
|
|
1511
|
-
`${
|
|
1657
|
+
`${path}[${i}]`,
|
|
1512
1658
|
depth + 1
|
|
1513
1659
|
);
|
|
1514
1660
|
if (!childValidation.isValid) {
|
|
@@ -1520,7 +1666,7 @@ var DevTools = class {
|
|
|
1520
1666
|
if (props && typeof props === "object" && props.children) {
|
|
1521
1667
|
const childValidation = this.deepValidateComponent(
|
|
1522
1668
|
props.children,
|
|
1523
|
-
`${
|
|
1669
|
+
`${path}.${tag}.children`,
|
|
1524
1670
|
depth + 1
|
|
1525
1671
|
);
|
|
1526
1672
|
if (!childValidation.isValid) {
|
|
@@ -1562,26 +1708,27 @@ var DevTools = class {
|
|
|
1562
1708
|
* Context analysis
|
|
1563
1709
|
*/
|
|
1564
1710
|
analyzeContext(context, renderId) {
|
|
1565
|
-
|
|
1566
|
-
if (contextSize > 1e4) {
|
|
1567
|
-
this.warnings.push({
|
|
1568
|
-
type: "context",
|
|
1569
|
-
message: `Large context object: ${contextSize} characters`,
|
|
1570
|
-
renderId,
|
|
1571
|
-
timestamp: Date.now()
|
|
1572
|
-
});
|
|
1573
|
-
}
|
|
1711
|
+
let contextSize;
|
|
1574
1712
|
try {
|
|
1575
|
-
JSON.stringify(context);
|
|
1713
|
+
contextSize = JSON.stringify(context)?.length ?? 0;
|
|
1576
1714
|
} catch (_error) {
|
|
1577
|
-
if (_error.message
|
|
1578
|
-
this.warnings
|
|
1715
|
+
if (/circular/i.test(_error.message)) {
|
|
1716
|
+
this.record(this.warnings, {
|
|
1579
1717
|
type: "context",
|
|
1580
1718
|
message: "Circular reference detected in context",
|
|
1581
1719
|
renderId,
|
|
1582
1720
|
timestamp: Date.now()
|
|
1583
1721
|
});
|
|
1584
1722
|
}
|
|
1723
|
+
return;
|
|
1724
|
+
}
|
|
1725
|
+
if (contextSize > 1e4) {
|
|
1726
|
+
this.record(this.warnings, {
|
|
1727
|
+
type: "context",
|
|
1728
|
+
message: `Large context object: ${contextSize} characters`,
|
|
1729
|
+
renderId,
|
|
1730
|
+
timestamp: Date.now()
|
|
1731
|
+
});
|
|
1585
1732
|
}
|
|
1586
1733
|
}
|
|
1587
1734
|
/**
|
|
@@ -1695,48 +1842,63 @@ ${indent}</${tag}>`;
|
|
|
1695
1842
|
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1696
1843
|
}
|
|
1697
1844
|
/**
|
|
1698
|
-
*
|
|
1845
|
+
* Record errors. console.error calls are captured (and still printed);
|
|
1846
|
+
* unhandled rejections only when `trackUnhandledRejections` is set, and
|
|
1847
|
+
* then they still crash the process exactly as they would without us.
|
|
1699
1848
|
*/
|
|
1700
1849
|
setupErrorHandling() {
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1850
|
+
if (this.options.captureConsoleErrors) {
|
|
1851
|
+
const originalConsoleError = console.error;
|
|
1852
|
+
const hooked = (...args) => {
|
|
1853
|
+
this.record(this.errors, {
|
|
1854
|
+
type: "console",
|
|
1855
|
+
message: args.map((arg) => String(arg)).join(" "),
|
|
1856
|
+
timestamp: Date.now(),
|
|
1857
|
+
stack: new Error().stack
|
|
1858
|
+
});
|
|
1859
|
+
originalConsoleError.apply(console, args);
|
|
1860
|
+
};
|
|
1861
|
+
console.error = hooked;
|
|
1862
|
+
this._teardown.push(() => {
|
|
1863
|
+
if (console.error === hooked) console.error = originalConsoleError;
|
|
1708
1864
|
});
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
this.errors.push({
|
|
1865
|
+
}
|
|
1866
|
+
if (this.options.trackUnhandledRejections && typeof process !== "undefined" && typeof process.on === "function") {
|
|
1867
|
+
const listener = (reason) => {
|
|
1868
|
+
this.record(this.errors, {
|
|
1714
1869
|
type: "unhandled-rejection",
|
|
1715
|
-
message: reason.
|
|
1716
|
-
|
|
1870
|
+
message: reason instanceof Error ? reason.message : String(reason),
|
|
1871
|
+
stack: reason instanceof Error ? reason.stack : void 0,
|
|
1717
1872
|
timestamp: Date.now()
|
|
1718
1873
|
});
|
|
1719
|
-
|
|
1874
|
+
if (process.listenerCount("unhandledRejection") === 1) {
|
|
1875
|
+
throw reason instanceof Error ? reason : new Error(`Unhandled promise rejection: ${String(reason)}`);
|
|
1876
|
+
}
|
|
1877
|
+
};
|
|
1878
|
+
process.on("unhandledRejection", listener);
|
|
1879
|
+
this._teardown.push(() => process.off("unhandledRejection", listener));
|
|
1720
1880
|
}
|
|
1721
|
-
if (typeof window !== "undefined") {
|
|
1722
|
-
|
|
1723
|
-
this.errors
|
|
1724
|
-
type: "browser-
|
|
1881
|
+
if (typeof window !== "undefined" && typeof window.addEventListener === "function") {
|
|
1882
|
+
const listener = (event) => {
|
|
1883
|
+
this.record(this.errors, {
|
|
1884
|
+
type: "browser-error",
|
|
1725
1885
|
message: event.message,
|
|
1726
1886
|
filename: event.filename,
|
|
1727
1887
|
lineno: event.lineno,
|
|
1728
1888
|
colno: event.colno,
|
|
1729
1889
|
timestamp: Date.now()
|
|
1730
1890
|
});
|
|
1731
|
-
}
|
|
1891
|
+
};
|
|
1892
|
+
window.addEventListener("error", listener);
|
|
1893
|
+
this._teardown.push(() => window.removeEventListener("error", listener));
|
|
1732
1894
|
}
|
|
1733
1895
|
}
|
|
1734
1896
|
/**
|
|
1735
1897
|
* Handle render errors specifically
|
|
1736
1898
|
*/
|
|
1737
1899
|
handleRenderError(_error, component, context, renderId) {
|
|
1738
|
-
this.errors
|
|
1739
|
-
type: "render-
|
|
1900
|
+
this.record(this.errors, {
|
|
1901
|
+
type: "render-error",
|
|
1740
1902
|
message: _error.message,
|
|
1741
1903
|
stack: _error.stack,
|
|
1742
1904
|
component: this.serializeComponent(component),
|
|
@@ -1748,23 +1910,27 @@ ${indent}</${tag}>`;
|
|
|
1748
1910
|
console.error("Component:", this.serializeComponent(component));
|
|
1749
1911
|
}
|
|
1750
1912
|
/**
|
|
1751
|
-
*
|
|
1913
|
+
* Connect to a hot-reload WebSocket, only when `hotReloadUrl` is set
|
|
1914
|
+
* (browser only).
|
|
1752
1915
|
*/
|
|
1753
1916
|
setupHotReload() {
|
|
1754
|
-
if (typeof window !== "undefined" && "WebSocket" in window) {
|
|
1755
|
-
this.setupBrowserHotReload();
|
|
1756
|
-
} else if (typeof __require !== "undefined") {
|
|
1757
|
-
this.setupNodeHotReload();
|
|
1917
|
+
if (this.options.hotReloadUrl && typeof window !== "undefined" && "WebSocket" in window) {
|
|
1918
|
+
this.setupBrowserHotReload(this.options.hotReloadUrl);
|
|
1758
1919
|
}
|
|
1759
1920
|
}
|
|
1760
1921
|
/**
|
|
1761
1922
|
* Browser hot reload setup
|
|
1762
1923
|
*/
|
|
1763
|
-
setupBrowserHotReload() {
|
|
1924
|
+
setupBrowserHotReload(url) {
|
|
1764
1925
|
try {
|
|
1765
|
-
const ws = new WebSocket(
|
|
1926
|
+
const ws = new window.WebSocket(url);
|
|
1766
1927
|
ws.onmessage = (event) => {
|
|
1767
|
-
|
|
1928
|
+
let data;
|
|
1929
|
+
try {
|
|
1930
|
+
data = JSON.parse(event.data);
|
|
1931
|
+
} catch {
|
|
1932
|
+
return;
|
|
1933
|
+
}
|
|
1768
1934
|
if (data.type === "component-updated") {
|
|
1769
1935
|
console.log("\u{1F504} Component updated:", data.componentName);
|
|
1770
1936
|
this.handleComponentUpdate(data);
|
|
@@ -1780,24 +1946,7 @@ ${indent}</${tag}>`;
|
|
|
1780
1946
|
console.log("\u{1F50C} Disconnected from dev server");
|
|
1781
1947
|
this.hotReloadEnabled = false;
|
|
1782
1948
|
};
|
|
1783
|
-
|
|
1784
|
-
}
|
|
1785
|
-
}
|
|
1786
|
-
/**
|
|
1787
|
-
* Node.js hot reload setup
|
|
1788
|
-
*/
|
|
1789
|
-
setupNodeHotReload() {
|
|
1790
|
-
try {
|
|
1791
|
-
const fs = __require("fs");
|
|
1792
|
-
const path2 = __require("path");
|
|
1793
|
-
const watchDir = path2.join(process.cwd(), "src");
|
|
1794
|
-
fs.watch(watchDir, { recursive: true }, (eventType, filename) => {
|
|
1795
|
-
if (filename && filename.endsWith(".js")) {
|
|
1796
|
-
console.log(`\u{1F504} File changed: ${filename}`);
|
|
1797
|
-
this.handleFileChange(filename, eventType);
|
|
1798
|
-
}
|
|
1799
|
-
});
|
|
1800
|
-
this.hotReloadEnabled = true;
|
|
1949
|
+
this._teardown.push(() => ws.close());
|
|
1801
1950
|
} catch {
|
|
1802
1951
|
}
|
|
1803
1952
|
}
|
|
@@ -1805,7 +1954,7 @@ ${indent}</${tag}>`;
|
|
|
1805
1954
|
* Handle component updates
|
|
1806
1955
|
*/
|
|
1807
1956
|
handleComponentUpdate(updateData) {
|
|
1808
|
-
if (this.coherent
|
|
1957
|
+
if (this.coherent?.cache?.invalidatePattern) {
|
|
1809
1958
|
this.coherent.cache.invalidatePattern(updateData.componentName);
|
|
1810
1959
|
}
|
|
1811
1960
|
this.componentRegistry.set(updateData.componentName, {
|
|
@@ -1816,22 +1965,12 @@ ${indent}</${tag}>`;
|
|
|
1816
1965
|
window.location.reload();
|
|
1817
1966
|
}
|
|
1818
1967
|
}
|
|
1819
|
-
/**
|
|
1820
|
-
* Handle file changes
|
|
1821
|
-
*/
|
|
1822
|
-
handleFileChange(filename, eventType) {
|
|
1823
|
-
if (typeof __require !== "undefined" && __require.cache) {
|
|
1824
|
-
const fullPath = __require.resolve(path.resolve(filename));
|
|
1825
|
-
delete __require.cache[fullPath];
|
|
1826
|
-
}
|
|
1827
|
-
console.log(`\u{1F4DD} ${eventType}: ${filename}`);
|
|
1828
|
-
}
|
|
1829
1968
|
/**
|
|
1830
1969
|
* Setup browser-specific dev tools
|
|
1831
1970
|
*/
|
|
1832
1971
|
setupBrowserDevTools() {
|
|
1833
1972
|
this.createDevPanel();
|
|
1834
|
-
|
|
1973
|
+
const onKeyDown = (e) => {
|
|
1835
1974
|
if (e.ctrlKey && e.shiftKey && e.code === "KeyC") {
|
|
1836
1975
|
this.toggleDevPanel();
|
|
1837
1976
|
e.preventDefault();
|
|
@@ -1840,7 +1979,9 @@ ${indent}</${tag}>`;
|
|
|
1840
1979
|
console.table(this.getPerformanceInsights());
|
|
1841
1980
|
e.preventDefault();
|
|
1842
1981
|
}
|
|
1843
|
-
}
|
|
1982
|
+
};
|
|
1983
|
+
document.addEventListener("keydown", onKeyDown);
|
|
1984
|
+
this._teardown.push(() => document.removeEventListener("keydown", onKeyDown));
|
|
1844
1985
|
}
|
|
1845
1986
|
/**
|
|
1846
1987
|
* Create development panel in browser
|
|
@@ -1866,6 +2007,10 @@ ${indent}</${tag}>`;
|
|
|
1866
2007
|
`;
|
|
1867
2008
|
document.body.appendChild(panel);
|
|
1868
2009
|
this.devPanel = panel;
|
|
2010
|
+
this._teardown.push(() => {
|
|
2011
|
+
panel.remove();
|
|
2012
|
+
this.devPanel = null;
|
|
2013
|
+
});
|
|
1869
2014
|
this.updateDevPanel();
|
|
1870
2015
|
}
|
|
1871
2016
|
/**
|
|
@@ -1929,15 +2074,6 @@ ${indent}</${tag}>`;
|
|
|
1929
2074
|
</div>
|
|
1930
2075
|
`;
|
|
1931
2076
|
}
|
|
1932
|
-
/**
|
|
1933
|
-
* Setup Node.js specific dev tools
|
|
1934
|
-
*/
|
|
1935
|
-
setupNodeDevTools() {
|
|
1936
|
-
process.on("SIGINT", () => {
|
|
1937
|
-
this.printDevSummary();
|
|
1938
|
-
process.exit();
|
|
1939
|
-
});
|
|
1940
|
-
}
|
|
1941
2077
|
/**
|
|
1942
2078
|
* Print development summary
|
|
1943
2079
|
*/
|
|
@@ -1972,7 +2108,7 @@ ${indent}</${tag}>`;
|
|
|
1972
2108
|
insights.slowestRender = Math.max(...times);
|
|
1973
2109
|
insights.fastestRender = Math.min(...times);
|
|
1974
2110
|
}
|
|
1975
|
-
if (this.coherent
|
|
2111
|
+
if (this.coherent?.cache?.getStats) {
|
|
1976
2112
|
const cacheStats = this.coherent.cache.getStats();
|
|
1977
2113
|
insights.cacheHits = cacheStats.hits;
|
|
1978
2114
|
insights.cacheHitRate = cacheStats.hitRate;
|
|
@@ -2032,7 +2168,7 @@ ${indent}</${tag}>`;
|
|
|
2032
2168
|
toggleFeature(feature) {
|
|
2033
2169
|
switch (feature) {
|
|
2034
2170
|
case "cache":
|
|
2035
|
-
if (this.coherent
|
|
2171
|
+
if (this.coherent?.cache) {
|
|
2036
2172
|
this.coherent.cache.enabled = !this.coherent.cache.enabled;
|
|
2037
2173
|
console.log(`Cache ${this.coherent.cache.enabled ? "enabled" : "disabled"}`);
|
|
2038
2174
|
}
|
|
@@ -2054,23 +2190,9 @@ ${indent}</${tag}>`;
|
|
|
2054
2190
|
validateComponent(component) {
|
|
2055
2191
|
return this.deepValidateComponent(component);
|
|
2056
2192
|
}
|
|
2057
|
-
setupComponentInspector() {
|
|
2058
|
-
const originalCreateComponent = this.coherent.createComponent;
|
|
2059
|
-
if (originalCreateComponent) {
|
|
2060
|
-
this.coherent.createComponent = (config) => {
|
|
2061
|
-
const component = originalCreateComponent.call(this.coherent, config);
|
|
2062
|
-
this.componentRegistry.set(config.name || "anonymous", {
|
|
2063
|
-
config,
|
|
2064
|
-
component,
|
|
2065
|
-
registeredAt: Date.now()
|
|
2066
|
-
});
|
|
2067
|
-
return component;
|
|
2068
|
-
};
|
|
2069
|
-
}
|
|
2070
|
-
}
|
|
2071
2193
|
};
|
|
2072
|
-
function createDevTools(coherentInstance) {
|
|
2073
|
-
return new DevTools(coherentInstance);
|
|
2194
|
+
function createDevTools(coherentInstance, options) {
|
|
2195
|
+
return new DevTools(coherentInstance, options);
|
|
2074
2196
|
}
|
|
2075
2197
|
|
|
2076
2198
|
// src/component-visualizer.js
|
|
@@ -2579,8 +2701,8 @@ var PerformanceDashboard = class {
|
|
|
2579
2701
|
* Update metrics from external sources
|
|
2580
2702
|
*/
|
|
2581
2703
|
updateMetrics() {
|
|
2582
|
-
const
|
|
2583
|
-
const uptime =
|
|
2704
|
+
const now2 = Date.now();
|
|
2705
|
+
const uptime = now2 - this.startTime;
|
|
2584
2706
|
const apiRate = this.metrics.api.requests / (uptime / 1e3);
|
|
2585
2707
|
const componentRate = this.metrics.components.renders / (uptime / 1e3);
|
|
2586
2708
|
const fullStackRate = this.metrics.fullstack.totalRequests / (uptime / 1e3);
|
|
@@ -2914,10 +3036,10 @@ var EnhancedErrorHandler = class {
|
|
|
2914
3036
|
/**
|
|
2915
3037
|
* Get component context tree
|
|
2916
3038
|
*/
|
|
2917
|
-
getComponentContext(component,
|
|
3039
|
+
getComponentContext(component, path = []) {
|
|
2918
3040
|
const context = {
|
|
2919
|
-
path:
|
|
2920
|
-
depth:
|
|
3041
|
+
path: path.join("."),
|
|
3042
|
+
depth: path.length,
|
|
2921
3043
|
component: this.summarizeComponent(component),
|
|
2922
3044
|
children: []
|
|
2923
3045
|
};
|
|
@@ -2929,7 +3051,7 @@ var EnhancedErrorHandler = class {
|
|
|
2929
3051
|
const children = Array.isArray(props.children) ? props.children : [props.children];
|
|
2930
3052
|
children.forEach((child, index) => {
|
|
2931
3053
|
if (child && typeof child === "object") {
|
|
2932
|
-
const childContext = this.getComponentContext(child, [...
|
|
3054
|
+
const childContext = this.getComponentContext(child, [...path, `${tagName}[${index}]`]);
|
|
2933
3055
|
context.children.push(childContext);
|
|
2934
3056
|
}
|
|
2935
3057
|
});
|