nuxt-devtools-observatory 0.1.8 → 0.1.9

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/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": "^3.0.0 || ^4.0.0"
6
6
  },
7
- "version": "0.1.8",
7
+ "version": "0.1.9",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { defineNuxtModule, createResolver, addVitePlugin, addPlugin } from '@nuxt/kit';
1
+ import { defineNuxtModule, createResolver, addVitePlugin, addPlugin, addServerPlugin } from '@nuxt/kit';
2
2
  import { parse } from '@babel/parser';
3
3
  import _traverse from '@babel/traverse';
4
4
  import _generate from '@babel/generator';
@@ -21,6 +21,9 @@ function extractScriptBlock(code) {
21
21
  const traverse$2 = _traverse.default ?? _traverse;
22
22
  const generate$2 = _generate.default ?? _generate;
23
23
  const FETCH_FNS = /* @__PURE__ */ new Set(["useFetch", "useAsyncData", "useLazyFetch", "useLazyAsyncData"]);
24
+ function isHandlerExpression(node) {
25
+ return Boolean(node && (t.isIdentifier(node) || t.isArrowFunctionExpression(node) || t.isFunctionExpression(node)));
26
+ }
24
27
  function fetchInstrumentPlugin() {
25
28
  return {
26
29
  name: "vite-plugin-observatory-fetch",
@@ -30,7 +33,7 @@ function fetchInstrumentPlugin() {
30
33
  if (!isVue && !id.endsWith(".ts") && !id.endsWith(".js")) {
31
34
  return;
32
35
  }
33
- if (id.includes("node_modules")) {
36
+ if (id.includes("node_modules") || id.includes("composable-registry") || id.includes("provide-inject-registry") || id.includes("fetch-registry")) {
34
37
  return;
35
38
  }
36
39
  let scriptCode = code;
@@ -52,9 +55,15 @@ function fetchInstrumentPlugin() {
52
55
  plugins: ["typescript"]
53
56
  });
54
57
  let modified = false;
55
- const hasImport = scriptCode.includes("__devFetch");
58
+ let needsFetchCallHelper = false;
59
+ let needsFetchHandlerHelper = false;
60
+ const hasFetchCallImport = scriptCode.includes("__devFetchCall");
61
+ const hasFetchHandlerImport = scriptCode.includes("__devFetchHandler");
56
62
  traverse$2(ast, {
57
63
  CallExpression(path) {
64
+ if (path.node.__observatoryTransformed) {
65
+ return;
66
+ }
58
67
  const callee = path.node.callee;
59
68
  if (!t.isIdentifier(callee)) {
60
69
  return;
@@ -62,7 +71,7 @@ function fetchInstrumentPlugin() {
62
71
  if (!FETCH_FNS.has(callee.name)) {
63
72
  return;
64
73
  }
65
- if (path.parent && t.isCallExpression(path.parent) && t.isIdentifier(path.parent.callee) && path.parent.callee.name === "__devFetch") {
74
+ if (path.parent && t.isCallExpression(path.parent) && t.isIdentifier(path.parent.callee) && ["__devFetchCall", "__devFetchHandler"].includes(path.parent.callee.name)) {
66
75
  return;
67
76
  }
68
77
  const originalName = callee.name;
@@ -74,22 +83,35 @@ function fetchInstrumentPlugin() {
74
83
  return t.isExpression(node) ? node : void 0;
75
84
  }
76
85
  if (originalName === "useAsyncData" || originalName === "useLazyAsyncData") {
77
- if (args.length === 1 && getExpr(args[0])) {
86
+ if (args.length === 1 && isHandlerExpression(getExpr(args[0]))) {
78
87
  handlerArg = getExpr(args[0]);
79
- } else if (args.length >= 2 && getExpr(args[0]) && getExpr(args[1])) {
88
+ } else if (args.length >= 2 && getExpr(args[0]) && isHandlerExpression(getExpr(args[1]))) {
80
89
  keyArg = getExpr(args[0]);
81
90
  handlerArg = getExpr(args[1]);
82
91
  optsArg = getExpr(args[2]) ?? t.objectExpression([]);
83
92
  } else {
84
- return;
93
+ keyArg = getExpr(args[0]) ?? t.stringLiteral("");
94
+ optsArg = getExpr(args[1]) ?? t.objectExpression([]);
95
+ handlerArg = void 0;
85
96
  }
86
97
  } else {
87
98
  keyArg = getExpr(args[0]) ?? t.stringLiteral("");
88
99
  optsArg = getExpr(args[1]) ?? t.objectExpression([]);
89
100
  }
90
101
  let key = originalName;
91
- if (keyArg && t.isStringLiteral(keyArg)) {
92
- key = keyArg.value.replace(/[^a-z0-9]/gi, "-").replace(/^-+|-+$/g, "");
102
+ if (originalName === "useFetch" || originalName === "useLazyFetch") {
103
+ if (optsArg && t.isObjectExpression(optsArg)) {
104
+ const keyProp = optsArg.properties.find(
105
+ (property) => t.isObjectProperty(property) && t.isIdentifier(property.key) && property.key.name === "key" && t.isStringLiteral(property.value)
106
+ );
107
+ if (keyProp && t.isStringLiteral(keyProp.value)) {
108
+ key = keyProp.value.value;
109
+ } else if (keyArg && t.isStringLiteral(keyArg)) {
110
+ key = keyArg.value.replace(/[^a-z0-9]/gi, "-").replace(/^-+|-+$/g, "");
111
+ }
112
+ } else if (keyArg && t.isStringLiteral(keyArg)) {
113
+ key = keyArg.value.replace(/[^a-z0-9]/gi, "-").replace(/^-+|-+$/g, "");
114
+ }
93
115
  }
94
116
  const loc = path.node.loc;
95
117
  const meta = t.objectExpression([
@@ -98,35 +120,50 @@ function fetchInstrumentPlugin() {
98
120
  t.objectProperty(t.identifier("line"), t.numericLiteral(loc?.start.line ?? 0)),
99
121
  t.objectProperty(t.identifier("originalFn"), t.stringLiteral(originalName))
100
122
  ]);
101
- if (originalName === "useAsyncData" || originalName === "useLazyAsyncData") {
123
+ if ((originalName === "useAsyncData" || originalName === "useLazyAsyncData") && handlerArg) {
102
124
  if (handlerArg) {
103
- const wrappedHandler = t.callExpression(t.identifier("__devFetch"), [
104
- handlerArg,
105
- keyArg ?? t.stringLiteral(key),
106
- meta
107
- ]);
125
+ const wrappedHandler = t.arrowFunctionExpression(
126
+ [t.restElement(t.identifier("args"))],
127
+ t.conditionalExpression(
128
+ t.logicalExpression(
129
+ "&&",
130
+ t.memberExpression(t.identifier("process"), t.identifier("dev")),
131
+ t.memberExpression(t.identifier("process"), t.identifier("client"))
132
+ ),
133
+ t.callExpression(
134
+ t.callExpression(t.identifier("__devFetchHandler"), [handlerArg, keyArg ?? t.stringLiteral(key), meta]),
135
+ [t.spreadElement(t.identifier("args"))]
136
+ ),
137
+ t.callExpression(handlerArg, [t.spreadElement(t.identifier("args"))])
138
+ )
139
+ );
140
+ wrappedHandler.__observatoryTransformed = true;
141
+ needsFetchHandlerHelper = true;
108
142
  if (keyArg) {
109
- path.replaceWith(
110
- t.callExpression(t.identifier(originalName), [
111
- keyArg,
112
- wrappedHandler,
113
- optsArg ?? t.objectExpression([])
114
- ])
115
- );
143
+ const newCall = t.callExpression(t.identifier(originalName), [
144
+ keyArg,
145
+ wrappedHandler,
146
+ optsArg ?? t.objectExpression([])
147
+ ]);
148
+ newCall.__observatoryTransformed = true;
149
+ path.replaceWith(newCall);
116
150
  } else {
117
- path.replaceWith(t.callExpression(t.identifier(originalName), [wrappedHandler]));
151
+ const newCall = t.callExpression(t.identifier(originalName), [wrappedHandler]);
152
+ newCall.__observatoryTransformed = true;
153
+ path.replaceWith(newCall);
118
154
  }
119
155
  modified = true;
120
156
  }
121
157
  } else {
122
- path.replaceWith(
123
- t.callExpression(t.identifier("__devFetch"), [
124
- t.identifier(originalName),
125
- keyArg ?? t.stringLiteral(""),
126
- optsArg ?? t.objectExpression([]),
127
- meta
128
- ])
129
- );
158
+ const newCall = t.callExpression(t.identifier("__devFetchCall"), [
159
+ t.identifier(originalName),
160
+ keyArg ?? t.stringLiteral(""),
161
+ optsArg ?? t.objectExpression([]),
162
+ meta
163
+ ]);
164
+ newCall.__observatoryTransformed = true;
165
+ needsFetchCallHelper = true;
166
+ path.replaceWith(newCall);
130
167
  modified = true;
131
168
  }
132
169
  }
@@ -134,8 +171,12 @@ function fetchInstrumentPlugin() {
134
171
  if (!modified) {
135
172
  return null;
136
173
  }
137
- const importStatement = hasImport ? "" : `import { __devFetch } from 'nuxt-devtools-observatory/runtime/fetch-registry';
138
- `;
174
+ const importNames = [
175
+ needsFetchCallHelper && !hasFetchCallImport ? "__devFetchCall" : "",
176
+ needsFetchHandlerHelper && !hasFetchHandlerImport ? "__devFetchHandler" : ""
177
+ ].filter(Boolean);
178
+ const importStatement = importNames.length ? `import { ${importNames.join(", ")} } from 'nuxt-devtools-observatory/runtime/fetch-registry';
179
+ ` : "";
139
180
  const output = generate$2(ast, { retainLines: true }, scriptCode);
140
181
  let finalCode;
141
182
  if (isVue) {
@@ -505,10 +546,7 @@ const module$1 = defineNuxtModule({
505
546
  }
506
547
  addPlugin(resolver.resolve("./runtime/plugin"));
507
548
  if (options.fetchDashboard) {
508
- nuxt.hook("nitro:config", (nitroConfig) => {
509
- nitroConfig.plugins = nitroConfig.plugins || [];
510
- nitroConfig.plugins.push(resolver.resolve("./nitro/fetch-capture"));
511
- });
549
+ addServerPlugin(resolver.resolve("./runtime/nitro/fetch-capture"));
512
550
  }
513
551
  const CLIENT_PORT = 4949;
514
552
  const clientOrigin = `http://localhost:${CLIENT_PORT}`;
@@ -27,6 +27,11 @@ interface FetchOptions {
27
27
  }) => void;
28
28
  [key: string]: unknown;
29
29
  }
30
+ interface FetchMeta {
31
+ key: string;
32
+ file: string;
33
+ line: number;
34
+ }
30
35
  /**
31
36
  * Sets up the fetch registry, which tracks all fetch requests and their
32
37
  * associated metadata (e.g. duration, size, origin).
@@ -69,9 +74,6 @@ export declare function setupFetchRegistry(): {
69
74
  readonly line?: number | undefined;
70
75
  }>>>;
71
76
  };
72
- export declare function __devFetch(originalFn: (url: string, opts: FetchOptions) => Promise<unknown>, url: string, opts: FetchOptions, meta: {
73
- key: string;
74
- file: string;
75
- line: number;
76
- }): Promise<unknown>;
77
+ export declare function __devFetchHandler(handler: (...args: unknown[]) => unknown, key: unknown, meta: FetchMeta): (...args: unknown[]) => Promise<unknown>;
78
+ export declare function __devFetchCall(originalFn: (url: string, opts: FetchOptions) => Promise<unknown>, url: string, opts: FetchOptions, meta: FetchMeta): Promise<unknown>;
77
79
  export {};
@@ -30,7 +30,49 @@ export function setupFetchRegistry() {
30
30
  }
31
31
  return { register, update, getAll, clear, entries: readonly(entries) };
32
32
  }
33
- export function __devFetch(originalFn, url, opts, meta) {
33
+ export function __devFetchHandler(handler, key, meta) {
34
+ if (!import.meta.dev || !import.meta.client) {
35
+ return (...args) => Promise.resolve(handler(...args));
36
+ }
37
+ const registry = window.__observatory__?.fetch;
38
+ if (!registry) {
39
+ return (...args) => Promise.resolve(handler(...args));
40
+ }
41
+ const normalizedKey = typeof key === "string" ? key : "useAsyncData";
42
+ return (...args) => {
43
+ const id = `${normalizedKey}::${Date.now()}`;
44
+ const startTime = performance.now();
45
+ registry.register({
46
+ id,
47
+ key: normalizedKey,
48
+ url: normalizedKey,
49
+ status: "pending",
50
+ origin: "csr",
51
+ startTime,
52
+ cached: false,
53
+ file: meta.file,
54
+ line: meta.line
55
+ });
56
+ return Promise.resolve(handler(...args)).then((result) => {
57
+ registry.update(id, {
58
+ status: "ok",
59
+ endTime: performance.now(),
60
+ ms: Math.round(performance.now() - startTime),
61
+ payload: result
62
+ });
63
+ return result;
64
+ }).catch((error) => {
65
+ registry.update(id, {
66
+ status: "error",
67
+ endTime: performance.now(),
68
+ ms: Math.round(performance.now() - startTime),
69
+ error
70
+ });
71
+ throw error;
72
+ });
73
+ };
74
+ }
75
+ export function __devFetchCall(originalFn, url, opts, meta) {
34
76
  if (!import.meta.dev || !import.meta.client) {
35
77
  return originalFn(url, opts);
36
78
  }
@@ -0,0 +1,8 @@
1
+ import type { H3Event } from 'h3';
2
+ interface NitroAppLike {
3
+ hooks: {
4
+ hook: (name: 'request' | 'afterResponse', handler: (event: H3Event) => void) => void;
5
+ };
6
+ }
7
+ export default function fetchCapturePlugin(nitroApp: NitroAppLike): void;
8
+ export {};
@@ -0,0 +1,14 @@
1
+ import { setResponseHeader } from "h3";
2
+ export default function fetchCapturePlugin(nitroApp) {
3
+ nitroApp.hooks.hook("request", (event) => {
4
+ ;
5
+ event.__ssrFetchStart = performance.now();
6
+ });
7
+ nitroApp.hooks.hook("afterResponse", (event) => {
8
+ const start = event.__ssrFetchStart;
9
+ if (start) {
10
+ const ms = Math.round(performance.now() - start);
11
+ setResponseHeader(event, "x-observatory-ssr-ms", String(ms));
12
+ }
13
+ });
14
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-devtools-observatory",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "Nuxt DevTools: useFetch Dashboard, provide/inject Graph, Composable Tracker, Render Heatmap",
5
5
  "type": "module",
6
6
  "main": "./dist/module.mjs",
@@ -27,6 +27,7 @@
27
27
  "dev": "nuxi dev playground",
28
28
  "build:client": "vite build --config client/vite.config.ts",
29
29
  "build": "npm run build:client && nuxt-module-build build",
30
+ "prepack": "npm run build",
30
31
  "lint": "eslint .",
31
32
  "format": "prettier --write '**/*.{ts,vue,js,json}' --ignore-path .gitignore && stylelint '**/*.{css,vue}' --ignore-pattern '.nuxt/**' --ignore-pattern '.output/**' --ignore-pattern 'coverage/**' --fix && eslint --fix '**/*.{ts,vue,js}' --ignore-pattern 'coverage/**'",
32
33
  "typecheck": "vue-tsc --noEmit",