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