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