nuxt-devtools-observatory 0.1.32 → 0.1.34
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 +37 -1
- package/client/.env.example +2 -0
- package/client/dist/assets/index-BO7neKEi.css +1 -0
- package/client/dist/assets/index-fFBuk6M6.js +20 -0
- package/client/dist/index.html +2 -2
- package/client/src/App.vue +8 -0
- package/client/src/components/Flamegraph.vue +4 -4
- package/client/src/components/SpanInspector.vue +1 -1
- package/client/src/composables/composable-search.ts +3 -0
- package/client/src/composables/trace-render-aggregation.ts +11 -2
- package/client/src/composables/useVirtualizationConfig.ts +40 -0
- package/client/src/composables/useVirtualizationFlags.ts +129 -0
- package/client/src/stores/observatory.ts +20 -0
- package/client/src/views/ComposableTracker.vue +212 -71
- package/client/src/views/FetchDashboard.vue +181 -16
- package/client/src/views/PiniaStoreTracker.vue +343 -0
- package/client/src/views/ProvideInjectGraph.vue +66 -18
- package/client/src/views/RenderHeatmap.vue +329 -75
- package/client/src/views/TraceViewer.vue +190 -20
- package/client/src/views/TransitionTimeline.vue +112 -19
- package/dist/module.d.mts +15 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +28 -24
- package/dist/runtime/composables/pinia-store-registry.d.ts +44 -0
- package/dist/runtime/composables/pinia-store-registry.js +447 -0
- package/dist/runtime/composables/provide-inject-registry.js +13 -8
- package/dist/runtime/composables/render-registry.js +6 -4
- package/dist/runtime/instrumentation/asyncData.d.ts +1 -1
- package/dist/runtime/instrumentation/fetch.d.ts +7 -1
- package/dist/runtime/instrumentation/fetch.js +22 -1
- package/dist/runtime/plugin.js +39 -2
- package/dist/runtime/test-bridge.d.ts +18 -0
- package/dist/runtime/test-bridge.js +100 -0
- package/package.json +14 -3
- package/client/dist/assets/index-5Wl1XYRH.js +0 -17
- package/client/dist/assets/index-DT_QUiIh.css +0 -1
|
@@ -2,15 +2,18 @@ import { isRef, isReactive, unref, getCurrentInstance, provide, inject } from "v
|
|
|
2
2
|
export function setupProvideInjectRegistry() {
|
|
3
3
|
let dirty = true;
|
|
4
4
|
let cachedSnapshot = '{"provides":[],"injects":[]}';
|
|
5
|
+
let hasLiveProvides = false;
|
|
5
6
|
function markDirty() {
|
|
6
7
|
dirty = true;
|
|
7
8
|
}
|
|
8
9
|
const provides = /* @__PURE__ */ new Map();
|
|
9
10
|
const injects = /* @__PURE__ */ new Map();
|
|
10
11
|
function registerProvide(entry) {
|
|
11
|
-
|
|
12
|
+
const internal = entry;
|
|
13
|
+
provides.set(`${entry.key}:${entry.componentUid}`, internal);
|
|
14
|
+
hasLiveProvides = hasLiveProvides || internal.__valueSource !== void 0;
|
|
12
15
|
markDirty();
|
|
13
|
-
emit("provide:register",
|
|
16
|
+
emit("provide:register", sanitizeProvide(internal));
|
|
14
17
|
}
|
|
15
18
|
function registerInject(entry) {
|
|
16
19
|
injects.set(`${entry.key}:${entry.componentUid}`, entry);
|
|
@@ -20,6 +23,7 @@ export function setupProvideInjectRegistry() {
|
|
|
20
23
|
function clear() {
|
|
21
24
|
provides.clear();
|
|
22
25
|
injects.clear();
|
|
26
|
+
hasLiveProvides = false;
|
|
23
27
|
markDirty();
|
|
24
28
|
emit("provide:clear", {});
|
|
25
29
|
}
|
|
@@ -32,9 +36,8 @@ export function setupProvideInjectRegistry() {
|
|
|
32
36
|
parentUid: entry.parentUid,
|
|
33
37
|
parentFile: entry.parentFile,
|
|
34
38
|
isReactive: entry.isReactive,
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
valueSnapshot: entry.valueSnapshot,
|
|
39
|
+
// Reactive values are materialized from their live source on every read.
|
|
40
|
+
valueSnapshot: entry.__valueSource !== void 0 ? safeSnapshot(unref(entry.__valueSource)) : entry.valueSnapshot,
|
|
38
41
|
line: entry.line,
|
|
39
42
|
scope: entry.scope,
|
|
40
43
|
isShadowing: entry.isShadowing
|
|
@@ -61,7 +64,7 @@ export function setupProvideInjectRegistry() {
|
|
|
61
64
|
};
|
|
62
65
|
}
|
|
63
66
|
function getSnapshot() {
|
|
64
|
-
if (!dirty) {
|
|
67
|
+
if (!dirty && !hasLiveProvides) {
|
|
65
68
|
return cachedSnapshot;
|
|
66
69
|
}
|
|
67
70
|
try {
|
|
@@ -103,6 +106,7 @@ export function __devProvide(key, value, meta) {
|
|
|
103
106
|
scope = "layout";
|
|
104
107
|
}
|
|
105
108
|
const isShadowing = findProvider(keyStr, instance) !== null;
|
|
109
|
+
const reactiveValue = isRef(value) || isReactive(value);
|
|
106
110
|
registry.registerProvide({
|
|
107
111
|
key: keyStr,
|
|
108
112
|
componentName: instance?.type?.__name ?? "unknown",
|
|
@@ -110,11 +114,12 @@ export function __devProvide(key, value, meta) {
|
|
|
110
114
|
componentUid: instance?.uid ?? -1,
|
|
111
115
|
parentUid: instance?.parent?.uid,
|
|
112
116
|
parentFile: instance?.parent?.type?.__file,
|
|
113
|
-
isReactive:
|
|
117
|
+
isReactive: reactiveValue,
|
|
114
118
|
valueSnapshot: safeSnapshot(unref(value)),
|
|
115
119
|
line: meta.line,
|
|
116
120
|
scope,
|
|
117
|
-
isShadowing
|
|
121
|
+
isShadowing,
|
|
122
|
+
...reactiveValue ? { __valueSource: value } : {}
|
|
118
123
|
});
|
|
119
124
|
}
|
|
120
125
|
export function __devInject(key, defaultValue, meta) {
|
|
@@ -74,7 +74,7 @@ export function setupRenderRegistry(nuxtApp, options = {}) {
|
|
|
74
74
|
markDirty();
|
|
75
75
|
}
|
|
76
76
|
function aggregateFromComponentSpans() {
|
|
77
|
-
const componentSpans = traceStore.getAllTraces().flatMap((trace) => trace.spans).filter((span) => span.type === "component");
|
|
77
|
+
const componentSpans = traceStore.getAllTraces().flatMap((trace) => trace.spans).filter((span) => span.type === "render" || span.type === "component");
|
|
78
78
|
const allSpansByUid = /* @__PURE__ */ new Map();
|
|
79
79
|
const postResetSpansByUid = /* @__PURE__ */ new Map();
|
|
80
80
|
for (const span of componentSpans) {
|
|
@@ -96,7 +96,8 @@ export function setupRenderRegistry(nuxtApp, options = {}) {
|
|
|
96
96
|
const allSpans = (allSpansByUid.get(uid) ?? []).sort((a, b) => a.startTime - b.startTime);
|
|
97
97
|
const postResetSpans = (postResetSpansByUid.get(uid) ?? []).sort((a, b) => a.startTime - b.startTime);
|
|
98
98
|
const timeline = postResetSpans.slice(-MAX_TIMELINE).map((span) => {
|
|
99
|
-
const
|
|
99
|
+
const isMountLifecycle = span.metadata?.lifecycle === "render:mount" || span.metadata?.lifecycle === "mounted";
|
|
100
|
+
const lifecycle = isMountLifecycle ? "mount" : "update";
|
|
100
101
|
const routeValue = span.metadata?.route;
|
|
101
102
|
const route = typeof routeValue === "string" && routeValue.length > 0 ? routeValue : entry.route;
|
|
102
103
|
return {
|
|
@@ -106,8 +107,9 @@ export function setupRenderRegistry(nuxtApp, options = {}) {
|
|
|
106
107
|
route
|
|
107
108
|
};
|
|
108
109
|
});
|
|
109
|
-
const
|
|
110
|
-
const
|
|
110
|
+
const isMountSpan = (span) => span.metadata?.lifecycle === "render:mount" || span.metadata?.lifecycle === "mounted";
|
|
111
|
+
const mountCount = allSpans.filter(isMountSpan).length;
|
|
112
|
+
const rerenders = postResetSpans.filter((span) => !isMountSpan(span)).length;
|
|
111
113
|
const totalMs = postResetSpans.reduce((sum, span) => sum + (span.durationMs ?? 0), 0);
|
|
112
114
|
const eventsCount = Math.max(postResetSpans.length, 1);
|
|
113
115
|
entry.mountCount = mountCount;
|
|
@@ -4,6 +4,6 @@ interface AsyncDataMeta {
|
|
|
4
4
|
line: number;
|
|
5
5
|
originalFn?: string;
|
|
6
6
|
}
|
|
7
|
-
type AnyFn = (...args:
|
|
7
|
+
type AnyFn = (...args: unknown[]) => unknown;
|
|
8
8
|
export declare function useTracedAsyncData<TFn extends AnyFn>(originalFn: TFn, args: unknown[], handlerIndex: number, key: unknown, meta: AsyncDataMeta): ReturnType<TFn>;
|
|
9
9
|
export {};
|
|
@@ -1,2 +1,8 @@
|
|
|
1
1
|
import type { NuxtApp } from '#app';
|
|
2
|
-
|
|
2
|
+
import type { FetchEntry } from '../composables/fetch-registry.js';
|
|
3
|
+
type FetchRegistry = {
|
|
4
|
+
register: (entry: FetchEntry) => void;
|
|
5
|
+
update: (id: string, patch: Partial<FetchEntry>) => void;
|
|
6
|
+
};
|
|
7
|
+
export declare function setupFetchInstrumentation(nuxtApp: NuxtApp, fetchRegistry?: FetchRegistry): void;
|
|
8
|
+
export {};
|
|
@@ -27,7 +27,7 @@ function resolveErrorStatus(error) {
|
|
|
27
27
|
return target?.response?.status ?? target?.statusCode ?? target?.status;
|
|
28
28
|
}
|
|
29
29
|
const WRAPPED_FETCH_FLAG = "__observatory_wrapped_fetch__";
|
|
30
|
-
export function setupFetchInstrumentation(nuxtApp) {
|
|
30
|
+
export function setupFetchInstrumentation(nuxtApp, fetchRegistry) {
|
|
31
31
|
const original = nuxtApp.$fetch;
|
|
32
32
|
if (!original) {
|
|
33
33
|
return;
|
|
@@ -39,6 +39,7 @@ export function setupFetchInstrumentation(nuxtApp) {
|
|
|
39
39
|
const url = resolveUrl(request);
|
|
40
40
|
const method = resolveMethod(request, options);
|
|
41
41
|
const startedAt = performance.now();
|
|
42
|
+
const entryId = `$fetch::${Date.now()}::${Math.random().toString(36).slice(2, 7)}`;
|
|
42
43
|
const span = startSpan({
|
|
43
44
|
name: "$fetch",
|
|
44
45
|
type: "fetch",
|
|
@@ -49,6 +50,15 @@ export function setupFetchInstrumentation(nuxtApp) {
|
|
|
49
50
|
status: "pending"
|
|
50
51
|
}
|
|
51
52
|
});
|
|
53
|
+
fetchRegistry?.register({
|
|
54
|
+
id: entryId,
|
|
55
|
+
key: url,
|
|
56
|
+
url,
|
|
57
|
+
status: "pending",
|
|
58
|
+
origin: "csr",
|
|
59
|
+
startTime: startedAt,
|
|
60
|
+
cached: false
|
|
61
|
+
});
|
|
52
62
|
return Promise.resolve(original(request, options)).then((result) => {
|
|
53
63
|
const durationMs = Math.max(performance.now() - startedAt, 0);
|
|
54
64
|
span.end({
|
|
@@ -61,6 +71,11 @@ export function setupFetchInstrumentation(nuxtApp) {
|
|
|
61
71
|
durationMs: Math.round(durationMs * 10) / 10
|
|
62
72
|
}
|
|
63
73
|
});
|
|
74
|
+
fetchRegistry?.update(entryId, {
|
|
75
|
+
status: "ok",
|
|
76
|
+
endTime: performance.now(),
|
|
77
|
+
ms: Math.round(durationMs * 10) / 10
|
|
78
|
+
});
|
|
64
79
|
return result;
|
|
65
80
|
}).catch((error) => {
|
|
66
81
|
const durationMs = Math.max(performance.now() - startedAt, 0);
|
|
@@ -76,6 +91,12 @@ export function setupFetchInstrumentation(nuxtApp) {
|
|
|
76
91
|
durationMs: Math.round(durationMs * 10) / 10
|
|
77
92
|
}
|
|
78
93
|
});
|
|
94
|
+
fetchRegistry?.update(entryId, {
|
|
95
|
+
status: "error",
|
|
96
|
+
endTime: performance.now(),
|
|
97
|
+
ms: Math.round(durationMs * 10) / 10,
|
|
98
|
+
error
|
|
99
|
+
});
|
|
79
100
|
throw error;
|
|
80
101
|
});
|
|
81
102
|
});
|
package/dist/runtime/plugin.js
CHANGED
|
@@ -3,11 +3,13 @@ import { nextTick } from "vue";
|
|
|
3
3
|
import { setupComposableRegistry } from "./composables/composable-registry.js";
|
|
4
4
|
import { setupFetchRegistry } from "./composables/fetch-registry.js";
|
|
5
5
|
import { setupProvideInjectRegistry } from "./composables/provide-inject-registry.js";
|
|
6
|
+
import { setupPiniaStoreRegistry } from "./composables/pinia-store-registry.js";
|
|
6
7
|
import { setupRenderRegistry } from "./composables/render-registry.js";
|
|
7
8
|
import { setupTransitionRegistry } from "./composables/transition-registry.js";
|
|
8
9
|
import { setupComponentInstrumentation } from "./instrumentation/component.js";
|
|
9
10
|
import { setupFetchInstrumentation } from "./instrumentation/fetch.js";
|
|
10
11
|
import { setupRouteInstrumentation } from "./instrumentation/route.js";
|
|
12
|
+
import { injectTestBridge } from "./test-bridge.js";
|
|
11
13
|
import { traceStore } from "./tracing/traceStore.js";
|
|
12
14
|
export default defineNuxtPlugin(() => {
|
|
13
15
|
if (!import.meta.dev) {
|
|
@@ -37,6 +39,13 @@ export default defineNuxtPlugin(() => {
|
|
|
37
39
|
if (config.composableTracker) {
|
|
38
40
|
registries.composable = setupComposableRegistry();
|
|
39
41
|
}
|
|
42
|
+
if (config.piniaTracker) {
|
|
43
|
+
registries.pinia = setupPiniaStoreRegistry({
|
|
44
|
+
pinia: nuxtApp.$pinia,
|
|
45
|
+
nuxtPayload: nuxtApp.payload,
|
|
46
|
+
maxTimeline: config.maxPiniaTimeline
|
|
47
|
+
});
|
|
48
|
+
}
|
|
40
49
|
if (config.renderHeatmap) {
|
|
41
50
|
registries.render = setupRenderRegistry(nuxtApp, {
|
|
42
51
|
isHydrating: () => (nuxtApp.isHydrating ?? false) && nuxtApp.payload?.serverRendered === true
|
|
@@ -91,17 +100,26 @@ export default defineNuxtPlugin(() => {
|
|
|
91
100
|
if (import.meta.client) {
|
|
92
101
|
if (config.traceViewer) {
|
|
93
102
|
setupComponentInstrumentation(nuxtApp);
|
|
94
|
-
setupFetchInstrumentation(nuxtApp);
|
|
103
|
+
setupFetchInstrumentation(nuxtApp, registries.fetch);
|
|
95
104
|
mergeSsrSpans();
|
|
105
|
+
} else if (config.fetchDashboard) {
|
|
106
|
+
setupFetchInstrumentation(nuxtApp, registries.fetch);
|
|
96
107
|
}
|
|
97
108
|
delete window.__observatory__;
|
|
98
109
|
window.__observatory__ = registries;
|
|
110
|
+
injectTestBridge();
|
|
99
111
|
const composableRegistry = registries.composable;
|
|
112
|
+
const piniaRegistry = registries.pinia;
|
|
100
113
|
if (composableRegistry && composableRegistry.onComposableChange) {
|
|
101
114
|
composableRegistry.onComposableChange(() => {
|
|
102
115
|
broadcastAll("composable:onChange");
|
|
103
116
|
});
|
|
104
117
|
}
|
|
118
|
+
if (piniaRegistry?.onChange) {
|
|
119
|
+
piniaRegistry.onChange(() => {
|
|
120
|
+
broadcastAll("pinia:onChange");
|
|
121
|
+
});
|
|
122
|
+
}
|
|
105
123
|
import.meta.hot?.on("observatory:command", (rawPayload) => {
|
|
106
124
|
if (!rawPayload || typeof rawPayload !== "object") {
|
|
107
125
|
return;
|
|
@@ -135,10 +153,24 @@ export default defineNuxtPlugin(() => {
|
|
|
135
153
|
if (payload.cmd === "edit-composable") {
|
|
136
154
|
debugLog("received command: edit-composable", { id: payload.id, key: payload.key });
|
|
137
155
|
composableRegistry?.editValue(payload.id, payload.key, payload.value);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (payload.cmd === "clear-pinia") {
|
|
159
|
+
debugLog("received command: clear-pinia");
|
|
160
|
+
piniaRegistry?.clear();
|
|
161
|
+
broadcastAll("command:clear-pinia");
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (payload.cmd === "edit-pinia") {
|
|
165
|
+
debugLog("received command: edit-pinia", { storeId: payload.storeId, path: payload.path });
|
|
166
|
+
piniaRegistry?.editState(payload.storeId, payload.path, payload.value);
|
|
167
|
+
broadcastAll("command:edit-pinia");
|
|
138
168
|
}
|
|
139
169
|
});
|
|
140
170
|
nuxtApp.hook("app:beforeUnmount", () => {
|
|
141
171
|
import.meta.hot?.off("observatory:command");
|
|
172
|
+
const pinia = registries.pinia;
|
|
173
|
+
pinia?.teardown?.();
|
|
142
174
|
if (heartbeatId !== null) {
|
|
143
175
|
window.clearInterval(heartbeatId);
|
|
144
176
|
heartbeatId = null;
|
|
@@ -231,6 +263,7 @@ export default defineNuxtPlugin(() => {
|
|
|
231
263
|
reason,
|
|
232
264
|
fetch: Array.isArray(snapshot.fetch) ? snapshot.fetch.length : 0,
|
|
233
265
|
composables: Array.isArray(snapshot.composables) ? snapshot.composables.length : 0,
|
|
266
|
+
piniaStores: Array.isArray(snapshot.piniaStores) ? snapshot.piniaStores.length : 0,
|
|
234
267
|
renders: Array.isArray(snapshot.renders) ? snapshot.renders.length : 0,
|
|
235
268
|
transitions: Array.isArray(snapshot.transitions) ? snapshot.transitions.length : 0,
|
|
236
269
|
traces: Array.isArray(snapshot.traces) ? snapshot.traces.length : 0
|
|
@@ -256,6 +289,7 @@ export default defineNuxtPlugin(() => {
|
|
|
256
289
|
{ key: "fetch", fallback: [] },
|
|
257
290
|
{ key: "provideInject", fallback: { provides: [], injects: [] } },
|
|
258
291
|
{ key: "composable", fallback: [] },
|
|
292
|
+
{ key: "pinia", fallback: [] },
|
|
259
293
|
{ key: "render", fallback: {} },
|
|
260
294
|
{ key: "transition", fallback: {} }
|
|
261
295
|
];
|
|
@@ -263,7 +297,8 @@ export default defineNuxtPlugin(() => {
|
|
|
263
297
|
for (const { key, fallback } of trackerDefs) {
|
|
264
298
|
const reg = registries[key];
|
|
265
299
|
const hasGetSnapshot = reg && typeof reg.getSnapshot === "function";
|
|
266
|
-
|
|
300
|
+
const snapshotKey = key === "composable" ? "composables" : key === "pinia" ? "piniaStores" : key === "render" ? "renders" : key === "transition" ? "transitions" : key;
|
|
301
|
+
snapshot[snapshotKey] = hasGetSnapshot ? safeParse(reg.getSnapshot(), fallback) : fallback;
|
|
267
302
|
}
|
|
268
303
|
snapshot.traces = config.traceViewer ? traceStore.getAllTraces().map((trace) => ({
|
|
269
304
|
id: trace.id,
|
|
@@ -290,7 +325,9 @@ export default defineNuxtPlugin(() => {
|
|
|
290
325
|
fetchDashboard: !!registries.fetch,
|
|
291
326
|
provideInjectGraph: !!registries.provideInject,
|
|
292
327
|
composableTracker: !!registries.composable,
|
|
328
|
+
piniaTracker: !!registries.pinia,
|
|
293
329
|
composableNavigationMode,
|
|
330
|
+
fetchPageSize: typeof config.fetchPageSize === "number" ? config.fetchPageSize : 20,
|
|
294
331
|
renderHeatmap: !!registries.render,
|
|
295
332
|
transitionTracker: !!registries.transition,
|
|
296
333
|
traceViewer: !!config.traceViewer
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ObservatoryTestAPI } from '../../tests/verification/types/observatory.types.js';
|
|
2
|
+
interface VueApp {
|
|
3
|
+
_context?: {
|
|
4
|
+
app?: {
|
|
5
|
+
_component?: unknown;
|
|
6
|
+
};
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
declare global {
|
|
10
|
+
interface Window {
|
|
11
|
+
__NUXT__?: {
|
|
12
|
+
vueApp?: VueApp;
|
|
13
|
+
};
|
|
14
|
+
__OBSERVATORY_TEST_BRIDGE?: ObservatoryTestAPI;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export declare function injectTestBridge(): void;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
function walkComponentTree(instance, counts) {
|
|
2
|
+
if (!instance) {
|
|
3
|
+
return;
|
|
4
|
+
}
|
|
5
|
+
const componentName = instance.type?.name ?? instance.type?.__name ?? "Anonymous";
|
|
6
|
+
if (instance.ctx?.__observatoryMountCount) {
|
|
7
|
+
const currentCount = counts.componentMounts[componentName] ?? 0;
|
|
8
|
+
counts.componentMounts[componentName] = currentCount + instance.ctx.__observatoryMountCount;
|
|
9
|
+
}
|
|
10
|
+
if (instance.subTree?.children) {
|
|
11
|
+
instance.subTree.children.forEach((child) => walkComponentTree(child, counts));
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function injectTestBridge() {
|
|
15
|
+
if (!import.meta.dev || typeof window === "undefined") {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const bridge = {
|
|
19
|
+
async getTraces() {
|
|
20
|
+
const { traceStore } = await import("./tracing/traceStore.js");
|
|
21
|
+
return Array.from(traceStore.entries());
|
|
22
|
+
},
|
|
23
|
+
async getHeatmapData() {
|
|
24
|
+
const { renderRegistry } = await import("./composables/render-registry.js");
|
|
25
|
+
return renderRegistry.getData();
|
|
26
|
+
},
|
|
27
|
+
async getComposableEntries() {
|
|
28
|
+
const { composableRegistry } = await import("./composables/composable-registry.js");
|
|
29
|
+
return composableRegistry.getEntries();
|
|
30
|
+
},
|
|
31
|
+
async getFetchEntries() {
|
|
32
|
+
const { fetchRegistry } = await import("./composables/fetch-registry.js");
|
|
33
|
+
return fetchRegistry.getEntries();
|
|
34
|
+
},
|
|
35
|
+
async getProvideInjectGraph() {
|
|
36
|
+
const { provideInjectRegistry } = await import("./composables/provide-inject-registry.js");
|
|
37
|
+
return provideInjectRegistry.getGraph();
|
|
38
|
+
},
|
|
39
|
+
async getTransitionEntries() {
|
|
40
|
+
const { transitionRegistry } = await import("./composables/transition-registry.js");
|
|
41
|
+
return transitionRegistry.getEntries();
|
|
42
|
+
},
|
|
43
|
+
async getPiniaStores() {
|
|
44
|
+
const observatory = window.__observatory__;
|
|
45
|
+
const registry = observatory?.pinia;
|
|
46
|
+
if (!registry?.getAll) {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
return registry.getAll();
|
|
50
|
+
},
|
|
51
|
+
async getInternalCounts() {
|
|
52
|
+
const counts = {
|
|
53
|
+
componentMounts: {},
|
|
54
|
+
renderOperations: {},
|
|
55
|
+
fetchOperations: {}
|
|
56
|
+
};
|
|
57
|
+
const vueApp = window.__NUXT__?.vueApp;
|
|
58
|
+
if (!vueApp) {
|
|
59
|
+
return counts;
|
|
60
|
+
}
|
|
61
|
+
walkComponentTree(vueApp._context?.app?._component, counts);
|
|
62
|
+
return counts;
|
|
63
|
+
},
|
|
64
|
+
async clearAllData() {
|
|
65
|
+
const { traceStore } = await import("./tracing/traceStore.js");
|
|
66
|
+
const { renderRegistry } = await import("./composables/render-registry.js");
|
|
67
|
+
const { composableRegistry } = await import("./composables/composable-registry.js");
|
|
68
|
+
const { fetchRegistry } = await import("./composables/fetch-registry.js");
|
|
69
|
+
const observatory = window.__observatory__;
|
|
70
|
+
const piniaRegistry = observatory?.pinia;
|
|
71
|
+
traceStore.clear();
|
|
72
|
+
renderRegistry?.clear?.();
|
|
73
|
+
composableRegistry?.clear?.();
|
|
74
|
+
fetchRegistry?.clear?.();
|
|
75
|
+
if (piniaRegistry?.clear) {
|
|
76
|
+
piniaRegistry.clear();
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
async startRecording() {
|
|
80
|
+
const { traceStore } = await import("./tracing/traceStore.js");
|
|
81
|
+
traceStore.startRecording();
|
|
82
|
+
},
|
|
83
|
+
async stopRecording() {
|
|
84
|
+
const { traceStore } = await import("./tracing/traceStore.js");
|
|
85
|
+
traceStore.stopRecording();
|
|
86
|
+
},
|
|
87
|
+
async exportSnapshot() {
|
|
88
|
+
const snapshot = {
|
|
89
|
+
traces: await this.getTraces(),
|
|
90
|
+
heatmap: await this.getHeatmapData(),
|
|
91
|
+
composables: await this.getComposableEntries(),
|
|
92
|
+
fetches: await this.getFetchEntries(),
|
|
93
|
+
transitions: await this.getTransitionEntries(),
|
|
94
|
+
piniaStores: await this.getPiniaStores()
|
|
95
|
+
};
|
|
96
|
+
return JSON.stringify(snapshot, null, 2);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
window.__OBSERVATORY_TEST_BRIDGE = bridge;
|
|
100
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-devtools-observatory",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.34",
|
|
4
4
|
"description": "Nuxt DevTools: useFetch Dashboard, provide/inject Graph, Composable Tracker, Render Heatmap",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -49,14 +49,21 @@
|
|
|
49
49
|
"build": "npm run build:client && nuxt-module-build build",
|
|
50
50
|
"prepack": "npm run build",
|
|
51
51
|
"lint": "eslint .",
|
|
52
|
-
"format": "prettier --write '**/*.{ts,vue,js,json}' --ignore-path .prettierignore && stylelint '**/*.{css,vue}' --ignore-pattern '**/.nuxt/**' --ignore-pattern '**/.output/**' --ignore-pattern 'coverage/**' --ignore-pattern 'client/dist/**' --ignore-pattern 'scripts/**' --fix && eslint --fix '**/*.{ts,vue,js}' --ignore-pattern '**/.nuxt/**' --ignore-pattern '**/.output/**' --ignore-pattern 'coverage/**' --ignore-pattern 'client/dist/**' --ignore-pattern 'scripts/**' --ignore-pattern 'docs/dist/**'",
|
|
52
|
+
"format": "prettier --write '**/*.{ts,vue,js,json}' --ignore-path .prettierignore && stylelint '**/*.{css,vue}' --ignore-pattern '**/.nuxt/**' --ignore-pattern '**/.output/**' --ignore-pattern 'coverage/**' --ignore-pattern 'client/dist/**' --ignore-pattern 'scripts/**' --ignore-pattern 'docs/dist/**' --fix && eslint --fix '**/*.{ts,vue,js}' --ignore-pattern '**/.nuxt/**' --ignore-pattern '**/.output/**' --ignore-pattern 'coverage/**' --ignore-pattern 'client/dist/**' --ignore-pattern 'scripts/**' --ignore-pattern 'docs/dist/**'",
|
|
53
53
|
"typecheck": "vue-tsc --noEmit",
|
|
54
54
|
"test": "vitest run",
|
|
55
55
|
"test:watch": "vitest",
|
|
56
56
|
"test:coverage": "vitest run --coverage",
|
|
57
57
|
"test:screenshots": "playwright test scripts/playwright/screenshot-trackers.spec.ts",
|
|
58
58
|
"test:e2e": "playwright test scripts/playwright/playground-pages.spec.ts",
|
|
59
|
-
"capture:screenshots": "node scripts/playwright/capture-observatory-screenshots.cjs"
|
|
59
|
+
"capture:screenshots": "node scripts/playwright/capture-observatory-screenshots.cjs",
|
|
60
|
+
"verify": "tsx scripts/run-verification.ts",
|
|
61
|
+
"verify:debug": "playwright test --debug",
|
|
62
|
+
"verify:ui": "playwright test --ui",
|
|
63
|
+
"verify:trace": "playwright test --trace on",
|
|
64
|
+
"verify:report": "playwright show-report",
|
|
65
|
+
"test:accuracy": "vitest run tests/verification/accuracy.test.ts",
|
|
66
|
+
"type-check": "tsc --noEmit --project tests/verification/tsconfig.json"
|
|
60
67
|
},
|
|
61
68
|
"dependencies": {
|
|
62
69
|
"@babel/generator": "^7.29.1",
|
|
@@ -73,11 +80,13 @@
|
|
|
73
80
|
"@nuxt/schema": "^3.0.0",
|
|
74
81
|
"@pinia/nuxt": "^0.11.3",
|
|
75
82
|
"@playwright/test": "^1.58.2",
|
|
83
|
+
"@tanstack/vue-virtual": "^3.13.12",
|
|
76
84
|
"@types/babel__generator": "^7.27.0",
|
|
77
85
|
"@types/babel__traverse": "^7.28.0",
|
|
78
86
|
"@types/node": "^25.5.0",
|
|
79
87
|
"@vitejs/plugin-vue": "^6.0.0",
|
|
80
88
|
"@vitest/coverage-v8": "^4.1.0",
|
|
89
|
+
"@vitest/ui": "^4.1.4",
|
|
81
90
|
"concurrently": "^9.2.1",
|
|
82
91
|
"eslint": "^9.39.2",
|
|
83
92
|
"eslint-config-prettier": "^10.1.8",
|
|
@@ -92,10 +101,12 @@
|
|
|
92
101
|
"playwright": "^1.58.2",
|
|
93
102
|
"postcss-html": "^1.8.1",
|
|
94
103
|
"prettier": "^3.8.1",
|
|
104
|
+
"sinon": "^21.1.2",
|
|
95
105
|
"sirv": "^3.0.2",
|
|
96
106
|
"stylelint": "^17.4.0",
|
|
97
107
|
"stylelint-config-standard": "^40.0.0",
|
|
98
108
|
"stylelint-config-standard-vue": "^1.0.0",
|
|
109
|
+
"tsx": "^4.21.0",
|
|
99
110
|
"typescript": "^5.9.3",
|
|
100
111
|
"typescript-eslint": "^8.54.0",
|
|
101
112
|
"unbuild": "^3.6.1",
|