nuxt-devtools-observatory 0.1.16 → 0.1.18

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,16 +1,41 @@
1
1
  import { ref, readonly } from "vue";
2
+ import { getCurrentInstance } from "vue";
3
+ const MAX_FETCH_ENTRIES = 200;
4
+ const MAX_PAYLOAD_BYTES = 1e4;
5
+ function truncatePayload(payload) {
6
+ if (payload === void 0 || payload === null) {
7
+ return payload;
8
+ }
9
+ try {
10
+ const str = JSON.stringify(payload);
11
+ if (str.length <= MAX_PAYLOAD_BYTES) {
12
+ return JSON.parse(str);
13
+ }
14
+ return str.slice(0, MAX_PAYLOAD_BYTES) + "\u2026 (truncated)";
15
+ } catch {
16
+ return "[unserializable]";
17
+ }
18
+ }
2
19
  export function setupFetchRegistry() {
3
20
  const entries = ref(/* @__PURE__ */ new Map());
4
21
  function register(entry) {
5
- entries.value.set(entry.id, entry);
6
- emit("fetch:start", entry);
22
+ if (entries.value.size >= MAX_FETCH_ENTRIES) {
23
+ const oldestKey = entries.value.keys().next().value;
24
+ if (oldestKey !== void 0) {
25
+ entries.value.delete(oldestKey);
26
+ }
27
+ }
28
+ const safeEntry = entry.payload !== void 0 ? { ...entry, payload: truncatePayload(entry.payload) } : entry;
29
+ entries.value.set(safeEntry.id, safeEntry);
30
+ emit("fetch:start", safeEntry);
7
31
  }
8
32
  function update(id, patch) {
9
33
  const existing = entries.value.get(id);
10
34
  if (!existing) {
11
35
  return;
12
36
  }
13
- const updated = { ...existing, ...patch };
37
+ const safePatch = patch.payload !== void 0 ? { ...patch, payload: truncatePayload(patch.payload) } : patch;
38
+ const updated = { ...existing, ...safePatch };
14
39
  entries.value.set(id, updated);
15
40
  emit("fetch:update", updated);
16
41
  }
@@ -54,18 +79,20 @@ export function __devFetchHandler(handler, key, meta) {
54
79
  line: meta.line
55
80
  });
56
81
  return Promise.resolve(handler(...args)).then((result) => {
82
+ const endTime = performance.now();
57
83
  registry.update(id, {
58
84
  status: "ok",
59
- endTime: performance.now(),
60
- ms: Math.round(performance.now() - startTime),
85
+ endTime,
86
+ ms: Math.round(endTime - startTime),
61
87
  payload: result
62
88
  });
63
89
  return result;
64
90
  }).catch((error) => {
91
+ const endTime = performance.now();
65
92
  registry.update(id, {
66
93
  status: "error",
67
- endTime: performance.now(),
68
- ms: Math.round(performance.now() - startTime),
94
+ endTime,
95
+ ms: Math.round(endTime - startTime),
69
96
  error
70
97
  });
71
98
  throw error;
@@ -95,66 +122,123 @@ export function __devFetchCall(originalFn, url, opts, meta) {
95
122
  }
96
123
  const id = `${meta.key}::${Date.now()}`;
97
124
  const startTime = performance.now();
98
- const payload = window.__NUXT__?.data ?? {};
99
- const fromPayload = Object.prototype.hasOwnProperty.call(payload, meta.key);
100
- const origin = fromPayload ? "ssr" : "csr";
101
125
  const resolvedUrl = resolveUrl(url);
102
- registry.register({
103
- id,
104
- key: meta.key,
105
- url: resolvedUrl,
106
- status: fromPayload ? "cached" : "pending",
107
- origin,
108
- startTime,
109
- cached: fromPayload,
110
- payload: fromPayload ? payload[meta.key] : void 0,
111
- file: meta.file,
112
- line: meta.line
113
- });
114
- if (fromPayload) {
115
- const optsWithHooks = {
116
- ...opts,
117
- onResponse: function(ctx) {
118
- const entry = registry.getAll().find((e) => e.id === id);
119
- if (entry) {
120
- registry.update(id, { payload: ctx.response._data });
121
- }
122
- if (typeof opts.onResponse === "function") {
123
- opts.onResponse(ctx);
124
- }
125
- },
126
- onResponseError: typeof opts.onResponseError === "function" ? opts.onResponseError : () => {
126
+ if (getCurrentInstance() === null) {
127
+ registry.register({
128
+ id,
129
+ key: meta.key,
130
+ url: resolvedUrl,
131
+ status: "pending",
132
+ origin: "csr",
133
+ startTime,
134
+ cached: false,
135
+ file: meta.file,
136
+ line: meta.line
137
+ });
138
+ fetch(resolvedUrl).then(async (r) => {
139
+ const endTime = performance.now();
140
+ const size = Number(r.headers.get("content-length")) || void 0;
141
+ let payload;
142
+ try {
143
+ payload = await r.clone().json();
144
+ } catch {
127
145
  }
128
- };
129
- return originalFn(url, optsWithHooks);
146
+ registry.update(id, {
147
+ status: r.ok ? "ok" : "error",
148
+ endTime,
149
+ ms: Math.round(endTime - startTime),
150
+ size,
151
+ payload
152
+ });
153
+ }).catch(() => {
154
+ const endTime = performance.now();
155
+ registry.update(id, { status: "error", endTime, ms: Math.round(endTime - startTime) });
156
+ });
157
+ return originalFn(url, opts);
130
158
  }
131
- return originalFn(url, {
159
+ let responseCount = 0;
160
+ let lastCallStart = startTime;
161
+ let initialWasSsr = false;
162
+ const result = originalFn(url, {
132
163
  ...opts,
164
+ onRequest() {
165
+ lastCallStart = performance.now();
166
+ if (typeof opts.onRequest === "function") {
167
+ ;
168
+ opts.onRequest();
169
+ }
170
+ },
133
171
  onResponse({ response }) {
134
- const ms = Math.round(performance.now() - startTime);
172
+ responseCount++;
173
+ const endTime = performance.now();
174
+ const ms = Math.round(endTime - lastCallStart);
135
175
  const size = Number(response.headers?.get("content-length")) || void 0;
136
176
  const cached = response.headers?.get("x-nuxt-cache") === "HIT";
137
- registry.update(id, {
138
- status: cached ? "cached" : response.ok ? "ok" : "error",
139
- endTime: performance.now(),
140
- ms,
141
- size,
142
- cached,
143
- payload: response._data
144
- });
177
+ const status = cached ? "cached" : response.ok ? "ok" : "error";
178
+ if (responseCount === 1 && !initialWasSsr) {
179
+ registry.update(id, { status, endTime, ms, size, cached, payload: response._data });
180
+ } else {
181
+ registry.register({
182
+ id: `${id}::refresh::${responseCount}`,
183
+ key: meta.key,
184
+ url: resolvedUrl,
185
+ status,
186
+ origin: "csr",
187
+ startTime: lastCallStart,
188
+ endTime,
189
+ ms,
190
+ size,
191
+ cached,
192
+ payload: response._data,
193
+ file: meta.file,
194
+ line: meta.line
195
+ });
196
+ }
145
197
  if (typeof opts.onResponse === "function") {
146
198
  opts.onResponse({ response });
147
199
  }
148
200
  },
149
201
  onResponseError({ response }) {
150
- registry.update(id, {
151
- status: "error",
152
- endTime: performance.now(),
153
- ms: Math.round(performance.now() - startTime)
154
- });
155
202
  if (typeof opts.onResponseError === "function") {
156
203
  opts.onResponseError({ response });
157
204
  }
158
205
  }
159
206
  });
207
+ const statusValue = result?.status?.value;
208
+ const isSsrHydrated = statusValue === "success";
209
+ const isDeduplicatedError = statusValue === "error";
210
+ registry.register({
211
+ id,
212
+ key: meta.key,
213
+ url: resolvedUrl,
214
+ status: isSsrHydrated ? "cached" : isDeduplicatedError ? "error" : "pending",
215
+ origin: isSsrHydrated ? "ssr" : "csr",
216
+ startTime,
217
+ endTime: isSsrHydrated || isDeduplicatedError ? startTime : void 0,
218
+ ms: isSsrHydrated || isDeduplicatedError ? 0 : void 0,
219
+ cached: isSsrHydrated,
220
+ payload: isSsrHydrated ? result?.data?.value : void 0,
221
+ file: meta.file,
222
+ line: meta.line
223
+ });
224
+ if (isSsrHydrated) {
225
+ initialWasSsr = true;
226
+ }
227
+ if (!isSsrHydrated && !isDeduplicatedError && typeof result?.then === "function") {
228
+ result.then(() => {
229
+ const endTime = performance.now();
230
+ const existing = registry.getAll().find((e) => e.id === id);
231
+ if (existing?.status === "pending") {
232
+ const settled = result?.status?.value;
233
+ registry.update(id, {
234
+ status: settled === "success" ? "ok" : "error",
235
+ endTime,
236
+ ms: Math.round(endTime - startTime),
237
+ payload: settled === "success" ? result?.data?.value : void 0
238
+ });
239
+ }
240
+ }).catch(() => {
241
+ });
242
+ }
243
+ return result;
160
244
  }
@@ -1,46 +1,20 @@
1
- import { ref, isRef, isReactive, unref, getCurrentInstance, provide, inject } from "vue";
1
+ import { isRef, isReactive, unref, getCurrentInstance, provide, inject } from "vue";
2
2
  export function setupProvideInjectRegistry() {
3
- const provides = ref([]);
4
- const injects = ref([]);
3
+ const provides = /* @__PURE__ */ new Map();
4
+ const injects = /* @__PURE__ */ new Map();
5
5
  function registerProvide(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
- }
6
+ provides.set(`${entry.key}:${entry.componentUid}`, entry);
12
7
  emit("provide:register", entry);
13
8
  }
14
9
  function registerInject(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
- }
10
+ injects.set(`${entry.key}:${entry.componentUid}`, entry);
21
11
  emit("inject:register", entry);
22
12
  }
23
13
  function clear() {
24
- provides.value = [];
25
- injects.value = [];
14
+ provides.clear();
15
+ injects.clear();
26
16
  emit("provide:clear", {});
27
17
  }
28
- function safeValue(val) {
29
- if (val === void 0 || val === null) {
30
- return val;
31
- }
32
- if (typeof val === "function") {
33
- return void 0;
34
- }
35
- if (typeof val === "object") {
36
- try {
37
- return JSON.parse(JSON.stringify(val));
38
- } catch {
39
- return String(val);
40
- }
41
- }
42
- return val;
43
- }
44
18
  function sanitizeProvide(entry) {
45
19
  return {
46
20
  key: entry.key,
@@ -50,7 +24,9 @@ export function setupProvideInjectRegistry() {
50
24
  parentUid: entry.parentUid,
51
25
  parentFile: entry.parentFile,
52
26
  isReactive: entry.isReactive,
53
- valueSnapshot: safeValue(entry.valueSnapshot),
27
+ // valueSnapshot is already a plain serializable value captured at provide()
28
+ // time by safeSnapshot() in the shim — no need to deep-clone it again here.
29
+ valueSnapshot: entry.valueSnapshot,
54
30
  line: entry.line,
55
31
  scope: entry.scope,
56
32
  isShadowing: entry.isShadowing
@@ -72,8 +48,8 @@ export function setupProvideInjectRegistry() {
72
48
  }
73
49
  function getAll() {
74
50
  return {
75
- provides: provides.value.map(sanitizeProvide),
76
- injects: injects.value.map(sanitizeInject)
51
+ provides: [...provides.values()].map(sanitizeProvide),
52
+ injects: [...injects.values()].map(sanitizeInject)
77
53
  };
78
54
  }
79
55
  function emit(event, data) {
@@ -3,7 +3,6 @@ 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 preResetUids = /* @__PURE__ */ new Set();
7
6
  let currentRoute = "/";
8
7
  const MAX_TIMELINE = 100;
9
8
  function setRoute(path) {
@@ -16,17 +15,38 @@ export function setupRenderRegistry(nuxtApp, options = {}) {
16
15
  }
17
16
  return entries.value.get(uid);
18
17
  }
19
- function syncRect(entry, instance) {
20
- const rect = instance.$el?.getBoundingClientRect?.();
21
- entry.rect = rect ? {
22
- x: Math.round(rect.x),
23
- y: Math.round(rect.y),
24
- width: Math.round(rect.width),
25
- height: Math.round(rect.height),
26
- top: Math.round(rect.top),
27
- left: Math.round(rect.left)
28
- } : void 0;
18
+ const dirtyRects = /* @__PURE__ */ new Set();
19
+ function markRectDirty(uid) {
20
+ dirtyRects.add(uid);
29
21
  }
22
+ function flushDirtyRects() {
23
+ if (dirtyRects.size === 0) {
24
+ return;
25
+ }
26
+ for (const uid of dirtyRects) {
27
+ const entry = entries.value.get(uid);
28
+ if (!entry) {
29
+ dirtyRects.delete(uid);
30
+ continue;
31
+ }
32
+ const el = _liveElements.get(uid);
33
+ if (!el) {
34
+ dirtyRects.delete(uid);
35
+ continue;
36
+ }
37
+ const rect = el.getBoundingClientRect?.();
38
+ entry.rect = rect ? {
39
+ x: Math.round(rect.x),
40
+ y: Math.round(rect.y),
41
+ width: Math.round(rect.width),
42
+ height: Math.round(rect.height),
43
+ top: Math.round(rect.top),
44
+ left: Math.round(rect.left)
45
+ } : void 0;
46
+ dirtyRects.delete(uid);
47
+ }
48
+ }
49
+ const _liveElements = /* @__PURE__ */ new Map();
30
50
  function removeEntry(instance) {
31
51
  const uid = instance.$.uid;
32
52
  entries.value.delete(uid);
@@ -64,10 +84,11 @@ export function setupRenderRegistry(nuxtApp, options = {}) {
64
84
  }
65
85
  }
66
86
  function reset() {
67
- preResetUids = new Set(entries.value.keys());
68
87
  pendingTriggeredRenders.clear();
69
88
  renderStartTimes.clear();
89
+ dirtyRects.clear();
70
90
  for (const entry of entries.value.values()) {
91
+ entry.isPersistent = true;
71
92
  entry.rerenders = 0;
72
93
  entry.totalMs = 0;
73
94
  entry.avgMs = 0;
@@ -82,16 +103,14 @@ export function setupRenderRegistry(nuxtApp, options = {}) {
82
103
  mounted() {
83
104
  const entry = ensureEntry(this);
84
105
  entry.mountCount++;
85
- if (preResetUids.has(entry.uid)) {
86
- entry.isPersistent = true;
87
- }
88
106
  const isHydration = options.isHydrating?.() ?? false;
89
107
  if (isHydration && entry.mountCount === 1) {
90
108
  entry.isHydrationMount = true;
91
109
  } else if (entry.mountCount > 1) {
92
110
  entry.rerenders++;
93
111
  }
94
- syncRect(entry, this);
112
+ _liveElements.set(entry.uid, this.$el);
113
+ markRectDirty(entry.uid);
95
114
  if (!entry.isPersistent || entry.mountCount === 1) {
96
115
  recordRenderDuration(entry, "mount");
97
116
  } else {
@@ -114,15 +133,18 @@ export function setupRenderRegistry(nuxtApp, options = {}) {
114
133
  const entry = ensureEntry(this);
115
134
  pendingTriggeredRenders.delete(entry.uid);
116
135
  entry.rerenders++;
117
- syncRect(entry, this);
136
+ markRectDirty(entry.uid);
118
137
  recordRenderDuration(entry, "update");
119
138
  emit("render:update", { uid: entry.uid, renders: entry.rerenders });
120
139
  },
121
140
  unmounted() {
122
- pendingTriggeredRenders.delete(this.$.uid);
123
- renderStartTimes.delete(this.$.uid);
141
+ const uid = this.$.uid;
142
+ pendingTriggeredRenders.delete(uid);
143
+ renderStartTimes.delete(uid);
144
+ dirtyRects.delete(uid);
145
+ _liveElements.delete(uid);
124
146
  removeEntry(this);
125
- emit("render:remove", { uid: this.$.uid });
147
+ emit("render:remove", { uid });
126
148
  }
127
149
  });
128
150
  function sanitize(entry) {
@@ -152,6 +174,7 @@ export function setupRenderRegistry(nuxtApp, options = {}) {
152
174
  };
153
175
  }
154
176
  function getAll() {
177
+ flushDirtyRects();
155
178
  return [...entries.value.values()].map(sanitize);
156
179
  }
157
180
  function snapshot() {
@@ -15,6 +15,7 @@ export declare function setupTransitionRegistry(): {
15
15
  register: (entry: TransitionEntry) => void;
16
16
  update: (id: string, patch: Partial<TransitionEntry>) => void;
17
17
  getAll: () => TransitionEntry[];
18
+ clear: () => void;
18
19
  };
19
20
  export declare function createTrackedTransition(registry: ReturnType<typeof setupTransitionRegistry>): import("vue").DefineComponent<{}, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
20
21
  [key: string]: any;
@@ -1,10 +1,21 @@
1
1
  import { ref, h, defineComponent, getCurrentInstance, onUnmounted, Transition as VueTransition } from "vue";
2
+ const MAX_TRANSITIONS = 500;
2
3
  export function setupTransitionRegistry() {
3
4
  const entries = ref(/* @__PURE__ */ new Map());
4
5
  function register(entry) {
6
+ if (entries.value.size >= MAX_TRANSITIONS) {
7
+ const oldestKey = entries.value.keys().next().value;
8
+ if (oldestKey !== void 0) {
9
+ entries.value.delete(oldestKey);
10
+ }
11
+ }
5
12
  entries.value.set(entry.id, entry);
6
13
  emit("transition:register", entry);
7
14
  }
15
+ function clear() {
16
+ entries.value.clear();
17
+ emit("transition:clear", {});
18
+ }
8
19
  function update(id, patch) {
9
20
  const existing = entries.value.get(id);
10
21
  if (!existing) {
@@ -42,8 +53,9 @@ export function setupTransitionRegistry() {
42
53
  const channel = window.__nuxt_devtools__?.channel;
43
54
  channel?.send(event, data);
44
55
  }
45
- return { register, update, getAll };
56
+ return { register, update, getAll, clear };
46
57
  }
58
+ let _transitionSeq = 0;
47
59
  function mergeHook(original, ours) {
48
60
  return (el) => {
49
61
  ours(el);
@@ -78,7 +90,7 @@ export function createTrackedTransition(registry) {
78
90
  ...attrs,
79
91
  onBeforeEnter: mergeHook(attrs.onBeforeEnter, () => {
80
92
  const now = performance.now();
81
- const id = `${transitionName}::enter::${now}`;
93
+ const id = `${transitionName}::enter::${now}::${++_transitionSeq}`;
82
94
  enterEntryId = id;
83
95
  registry.register({
84
96
  id,
@@ -106,7 +118,7 @@ export function createTrackedTransition(registry) {
106
118
  }),
107
119
  onBeforeLeave: mergeHook(attrs.onBeforeLeave, () => {
108
120
  const now = performance.now();
109
- const id = `${transitionName}::leave::${now}`;
121
+ const id = `${transitionName}::leave::${now}::${++_transitionSeq}`;
110
122
  leaveEntryId = id;
111
123
  registry.register({
112
124
  id,