nuxt-devtools-observatory 0.1.11 → 0.1.13

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.
@@ -1,10 +1,99 @@
1
- import { ref, isRef, isReadonly, unref, getCurrentInstance, onUnmounted } from "vue";
1
+ import { ref, isRef, isReactive, isReadonly, unref, computed, watchEffect, getCurrentInstance, onUnmounted } from "vue";
2
2
  export function setupComposableRegistry() {
3
3
  const entries = ref(/* @__PURE__ */ new Map());
4
+ const liveRefs = /* @__PURE__ */ new Map();
5
+ const liveRefWatchers = /* @__PURE__ */ new Map();
6
+ const MAX_HISTORY = 50;
7
+ const entryHistory = /* @__PURE__ */ new Map();
8
+ const prevValues = /* @__PURE__ */ new Map();
9
+ const rawRefs = /* @__PURE__ */ new Map();
10
+ function computeSharedKeys(id, name) {
11
+ const ownRaw = rawRefs.get(id);
12
+ if (!ownRaw) {
13
+ return [];
14
+ }
15
+ const shared = /* @__PURE__ */ new Set();
16
+ for (const [otherId, entry] of entries.value.entries()) {
17
+ if (otherId === id || entry.name !== name) {
18
+ continue;
19
+ }
20
+ const otherRaw = rawRefs.get(otherId);
21
+ if (!otherRaw) {
22
+ continue;
23
+ }
24
+ for (const [key, obj] of Object.entries(ownRaw)) {
25
+ if (key in otherRaw && otherRaw[key] === obj) {
26
+ shared.add(key);
27
+ }
28
+ }
29
+ }
30
+ return [...shared];
31
+ }
32
+ let currentRoute = "/";
33
+ function setRoute(path) {
34
+ currentRoute = path;
35
+ }
36
+ function getRoute() {
37
+ return currentRoute;
38
+ }
4
39
  function register(entry) {
5
40
  entries.value.set(entry.id, entry);
6
41
  emit("composable:register", entry);
7
42
  }
43
+ function registerLiveRefs(id, refs) {
44
+ const prevStop = liveRefWatchers.get(id);
45
+ if (prevStop) {
46
+ prevStop();
47
+ }
48
+ liveRefWatchers.delete(id);
49
+ if (Object.keys(refs).length === 0) {
50
+ liveRefs.delete(id);
51
+ rawRefs.delete(id);
52
+ prevValues.delete(id);
53
+ return;
54
+ }
55
+ liveRefs.set(id, refs);
56
+ prevValues.set(
57
+ id,
58
+ Object.fromEntries(
59
+ Object.entries(refs).map(([k, r]) => {
60
+ try {
61
+ return [k, JSON.stringify(unref(r)) ?? ""];
62
+ } catch {
63
+ return [k, ""];
64
+ }
65
+ })
66
+ )
67
+ );
68
+ const stop = watchEffect(() => {
69
+ const prev = prevValues.get(id) ?? {};
70
+ const now = {};
71
+ const t = typeof performance !== "undefined" ? performance.now() : Date.now();
72
+ for (const [k, r] of Object.entries(refs)) {
73
+ const val = unref(r);
74
+ const serialised = JSON.stringify(val) ?? "";
75
+ now[k] = serialised;
76
+ if (serialised !== prev[k]) {
77
+ const history = entryHistory.get(id) ?? [];
78
+ history.push({ t, key: k, value: safeValue(val) });
79
+ if (history.length > MAX_HISTORY) {
80
+ history.shift();
81
+ }
82
+ entryHistory.set(id, history);
83
+ }
84
+ }
85
+ prevValues.set(id, now);
86
+ _onChange?.();
87
+ });
88
+ liveRefWatchers.set(id, stop);
89
+ }
90
+ function registerRawRefs(id, refs) {
91
+ rawRefs.set(id, refs);
92
+ }
93
+ let _onChange = null;
94
+ function onComposableChange(cb) {
95
+ _onChange = cb;
96
+ }
8
97
  function update(id, patch) {
9
98
  const existing = entries.value.get(id);
10
99
  if (!existing) {
@@ -31,6 +120,25 @@ export function setupComposableRegistry() {
31
120
  return val;
32
121
  }
33
122
  function sanitize(entry) {
123
+ const live = liveRefs.get(entry.id);
124
+ const hasLive = live != null && Object.keys(live).length > 0;
125
+ const freshRefs = hasLive ? Object.fromEntries(
126
+ Object.entries(live).map(([k, r]) => [
127
+ k,
128
+ {
129
+ type: entry.refs[k]?.type ?? "ref",
130
+ value: safeValue(unref(r))
131
+ }
132
+ ])
133
+ ) : Object.fromEntries(
134
+ Object.entries(entry.refs).map(([k, v]) => [
135
+ k,
136
+ {
137
+ type: v.type,
138
+ value: safeValue(typeof v.value === "object" && v.value !== null && "value" in v.value ? v.value.value : v.value)
139
+ }
140
+ ])
141
+ );
34
142
  return {
35
143
  id: entry.id,
36
144
  name: entry.name,
@@ -39,20 +147,15 @@ export function setupComposableRegistry() {
39
147
  status: entry.status,
40
148
  leak: entry.leak,
41
149
  leakReason: entry.leakReason,
42
- refs: Object.fromEntries(
43
- Object.entries(entry.refs).map(([k, v]) => [
44
- k,
45
- {
46
- type: v.type,
47
- value: safeValue(typeof v.value === "object" && v.value !== null && "value" in v.value ? v.value.value : v.value)
48
- }
49
- ])
50
- ),
150
+ refs: freshRefs,
151
+ history: entryHistory.get(entry.id) ?? [],
152
+ sharedKeys: computeSharedKeys(entry.id, entry.name),
51
153
  watcherCount: entry.watcherCount,
52
154
  intervalCount: entry.intervalCount,
53
155
  lifecycle: entry.lifecycle,
54
156
  file: entry.file,
55
- line: entry.line
157
+ line: entry.line,
158
+ route: entry.route
56
159
  };
57
160
  }
58
161
  function getAll() {
@@ -65,7 +168,35 @@ export function setupComposableRegistry() {
65
168
  const channel = window.__nuxt_devtools__?.channel;
66
169
  channel?.send(event, data);
67
170
  }
68
- return { register, update, getAll };
171
+ function clear() {
172
+ for (const stop of liveRefWatchers.values()) stop();
173
+ liveRefWatchers.clear();
174
+ liveRefs.clear();
175
+ rawRefs.clear();
176
+ prevValues.clear();
177
+ entryHistory.clear();
178
+ entries.value.clear();
179
+ emit("composable:clear", {});
180
+ }
181
+ function editValue(id, key, value) {
182
+ const live = liveRefs.get(id);
183
+ if (!live) {
184
+ return;
185
+ }
186
+ const r = live[key];
187
+ if (!r) {
188
+ return;
189
+ }
190
+ const entry = entries.value.get(id);
191
+ if (!entry) {
192
+ return;
193
+ }
194
+ if (entry.refs[key]?.type === "computed") {
195
+ return;
196
+ }
197
+ r.value = value;
198
+ }
199
+ return { register, registerLiveRefs, registerRawRefs, onComposableChange, clear, setRoute, getRoute, update, getAll, editValue };
69
200
  }
70
201
  export function __trackComposable(name, callFn, meta) {
71
202
  if (!import.meta.dev) {
@@ -79,40 +210,55 @@ export function __trackComposable(name, callFn, meta) {
79
210
  return callFn();
80
211
  }
81
212
  const instance = getCurrentInstance();
82
- const id = `${name}::${instance?.uid ?? "global"}::${meta.file}:${meta.line}::${Date.now()}`;
213
+ const id = `${name}::${instance?.uid ?? "global"}::${meta.file}:${meta.line}::${Date.now()}::${Math.random().toString(36).slice(2, 7)}`;
83
214
  const trackedIntervals = [];
84
- const originalSetInterval = window.setInterval;
85
- const originalClearInterval = window.clearInterval;
86
215
  const clearedIntervals = /* @__PURE__ */ new Set();
87
- window.setInterval = ((fn, ms, ...rest) => {
88
- const id2 = originalSetInterval(fn, ms, ...rest);
89
- trackedIntervals.push(id2);
90
- return id2;
91
- });
92
- window.clearInterval = ((id2) => {
93
- if (id2 !== void 0) {
94
- clearedIntervals.add(id2);
95
- }
96
- originalClearInterval(id2);
97
- });
216
+ const alreadyPatched = !!window.setInterval.__obs;
217
+ let originalSetInterval = null;
218
+ let originalClearInterval = null;
219
+ if (!alreadyPatched) {
220
+ originalSetInterval = window.setInterval;
221
+ originalClearInterval = window.clearInterval;
222
+ const patchedSetInterval = ((fn, ms, ...rest) => {
223
+ const timerId = originalSetInterval(fn, ms, ...rest);
224
+ trackedIntervals.push(timerId);
225
+ return timerId;
226
+ });
227
+ patchedSetInterval.__obs = true;
228
+ window.setInterval = patchedSetInterval;
229
+ window.clearInterval = ((timerId) => {
230
+ if (timerId !== void 0) {
231
+ clearedIntervals.add(timerId);
232
+ }
233
+ originalClearInterval(timerId);
234
+ });
235
+ }
98
236
  const effectsBefore = new Set(instance?.scope?.effects ?? []);
99
237
  const mountedHooksBefore = instance?.bm?.length ?? 0;
100
238
  const unmountedHooksBefore = instance?.um?.length ?? 0;
101
239
  const result = callFn();
102
- window.setInterval = originalSetInterval;
103
- window.clearInterval = originalClearInterval;
240
+ if (!alreadyPatched && originalSetInterval) {
241
+ window.setInterval = originalSetInterval;
242
+ window.clearInterval = originalClearInterval;
243
+ }
104
244
  const trackedWatchers = (instance?.scope?.effects ?? []).filter((effect) => !effectsBefore.has(effect)).map((effect) => ({
105
245
  effect,
106
246
  stop: () => effect.stop?.()
107
247
  }));
108
248
  const refs = {};
249
+ const liveRefMap = {};
250
+ const rawRefMap = {};
109
251
  if (result && typeof result === "object") {
110
252
  for (const [key, val] of Object.entries(result)) {
111
253
  if (isRef(val)) {
112
- refs[key] = {
113
- type: isReadonly(val) ? "computed" : "ref",
114
- value: safeSnapshot(unref(val))
115
- };
254
+ const type = isReadonly(val) ? "computed" : "ref";
255
+ refs[key] = { type, value: safeSnapshot(unref(val)) };
256
+ liveRefMap[key] = val;
257
+ rawRefMap[key] = val;
258
+ } else if (isReactive(val)) {
259
+ refs[key] = { type: "reactive", value: safeSnapshot(val) };
260
+ liveRefMap[key] = computed(() => val);
261
+ rawRefMap[key] = val;
116
262
  }
117
263
  }
118
264
  }
@@ -124,6 +270,9 @@ export function __trackComposable(name, callFn, meta) {
124
270
  status: "mounted",
125
271
  leak: false,
126
272
  refs,
273
+ history: [],
274
+ sharedKeys: [],
275
+ // computed lazily in sanitize() after all instances are registered
127
276
  watcherCount: trackedWatchers.length,
128
277
  intervalCount: trackedIntervals.length,
129
278
  lifecycle: {
@@ -133,31 +282,37 @@ export function __trackComposable(name, callFn, meta) {
133
282
  intervalsCleaned: true
134
283
  },
135
284
  file: meta.file,
136
- line: meta.line
285
+ line: meta.line,
286
+ route: registry.getRoute()
137
287
  };
138
288
  registry.register(entry);
139
- onUnmounted(() => {
140
- const leakedWatchers = trackedWatchers.filter((w) => w.effect.active);
141
- const leakedIntervals = trackedIntervals.filter((id2) => !clearedIntervals.has(id2));
142
- const leak = leakedWatchers.length > 0 || leakedIntervals.length > 0;
143
- const reasons = [];
144
- if (leakedWatchers.length > 0) {
145
- reasons.push(`${leakedWatchers.length} watcher${leakedWatchers.length > 1 ? "s" : ""} still active after unmount`);
146
- }
147
- if (leakedIntervals.length > 0) {
148
- reasons.push(`setInterval #${leakedIntervals.join(", #")} never cleared`);
149
- }
150
- registry.update(id, {
151
- status: "unmounted",
152
- leak,
153
- leakReason: reasons.join(" \xB7 ") || void 0,
154
- lifecycle: {
155
- ...entry.lifecycle,
156
- watchersCleaned: leakedWatchers.length === 0,
157
- intervalsCleaned: leakedIntervals.length === 0
289
+ registry.registerLiveRefs(id, liveRefMap);
290
+ registry.registerRawRefs(id, rawRefMap);
291
+ if (instance) {
292
+ onUnmounted(() => {
293
+ const leakedWatchers = trackedWatchers.filter((w) => w.effect.active);
294
+ const leakedIntervals = trackedIntervals.filter((timerId) => !clearedIntervals.has(timerId));
295
+ const leak = leakedWatchers.length > 0 || leakedIntervals.length > 0;
296
+ const reasons = [];
297
+ if (leakedWatchers.length > 0) {
298
+ reasons.push(`${leakedWatchers.length} watcher${leakedWatchers.length > 1 ? "s" : ""} still active after unmount`);
158
299
  }
300
+ if (leakedIntervals.length > 0) {
301
+ reasons.push(`setInterval #${leakedIntervals.join(", #")} never cleared`);
302
+ }
303
+ registry.update(id, {
304
+ status: "unmounted",
305
+ leak,
306
+ leakReason: reasons.join(" \xB7 ") || void 0,
307
+ lifecycle: {
308
+ ...entry.lifecycle,
309
+ watchersCleaned: leakedWatchers.length === 0,
310
+ intervalsCleaned: leakedIntervals.length === 0
311
+ }
312
+ });
313
+ registry.registerLiveRefs(id, {});
159
314
  });
160
- });
315
+ }
161
316
  return result;
162
317
  }
163
318
  function safeSnapshot(value) {
@@ -8,6 +8,17 @@ export interface ProvideEntry {
8
8
  isReactive: boolean;
9
9
  valueSnapshot: unknown;
10
10
  line: number;
11
+ /**
12
+ * Scope of the provide:
13
+ * - 'global' — provided via app.provide() or at the app root (no parent component)
14
+ * - 'layout' — provided by a layout component (file path contains 'layout')
15
+ * - 'component' — provided by a regular component
16
+ */
17
+ scope: 'global' | 'layout' | 'component';
18
+ /**
19
+ * True when a parent component already provides the same key — this provide shadows it.
20
+ */
21
+ isShadowing: boolean;
11
22
  }
12
23
  export interface InjectEntry {
13
24
  key: string;
@@ -24,7 +35,7 @@ export interface InjectEntry {
24
35
  /**
25
36
  * Sets up the provide/inject registry, which tracks all provide/inject calls
26
37
  * and their associated metadata (e.g. component name, file, line).
27
- * @returns {object} The provide/inject registry with `registerProvide`, `registerInject`, and `getAll` members.
38
+ * @returns {object} The provide/inject registry with `registerProvide`, `registerInject`, `getAll`, and `clear` members.
28
39
  */
29
40
  export declare function setupProvideInjectRegistry(): {
30
41
  registerProvide: (entry: ProvideEntry) => void;
@@ -33,6 +44,7 @@ export declare function setupProvideInjectRegistry(): {
33
44
  provides: ProvideEntry[];
34
45
  injects: InjectEntry[];
35
46
  };
47
+ clear: () => void;
36
48
  };
37
49
  export declare function __devProvide(key: string | symbol, value: unknown, meta: {
38
50
  file: string;
@@ -3,13 +3,28 @@ export function setupProvideInjectRegistry() {
3
3
  const provides = ref([]);
4
4
  const injects = ref([]);
5
5
  function registerProvide(entry) {
6
- provides.value.push(entry);
6
+ const idx = provides.value.findIndex((e) => e.key === entry.key && e.componentUid === entry.componentUid);
7
+ if (idx !== -1) {
8
+ provides.value[idx] = entry;
9
+ } else {
10
+ provides.value.push(entry);
11
+ }
7
12
  emit("provide:register", entry);
8
13
  }
9
14
  function registerInject(entry) {
10
- injects.value.push(entry);
15
+ const idx = injects.value.findIndex((e) => e.key === entry.key && e.componentUid === entry.componentUid);
16
+ if (idx !== -1) {
17
+ injects.value[idx] = entry;
18
+ } else {
19
+ injects.value.push(entry);
20
+ }
11
21
  emit("inject:register", entry);
12
22
  }
23
+ function clear() {
24
+ provides.value = [];
25
+ injects.value = [];
26
+ emit("provide:clear", {});
27
+ }
13
28
  function safeValue(val) {
14
29
  if (val === void 0 || val === null) {
15
30
  return val;
@@ -36,7 +51,9 @@ export function setupProvideInjectRegistry() {
36
51
  parentFile: entry.parentFile,
37
52
  isReactive: entry.isReactive,
38
53
  valueSnapshot: safeValue(entry.valueSnapshot),
39
- line: entry.line
54
+ line: entry.line,
55
+ scope: entry.scope,
56
+ isShadowing: entry.isShadowing
40
57
  };
41
58
  }
42
59
  function sanitizeInject(entry) {
@@ -66,7 +83,7 @@ export function setupProvideInjectRegistry() {
66
83
  const channel = window.__nuxt_devtools__?.channel;
67
84
  channel?.send(event, data);
68
85
  }
69
- return { registerProvide, registerInject, getAll };
86
+ return { registerProvide, registerInject, getAll, clear };
70
87
  }
71
88
  export function __devProvide(key, value, meta) {
72
89
  provide(key, value);
@@ -78,8 +95,17 @@ export function __devProvide(key, value, meta) {
78
95
  return;
79
96
  }
80
97
  const instance = getCurrentInstance();
98
+ const keyStr = String(key);
99
+ const file = meta.file.toLowerCase();
100
+ let scope = "component";
101
+ if (!instance?.parent || instance?.parent?.type === instance?.appContext?.app?._component) {
102
+ scope = "global";
103
+ } else if (file.includes("layout") || file.includes("layouts")) {
104
+ scope = "layout";
105
+ }
106
+ const isShadowing = findProvider(keyStr, instance) !== null;
81
107
  registry.registerProvide({
82
- key: String(key),
108
+ key: keyStr,
83
109
  componentName: instance?.type?.__name ?? "unknown",
84
110
  componentFile: meta.file,
85
111
  componentUid: instance?.uid ?? -1,
@@ -87,7 +113,9 @@ export function __devProvide(key, value, meta) {
87
113
  parentFile: instance?.parent?.type?.__file,
88
114
  isReactive: isRef(value) || isReactive(value),
89
115
  valueSnapshot: safeSnapshot(unref(value)),
90
- line: meta.line
116
+ line: meta.line,
117
+ scope,
118
+ isShadowing
91
119
  });
92
120
  }
93
121
  export function __devInject(key, defaultValue, meta) {
@@ -3,8 +3,11 @@ export interface RenderEntry {
3
3
  name: string;
4
4
  file: string;
5
5
  element?: string;
6
- renders: number;
7
- navigationRenders: number;
6
+ /** Total times this instance mounted (usually 1, >1 means it was unmounted+remounted) */
7
+ mountCount: number;
8
+ /** Re-renders triggered by reactive state changes (excludes initial mount) */
9
+ rerenders: number;
10
+ /** Subset of rerenders that happened within 800ms of a navigation */
8
11
  totalMs: number;
9
12
  avgMs: number;
10
13
  triggers: Array<{
@@ -20,20 +23,27 @@ export interface RenderEntry {
20
23
  top: number;
21
24
  left: number;
22
25
  };
23
- children: number[];
24
26
  parentUid?: number;
27
+ /** True if this component survived at least one reset() — indicates a layout/persistent component */
28
+ isPersistent: boolean;
29
+ /** True if the first mount of this component happened during SSR hydration */
30
+ isHydrationMount: boolean;
25
31
  }
26
32
  /**
27
- * Sets up a render registry for the given Nuxt app.
28
- * @param {{ vueApp: import('vue').App }} nuxtApp - The Nuxt app object.
29
- * @param {object} nuxtApp.vueApp - The Vue app instance.
30
- * @returns {object} The render registry object.
33
+ * Sets up a render registry for tracking render-related metrics (e.g. rerenders, render time, etc.)
34
+ * The registry is exposed over the WebSocket channel, and can be accessed from the browser's devtools.
35
+ * @param {object} nuxtApp - The Nuxt app instance.
36
+ * @param {import('vue').App} nuxtApp.vueApp - The Vue app instance used to register lifecycle hooks.
37
+ * @param {object} [options] - Optional configuration object.
38
+ * @param {function(): boolean} [options.isHydrating] - Function to determine if the current render is during SSR hydration.
39
+ * @returns {object} An object containing the render registry's API methods: `getAll()`, `snapshot()`, and `reset()`.
31
40
  */
32
41
  export declare function setupRenderRegistry(nuxtApp: {
33
42
  vueApp: import('vue').App;
43
+ }, options?: {
44
+ isHydrating?: () => boolean;
34
45
  }): {
35
46
  getAll: () => RenderEntry[];
36
47
  snapshot: () => RenderEntry[];
37
- markNavigation: () => void;
38
48
  reset: () => void;
39
49
  };
@@ -1,9 +1,9 @@
1
1
  import { ref } from "vue";
2
- export function setupRenderRegistry(nuxtApp) {
2
+ export function setupRenderRegistry(nuxtApp, options = {}) {
3
3
  const entries = ref(/* @__PURE__ */ new Map());
4
4
  const pendingTriggeredRenders = /* @__PURE__ */ new Set();
5
5
  const renderStartTimes = /* @__PURE__ */ new Map();
6
- let navigationWindowUntil = 0;
6
+ let preResetUids = /* @__PURE__ */ new Set();
7
7
  function ensureEntry(instance) {
8
8
  const uid = instance.$.uid;
9
9
  if (!entries.value.has(uid)) {
@@ -26,9 +26,6 @@ export function setupRenderRegistry(nuxtApp) {
26
26
  const uid = instance.$.uid;
27
27
  entries.value.delete(uid);
28
28
  }
29
- function isNavigationRender() {
30
- return typeof performance !== "undefined" && performance.now() <= navigationWindowUntil;
31
- }
32
29
  function startRenderTimer(uid) {
33
30
  if (typeof performance === "undefined") {
34
31
  return;
@@ -45,20 +42,15 @@ export function setupRenderRegistry(nuxtApp) {
45
42
  }
46
43
  renderStartTimes.delete(entry.uid);
47
44
  entry.totalMs += Math.max(performance.now() - startedAt, 0);
48
- entry.avgMs = Math.round(entry.totalMs / Math.max(entry.renders, 1) * 10) / 10;
49
- }
50
- function markNavigation() {
51
- if (typeof performance === "undefined") {
52
- return;
53
- }
54
- navigationWindowUntil = performance.now() + 800;
45
+ const totalEvents = (entry.rerenders ?? 0) + Math.max(entry.mountCount, 1);
46
+ entry.avgMs = Math.round(entry.totalMs / totalEvents * 10) / 10;
55
47
  }
56
48
  function reset() {
49
+ preResetUids = new Set(entries.value.keys());
57
50
  pendingTriggeredRenders.clear();
58
51
  renderStartTimes.clear();
59
52
  for (const entry of entries.value.values()) {
60
- entry.renders = 0;
61
- entry.navigationRenders = 0;
53
+ entry.rerenders = 0;
62
54
  entry.totalMs = 0;
63
55
  entry.avgMs = 0;
64
56
  entry.triggers = [];
@@ -70,13 +62,19 @@ export function setupRenderRegistry(nuxtApp) {
70
62
  },
71
63
  mounted() {
72
64
  const entry = ensureEntry(this);
73
- entry.renders++;
74
- if (isNavigationRender()) {
75
- entry.navigationRenders++;
65
+ entry.mountCount++;
66
+ if (preResetUids.has(entry.uid)) {
67
+ entry.isPersistent = true;
68
+ }
69
+ const isHydration = options.isHydrating?.() ?? false;
70
+ if (isHydration && entry.mountCount === 1) {
71
+ entry.isHydrationMount = true;
72
+ } else if (entry.mountCount > 1) {
73
+ entry.rerenders++;
76
74
  }
77
75
  syncRect(entry, this);
78
76
  recordRenderDuration(entry);
79
- emit("render:update", { uid: entry.uid, renders: entry.renders });
77
+ emit("render:update", { uid: entry.uid, renders: entry.rerenders });
80
78
  },
81
79
  beforeUpdate() {
82
80
  startRenderTimer(this.$.uid);
@@ -91,17 +89,11 @@ export function setupRenderRegistry(nuxtApp) {
91
89
  },
92
90
  updated() {
93
91
  const entry = ensureEntry(this);
94
- if (!pendingTriggeredRenders.has(entry.uid)) {
95
- return;
96
- }
97
92
  pendingTriggeredRenders.delete(entry.uid);
98
- entry.renders++;
99
- if (isNavigationRender()) {
100
- entry.navigationRenders++;
101
- }
93
+ entry.rerenders++;
102
94
  syncRect(entry, this);
103
95
  recordRenderDuration(entry);
104
- emit("render:update", { uid: entry.uid, renders: entry.renders });
96
+ emit("render:update", { uid: entry.uid, renders: entry.rerenders });
105
97
  },
106
98
  unmounted() {
107
99
  pendingTriggeredRenders.delete(this.$.uid);
@@ -116,8 +108,8 @@ export function setupRenderRegistry(nuxtApp) {
116
108
  name: entry.name,
117
109
  file: entry.file,
118
110
  element: entry.element,
119
- renders: entry.renders,
120
- navigationRenders: entry.navigationRenders,
111
+ mountCount: entry.mountCount,
112
+ rerenders: entry.rerenders,
121
113
  totalMs: entry.totalMs,
122
114
  avgMs: entry.avgMs,
123
115
  triggers: entry.triggers,
@@ -129,8 +121,9 @@ export function setupRenderRegistry(nuxtApp) {
129
121
  top: entry.rect.top,
130
122
  left: entry.rect.left
131
123
  } : void 0,
132
- children: entry.children,
133
- parentUid: entry.parentUid
124
+ parentUid: entry.parentUid,
125
+ isPersistent: entry.isPersistent,
126
+ isHydrationMount: entry.isHydrationMount
134
127
  };
135
128
  }
136
129
  function getAll() {
@@ -146,7 +139,7 @@ export function setupRenderRegistry(nuxtApp) {
146
139
  const channel = window.__nuxt_devtools__?.channel;
147
140
  channel?.send(event, data);
148
141
  }
149
- return { getAll, snapshot, markNavigation, reset };
142
+ return { getAll, snapshot, reset };
150
143
  }
151
144
  function makeEntry(uid, instance) {
152
145
  const type = instance.$.type;
@@ -160,13 +153,14 @@ function makeEntry(uid, instance) {
160
153
  name: ownLabel ?? inferAnonymousLabel(parentLabel, element) ?? `Component#${uid}`,
161
154
  file,
162
155
  element,
163
- renders: 0,
164
- navigationRenders: 0,
156
+ mountCount: 0,
157
+ rerenders: 0,
165
158
  totalMs: 0,
166
159
  avgMs: 0,
167
160
  triggers: [],
168
- children: [],
169
- parentUid: instance.$parent?.$.uid
161
+ parentUid: instance.$parent?.$.uid,
162
+ isPersistent: false,
163
+ isHydrationMount: false
170
164
  };
171
165
  }
172
166
  function describeElement(el) {
@@ -18,13 +18,19 @@ export function setupTransitionRegistry() {
18
18
  emit("transition:update", updated);
19
19
  }
20
20
  function sanitize(entry) {
21
- const clone = { ...entry };
22
- for (const key in clone) {
23
- if (typeof clone[key] === "function") {
24
- delete clone[key];
25
- }
26
- }
27
- return clone;
21
+ return {
22
+ id: entry.id,
23
+ transitionName: entry.transitionName,
24
+ parentComponent: entry.parentComponent,
25
+ direction: entry.direction,
26
+ phase: entry.phase,
27
+ startTime: entry.startTime,
28
+ endTime: entry.endTime,
29
+ durationMs: entry.durationMs,
30
+ cancelled: entry.cancelled,
31
+ appear: entry.appear,
32
+ mode: entry.mode
33
+ };
28
34
  }
29
35
  function getAll() {
30
36
  return [...entries.value.values()].map(sanitize);