@svelte-vitals/vite 0.3.5 → 0.5.0
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/{chunk-3MTVPSNS.js → chunk-AHRJ6LY3.js} +10 -1
- package/dist/hooks/index.js +38 -3
- package/dist/index.d.ts +3 -1
- package/dist/index.js +124 -2
- package/package.json +2 -2
|
@@ -24,7 +24,16 @@ function parseHtmlHead(html) {
|
|
|
24
24
|
for (const link of head.querySelectorAll("link")) {
|
|
25
25
|
const rel = link.getAttribute("rel");
|
|
26
26
|
if (!rel) continue;
|
|
27
|
-
|
|
27
|
+
const asAttr = link.getAttribute("as");
|
|
28
|
+
const hasCrossorigin = link.hasAttribute("crossorigin");
|
|
29
|
+
tags.push({
|
|
30
|
+
kind: "link",
|
|
31
|
+
rel,
|
|
32
|
+
presence: "own",
|
|
33
|
+
value: attrValue(link.getAttribute("href")),
|
|
34
|
+
...asAttr != null ? { hasAs: true, as: asAttr } : {},
|
|
35
|
+
...hasCrossorigin ? { hasCrossorigin: true } : {}
|
|
36
|
+
});
|
|
28
37
|
}
|
|
29
38
|
for (const script of head.querySelectorAll("script")) {
|
|
30
39
|
if (script.getAttribute("type") === "application/ld+json") {
|
package/dist/hooks/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
parseHtmlHead
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-AHRJ6LY3.js";
|
|
4
4
|
|
|
5
5
|
// src/hooks/handle.ts
|
|
6
6
|
import { DEV } from "esm-env";
|
|
@@ -35,7 +35,33 @@ function findingSignature(results, config) {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
// src/hooks/handle.ts
|
|
38
|
-
|
|
38
|
+
function isLoopbackOrigin(origin) {
|
|
39
|
+
try {
|
|
40
|
+
const host = new URL(origin).hostname;
|
|
41
|
+
return host === "localhost" || host === "127.0.0.1" || host === "[::1]" || host === "::1";
|
|
42
|
+
} catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async function postIngest(origin, route, results) {
|
|
47
|
+
if (!isLoopbackOrigin(origin)) {
|
|
48
|
+
if (globalThis.process?.env?.SVELTE_VITALS_DEBUG) {
|
|
49
|
+
console.warn(
|
|
50
|
+
`[svelte-vitals] live UI ingest skipped for non-loopback origin ${origin} \u2014 open the dashboard via localhost`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
await fetch(`${origin}/__svelte-vitals/ingest`, {
|
|
57
|
+
method: "POST",
|
|
58
|
+
headers: { "content-type": "application/json" },
|
|
59
|
+
body: JSON.stringify({ route, results })
|
|
60
|
+
});
|
|
61
|
+
} catch {
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async function analyzeAndWarn(html, route, origin, rules, config, lastSignature) {
|
|
39
65
|
try {
|
|
40
66
|
const { tags, htmlLang } = parseHtmlHead(html);
|
|
41
67
|
const head = { route, source: "rendered", tags, file: route };
|
|
@@ -46,6 +72,7 @@ async function analyzeAndWarn(html, route, rules, config, lastSignature) {
|
|
|
46
72
|
lastSignature.set(route, signature);
|
|
47
73
|
const report = formatDevReport(route, results, config);
|
|
48
74
|
if (report) console.warn(report);
|
|
75
|
+
if (globalThis.process?.env?.SVELTE_VITALS_UI) void postIngest(origin, route, results);
|
|
49
76
|
} catch (err) {
|
|
50
77
|
if (globalThis.process?.env?.SVELTE_VITALS_DEBUG) {
|
|
51
78
|
console.warn("[svelte-vitals] dev analysis failed:", err);
|
|
@@ -65,7 +92,15 @@ function svelteVitalsHandle(options = {}) {
|
|
|
65
92
|
return resolve(event, {
|
|
66
93
|
transformPageChunk: ({ html, done }) => {
|
|
67
94
|
buffer += html;
|
|
68
|
-
if (done)
|
|
95
|
+
if (done)
|
|
96
|
+
void analyzeAndWarn(
|
|
97
|
+
buffer,
|
|
98
|
+
event.route.id ?? event.url.pathname,
|
|
99
|
+
event.url.origin,
|
|
100
|
+
rules,
|
|
101
|
+
config,
|
|
102
|
+
lastSignature
|
|
103
|
+
);
|
|
69
104
|
return html;
|
|
70
105
|
}
|
|
71
106
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -15,8 +15,10 @@ interface SvelteVitalsOptions {
|
|
|
15
15
|
outFile?: string;
|
|
16
16
|
/** Override the prerendered-pages directory (default: .svelte-kit/output/prerendered/pages). */
|
|
17
17
|
prerenderDir?: string;
|
|
18
|
+
/** Serve a live dashboard at /__svelte-vitals/ during `vite dev` (requires svelteVitalsHandle in hooks.server.ts). */
|
|
19
|
+
ui?: boolean;
|
|
18
20
|
}
|
|
19
21
|
/** svelte-vitals Vite/SvelteKit plugin. */
|
|
20
|
-
declare function svelteVitals(options?: SvelteVitalsOptions): Plugin;
|
|
22
|
+
declare function svelteVitals(options?: SvelteVitalsOptions): Plugin | Plugin[];
|
|
21
23
|
|
|
22
24
|
export { type SvelteVitalsOptions, svelteVitals };
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
parseHtmlHead
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-AHRJ6LY3.js";
|
|
4
4
|
|
|
5
5
|
// src/plugin.ts
|
|
6
6
|
import { existsSync } from "fs";
|
|
7
7
|
import { writeFile, mkdir } from "fs/promises";
|
|
8
8
|
import { join as join3, isAbsolute, dirname } from "path";
|
|
9
|
+
import { defineConfig as defineConfig2 } from "@svelte-vitals/core";
|
|
9
10
|
|
|
10
11
|
// src/analyze.ts
|
|
11
12
|
import {
|
|
@@ -117,11 +118,113 @@ async function analyze(prerenderPagesDir, cwd, options) {
|
|
|
117
118
|
};
|
|
118
119
|
}
|
|
119
120
|
|
|
121
|
+
// src/ui/store.ts
|
|
122
|
+
function createStore() {
|
|
123
|
+
const byRoute = /* @__PURE__ */ new Map();
|
|
124
|
+
const subs = /* @__PURE__ */ new Set();
|
|
125
|
+
return {
|
|
126
|
+
set(route, results) {
|
|
127
|
+
byRoute.set(
|
|
128
|
+
route,
|
|
129
|
+
results.map((r) => r.route ? r : { ...r, route })
|
|
130
|
+
);
|
|
131
|
+
for (const fn of subs) fn();
|
|
132
|
+
},
|
|
133
|
+
snapshot() {
|
|
134
|
+
return [...byRoute.values()].flat();
|
|
135
|
+
},
|
|
136
|
+
subscribe(fn) {
|
|
137
|
+
subs.add(fn);
|
|
138
|
+
return () => subs.delete(fn);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/ui/serve.ts
|
|
144
|
+
import { buildHtmlDocument, buildJsonReport } from "@svelte-vitals/core";
|
|
145
|
+
var LIVE_SCRIPT = `<script data-live>
|
|
146
|
+
(function(){
|
|
147
|
+
var es=new EventSource('/__svelte-vitals/events');
|
|
148
|
+
es.addEventListener('update',function(){
|
|
149
|
+
fetch('/__svelte-vitals/').then(function(r){return r.text();}).then(function(html){
|
|
150
|
+
var doc=new DOMParser().parseFromString(html,'text/html');
|
|
151
|
+
var next=doc.querySelector('.wrap'),cur=document.querySelector('.wrap');
|
|
152
|
+
if(next&&cur)cur.replaceWith(next);
|
|
153
|
+
var scripts=doc.querySelectorAll('body > script:not([data-live])');
|
|
154
|
+
var core=scripts[scripts.length-1];
|
|
155
|
+
if(core){var s=document.createElement('script');s.textContent=core.textContent;document.body.appendChild(s);s.remove();}
|
|
156
|
+
}).catch(function(){});
|
|
157
|
+
});
|
|
158
|
+
})();
|
|
159
|
+
</script>`;
|
|
160
|
+
function renderDashboard(results, config, meta) {
|
|
161
|
+
const html = buildHtmlDocument(buildJsonReport(results, config, meta), meta);
|
|
162
|
+
return html.replace("</body>", LIVE_SCRIPT + "</body>");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// src/ui/middleware.ts
|
|
166
|
+
var SEVERITIES = /* @__PURE__ */ new Set(["critical", "warning", "info"]);
|
|
167
|
+
function isResultLike(x) {
|
|
168
|
+
if (typeof x !== "object" || x === null) return false;
|
|
169
|
+
const r = x;
|
|
170
|
+
const d = r.detection;
|
|
171
|
+
return typeof r.id === "string" && typeof r.message === "string" && typeof r.severity === "string" && SEVERITIES.has(r.severity) && typeof d === "object" && d !== null && typeof d.presence === "string" && typeof d.value === "string";
|
|
172
|
+
}
|
|
173
|
+
function installUiMiddleware(server, config, version) {
|
|
174
|
+
const store = createStore();
|
|
175
|
+
const clients = /* @__PURE__ */ new Set();
|
|
176
|
+
store.subscribe(() => {
|
|
177
|
+
for (const res of clients) {
|
|
178
|
+
try {
|
|
179
|
+
res.write("event: update\ndata: {}\n\n");
|
|
180
|
+
} catch {
|
|
181
|
+
clients.delete(res);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
server.httpServer?.once("close", () => {
|
|
186
|
+
for (const res of clients) res.end();
|
|
187
|
+
clients.clear();
|
|
188
|
+
});
|
|
189
|
+
server.middlewares.use("/__svelte-vitals", (req, res) => {
|
|
190
|
+
const url = req.url ?? "/";
|
|
191
|
+
if (req.method === "POST" && url.startsWith("/ingest")) {
|
|
192
|
+
const chunks = [];
|
|
193
|
+
req.on("data", (c) => chunks.push(c));
|
|
194
|
+
req.on("end", () => {
|
|
195
|
+
try {
|
|
196
|
+
const { route, results } = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
197
|
+
if (typeof route === "string" && Array.isArray(results)) {
|
|
198
|
+
store.set(route, results.filter(isResultLike));
|
|
199
|
+
}
|
|
200
|
+
} catch {
|
|
201
|
+
}
|
|
202
|
+
res.statusCode = 204;
|
|
203
|
+
res.end();
|
|
204
|
+
});
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (url.startsWith("/events")) {
|
|
208
|
+
res.writeHead(200, {
|
|
209
|
+
"Content-Type": "text/event-stream",
|
|
210
|
+
"Cache-Control": "no-cache",
|
|
211
|
+
Connection: "keep-alive"
|
|
212
|
+
});
|
|
213
|
+
res.write("\n");
|
|
214
|
+
clients.add(res);
|
|
215
|
+
req.on("close", () => clients.delete(res));
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
res.setHeader("Content-Type", "text/html");
|
|
219
|
+
res.end(renderDashboard(store.snapshot(), config, { version }));
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
120
223
|
// src/plugin.ts
|
|
121
224
|
var DEFAULT_PRERENDER_DIR = ".svelte-kit/output/prerendered/pages";
|
|
122
225
|
function svelteVitals(options = {}) {
|
|
123
226
|
let root = options.cwd ?? process.cwd();
|
|
124
|
-
|
|
227
|
+
const buildPlugin = {
|
|
125
228
|
name: "svelte-vitals",
|
|
126
229
|
apply: "build",
|
|
127
230
|
enforce: "post",
|
|
@@ -154,6 +257,25 @@ function svelteVitals(options = {}) {
|
|
|
154
257
|
}
|
|
155
258
|
}
|
|
156
259
|
};
|
|
260
|
+
if (!options.ui) return buildPlugin;
|
|
261
|
+
const uiPlugin = {
|
|
262
|
+
name: "svelte-vitals:ui",
|
|
263
|
+
apply: "serve",
|
|
264
|
+
configureServer(server) {
|
|
265
|
+
process.env.SVELTE_VITALS_UI = "1";
|
|
266
|
+
server.httpServer?.once("close", () => {
|
|
267
|
+
delete process.env.SVELTE_VITALS_UI;
|
|
268
|
+
});
|
|
269
|
+
const config = defineConfig2({
|
|
270
|
+
treatDynamicAs: options.treatDynamicAs ?? "pass",
|
|
271
|
+
metaComponents: options.metaComponents ?? [],
|
|
272
|
+
rules: options.rules ?? {},
|
|
273
|
+
failOn: options.failOn ?? "critical"
|
|
274
|
+
});
|
|
275
|
+
installUiMiddleware(server, config, readPackageVersion());
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
return [buildPlugin, uiPlugin];
|
|
157
279
|
}
|
|
158
280
|
export {
|
|
159
281
|
svelteVitals
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@svelte-vitals/vite",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Vite/SvelteKit plugin for svelte-vitals — analyzes prerendered HTML during vite build.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"esm-env": "^1.2.2",
|
|
43
43
|
"node-html-parser": "^6.1.13",
|
|
44
44
|
"tinyglobby": "^0.2.17",
|
|
45
|
-
"@svelte-vitals/core": "0.
|
|
45
|
+
"@svelte-vitals/core": "0.12.0"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"@sveltejs/kit": "^2.0.0",
|