@lytjs/devtools 6.4.0 → 6.6.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 +723 -0
- package/dist/index.cjs +638 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +228 -101
- package/dist/index.d.ts +228 -101
- package/dist/index.mjs +620 -13
- package/dist/index.mjs.map +1 -1
- package/package.json +60 -60
package/dist/index.cjs
CHANGED
|
@@ -36,7 +36,10 @@ function buildComponentTreeRecursive(component, parentId) {
|
|
|
36
36
|
if (component.children && Array.isArray(component.children)) {
|
|
37
37
|
const children = [];
|
|
38
38
|
for (const child of component.children) {
|
|
39
|
-
const childNode = buildComponentTreeRecursive(
|
|
39
|
+
const childNode = buildComponentTreeRecursive(
|
|
40
|
+
child,
|
|
41
|
+
node.id
|
|
42
|
+
);
|
|
40
43
|
if (childNode) {
|
|
41
44
|
children.push(childNode);
|
|
42
45
|
}
|
|
@@ -134,16 +137,14 @@ function setStoreState(storeId, path, value) {
|
|
|
134
137
|
const store = storeRegistry.get(storeId);
|
|
135
138
|
if (!store) return false;
|
|
136
139
|
const keys = path.split(".");
|
|
137
|
-
let current = store
|
|
140
|
+
let current = store;
|
|
138
141
|
for (let i = 0; i < keys.length - 1; i++) {
|
|
139
142
|
const key = keys[i];
|
|
140
|
-
if (!commonIs.isObject(current
|
|
141
|
-
return false;
|
|
142
|
-
}
|
|
143
|
+
if (!commonIs.isObject(current)) return false;
|
|
143
144
|
current = current[key];
|
|
144
145
|
}
|
|
145
146
|
const lastKey = keys[keys.length - 1];
|
|
146
|
-
if (lastKey) {
|
|
147
|
+
if (lastKey && commonIs.isObject(current)) {
|
|
147
148
|
current[lastKey] = value;
|
|
148
149
|
}
|
|
149
150
|
return true;
|
|
@@ -186,7 +187,7 @@ function subscribeStore(storeId) {
|
|
|
186
187
|
unsubscribeStore(storeId);
|
|
187
188
|
}
|
|
188
189
|
if (commonIs.isFunction(store.$subscribe)) {
|
|
189
|
-
const unsubscribe = store.$subscribe((_mutation, state) => {
|
|
190
|
+
const unsubscribe = store.$subscribe?.((_mutation, state) => {
|
|
190
191
|
const currentState = commonIs.isObject(state) ? deepClone(state) : {};
|
|
191
192
|
globalChangeCallbacks.forEach((cb) => cb(storeId, currentState));
|
|
192
193
|
});
|
|
@@ -253,7 +254,7 @@ function watchRouteChanges() {
|
|
|
253
254
|
if (afterEachHandler !== null) {
|
|
254
255
|
unwatchRouteChanges();
|
|
255
256
|
}
|
|
256
|
-
routerInstance.afterEach((to) => {
|
|
257
|
+
routerInstance.afterEach?.((to) => {
|
|
257
258
|
const routeInfo = {
|
|
258
259
|
path: to.path || "/",
|
|
259
260
|
name: to.name || null,
|
|
@@ -287,7 +288,8 @@ function navigateToName(name, params) {
|
|
|
287
288
|
if (!routerInstance) {
|
|
288
289
|
return Promise.reject(new Error("Router not registered"));
|
|
289
290
|
}
|
|
290
|
-
|
|
291
|
+
const path = `/${name}${params ? "?" + new URLSearchParams(params).toString() : ""}`;
|
|
292
|
+
return routerInstance.push?.(path) || Promise.resolve();
|
|
291
293
|
}
|
|
292
294
|
function goBack() {
|
|
293
295
|
if (!routerInstance) {
|
|
@@ -333,6 +335,14 @@ function clearRouteHistory() {
|
|
|
333
335
|
}
|
|
334
336
|
|
|
335
337
|
// src/signalsInspector.ts
|
|
338
|
+
var DEFAULT_LAYOUT_OPTIONS = {
|
|
339
|
+
nodeWidth: 120,
|
|
340
|
+
nodeHeight: 40,
|
|
341
|
+
horizontalSpacing: 60,
|
|
342
|
+
verticalSpacing: 80,
|
|
343
|
+
centerX: 300,
|
|
344
|
+
centerY: 200
|
|
345
|
+
};
|
|
336
346
|
var signalRegistry = /* @__PURE__ */ new Map();
|
|
337
347
|
var snapshotManager = {
|
|
338
348
|
snapshots: [],
|
|
@@ -488,6 +498,9 @@ function createSnapshot(label) {
|
|
|
488
498
|
function getSnapshots() {
|
|
489
499
|
return snapshotManager.getAll();
|
|
490
500
|
}
|
|
501
|
+
function getSnapshot(index) {
|
|
502
|
+
return snapshotManager.get(index);
|
|
503
|
+
}
|
|
491
504
|
function getTimeTravelState() {
|
|
492
505
|
const length = snapshotManager.getLength();
|
|
493
506
|
return {
|
|
@@ -641,6 +654,352 @@ function serializePerformanceStats() {
|
|
|
641
654
|
});
|
|
642
655
|
return result;
|
|
643
656
|
}
|
|
657
|
+
function calculateNodeLevels() {
|
|
658
|
+
const levels = /* @__PURE__ */ new Map();
|
|
659
|
+
const visited = /* @__PURE__ */ new Set();
|
|
660
|
+
function visitNode(nodeId, level) {
|
|
661
|
+
if (visited.has(nodeId)) {
|
|
662
|
+
const existingLevel = levels.get(nodeId);
|
|
663
|
+
if (existingLevel !== void 0 && level > existingLevel) {
|
|
664
|
+
levels.set(nodeId, level);
|
|
665
|
+
}
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
visited.add(nodeId);
|
|
669
|
+
levels.set(nodeId, level);
|
|
670
|
+
const node = signalRegistry.get(nodeId);
|
|
671
|
+
if (node) {
|
|
672
|
+
node.dependents.forEach((depId) => {
|
|
673
|
+
visitNode(depId, level + 1);
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
signalRegistry.forEach((node, id) => {
|
|
678
|
+
if (node.dependencies.size === 0) {
|
|
679
|
+
visitNode(id, 0);
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
signalRegistry.forEach((_, id) => {
|
|
683
|
+
if (!visited.has(id)) {
|
|
684
|
+
visitNode(id, 0);
|
|
685
|
+
}
|
|
686
|
+
});
|
|
687
|
+
return levels;
|
|
688
|
+
}
|
|
689
|
+
function calculateNodeDegrees() {
|
|
690
|
+
const degrees = /* @__PURE__ */ new Map();
|
|
691
|
+
signalRegistry.forEach((node, id) => {
|
|
692
|
+
degrees.set(id, {
|
|
693
|
+
inDegree: node.dependencies.size,
|
|
694
|
+
outDegree: node.dependents.size
|
|
695
|
+
});
|
|
696
|
+
});
|
|
697
|
+
return degrees;
|
|
698
|
+
}
|
|
699
|
+
function getVisualLayoutGraph(options2) {
|
|
700
|
+
const opts = { ...DEFAULT_LAYOUT_OPTIONS, ...options2 };
|
|
701
|
+
if (signalRegistry.size === 0) {
|
|
702
|
+
return { nodes: [], edges: [], width: 0, height: 0 };
|
|
703
|
+
}
|
|
704
|
+
const levels = calculateNodeLevels();
|
|
705
|
+
const degrees = calculateNodeDegrees();
|
|
706
|
+
const levelNodes = /* @__PURE__ */ new Map();
|
|
707
|
+
levels.forEach((level, nodeId) => {
|
|
708
|
+
const nodes2 = levelNodes.get(level) || [];
|
|
709
|
+
nodes2.push(nodeId);
|
|
710
|
+
levelNodes.set(level, nodes2);
|
|
711
|
+
});
|
|
712
|
+
const maxLevel = Math.max(...Array.from(levels.values()), 0);
|
|
713
|
+
const maxNodesInLevel = Math.max(...Array.from(levelNodes.values()).map((n) => n.length), 1);
|
|
714
|
+
const graphWidth = (maxNodesInLevel + 1) * (opts.nodeWidth + opts.horizontalSpacing);
|
|
715
|
+
const graphHeight = (maxLevel + 2) * (opts.nodeHeight + opts.verticalSpacing);
|
|
716
|
+
const nodes = [];
|
|
717
|
+
const edges = [];
|
|
718
|
+
levelNodes.forEach((nodeIds, level) => {
|
|
719
|
+
const levelWidth = nodeIds.length * (opts.nodeWidth + opts.horizontalSpacing) - opts.horizontalSpacing;
|
|
720
|
+
const startX = (graphWidth - levelWidth) / 2;
|
|
721
|
+
nodeIds.forEach((nodeId, index) => {
|
|
722
|
+
const node = signalRegistry.get(nodeId);
|
|
723
|
+
if (!node) return;
|
|
724
|
+
const degree = degrees.get(nodeId) || { inDegree: 0, outDegree: 0 };
|
|
725
|
+
const x = startX + index * (opts.nodeWidth + opts.horizontalSpacing);
|
|
726
|
+
const y = opts.centerY - graphHeight / 2 + level * (opts.nodeHeight + opts.verticalSpacing) + opts.verticalSpacing / 2;
|
|
727
|
+
nodes.push({
|
|
728
|
+
id: nodeId,
|
|
729
|
+
name: node.name,
|
|
730
|
+
type: node.type,
|
|
731
|
+
x,
|
|
732
|
+
y,
|
|
733
|
+
level,
|
|
734
|
+
width: opts.nodeWidth,
|
|
735
|
+
height: opts.nodeHeight,
|
|
736
|
+
inDegree: degree.inDegree,
|
|
737
|
+
outDegree: degree.outDegree
|
|
738
|
+
});
|
|
739
|
+
node.dependencies.forEach((depId) => {
|
|
740
|
+
const depNode = nodes.find((n) => n.id === depId);
|
|
741
|
+
if (depNode) {
|
|
742
|
+
edges.push({
|
|
743
|
+
source: depId,
|
|
744
|
+
target: nodeId,
|
|
745
|
+
sourceX: depNode.x + opts.nodeWidth,
|
|
746
|
+
sourceY: depNode.y + opts.nodeHeight / 2,
|
|
747
|
+
targetX: x,
|
|
748
|
+
targetY: y + opts.nodeHeight / 2,
|
|
749
|
+
type: "dependency"
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
});
|
|
754
|
+
});
|
|
755
|
+
return { nodes, edges, width: graphWidth, height: graphHeight };
|
|
756
|
+
}
|
|
757
|
+
function getSubgraph(centerId, depth = 2) {
|
|
758
|
+
const includedNodes = /* @__PURE__ */ new Set([centerId]);
|
|
759
|
+
const queue = [{ id: centerId, currentDepth: 0 }];
|
|
760
|
+
while (queue.length > 0) {
|
|
761
|
+
const { id, currentDepth: currentDepth2 } = queue.shift();
|
|
762
|
+
if (currentDepth2 >= depth) continue;
|
|
763
|
+
const node = signalRegistry.get(id);
|
|
764
|
+
if (node) {
|
|
765
|
+
node.dependencies.forEach((depId) => {
|
|
766
|
+
if (!includedNodes.has(depId)) {
|
|
767
|
+
includedNodes.add(depId);
|
|
768
|
+
queue.push({ id: depId, currentDepth: currentDepth2 + 1 });
|
|
769
|
+
}
|
|
770
|
+
});
|
|
771
|
+
node.dependents.forEach((depId) => {
|
|
772
|
+
if (!includedNodes.has(depId)) {
|
|
773
|
+
includedNodes.add(depId);
|
|
774
|
+
queue.push({ id: depId, currentDepth: currentDepth2 + 1 });
|
|
775
|
+
}
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
const tempRegistry = new Map(signalRegistry);
|
|
780
|
+
signalRegistry.forEach((_, id) => {
|
|
781
|
+
if (!includedNodes.has(id)) {
|
|
782
|
+
signalRegistry.delete(id);
|
|
783
|
+
}
|
|
784
|
+
});
|
|
785
|
+
const subgraph = getVisualLayoutGraph();
|
|
786
|
+
signalRegistry.clear();
|
|
787
|
+
tempRegistry.forEach((value, key) => {
|
|
788
|
+
signalRegistry.set(key, value);
|
|
789
|
+
});
|
|
790
|
+
return subgraph;
|
|
791
|
+
}
|
|
792
|
+
function searchSignals(query) {
|
|
793
|
+
const lowerQuery = query.toLowerCase();
|
|
794
|
+
return getSignalNodes().filter(
|
|
795
|
+
(node) => node.name.toLowerCase().includes(lowerQuery) || node.id.toLowerCase().includes(lowerQuery) || node.type.toLowerCase().includes(lowerQuery)
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
function filterSignals(options2) {
|
|
799
|
+
return getSignalNodes().filter((node) => {
|
|
800
|
+
if (options2.types && !options2.types.includes(node.type)) {
|
|
801
|
+
return false;
|
|
802
|
+
}
|
|
803
|
+
if (options2.minUpdateCount !== void 0 && node.updateCount < options2.minUpdateCount) {
|
|
804
|
+
return false;
|
|
805
|
+
}
|
|
806
|
+
if (options2.hasDependencies && node.dependencies.length === 0) {
|
|
807
|
+
return false;
|
|
808
|
+
}
|
|
809
|
+
if (options2.hasDependents && node.dependents.length === 0) {
|
|
810
|
+
return false;
|
|
811
|
+
}
|
|
812
|
+
return true;
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
function compareSnapshots(snapshot1, snapshot2) {
|
|
816
|
+
const diff = {
|
|
817
|
+
added: [],
|
|
818
|
+
removed: [],
|
|
819
|
+
changed: []
|
|
820
|
+
};
|
|
821
|
+
const ids1 = new Set(Object.keys(snapshot1.signals));
|
|
822
|
+
const ids2 = new Set(Object.keys(snapshot2.signals));
|
|
823
|
+
ids2.forEach((id) => {
|
|
824
|
+
if (!ids1.has(id)) {
|
|
825
|
+
diff.added.push({
|
|
826
|
+
id,
|
|
827
|
+
value: snapshot2.signals[id].value
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
});
|
|
831
|
+
ids1.forEach((id) => {
|
|
832
|
+
if (!ids2.has(id)) {
|
|
833
|
+
diff.removed.push({
|
|
834
|
+
id,
|
|
835
|
+
value: snapshot1.signals[id].value
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
});
|
|
839
|
+
ids1.forEach((id) => {
|
|
840
|
+
if (ids2.has(id)) {
|
|
841
|
+
const oldValue = snapshot1.signals[id].value;
|
|
842
|
+
const newValue = snapshot2.signals[id].value;
|
|
843
|
+
if (!Object.is(oldValue, newValue)) {
|
|
844
|
+
diff.changed.push({ id, oldValue, newValue });
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
});
|
|
848
|
+
return diff;
|
|
849
|
+
}
|
|
850
|
+
function serializeSnapshotDiff(diff) {
|
|
851
|
+
let result = "";
|
|
852
|
+
if (diff.added.length > 0) {
|
|
853
|
+
result += `\u2795 \u65B0\u589E\u4FE1\u53F7 (${diff.added.length}):
|
|
854
|
+
`;
|
|
855
|
+
diff.added.forEach(({ id, value }) => {
|
|
856
|
+
result += ` ${id}: ${JSON.stringify(value)}
|
|
857
|
+
`;
|
|
858
|
+
});
|
|
859
|
+
result += "\n";
|
|
860
|
+
}
|
|
861
|
+
if (diff.removed.length > 0) {
|
|
862
|
+
result += `\u2796 \u79FB\u9664\u4FE1\u53F7 (${diff.removed.length}):
|
|
863
|
+
`;
|
|
864
|
+
diff.removed.forEach(({ id, value }) => {
|
|
865
|
+
result += ` ${id}: ${JSON.stringify(value)}
|
|
866
|
+
`;
|
|
867
|
+
});
|
|
868
|
+
result += "\n";
|
|
869
|
+
}
|
|
870
|
+
if (diff.changed.length > 0) {
|
|
871
|
+
result += `\u{1F504} \u53D8\u66F4\u4FE1\u53F7 (${diff.changed.length}):
|
|
872
|
+
`;
|
|
873
|
+
diff.changed.forEach(({ id, oldValue, newValue }) => {
|
|
874
|
+
result += ` ${id}:
|
|
875
|
+
`;
|
|
876
|
+
result += ` \u65E7\u503C: ${JSON.stringify(oldValue)}
|
|
877
|
+
`;
|
|
878
|
+
result += ` \u65B0\u503C: ${JSON.stringify(newValue)}
|
|
879
|
+
`;
|
|
880
|
+
});
|
|
881
|
+
result += "\n";
|
|
882
|
+
}
|
|
883
|
+
if (result === "") {
|
|
884
|
+
result = "\u2728 \u5FEB\u7167\u5B8C\u5168\u76F8\u540C";
|
|
885
|
+
}
|
|
886
|
+
return result;
|
|
887
|
+
}
|
|
888
|
+
function getDiffBetweenSnapshots(index1, index2) {
|
|
889
|
+
const snapshot1 = getSnapshot(index1);
|
|
890
|
+
const snapshot2 = getSnapshot(index2);
|
|
891
|
+
if (!snapshot1 || !snapshot2) {
|
|
892
|
+
return null;
|
|
893
|
+
}
|
|
894
|
+
return compareSnapshots(snapshot1, snapshot2);
|
|
895
|
+
}
|
|
896
|
+
function getTimeTravelNavigator(index) {
|
|
897
|
+
const snapshots = getSnapshots();
|
|
898
|
+
const currentIndex = index !== void 0 ? index : Math.max(0, snapshots.length - 1);
|
|
899
|
+
return {
|
|
900
|
+
currentIndex,
|
|
901
|
+
total: snapshots.length,
|
|
902
|
+
canGoBack: currentIndex > 0,
|
|
903
|
+
canGoForward: currentIndex < snapshots.length - 1,
|
|
904
|
+
currentSnapshot: snapshots[currentIndex] ?? null,
|
|
905
|
+
previousSnapshot: currentIndex > 0 ? snapshots[currentIndex - 1] ?? null : null,
|
|
906
|
+
nextSnapshot: snapshots[currentIndex + 1] ?? null,
|
|
907
|
+
diff: currentIndex > 0 ? compareSnapshots(snapshots[currentIndex - 1], snapshots[currentIndex]) : null
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
function timeTravelBack() {
|
|
911
|
+
const navigator = getTimeTravelNavigator();
|
|
912
|
+
if (!navigator.canGoBack) return null;
|
|
913
|
+
return restoreSnapshot(navigator.currentIndex - 1) ?? null;
|
|
914
|
+
}
|
|
915
|
+
function timeTravelForward() {
|
|
916
|
+
const navigator = getTimeTravelNavigator();
|
|
917
|
+
if (!navigator.canGoForward) return null;
|
|
918
|
+
return restoreSnapshot(navigator.currentIndex + 1) ?? null;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// src/vdomInspector.ts
|
|
922
|
+
var registry = {
|
|
923
|
+
roots: /* @__PURE__ */ new Map(),
|
|
924
|
+
nodes: /* @__PURE__ */ new Map()};
|
|
925
|
+
function getVDOMRoots() {
|
|
926
|
+
return Array.from(registry.roots.values());
|
|
927
|
+
}
|
|
928
|
+
function getVDOMTree() {
|
|
929
|
+
return getVDOMRoots();
|
|
930
|
+
}
|
|
931
|
+
function getVDOMStats() {
|
|
932
|
+
let maxDepth = 0;
|
|
933
|
+
let componentCount = 0;
|
|
934
|
+
let elementCount = 0;
|
|
935
|
+
let textCount = 0;
|
|
936
|
+
for (const node of registry.nodes.values()) {
|
|
937
|
+
if (node.depth > maxDepth) maxDepth = node.depth;
|
|
938
|
+
if (node.isComponent) componentCount++;
|
|
939
|
+
else if (node.type === "element") elementCount++;
|
|
940
|
+
else if (node.type === "text") textCount++;
|
|
941
|
+
}
|
|
942
|
+
return {
|
|
943
|
+
totalNodes: registry.nodes.size,
|
|
944
|
+
rootCount: registry.roots.size,
|
|
945
|
+
maxDepth,
|
|
946
|
+
componentCount,
|
|
947
|
+
elementCount,
|
|
948
|
+
textCount
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
function serializeVDOMNode(node, indent = 0) {
|
|
952
|
+
const spaces = " ".repeat(indent);
|
|
953
|
+
let result = "";
|
|
954
|
+
if (node.type === "text") {
|
|
955
|
+
result += `${spaces}\u{1F4DD} "${node.text || ""}"
|
|
956
|
+
`;
|
|
957
|
+
} else if (node.isComponent) {
|
|
958
|
+
result += `${spaces}\u{1F9E9} <${node.tagName || "Component"}`;
|
|
959
|
+
if (node.key !== void 0) result += ` key="${node.key}"`;
|
|
960
|
+
result += ">\n";
|
|
961
|
+
} else if (node.type === "element") {
|
|
962
|
+
result += `${spaces}\u{1F3F7}\uFE0F <${node.tagName || "unknown"}`;
|
|
963
|
+
if (node.key !== void 0) result += ` key="${node.key}"`;
|
|
964
|
+
if (node.props) {
|
|
965
|
+
const propStr = Object.entries(node.props).slice(0, 3).map(([k, v]) => `${k}="${v}"`).join(" ");
|
|
966
|
+
if (propStr) result += ` ${propStr}`;
|
|
967
|
+
if (Object.keys(node.props).length > 3) {
|
|
968
|
+
result += " ...";
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
result += ">\n";
|
|
972
|
+
}
|
|
973
|
+
for (const child of node.children) {
|
|
974
|
+
result += serializeVDOMNode(child, indent + 1);
|
|
975
|
+
}
|
|
976
|
+
if ((node.isComponent || node.type === "element") && node.children.length === 0) {
|
|
977
|
+
result += `${spaces} (empty)
|
|
978
|
+
`;
|
|
979
|
+
}
|
|
980
|
+
return result;
|
|
981
|
+
}
|
|
982
|
+
function serializeVDOMTree() {
|
|
983
|
+
const roots = getVDOMRoots();
|
|
984
|
+
if (roots.length === 0) {
|
|
985
|
+
return "No VDOM roots registered.";
|
|
986
|
+
}
|
|
987
|
+
let result = `\u{1F4E6} VDOM Tree (${roots.length} root(s))
|
|
988
|
+
`;
|
|
989
|
+
result += "\u2500".repeat(40) + "\n";
|
|
990
|
+
for (const root of roots) {
|
|
991
|
+
result += serializeVDOMNode(root);
|
|
992
|
+
result += "\n";
|
|
993
|
+
}
|
|
994
|
+
const stats = getVDOMStats();
|
|
995
|
+
result += "\u2500".repeat(40) + "\n";
|
|
996
|
+
result += `\u{1F4CA} Stats: ${stats.totalNodes} nodes, ${stats.componentCount} components, `;
|
|
997
|
+
result += `${stats.elementCount} elements, ${stats.textCount} text nodes
|
|
998
|
+
`;
|
|
999
|
+
result += `\u{1F4CF} Max depth: ${stats.maxDepth}
|
|
1000
|
+
`;
|
|
1001
|
+
return result;
|
|
1002
|
+
}
|
|
644
1003
|
|
|
645
1004
|
// src/performance.ts
|
|
646
1005
|
var DEFAULT_OPTIONS = {
|
|
@@ -885,7 +1244,7 @@ function outputAlert(alert) {
|
|
|
885
1244
|
error: "color: #ff4d4f;",
|
|
886
1245
|
critical: "color: #722ed1; background: #ff4d4f; padding: 2px 5px;"
|
|
887
1246
|
};
|
|
888
|
-
console.
|
|
1247
|
+
console.warn(
|
|
889
1248
|
`%c[LytJS Alert] ${alert.ruleName}%c ${alert.message}`,
|
|
890
1249
|
levelStyles[alert.level],
|
|
891
1250
|
"color: inherit;"
|
|
@@ -972,6 +1331,151 @@ function startTimer(name, type = "custom") {
|
|
|
972
1331
|
recordMetric({ name, type, duration });
|
|
973
1332
|
};
|
|
974
1333
|
}
|
|
1334
|
+
var timelineEventStack = [];
|
|
1335
|
+
var timelineEvents = [];
|
|
1336
|
+
var maxTimelineEvents = 1e3;
|
|
1337
|
+
var currentDepth = 0;
|
|
1338
|
+
function beginTimelineEvent(name, category = "custom", metadata) {
|
|
1339
|
+
const id = `timeline_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
1340
|
+
const event = {
|
|
1341
|
+
id,
|
|
1342
|
+
name,
|
|
1343
|
+
category,
|
|
1344
|
+
startTime: performance.now(),
|
|
1345
|
+
duration: 0,
|
|
1346
|
+
depth: currentDepth,
|
|
1347
|
+
metadata
|
|
1348
|
+
};
|
|
1349
|
+
timelineEventStack.push({
|
|
1350
|
+
event,
|
|
1351
|
+
startTime: performance.now()
|
|
1352
|
+
});
|
|
1353
|
+
currentDepth++;
|
|
1354
|
+
return id;
|
|
1355
|
+
}
|
|
1356
|
+
function endTimelineEvent(id) {
|
|
1357
|
+
const stackIndex = timelineEventStack.findIndex((item) => item.event.id === id);
|
|
1358
|
+
if (stackIndex === -1) {
|
|
1359
|
+
console.warn(`[DevTools] Timeline event ${id} not found in stack`);
|
|
1360
|
+
return null;
|
|
1361
|
+
}
|
|
1362
|
+
const { event } = timelineEventStack[stackIndex];
|
|
1363
|
+
const endTime = performance.now();
|
|
1364
|
+
event.duration = endTime - event.startTime;
|
|
1365
|
+
for (let i = stackIndex + 1; i < timelineEventStack.length; i++) {
|
|
1366
|
+
const nestedEvent = timelineEventStack[i];
|
|
1367
|
+
event.duration = Math.max(
|
|
1368
|
+
event.duration,
|
|
1369
|
+
nestedEvent.startTime - event.startTime + nestedEvent.event.duration
|
|
1370
|
+
);
|
|
1371
|
+
}
|
|
1372
|
+
timelineEventStack.splice(stackIndex);
|
|
1373
|
+
currentDepth = Math.max(0, currentDepth - 1);
|
|
1374
|
+
timelineEvents.push(event);
|
|
1375
|
+
if (timelineEvents.length > maxTimelineEvents) {
|
|
1376
|
+
timelineEvents.shift();
|
|
1377
|
+
}
|
|
1378
|
+
return event;
|
|
1379
|
+
}
|
|
1380
|
+
function getTimelineEvents() {
|
|
1381
|
+
return [...timelineEvents];
|
|
1382
|
+
}
|
|
1383
|
+
function getTimelineEventsInRange(startTime, endTime) {
|
|
1384
|
+
return timelineEvents.filter(
|
|
1385
|
+
(event) => event.startTime >= startTime && event.startTime <= endTime
|
|
1386
|
+
);
|
|
1387
|
+
}
|
|
1388
|
+
function getSlowOperations(limit = 10, threshold) {
|
|
1389
|
+
const sorted = [...timelineEvents].sort((a, b) => b.duration - a.duration);
|
|
1390
|
+
const result = threshold !== void 0 ? sorted.filter((e) => e.duration >= threshold) : sorted;
|
|
1391
|
+
return result.slice(0, limit);
|
|
1392
|
+
}
|
|
1393
|
+
function getFlameGraphData() {
|
|
1394
|
+
const root = {
|
|
1395
|
+
name: "root",
|
|
1396
|
+
value: 0,
|
|
1397
|
+
children: [],
|
|
1398
|
+
category: "custom"
|
|
1399
|
+
};
|
|
1400
|
+
const nodeMap = /* @__PURE__ */ new Map();
|
|
1401
|
+
nodeMap.set("root", root);
|
|
1402
|
+
timelineEvents.forEach((event) => {
|
|
1403
|
+
const node = {
|
|
1404
|
+
name: event.name,
|
|
1405
|
+
value: event.duration,
|
|
1406
|
+
category: event.category,
|
|
1407
|
+
children: []
|
|
1408
|
+
};
|
|
1409
|
+
const parentNode = event.depth === 0 ? root : findParentNode(root, event.depth);
|
|
1410
|
+
if (parentNode) {
|
|
1411
|
+
if (!parentNode.children) {
|
|
1412
|
+
parentNode.children = [];
|
|
1413
|
+
}
|
|
1414
|
+
parentNode.children.push(node);
|
|
1415
|
+
}
|
|
1416
|
+
nodeMap.set(event.id, node);
|
|
1417
|
+
});
|
|
1418
|
+
aggregateFlameGraphValues(root);
|
|
1419
|
+
return root;
|
|
1420
|
+
}
|
|
1421
|
+
function findParentNode(node, depth) {
|
|
1422
|
+
if (!node.children || node.children.length === 0) {
|
|
1423
|
+
return depth === 1 ? node : null;
|
|
1424
|
+
}
|
|
1425
|
+
let targetDepth = depth - 1;
|
|
1426
|
+
let currentNode = node;
|
|
1427
|
+
while (targetDepth > 0 && currentNode) {
|
|
1428
|
+
const children = currentNode.children || [];
|
|
1429
|
+
if (children.length === 0) {
|
|
1430
|
+
return currentNode;
|
|
1431
|
+
}
|
|
1432
|
+
const lastChild = children[children.length - 1];
|
|
1433
|
+
currentNode = lastChild;
|
|
1434
|
+
targetDepth--;
|
|
1435
|
+
}
|
|
1436
|
+
return currentNode;
|
|
1437
|
+
}
|
|
1438
|
+
function aggregateFlameGraphValues(node) {
|
|
1439
|
+
if (!node.children || node.children.length === 0) {
|
|
1440
|
+
return node.value;
|
|
1441
|
+
}
|
|
1442
|
+
let totalValue = node.value;
|
|
1443
|
+
node.children.forEach((child) => {
|
|
1444
|
+
totalValue += aggregateFlameGraphValues(child);
|
|
1445
|
+
});
|
|
1446
|
+
node.value = totalValue;
|
|
1447
|
+
return totalValue;
|
|
1448
|
+
}
|
|
1449
|
+
function clearTimelineEvents() {
|
|
1450
|
+
timelineEvents.length = 0;
|
|
1451
|
+
timelineEventStack.length = 0;
|
|
1452
|
+
currentDepth = 0;
|
|
1453
|
+
}
|
|
1454
|
+
function exportTimelineAsJSON() {
|
|
1455
|
+
return JSON.stringify(
|
|
1456
|
+
{
|
|
1457
|
+
events: timelineEvents,
|
|
1458
|
+
exportedAt: Date.now()
|
|
1459
|
+
},
|
|
1460
|
+
null,
|
|
1461
|
+
2
|
|
1462
|
+
);
|
|
1463
|
+
}
|
|
1464
|
+
function serializeTimelineEvents() {
|
|
1465
|
+
if (timelineEvents.length === 0) {
|
|
1466
|
+
return "\u6682\u65E0\u65F6\u5E8F\u4E8B\u4EF6\u6570\u636E";
|
|
1467
|
+
}
|
|
1468
|
+
let result = `\u{1F4CA} \u65F6\u5E8F\u4E8B\u4EF6 (${timelineEvents.length} \u4E2A)
|
|
1469
|
+
|
|
1470
|
+
`;
|
|
1471
|
+
timelineEvents.slice(-20).forEach((event) => {
|
|
1472
|
+
const indent = " ".repeat(event.depth);
|
|
1473
|
+
const duration = event.duration.toFixed(2);
|
|
1474
|
+
result += `${indent}\u251C\u2500 ${event.name} [${event.category}] ${duration}ms
|
|
1475
|
+
`;
|
|
1476
|
+
});
|
|
1477
|
+
return result;
|
|
1478
|
+
}
|
|
975
1479
|
|
|
976
1480
|
// src/benchmark.ts
|
|
977
1481
|
var LARGE_SCALE_SCENARIOS = [
|
|
@@ -1212,7 +1716,7 @@ var DevTools = class {
|
|
|
1212
1716
|
this.toggle();
|
|
1213
1717
|
}
|
|
1214
1718
|
});
|
|
1215
|
-
console.
|
|
1719
|
+
console.warn("[LytJS DevTools] Initialized. Press Ctrl+Shift+D to toggle.");
|
|
1216
1720
|
}
|
|
1217
1721
|
/**
|
|
1218
1722
|
* 创建 DevTools 面板
|
|
@@ -1300,6 +1804,15 @@ var DevTools = class {
|
|
|
1300
1804
|
cursor: pointer;
|
|
1301
1805
|
border-bottom: 2px solid transparent;
|
|
1302
1806
|
">Signals</button>
|
|
1807
|
+
<button class="lytjs-devtools-tab" data-tab="vdom" style="
|
|
1808
|
+
flex: 1;
|
|
1809
|
+
padding: 10px;
|
|
1810
|
+
background: transparent;
|
|
1811
|
+
border: none;
|
|
1812
|
+
color: #d4d4d4;
|
|
1813
|
+
cursor: pointer;
|
|
1814
|
+
border-bottom: 2px solid transparent;
|
|
1815
|
+
">VDOM</button>
|
|
1303
1816
|
<button class="lytjs-devtools-tab" data-tab="performance" style="
|
|
1304
1817
|
flex: 1;
|
|
1305
1818
|
padding: 10px;
|
|
@@ -1356,6 +1869,9 @@ var DevTools = class {
|
|
|
1356
1869
|
case "signals":
|
|
1357
1870
|
content.innerHTML = this.renderSignalsTab();
|
|
1358
1871
|
break;
|
|
1872
|
+
case "vdom":
|
|
1873
|
+
content.innerHTML = this.renderVDOMTab();
|
|
1874
|
+
break;
|
|
1359
1875
|
case "performance":
|
|
1360
1876
|
content.innerHTML = this.renderPerformanceTab();
|
|
1361
1877
|
break;
|
|
@@ -1390,7 +1906,7 @@ var DevTools = class {
|
|
|
1390
1906
|
*/
|
|
1391
1907
|
renderSignalsTab() {
|
|
1392
1908
|
const nodes = getSignalNodes();
|
|
1393
|
-
|
|
1909
|
+
const html = `
|
|
1394
1910
|
<div style="margin-bottom: 15px;">
|
|
1395
1911
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
|
|
1396
1912
|
<strong>\u{1F4CA} Signals (${nodes.length})</strong>
|
|
@@ -1495,6 +2011,97 @@ var DevTools = class {
|
|
|
1495
2011
|
html += "</div>";
|
|
1496
2012
|
return html;
|
|
1497
2013
|
}
|
|
2014
|
+
/**
|
|
2015
|
+
* 渲染 VDOM 标签页
|
|
2016
|
+
*/
|
|
2017
|
+
renderVDOMTab() {
|
|
2018
|
+
const roots = getVDOMTree();
|
|
2019
|
+
const stats = getVDOMStats();
|
|
2020
|
+
if (roots.length === 0) {
|
|
2021
|
+
return `
|
|
2022
|
+
<div style="color: #666; text-align: center; padding: 40px;">
|
|
2023
|
+
<div style="font-size: 48px; margin-bottom: 15px;">\u{1F333}</div>
|
|
2024
|
+
<div>No VDOM roots registered.</div>
|
|
2025
|
+
<div style="font-size: 11px; margin-top: 10px; color: #555;">
|
|
2026
|
+
Use registerVDOMRoot() to register your VDOM roots.
|
|
2027
|
+
</div>
|
|
2028
|
+
</div>
|
|
2029
|
+
`;
|
|
2030
|
+
}
|
|
2031
|
+
const html = `
|
|
2032
|
+
<div style="margin-bottom: 15px;">
|
|
2033
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
|
|
2034
|
+
<strong>\u{1F333} VDOM Tree</strong>
|
|
2035
|
+
<button id="lytjs-devtools-clear-vdom" style="
|
|
2036
|
+
background: #ff4d4f;
|
|
2037
|
+
border: none;
|
|
2038
|
+
color: white;
|
|
2039
|
+
padding: 5px 10px;
|
|
2040
|
+
border-radius: 4px;
|
|
2041
|
+
cursor: pointer;
|
|
2042
|
+
font-size: 11px;
|
|
2043
|
+
">\u6E05\u7A7A</button>
|
|
2044
|
+
</div>
|
|
2045
|
+
|
|
2046
|
+
<div style="background: #252526; border-radius: 4px; padding: 15px; margin-bottom: 15px;">
|
|
2047
|
+
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; text-align: center;">
|
|
2048
|
+
<div>
|
|
2049
|
+
<div style="color: #888; font-size: 11px;">\u603B\u8282\u70B9\u6570</div>
|
|
2050
|
+
<div style="font-size: 20px; font-weight: bold; color: #4fc08d;">${stats.totalNodes}</div>
|
|
2051
|
+
</div>
|
|
2052
|
+
<div>
|
|
2053
|
+
<div style="color: #888; font-size: 11px;">\u7EC4\u4EF6\u6570</div>
|
|
2054
|
+
<div style="font-size: 20px; font-weight: bold; color: #1890ff;">${stats.componentCount}</div>
|
|
2055
|
+
</div>
|
|
2056
|
+
<div>
|
|
2057
|
+
<div style="color: #888; font-size: 11px;">\u5143\u7D20\u6570</div>
|
|
2058
|
+
<div style="font-size: 20px; font-weight: bold; color: #faad14;">${stats.elementCount}</div>
|
|
2059
|
+
</div>
|
|
2060
|
+
</div>
|
|
2061
|
+
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; text-align: center; margin-top: 10px;">
|
|
2062
|
+
<div>
|
|
2063
|
+
<div style="color: #888; font-size: 11px;">\u6587\u672C\u8282\u70B9</div>
|
|
2064
|
+
<div style="font-size: 16px; font-weight: bold; color: #888;">${stats.textCount}</div>
|
|
2065
|
+
</div>
|
|
2066
|
+
<div>
|
|
2067
|
+
<div style="color: #888; font-size: 11px;">\u6839\u8282\u70B9</div>
|
|
2068
|
+
<div style="font-size: 16px; font-weight: bold; color: #888;">${stats.rootCount}</div>
|
|
2069
|
+
</div>
|
|
2070
|
+
<div>
|
|
2071
|
+
<div style="color: #888; font-size: 11px;">\u6700\u5927\u6DF1\u5EA6</div>
|
|
2072
|
+
<div style="font-size: 16px; font-weight: bold; color: #888;">${stats.maxDepth}</div>
|
|
2073
|
+
</div>
|
|
2074
|
+
</div>
|
|
2075
|
+
</div>
|
|
2076
|
+
|
|
2077
|
+
<div style="margin-bottom: 10px;">
|
|
2078
|
+
<input id="lytjs-devtools-vdom-search" type="text" placeholder="\u641C\u7D22\u6807\u7B7E\u540D..." style="
|
|
2079
|
+
width: 100%;
|
|
2080
|
+
padding: 8px;
|
|
2081
|
+
background: #252526;
|
|
2082
|
+
border: 1px solid #3d3d3d;
|
|
2083
|
+
border-radius: 4px;
|
|
2084
|
+
color: #d4d4d4;
|
|
2085
|
+
font-size: 12px;
|
|
2086
|
+
" />
|
|
2087
|
+
</div>
|
|
2088
|
+
|
|
2089
|
+
<div id="lytjs-devtools-vdom-tree" style="
|
|
2090
|
+
background: #252526;
|
|
2091
|
+
border-radius: 4px;
|
|
2092
|
+
padding: 15px;
|
|
2093
|
+
max-height: 400px;
|
|
2094
|
+
overflow: auto;
|
|
2095
|
+
font-family: 'Consolas', 'Monaco', monospace;
|
|
2096
|
+
font-size: 11px;
|
|
2097
|
+
line-height: 1.6;
|
|
2098
|
+
">
|
|
2099
|
+
<pre style="margin: 0; white-space: pre-wrap; word-break: break-all;">${serializeVDOMTree()}</pre>
|
|
2100
|
+
</div>
|
|
2101
|
+
</div>
|
|
2102
|
+
`;
|
|
2103
|
+
return html;
|
|
2104
|
+
}
|
|
1498
2105
|
/**
|
|
1499
2106
|
* 渲染 Performance 标签页
|
|
1500
2107
|
*/
|
|
@@ -1671,6 +2278,7 @@ exports.LARGE_SCALE_SCENARIOS = LARGE_SCALE_SCENARIOS;
|
|
|
1671
2278
|
exports.acknowledgeAlert = acknowledgeAlert;
|
|
1672
2279
|
exports.acknowledgeAllAlerts = acknowledgeAllAlerts;
|
|
1673
2280
|
exports.addObserver = addObserver;
|
|
2281
|
+
exports.beginTimelineEvent = beginTimelineEvent;
|
|
1674
2282
|
exports.clearAlerts = clearAlerts;
|
|
1675
2283
|
exports.clearBenchmarkResults = clearBenchmarkResults;
|
|
1676
2284
|
exports.clearMetrics = clearMetrics;
|
|
@@ -1679,12 +2287,17 @@ exports.clearRouteHistory = clearRouteHistory;
|
|
|
1679
2287
|
exports.clearSignalRegistry = clearSignalRegistry;
|
|
1680
2288
|
exports.clearSnapshots = clearSnapshots;
|
|
1681
2289
|
exports.clearStoreRegistry = clearStoreRegistry;
|
|
2290
|
+
exports.clearTimelineEvents = clearTimelineEvents;
|
|
1682
2291
|
exports.compareBenchmarkResults = compareBenchmarkResults;
|
|
2292
|
+
exports.compareSnapshots = compareSnapshots;
|
|
1683
2293
|
exports.createLargeScaleBenchmark = createLargeScaleBenchmark;
|
|
1684
2294
|
exports.createRegressionDetector = createRegressionDetector;
|
|
1685
2295
|
exports.createSnapshot = createSnapshot;
|
|
1686
2296
|
exports.default = installDevTools;
|
|
1687
2297
|
exports.dispatchStoreAction = dispatchStoreAction;
|
|
2298
|
+
exports.endTimelineEvent = endTimelineEvent;
|
|
2299
|
+
exports.exportTimelineAsJSON = exportTimelineAsJSON;
|
|
2300
|
+
exports.filterSignals = filterSignals;
|
|
1688
2301
|
exports.getAlertRules = getAlertRules;
|
|
1689
2302
|
exports.getAlerts = getAlerts;
|
|
1690
2303
|
exports.getBenchmarkResults = getBenchmarkResults;
|
|
@@ -1692,6 +2305,8 @@ exports.getComponentTree = getComponentTree;
|
|
|
1692
2305
|
exports.getCurrentRoute = getCurrentRoute;
|
|
1693
2306
|
exports.getDependencyGraph = getDependencyGraph;
|
|
1694
2307
|
exports.getDevTools = getDevTools;
|
|
2308
|
+
exports.getDiffBetweenSnapshots = getDiffBetweenSnapshots;
|
|
2309
|
+
exports.getFlameGraphData = getFlameGraphData;
|
|
1695
2310
|
exports.getLatestBenchmarkResult = getLatestBenchmarkResult;
|
|
1696
2311
|
exports.getMemoryUsage = getMemoryUsage;
|
|
1697
2312
|
exports.getMetrics = getMetrics;
|
|
@@ -1703,11 +2318,17 @@ exports.getRouteHistory = getRouteHistory;
|
|
|
1703
2318
|
exports.getRoutes = getRoutes;
|
|
1704
2319
|
exports.getSignalNode = getSignalNode;
|
|
1705
2320
|
exports.getSignalNodes = getSignalNodes;
|
|
2321
|
+
exports.getSlowOperations = getSlowOperations;
|
|
1706
2322
|
exports.getSnapshots = getSnapshots;
|
|
1707
2323
|
exports.getStats = getStats;
|
|
1708
2324
|
exports.getStoreState = getStoreState;
|
|
1709
2325
|
exports.getStoreStates = getStoreStates;
|
|
2326
|
+
exports.getSubgraph = getSubgraph;
|
|
2327
|
+
exports.getTimeTravelNavigator = getTimeTravelNavigator;
|
|
1710
2328
|
exports.getTimeTravelState = getTimeTravelState;
|
|
2329
|
+
exports.getTimelineEvents = getTimelineEvents;
|
|
2330
|
+
exports.getTimelineEventsInRange = getTimelineEventsInRange;
|
|
2331
|
+
exports.getVisualLayoutGraph = getVisualLayoutGraph;
|
|
1711
2332
|
exports.goBack = goBack;
|
|
1712
2333
|
exports.initPerformanceMonitor = initPerformanceMonitor;
|
|
1713
2334
|
exports.installDevTools = installDevTools;
|
|
@@ -1728,6 +2349,7 @@ exports.resetPerformanceMonitor = resetPerformanceMonitor;
|
|
|
1728
2349
|
exports.restoreSnapshot = restoreSnapshot;
|
|
1729
2350
|
exports.runAsyncBenchmark = runAsyncBenchmark;
|
|
1730
2351
|
exports.runBenchmark = runBenchmark;
|
|
2352
|
+
exports.searchSignals = searchSignals;
|
|
1731
2353
|
exports.serializeAllBenchmarkResults = serializeAllBenchmarkResults;
|
|
1732
2354
|
exports.serializeBenchmarkResult = serializeBenchmarkResult;
|
|
1733
2355
|
exports.serializeComponentTree = serializeComponentTree;
|
|
@@ -1737,11 +2359,15 @@ exports.serializePerformanceReport = serializePerformanceReport;
|
|
|
1737
2359
|
exports.serializePerformanceStats = serializePerformanceStats;
|
|
1738
2360
|
exports.serializeRouteInfo = serializeRouteInfo;
|
|
1739
2361
|
exports.serializeSignalNode = serializeSignalNode;
|
|
2362
|
+
exports.serializeSnapshotDiff = serializeSnapshotDiff;
|
|
1740
2363
|
exports.serializeStoreStates = serializeStoreStates;
|
|
2364
|
+
exports.serializeTimelineEvents = serializeTimelineEvents;
|
|
1741
2365
|
exports.setAlertRuleEnabled = setAlertRuleEnabled;
|
|
1742
2366
|
exports.setStoreState = setStoreState;
|
|
1743
2367
|
exports.startTimer = startTimer;
|
|
1744
2368
|
exports.subscribeStore = subscribeStore;
|
|
2369
|
+
exports.timeTravelBack = timeTravelBack;
|
|
2370
|
+
exports.timeTravelForward = timeTravelForward;
|
|
1745
2371
|
exports.uninstallDevTools = uninstallDevTools;
|
|
1746
2372
|
exports.unregisterAlertRule = unregisterAlertRule;
|
|
1747
2373
|
exports.unregisterRootComponent = unregisterRootComponent;
|