nuxt-devtools-observatory 0.1.24 → 0.1.25
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/client/dist/assets/index-1-H6UMCK.css +1 -0
- package/client/dist/assets/index-eSUuhYQ0.js +17 -0
- package/client/dist/index.html +2 -2
- package/client/src/stores/observatory.ts +5 -0
- package/client/src/views/ComposableTracker.vue +80 -9
- package/dist/module.d.mts +7 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +4 -1
- package/dist/runtime/composables/composable-registry.d.ts +5 -0
- package/dist/runtime/composables/composable-registry.js +46 -2
- package/dist/runtime/plugin.js +13 -2
- package/package.json +1 -1
- package/client/dist/assets/index-B5GtSnq0.js +0 -17
- package/client/dist/assets/index-Ljohi1or.css +0 -1
|
@@ -2,7 +2,20 @@
|
|
|
2
2
|
import { ref, computed } from 'vue'
|
|
3
3
|
import { useObservatoryData, getObservatoryOrigin, type ComposableEntry as RuntimeComposableEntry } from '../stores/observatory'
|
|
4
4
|
|
|
5
|
-
const { composables: rawEntries, connected, clearComposables } = useObservatoryData()
|
|
5
|
+
const { composables: rawEntries, connected, features, clearComposables } = useObservatoryData()
|
|
6
|
+
|
|
7
|
+
const composableMode = computed<'route' | 'session'>(() => (features.value?.composableNavigationMode === 'session' ? 'session' : 'route'))
|
|
8
|
+
|
|
9
|
+
function toggleComposableMode() {
|
|
10
|
+
const origin = getObservatoryOrigin()
|
|
11
|
+
|
|
12
|
+
if (!origin) {
|
|
13
|
+
return
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const nextMode = composableMode.value === 'route' ? 'session' : 'route'
|
|
17
|
+
window.top?.postMessage({ type: 'observatory:set-composable-mode', mode: nextMode }, origin)
|
|
18
|
+
}
|
|
6
19
|
|
|
7
20
|
function clearSession() {
|
|
8
21
|
const origin = getObservatoryOrigin()
|
|
@@ -17,17 +30,28 @@ function clearSession() {
|
|
|
17
30
|
// same composable in different components are two separate rows.
|
|
18
31
|
|
|
19
32
|
function formatVal(v: unknown): string {
|
|
20
|
-
if (v === null)
|
|
21
|
-
|
|
22
|
-
|
|
33
|
+
if (v === null) {
|
|
34
|
+
return 'null'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (v === undefined) {
|
|
38
|
+
return 'undefined'
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (typeof v === 'string') {
|
|
42
|
+
return `"${v}"`
|
|
43
|
+
}
|
|
44
|
+
|
|
23
45
|
if (typeof v === 'object') {
|
|
24
46
|
try {
|
|
25
47
|
const s = JSON.stringify(v)
|
|
48
|
+
|
|
26
49
|
return s.length > 80 ? s.slice(0, 80) + '…' : s
|
|
27
50
|
} catch {
|
|
28
51
|
return String(v)
|
|
29
52
|
}
|
|
30
53
|
}
|
|
54
|
+
|
|
31
55
|
return String(v)
|
|
32
56
|
}
|
|
33
57
|
|
|
@@ -90,7 +114,11 @@ const counts = computed(() => ({
|
|
|
90
114
|
}))
|
|
91
115
|
|
|
92
116
|
const filtered = computed(() => {
|
|
93
|
-
|
|
117
|
+
// Newest entries are appended by the runtime registry, so reverse for recency-first UI.
|
|
118
|
+
const reversed = [...entries.value].reverse()
|
|
119
|
+
|
|
120
|
+
// Apply all filters first
|
|
121
|
+
const filtered = reversed.filter((entry) => {
|
|
94
122
|
if (filter.value === 'leak' && !entry.leak) {
|
|
95
123
|
return false
|
|
96
124
|
}
|
|
@@ -124,6 +152,21 @@ const filtered = computed(() => {
|
|
|
124
152
|
|
|
125
153
|
return true
|
|
126
154
|
})
|
|
155
|
+
|
|
156
|
+
// Partition into layout-level (pinned to top) and regular entries
|
|
157
|
+
const layoutEntries: RuntimeComposableEntry[] = []
|
|
158
|
+
const regularEntries: RuntimeComposableEntry[] = []
|
|
159
|
+
|
|
160
|
+
for (const entry of filtered) {
|
|
161
|
+
if (entry.isLayoutComposable) {
|
|
162
|
+
layoutEntries.push(entry)
|
|
163
|
+
} else {
|
|
164
|
+
regularEntries.push(entry)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Combine: layout entries first (already sorted by recency), then regular entries
|
|
169
|
+
return [...layoutEntries, ...regularEntries]
|
|
127
170
|
})
|
|
128
171
|
|
|
129
172
|
function lifecycleRows(entry: RuntimeComposableEntry) {
|
|
@@ -175,7 +218,10 @@ function refExpandKey(entryId: string, refKey: string) {
|
|
|
175
218
|
}
|
|
176
219
|
|
|
177
220
|
function isLongValue(v: unknown): boolean {
|
|
178
|
-
if (v === null || v === undefined || typeof v !== 'object')
|
|
221
|
+
if (v === null || v === undefined || typeof v !== 'object') {
|
|
222
|
+
return false
|
|
223
|
+
}
|
|
224
|
+
|
|
179
225
|
try {
|
|
180
226
|
return JSON.stringify(v).length > 60
|
|
181
227
|
} catch {
|
|
@@ -190,8 +236,13 @@ function isRefExpanded(entryId: string, refKey: string): boolean {
|
|
|
190
236
|
function toggleRefExpand(entryId: string, refKey: string) {
|
|
191
237
|
const key = refExpandKey(entryId, refKey)
|
|
192
238
|
const next = new Set(expandedRefs.value)
|
|
193
|
-
|
|
194
|
-
|
|
239
|
+
|
|
240
|
+
if (next.has(key)) {
|
|
241
|
+
next.delete(key)
|
|
242
|
+
} else {
|
|
243
|
+
next.add(key)
|
|
244
|
+
}
|
|
245
|
+
|
|
195
246
|
expandedRefs.value = next
|
|
196
247
|
}
|
|
197
248
|
|
|
@@ -247,6 +298,7 @@ function applyEdit() {
|
|
|
247
298
|
editError.value = ''
|
|
248
299
|
} catch (err) {
|
|
249
300
|
editError.value = `Invalid JSON: ${(err as Error).message}`
|
|
301
|
+
|
|
250
302
|
return
|
|
251
303
|
}
|
|
252
304
|
|
|
@@ -296,8 +348,17 @@ function applyEdit() {
|
|
|
296
348
|
<button :class="{ active: filter === 'mounted' }" @click="filter = 'mounted'">mounted</button>
|
|
297
349
|
<button :class="{ 'danger-active': filter === 'leak' }" @click="filter = 'leak'">leaks only</button>
|
|
298
350
|
<button :class="{ active: filter === 'unmounted' }" @click="filter = 'unmounted'">unmounted</button>
|
|
351
|
+
<button
|
|
352
|
+
class="mode-btn"
|
|
353
|
+
:title="`switch to ${composableMode === 'route' ? 'session' : 'route'} mode`"
|
|
354
|
+
@click="toggleComposableMode"
|
|
355
|
+
>
|
|
356
|
+
mode: {{ composableMode }}
|
|
357
|
+
</button>
|
|
299
358
|
<input v-model="search" type="search" placeholder="search name, file, or ref…" style="max-width: 220px; margin-left: auto" />
|
|
300
|
-
<button class="clear-btn" title="Clear session history" @click="clearSession">
|
|
359
|
+
<button v-if="composableMode === 'session'" class="clear-btn" title="Clear session history" @click="clearSession">
|
|
360
|
+
clear session
|
|
361
|
+
</button>
|
|
301
362
|
</div>
|
|
302
363
|
|
|
303
364
|
<div class="list">
|
|
@@ -543,6 +604,16 @@ function applyEdit() {
|
|
|
543
604
|
background: transparent;
|
|
544
605
|
}
|
|
545
606
|
|
|
607
|
+
.mode-btn {
|
|
608
|
+
border-color: color-mix(in srgb, var(--blue) 40%, var(--border));
|
|
609
|
+
color: var(--blue);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
.mode-btn:hover {
|
|
613
|
+
border-color: var(--blue);
|
|
614
|
+
background: color-mix(in srgb, var(--blue) 12%, transparent);
|
|
615
|
+
}
|
|
616
|
+
|
|
546
617
|
.list {
|
|
547
618
|
flex: 1;
|
|
548
619
|
overflow: auto;
|
package/dist/module.d.mts
CHANGED
|
@@ -39,6 +39,13 @@ interface ModuleOptions {
|
|
|
39
39
|
* @default 100
|
|
40
40
|
*/
|
|
41
41
|
maxRenderTimeline?: number;
|
|
42
|
+
/**
|
|
43
|
+
* Composable tracker navigation mode.
|
|
44
|
+
* - `route`: clear composable entries on every page navigation
|
|
45
|
+
* - `session`: keep entries across navigations until manually cleared
|
|
46
|
+
* @default 'route'
|
|
47
|
+
*/
|
|
48
|
+
composableNavigationMode?: 'route' | 'session';
|
|
42
49
|
/**
|
|
43
50
|
* Enable the useFetch / useAsyncData dashboard tab
|
|
44
51
|
* @default true
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -558,6 +558,7 @@ const defaults = {
|
|
|
558
558
|
maxComposableHistory: process.env.OBSERVATORY_MAX_COMPOSABLE_HISTORY ? Number(process.env.OBSERVATORY_MAX_COMPOSABLE_HISTORY) : 50,
|
|
559
559
|
maxComposableEntries: process.env.OBSERVATORY_MAX_COMPOSABLE_ENTRIES ? Number(process.env.OBSERVATORY_MAX_COMPOSABLE_ENTRIES) : 300,
|
|
560
560
|
maxRenderTimeline: process.env.OBSERVATORY_MAX_RENDER_TIMELINE ? Number(process.env.OBSERVATORY_MAX_RENDER_TIMELINE) : 100,
|
|
561
|
+
composableNavigationMode: process.env.OBSERVATORY_COMPOSABLE_NAVIGATION_MODE === "session" ? "session" : "route",
|
|
561
562
|
heatmapHideInternals: process.env.OBSERVATORY_HEATMAP_HIDE_INTERNALS === "true"
|
|
562
563
|
};
|
|
563
564
|
const module$1 = defineNuxtModule({
|
|
@@ -593,7 +594,8 @@ const module$1 = defineNuxtModule({
|
|
|
593
594
|
maxTransitions: options.maxTransitions ?? (process.env.OBSERVATORY_MAX_TRANSITIONS ? Number(process.env.OBSERVATORY_MAX_TRANSITIONS) : 500),
|
|
594
595
|
maxComposableHistory: options.maxComposableHistory ?? (process.env.OBSERVATORY_MAX_COMPOSABLE_HISTORY ? Number(process.env.OBSERVATORY_MAX_COMPOSABLE_HISTORY) : 50),
|
|
595
596
|
maxComposableEntries: options.maxComposableEntries ?? (process.env.OBSERVATORY_MAX_COMPOSABLE_ENTRIES ? Number(process.env.OBSERVATORY_MAX_COMPOSABLE_ENTRIES) : 300),
|
|
596
|
-
maxRenderTimeline: options.maxRenderTimeline ?? (process.env.OBSERVATORY_MAX_RENDER_TIMELINE ? Number(process.env.OBSERVATORY_MAX_RENDER_TIMELINE) : 100)
|
|
597
|
+
maxRenderTimeline: options.maxRenderTimeline ?? (process.env.OBSERVATORY_MAX_RENDER_TIMELINE ? Number(process.env.OBSERVATORY_MAX_RENDER_TIMELINE) : 100),
|
|
598
|
+
composableNavigationMode: options.composableNavigationMode ?? (process.env.OBSERVATORY_COMPOSABLE_NAVIGATION_MODE === "session" ? "session" : "route")
|
|
597
599
|
};
|
|
598
600
|
nuxt.hook("vite:extendConfig", (config) => {
|
|
599
601
|
const alias = config.resolve?.alias;
|
|
@@ -683,6 +685,7 @@ ${configScript}`);
|
|
|
683
685
|
maxComposableHistory: resolved.maxComposableHistory,
|
|
684
686
|
maxComposableEntries: resolved.maxComposableEntries,
|
|
685
687
|
maxRenderTimeline: resolved.maxRenderTimeline,
|
|
688
|
+
composableNavigationMode: resolved.composableNavigationMode,
|
|
686
689
|
heatmapHideInternals: resolved.heatmapHideInternals,
|
|
687
690
|
heatmapThresholdCount: resolved.heatmapThresholdCount,
|
|
688
691
|
heatmapThresholdTime: resolved.heatmapThresholdTime
|
|
@@ -34,6 +34,10 @@ export interface ComposableEntry {
|
|
|
34
34
|
line: number;
|
|
35
35
|
/** Route path the composable was registered on, e.g. "/products". */
|
|
36
36
|
route: string;
|
|
37
|
+
/** File path of the component that called this composable. */
|
|
38
|
+
callerComponentFile?: string;
|
|
39
|
+
/** Whether this composable is called from a layout component (persists across pages). */
|
|
40
|
+
isLayoutComposable?: boolean;
|
|
37
41
|
}
|
|
38
42
|
/**
|
|
39
43
|
* Registers a new composable entry, updates an existing one, or retrieves all entries.
|
|
@@ -62,6 +66,7 @@ export declare function setupComposableRegistry(): {
|
|
|
62
66
|
registerRawRefs: (id: string, refs: Record<string, unknown>) => void;
|
|
63
67
|
onComposableChange: (cb: () => void) => void;
|
|
64
68
|
clear: () => void;
|
|
69
|
+
clearNonLayout: () => void;
|
|
65
70
|
setRoute: (path: string) => void;
|
|
66
71
|
getRoute: () => string;
|
|
67
72
|
update: (id: string, patch: Partial<ComposableEntry>) => void;
|
|
@@ -213,7 +213,9 @@ export function setupComposableRegistry() {
|
|
|
213
213
|
lifecycle: entry.lifecycle,
|
|
214
214
|
file: entry.file,
|
|
215
215
|
line: entry.line,
|
|
216
|
-
route: entry.route
|
|
216
|
+
route: entry.route,
|
|
217
|
+
callerComponentFile: entry.callerComponentFile,
|
|
218
|
+
isLayoutComposable: entry.isLayoutComposable
|
|
217
219
|
};
|
|
218
220
|
}
|
|
219
221
|
function getAll() {
|
|
@@ -254,6 +256,30 @@ export function setupComposableRegistry() {
|
|
|
254
256
|
markDirty();
|
|
255
257
|
emit("composable:clear", {});
|
|
256
258
|
}
|
|
259
|
+
function clearNonLayout() {
|
|
260
|
+
if (_pendingFrame !== null) {
|
|
261
|
+
cancelAnimationFrame(_pendingFrame);
|
|
262
|
+
_pendingFrame = null;
|
|
263
|
+
}
|
|
264
|
+
const layoutIds = [...entries.entries()].filter(([, entry]) => entry.isLayoutComposable).map(([id]) => id);
|
|
265
|
+
for (const [id] of entries.entries()) {
|
|
266
|
+
if (!layoutIds.includes(id)) {
|
|
267
|
+
const stop = liveRefWatchers.get(id);
|
|
268
|
+
if (stop) {
|
|
269
|
+
stop();
|
|
270
|
+
liveRefWatchers.delete(id);
|
|
271
|
+
}
|
|
272
|
+
liveRefs.delete(id);
|
|
273
|
+
rawRefs.delete(id);
|
|
274
|
+
prevValues.delete(id);
|
|
275
|
+
entryHistory.delete(id);
|
|
276
|
+
entries.delete(id);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
sharedKeysCache.clear();
|
|
280
|
+
markDirty();
|
|
281
|
+
emit("composable:clear", {});
|
|
282
|
+
}
|
|
257
283
|
function editValue(id, key, value) {
|
|
258
284
|
const live = liveRefs.get(id);
|
|
259
285
|
if (!live) {
|
|
@@ -278,6 +304,7 @@ export function setupComposableRegistry() {
|
|
|
278
304
|
registerRawRefs,
|
|
279
305
|
onComposableChange,
|
|
280
306
|
clear,
|
|
307
|
+
clearNonLayout,
|
|
281
308
|
setRoute,
|
|
282
309
|
getRoute,
|
|
283
310
|
update,
|
|
@@ -350,6 +377,21 @@ export function __trackComposable(name, callFn, meta) {
|
|
|
350
377
|
}
|
|
351
378
|
}
|
|
352
379
|
}
|
|
380
|
+
const inst = instance;
|
|
381
|
+
let callerComponentFile;
|
|
382
|
+
if (inst?.type?.__file) {
|
|
383
|
+
callerComponentFile = inst.type.__file;
|
|
384
|
+
} else if (inst?.__vueParentComponent?.type?.__file) {
|
|
385
|
+
callerComponentFile = inst.__vueParentComponent.type.__file;
|
|
386
|
+
} else if (inst?.parent?.type?.__file) {
|
|
387
|
+
callerComponentFile = inst.parent.type.__file;
|
|
388
|
+
} else if (inst?.__vueParentComponent?.fileName) {
|
|
389
|
+
callerComponentFile = inst.__vueParentComponent.fileName;
|
|
390
|
+
} else if (inst?.type?.name && inst?.type?.name.includes("default")) {
|
|
391
|
+
callerComponentFile = `layouts/${inst.type.name}.vue`;
|
|
392
|
+
}
|
|
393
|
+
const normalizedFile = callerComponentFile?.replace(/\\/g, "/") ?? "";
|
|
394
|
+
const isLayoutComponent = normalizedFile.includes("/layouts/");
|
|
353
395
|
const entry = {
|
|
354
396
|
id,
|
|
355
397
|
name,
|
|
@@ -371,7 +413,9 @@ export function __trackComposable(name, callFn, meta) {
|
|
|
371
413
|
},
|
|
372
414
|
file: meta.file,
|
|
373
415
|
line: meta.line,
|
|
374
|
-
route: registry.getRoute()
|
|
416
|
+
route: registry.getRoute(),
|
|
417
|
+
callerComponentFile,
|
|
418
|
+
isLayoutComposable: isLayoutComponent
|
|
375
419
|
};
|
|
376
420
|
if (!instance && registry.getAll().some((e) => e.id === id)) {
|
|
377
421
|
registry.update(id, {
|
package/dist/runtime/plugin.js
CHANGED
|
@@ -11,6 +11,7 @@ export default defineNuxtPlugin(() => {
|
|
|
11
11
|
}
|
|
12
12
|
const nuxtApp = useNuxtApp();
|
|
13
13
|
const config = useRuntimeConfig().public.observatory;
|
|
14
|
+
let composableNavigationMode = config.composableNavigationMode === "session" ? "session" : "route";
|
|
14
15
|
if (config.renderHeatmap) {
|
|
15
16
|
nuxtApp.vueApp.config.performance = true;
|
|
16
17
|
}
|
|
@@ -55,6 +56,15 @@ export default defineNuxtPlugin(() => {
|
|
|
55
56
|
}
|
|
56
57
|
const source = event.source;
|
|
57
58
|
source?.postMessage({ type: "observatory:snapshot", data: buildSnapshot() }, event.origin);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (type === "observatory:set-composable-mode") {
|
|
62
|
+
const mode = event.data?.mode;
|
|
63
|
+
if (mode === "route" || mode === "session") {
|
|
64
|
+
composableNavigationMode = mode;
|
|
65
|
+
}
|
|
66
|
+
const source = event.source;
|
|
67
|
+
source?.postMessage({ type: "observatory:snapshot", data: buildSnapshot() }, event.origin);
|
|
58
68
|
}
|
|
59
69
|
if (type === "observatory:edit-composable") {
|
|
60
70
|
const { id, key, value } = event.data;
|
|
@@ -109,8 +119,8 @@ export default defineNuxtPlugin(() => {
|
|
|
109
119
|
if (provideInject && typeof provideInject.clear === "function")
|
|
110
120
|
provideInject.clear();
|
|
111
121
|
const composable = registries.composable;
|
|
112
|
-
if (composable && typeof composable.
|
|
113
|
-
composable.
|
|
122
|
+
if (composableNavigationMode === "route" && composable && typeof composable.clearNonLayout === "function")
|
|
123
|
+
composable.clearNonLayout();
|
|
114
124
|
const transition = registries.transition;
|
|
115
125
|
if (transition && typeof transition.clear === "function")
|
|
116
126
|
transition.clear();
|
|
@@ -171,6 +181,7 @@ export default defineNuxtPlugin(() => {
|
|
|
171
181
|
fetchDashboard: !!registries.fetch,
|
|
172
182
|
provideInjectGraph: !!registries.provideInject,
|
|
173
183
|
composableTracker: !!registries.composable,
|
|
184
|
+
composableNavigationMode,
|
|
174
185
|
renderHeatmap: !!registries.render,
|
|
175
186
|
transitionTracker: !!registries.transition
|
|
176
187
|
};
|