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 +1 -1
- package/dist/module.mjs +75 -37
- package/dist/runtime/composables/fetch-registry.d.ts +7 -5
- package/dist/runtime/composables/fetch-registry.js +43 -1
- package/dist/runtime/nitro/fetch-capture.d.ts +8 -0
- package/dist/runtime/nitro/fetch-capture.js +14 -0
- package/package.json +2 -1
package/dist/module.json
CHANGED
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
|
-
|
|
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
|
|
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
|
-
|
|
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 (
|
|
92
|
-
|
|
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.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
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
|
-
|
|
123
|
-
t.
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
73
|
-
|
|
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
|
|
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,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.
|
|
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",
|