@svelte-vitals/vite 0.16.0 → 0.18.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/index.js +14 -642
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -105,7 +105,10 @@ async function collectRenderedProject(cwd, htmlLang) {
|
|
|
105
105
|
import { readFile as readFile3, access as access2 } from "fs/promises";
|
|
106
106
|
import { join as join3 } from "path";
|
|
107
107
|
import { glob as glob2 } from "tinyglobby";
|
|
108
|
-
import {
|
|
108
|
+
import {
|
|
109
|
+
collectComponentFacts as collect,
|
|
110
|
+
collectKitModuleFacts as collectKit
|
|
111
|
+
} from "@svelte-vitals/core";
|
|
109
112
|
var nodeRuntime = {
|
|
110
113
|
readFile: (path) => readFile3(path, "utf8"),
|
|
111
114
|
async exists(path) {
|
|
@@ -122,6 +125,9 @@ var nodeRuntime = {
|
|
|
122
125
|
function collectComponentFacts(root) {
|
|
123
126
|
return collect(nodeRuntime, root);
|
|
124
127
|
}
|
|
128
|
+
function collectKitModuleFacts(root) {
|
|
129
|
+
return collectKit(nodeRuntime, root);
|
|
130
|
+
}
|
|
125
131
|
|
|
126
132
|
// src/version.ts
|
|
127
133
|
import { readFileSync } from "fs";
|
|
@@ -159,8 +165,9 @@ async function analyze(prerenderPagesDir, cwd, options) {
|
|
|
159
165
|
const { heads, headings, images, htmlLang } = await collectRenderedHeads(prerenderPagesDir);
|
|
160
166
|
const project = await collectRenderedProject(cwd, htmlLang);
|
|
161
167
|
const components = await collectComponentFacts(cwd);
|
|
168
|
+
const kitModules = await collectKitModuleFacts(cwd);
|
|
162
169
|
const results = applyRuleSeverities(
|
|
163
|
-
await runRules(selectRules(allRules, config), { heads, headings, images, project, components, config }),
|
|
170
|
+
await runRules(selectRules(allRules, config), { heads, headings, images, project, components, config, kitModules }),
|
|
164
171
|
config
|
|
165
172
|
);
|
|
166
173
|
const { score } = computeScore(results, config);
|
|
@@ -183,6 +190,9 @@ Scanned ${components.length} component(s) under src/ for Correctness/Security/Ar
|
|
|
183
190
|
};
|
|
184
191
|
}
|
|
185
192
|
|
|
193
|
+
// src/ui/middleware.ts
|
|
194
|
+
import { renderAppShell } from "@svelte-vitals/core";
|
|
195
|
+
|
|
186
196
|
// src/ui/snapshot.ts
|
|
187
197
|
import { buildJsonReport, safeHref } from "@svelte-vitals/core";
|
|
188
198
|
function sanitizeDocsUrl(issue) {
|
|
@@ -203,649 +213,11 @@ function buildSnapshot(store, config, meta) {
|
|
|
203
213
|
badges: store.badges(),
|
|
204
214
|
analyzing: store.isAnalyzing(),
|
|
205
215
|
sequence: store.sequence(),
|
|
216
|
+
live: true,
|
|
206
217
|
meta
|
|
207
218
|
};
|
|
208
219
|
}
|
|
209
220
|
|
|
210
|
-
// src/ui/dashboard-style.ts
|
|
211
|
-
var DASHBOARD_STYLE = `
|
|
212
|
-
:root{--ground:#f6f7f9;--panel:#fff;--ink:#0c1322;--muted:#5a6472;--faint:#8c95a3;--line:#e4e7ec;--line-strong:#d3d8e0;--accent:#ff3e00;--good:#2fa968;--warn:#e8a317;--poor:#e5484d;--code-bg:#0e1525;--code-ink:#e7ecf4;--active-bg:#0c1322;--active-ink:#fff;--mono:ui-monospace,"SF Mono","JetBrains Mono",Menlo,Consolas,monospace;--sans:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif}
|
|
213
|
-
:root[data-theme="dark"]{--ground:#0b0e14;--panel:#12161f;--ink:#e7ecf4;--muted:#9aa4b2;--faint:#6b7484;--line:#232838;--line-strong:#2d3345;--code-bg:#05070c;--code-ink:#e7ecf4;--active-bg:#e7ecf4;--active-ink:#0b0e14}
|
|
214
|
-
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){--ground:#0b0e14;--panel:#12161f;--ink:#e7ecf4;--muted:#9aa4b2;--faint:#6b7484;--line:#232838;--line-strong:#2d3345;--code-bg:#05070c;--code-ink:#e7ecf4;--active-bg:#e7ecf4;--active-ink:#0b0e14}}
|
|
215
|
-
*{box-sizing:border-box}
|
|
216
|
-
html,body{margin:0;height:100%}
|
|
217
|
-
body{background:var(--ground);color:var(--ink);font-family:var(--sans);line-height:1.5;-webkit-font-smoothing:antialiased}
|
|
218
|
-
.dv-app{display:grid;grid-template-rows:auto 1fr;grid-template-columns:280px 1fr;grid-template-areas:"top top" "side main";height:100vh}
|
|
219
|
-
.dv-topbar{grid-area:top;border-bottom:1px solid var(--line);background:var(--panel)}
|
|
220
|
-
.dv-topbar-inner{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;padding:12px 20px}
|
|
221
|
-
.dv-brand{display:flex;align-items:center;background:none;border:none;padding:0;cursor:pointer;border-radius:6px}
|
|
222
|
-
.dv-brand svg{height:28px;width:auto;display:block}
|
|
223
|
-
.dv-brand:focus-visible{outline:2px solid var(--accent);outline-offset:4px}
|
|
224
|
-
.dv-meta{font-family:var(--mono);font-size:12px;color:var(--muted);display:flex;gap:12px;flex-wrap:wrap}
|
|
225
|
-
.dv-status{display:flex;align-items:center;gap:10px}
|
|
226
|
-
.dv-analyzing{font-size:12px;color:var(--accent);font-weight:600}
|
|
227
|
-
.dv-conn{width:8px;height:8px;border-radius:50%;background:var(--faint);display:inline-block}
|
|
228
|
-
.dv-conn-connected{background:var(--good)}
|
|
229
|
-
.dv-conn-reconnecting{background:var(--warn)}
|
|
230
|
-
.dv-menu-toggle{display:none;border:1px solid var(--line-strong);background:var(--panel);color:var(--ink);border-radius:8px;width:28px;height:28px;cursor:pointer}
|
|
231
|
-
.dv-theme-toggle{border:1px solid var(--line-strong);background:var(--panel);color:var(--ink);border-radius:999px;width:28px;height:28px;cursor:pointer}
|
|
232
|
-
.dv-theme-toggle:focus-visible,.dv-menu-toggle:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
|
|
233
|
-
.dv-sidebar{grid-area:side;border-right:1px solid var(--line);background:var(--panel);overflow-y:auto}
|
|
234
|
-
.dv-sidebar-inner{display:flex;flex-direction:column;gap:10px;padding:14px}
|
|
235
|
-
.dv-search{font:inherit;font-size:13px;padding:7px 10px;border:1px solid var(--line-strong);border-radius:8px;background:var(--ground);color:var(--ink)}
|
|
236
|
-
.dv-sort{font:inherit;font-size:12.5px;padding:6px 8px;border:1px solid var(--line-strong);border-radius:8px;background:var(--ground);color:var(--ink)}
|
|
237
|
-
.dv-nav{display:flex;flex-direction:column;gap:2px}
|
|
238
|
-
.dv-nav-item{display:flex;flex-direction:column;gap:4px;padding:8px 10px;border-radius:8px;cursor:pointer}
|
|
239
|
-
.dv-nav-item:hover{background:var(--ground)}
|
|
240
|
-
.dv-nav-item.active{background:var(--active-bg);color:var(--active-ink)}
|
|
241
|
-
.dv-nav-item:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
|
|
242
|
-
.dv-nav-label{font-family:var(--mono);font-size:13px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
243
|
-
.dv-nav-meta{display:flex;align-items:center;gap:8px;font-size:11.5px;color:var(--muted)}
|
|
244
|
-
.dv-nav-item.active .dv-nav-meta{color:inherit}
|
|
245
|
-
.dv-nav-score{font-family:var(--mono);font-weight:700}
|
|
246
|
-
.dv-badge{font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;padding:1px 6px;border-radius:999px}
|
|
247
|
-
.dv-badge-measured{background:rgba(47,169,104,.16);color:var(--good)}
|
|
248
|
-
.dv-badge-static{background:rgba(140,149,163,.2);color:var(--muted)}
|
|
249
|
-
.dv-detail{grid-area:main;overflow-y:auto;padding:24px 28px 80px}
|
|
250
|
-
.dv-gauge{position:relative;width:132px;height:132px;margin-bottom:20px}
|
|
251
|
-
.dv-gauge svg{position:absolute;inset:0;transform:rotate(-90deg)}
|
|
252
|
-
.dv-gauge-track{stroke:var(--line)}
|
|
253
|
-
.dv-gauge-num{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center}
|
|
254
|
-
.dv-gauge-num strong{font-family:var(--mono);font-size:36px;font-weight:600}
|
|
255
|
-
.dv-gauge-num span{font-size:10px;text-transform:uppercase;letter-spacing:.14em;color:var(--muted)}
|
|
256
|
-
.dv-cats{display:flex;gap:22px;flex-wrap:wrap;margin-bottom:20px}
|
|
257
|
-
.dv-cat{min-width:180px;flex:1}
|
|
258
|
-
.dv-cat-top{display:flex;justify-content:space-between;font-size:13px;margin-bottom:6px}
|
|
259
|
-
.dv-bar{height:7px;border-radius:999px;background:var(--line);overflow:hidden}
|
|
260
|
-
.dv-bar>i{display:block;height:100%;border-radius:999px}
|
|
261
|
-
.dv-filters{display:flex;gap:8px;flex-wrap:wrap;margin:16px 0}
|
|
262
|
-
.dv-chip{font:inherit;font-size:12.5px;font-weight:600;cursor:pointer;background:var(--panel);border:1px solid var(--line-strong);color:var(--muted);padding:5px 12px;border-radius:999px}
|
|
263
|
-
.dv-chip[aria-pressed="true"]{background:var(--active-bg);border-color:var(--active-bg);color:var(--active-ink)}
|
|
264
|
-
.dv-chip:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
|
|
265
|
-
.dv-section h2{font-size:12px;text-transform:uppercase;letter-spacing:.12em;color:var(--muted);margin:24px 0 12px}
|
|
266
|
-
.dv-detail-header{display:flex;align-items:center;gap:12px;margin-bottom:14px;flex-wrap:wrap}
|
|
267
|
-
.dv-route-path{font-family:var(--mono);font-size:16px;font-weight:600}
|
|
268
|
-
.dv-score-chip{font-family:var(--mono);font-weight:700}
|
|
269
|
-
.dv-finding{background:var(--panel);border:1px solid var(--line);border-left-width:3px;border-radius:10px;padding:16px 18px;margin:0 0 12px}
|
|
270
|
-
.dv-finding-critical{border-left-color:var(--poor)}
|
|
271
|
-
.dv-finding-warning{border-left-color:var(--warn)}
|
|
272
|
-
.dv-finding-info{border-left-color:var(--faint)}
|
|
273
|
-
.dv-f-head{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
|
|
274
|
-
.dv-ruleid{font-family:var(--mono);font-size:12px;font-weight:600;background:var(--ground);padding:2px 8px;border-radius:6px}
|
|
275
|
-
.dv-f-title{font-weight:650;font-size:15px}
|
|
276
|
-
.dv-sev-tag{margin-left:auto;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.08em}
|
|
277
|
-
.dv-sev-critical{color:var(--poor)}
|
|
278
|
-
.dv-sev-warning{color:var(--warn)}
|
|
279
|
-
.dv-sev-info{color:var(--faint)}
|
|
280
|
-
.dv-f-route{display:block;font:inherit;font-family:var(--mono);font-size:12.5px;font-weight:600;color:var(--accent);background:none;border:none;padding:0;margin:8px 0 0;cursor:pointer;text-align:left}
|
|
281
|
-
.dv-f-route:hover{text-decoration:underline}
|
|
282
|
-
.dv-f-route:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
|
|
283
|
-
.dv-f-loc{font-family:var(--mono);font-size:12.5px;color:var(--muted);margin:8px 0 0}
|
|
284
|
-
.dv-f-rec{font-size:14px;margin:10px 0 0}
|
|
285
|
-
.dv-fix{margin:12px 0 0;background:var(--code-bg);border-radius:8px;overflow:hidden}
|
|
286
|
-
.dv-fix-label{font-family:var(--mono);font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:#8da0bd;padding:8px 14px 0}
|
|
287
|
-
.dv-fix pre{margin:0;padding:8px 14px 14px;overflow-x:auto}
|
|
288
|
-
.dv-fix code{font-family:var(--mono);font-size:12.5px;color:var(--code-ink);line-height:1.65;white-space:pre}
|
|
289
|
-
.tok-kw{color:#ff7ab8}
|
|
290
|
-
.tok-str{color:#9ece6a}
|
|
291
|
-
.tok-num{color:#ff9e64}
|
|
292
|
-
.tok-cm{color:#6b7280;font-style:italic}
|
|
293
|
-
.tok-id{color:var(--code-ink)}
|
|
294
|
-
.tok-pn{color:#8da0bd}
|
|
295
|
-
.dv-f-link{display:inline-block;margin-top:12px;font-size:13px;font-weight:600;color:var(--accent);text-decoration:none}
|
|
296
|
-
.dv-f-link:hover{text-decoration:underline}
|
|
297
|
-
.dv-ai-prompt{margin-top:12px;border:1px solid var(--line);border-radius:8px;overflow:hidden}
|
|
298
|
-
.dv-ai-prompt>summary{cursor:pointer;list-style:none;padding:8px 12px;font-size:12px;font-weight:600;color:var(--muted);display:flex;align-items:center;gap:6px;user-select:none}
|
|
299
|
-
.dv-ai-prompt>summary::-webkit-details-marker{display:none}
|
|
300
|
-
.dv-ai-prompt>summary::before{content:"\u25B8";display:inline-block;transition:transform .15s ease}
|
|
301
|
-
.dv-ai-prompt[open]>summary::before{transform:rotate(90deg)}
|
|
302
|
-
.dv-ai-prompt-body{padding:0 12px 12px;display:flex;flex-direction:column;gap:8px}
|
|
303
|
-
.dv-ai-prompt-pre{margin:0;padding:10px 12px;background:var(--code-bg);color:var(--code-ink);border-radius:8px;font-family:var(--mono);font-size:12px;line-height:1.6;white-space:pre-wrap;word-break:break-word;max-height:280px;overflow-y:auto}
|
|
304
|
-
.dv-ai-copy-btn{align-self:flex-start;font:inherit;font-size:12px;font-weight:600;cursor:pointer;background:var(--panel);border:1px solid var(--line-strong);color:var(--ink);padding:5px 12px;border-radius:999px}
|
|
305
|
-
.dv-ai-copy-btn:hover{border-color:var(--faint)}
|
|
306
|
-
.dv-ai-copy-btn:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
|
|
307
|
-
.dv-empty{color:var(--muted);font-size:13px}
|
|
308
|
-
@media (max-width:640px){.dv-app{grid-template-columns:1fr;grid-template-areas:"top" "main"}.dv-menu-toggle{display:inline-flex}.dv-sidebar{position:fixed;inset:0 20% 0 0;transform:translateX(-100%);transition:transform .2s ease;z-index:10}.dv-sidebar.open{transform:translateX(0)}}
|
|
309
|
-
@media (prefers-reduced-motion:reduce){*{transition:none!important}}
|
|
310
|
-
`;
|
|
311
|
-
|
|
312
|
-
// src/ui/dashboard-script.ts
|
|
313
|
-
var DASHBOARD_SCRIPT = `
|
|
314
|
-
(function(){
|
|
315
|
-
var BAND_COLOR = { good: '#2fa968', warn: '#e8a317', poor: '#e5484d' };
|
|
316
|
-
function scoreBand(score) { return score >= 90 ? 'good' : score >= 50 ? 'warn' : 'poor'; }
|
|
317
|
-
|
|
318
|
-
// Same mark as the docs site's hero wordmark (docs/public/wordmark.svg) \u2014 an inline
|
|
319
|
-
// copy, not an <img src>, since the dashboard is a single self-contained HTML response
|
|
320
|
-
// with no other static assets to serve alongside it. Fixed brand colors (not CSS custom
|
|
321
|
-
// properties), matching the docs usage: the wordmark reads the same in both themes.
|
|
322
|
-
var WORDMARK_SVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 380 56" role="img" aria-labelledby="dv-wordmark-title"><title id="dv-wordmark-title">svelte-vitals</title><defs><clipPath id="dv-wordmark-clip"><rect x="2" y="2" width="52" height="52" rx="14"/></clipPath></defs><rect x="2" y="2" width="52" height="52" rx="14" fill="#FF3E00"/><polyline clip-path="url(#dv-wordmark-clip)" points="4,28 15,28 17.5,23.5 20,28 23,28 26,7 29,49 32,28 35,28 37,24.5 39.5,28 52,28" fill="none" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/><text x="70" y="38" font-family="ui-sans-serif, system-ui, -apple-system, \\'Segoe UI\\', Roboto, sans-serif" font-size="30" font-weight="700" fill="#FF3E00">svelte-vitals</text></svg>';
|
|
323
|
-
|
|
324
|
-
function h(tag, attrs, kids) {
|
|
325
|
-
var n = document.createElement(tag);
|
|
326
|
-
if (attrs) {
|
|
327
|
-
for (var k in attrs) {
|
|
328
|
-
if (!Object.prototype.hasOwnProperty.call(attrs, k)) continue;
|
|
329
|
-
var v = attrs[k];
|
|
330
|
-
if (v === undefined || v === null || v === false) continue;
|
|
331
|
-
if (k === 'class') n.className = v;
|
|
332
|
-
else if (k === 'text') n.textContent = v;
|
|
333
|
-
else if (k.indexOf('on') === 0 && typeof v === 'function') n.addEventListener(k.slice(2), v);
|
|
334
|
-
else n.setAttribute(k, v === true ? '' : String(v));
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
(kids || []).forEach(function (c) {
|
|
338
|
-
if (c === undefined || c === null || c === false) return;
|
|
339
|
-
n.appendChild(typeof c === 'string' ? document.createTextNode(c) : c);
|
|
340
|
-
});
|
|
341
|
-
return n;
|
|
342
|
-
}
|
|
343
|
-
function clear(n) { while (n.firstChild) n.removeChild(n.firstChild); }
|
|
344
|
-
function mount(id, node) { var el = document.getElementById(id); clear(el); el.appendChild(node); }
|
|
345
|
-
|
|
346
|
-
var HL_KEYWORDS = ['import','export','from','const','let','var','function','return','if','else','for','while','class','new','await','async','default','type','interface','extends','implements','this','typeof','instanceof','of','in','true','false','null','undefined'];
|
|
347
|
-
var HL_LANGS = { js: 1, javascript: 1, ts: 1, typescript: 1, svelte: 1, html: 1, css: 1 };
|
|
348
|
-
|
|
349
|
-
function highlightTokens(code) {
|
|
350
|
-
var tokens = [];
|
|
351
|
-
var i = 0;
|
|
352
|
-
var n = code.length;
|
|
353
|
-
var reIdent = /[A-Za-z_$][A-Za-z0-9_$]*/y;
|
|
354
|
-
var reNum = /\\d+(\\.\\d+)?/y;
|
|
355
|
-
while (i < n) {
|
|
356
|
-
var ch = code[i];
|
|
357
|
-
if (ch === '/' && code[i + 1] === '/') {
|
|
358
|
-
var end = code.indexOf('\\n', i);
|
|
359
|
-
if (end === -1) end = n;
|
|
360
|
-
tokens.push({ text: code.slice(i, end), cls: 'cm' });
|
|
361
|
-
i = end;
|
|
362
|
-
continue;
|
|
363
|
-
}
|
|
364
|
-
if (ch === '/' && code[i + 1] === '*') {
|
|
365
|
-
var end2 = code.indexOf('*/', i + 2);
|
|
366
|
-
end2 = end2 === -1 ? n : end2 + 2;
|
|
367
|
-
tokens.push({ text: code.slice(i, end2), cls: 'cm' });
|
|
368
|
-
i = end2;
|
|
369
|
-
continue;
|
|
370
|
-
}
|
|
371
|
-
if (ch === '"' || ch === "'" || ch === '\`') {
|
|
372
|
-
var quote = ch;
|
|
373
|
-
var j = i + 1;
|
|
374
|
-
while (j < n && code[j] !== quote) {
|
|
375
|
-
if (code[j] === '\\\\') j++;
|
|
376
|
-
j++;
|
|
377
|
-
}
|
|
378
|
-
j = Math.min(j + 1, n);
|
|
379
|
-
tokens.push({ text: code.slice(i, j), cls: 'str' });
|
|
380
|
-
i = j;
|
|
381
|
-
continue;
|
|
382
|
-
}
|
|
383
|
-
reIdent.lastIndex = i;
|
|
384
|
-
var mIdent = reIdent.exec(code);
|
|
385
|
-
if (mIdent && mIdent.index === i) {
|
|
386
|
-
var word = mIdent[0];
|
|
387
|
-
tokens.push({ text: word, cls: HL_KEYWORDS.indexOf(word) !== -1 ? 'kw' : 'id' });
|
|
388
|
-
i += word.length;
|
|
389
|
-
continue;
|
|
390
|
-
}
|
|
391
|
-
reNum.lastIndex = i;
|
|
392
|
-
var mNum = reNum.exec(code);
|
|
393
|
-
if (mNum && mNum.index === i) {
|
|
394
|
-
tokens.push({ text: mNum[0], cls: 'num' });
|
|
395
|
-
i += mNum[0].length;
|
|
396
|
-
continue;
|
|
397
|
-
}
|
|
398
|
-
tokens.push({ text: ch, cls: 'pn' });
|
|
399
|
-
i += 1;
|
|
400
|
-
}
|
|
401
|
-
return tokens;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
function renderFixSnippet(fix) {
|
|
405
|
-
var pre = h('pre', null, []);
|
|
406
|
-
var code = h('code', null, []);
|
|
407
|
-
var lang = (fix.lang || 'svelte').toLowerCase();
|
|
408
|
-
if (HL_LANGS[lang]) {
|
|
409
|
-
highlightTokens(fix.snippet).forEach(function (t) {
|
|
410
|
-
code.appendChild(h('span', { class: 'tok-' + t.cls, text: t.text }, []));
|
|
411
|
-
});
|
|
412
|
-
} else {
|
|
413
|
-
code.textContent = fix.snippet;
|
|
414
|
-
}
|
|
415
|
-
pre.appendChild(code);
|
|
416
|
-
return pre;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// Plain-text, copy-pasteable prompt for a single finding \u2014 same ingredients as the
|
|
420
|
-
// agent reporter's per-finding block (rule id, location, recommendation, fix, docs),
|
|
421
|
-
// reshaped for a standalone request rather than a whole-project remediation doc.
|
|
422
|
-
function buildAiPrompt(issue, route) {
|
|
423
|
-
var lines = ['Fix this svelte-vitals finding:', ''];
|
|
424
|
-
lines.push('- Rule: ' + issue.id + ' \u2014 ' + issue.title + ' (' + issue.severity + ')');
|
|
425
|
-
if (route) lines.push('- Route: ' + route);
|
|
426
|
-
if (issue.location) {
|
|
427
|
-
lines.push('- Location: ' + issue.location + (issue.line !== undefined ? ':' + issue.line : ''));
|
|
428
|
-
}
|
|
429
|
-
if (issue.recommendation) lines.push('- Recommendation: ' + issue.recommendation);
|
|
430
|
-
if (issue.fix) {
|
|
431
|
-
lines.push('- Fix: ' + issue.fix.description);
|
|
432
|
-
if (issue.fix.snippet) {
|
|
433
|
-
lines.push('', '\`\`\`' + (issue.fix.lang || 'svelte'), issue.fix.snippet, '\`\`\`');
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
if (issue.docsUrl) lines.push('- Docs: ' + issue.docsUrl);
|
|
437
|
-
lines.push(
|
|
438
|
-
'',
|
|
439
|
-
'After fixing, re-run \`svelte-vitals --diff\` (or revisit this route) to confirm ' +
|
|
440
|
-
issue.id +
|
|
441
|
-
' passes' +
|
|
442
|
-
(route ? ' for ' + route : '') +
|
|
443
|
-
'.'
|
|
444
|
-
);
|
|
445
|
-
return lines.join('\\n');
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
function copyToClipboard(text, btn) {
|
|
449
|
-
var original = 'Copy';
|
|
450
|
-
function reset(label) {
|
|
451
|
-
btn.textContent = label;
|
|
452
|
-
setTimeout(function () { btn.textContent = original; }, 1500);
|
|
453
|
-
}
|
|
454
|
-
function done() { reset('Copied!'); }
|
|
455
|
-
function fail() { reset('Copy failed'); }
|
|
456
|
-
function fallbackCopy() {
|
|
457
|
-
var ta = document.createElement('textarea');
|
|
458
|
-
ta.value = text;
|
|
459
|
-
ta.setAttribute('readonly', '');
|
|
460
|
-
ta.style.position = 'fixed';
|
|
461
|
-
ta.style.opacity = '0';
|
|
462
|
-
document.body.appendChild(ta);
|
|
463
|
-
ta.select();
|
|
464
|
-
var ok = false;
|
|
465
|
-
try { ok = document.execCommand('copy'); } catch (e) {}
|
|
466
|
-
document.body.removeChild(ta);
|
|
467
|
-
return ok;
|
|
468
|
-
}
|
|
469
|
-
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
470
|
-
navigator.clipboard.writeText(text).then(done, function () { fallbackCopy() ? done() : fail(); });
|
|
471
|
-
} else {
|
|
472
|
-
fallbackCopy() ? done() : fail();
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
function renderAiPrompt(issue, route) {
|
|
477
|
-
var text = buildAiPrompt(issue, route);
|
|
478
|
-
var btn = h('button', { type: 'button', class: 'dv-ai-copy-btn', text: 'Copy' }, []);
|
|
479
|
-
btn.addEventListener('click', function () { copyToClipboard(text, btn); });
|
|
480
|
-
return h('details', { class: 'dv-ai-prompt' }, [
|
|
481
|
-
h('summary', { text: 'AI Prompt' }, []),
|
|
482
|
-
h('div', { class: 'dv-ai-prompt-body' }, [
|
|
483
|
-
h('pre', { class: 'dv-ai-prompt-pre', text: text }, []),
|
|
484
|
-
btn
|
|
485
|
-
])
|
|
486
|
-
]);
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
var state = {
|
|
490
|
-
snapshot: null,
|
|
491
|
-
selected: 'overview',
|
|
492
|
-
search: '',
|
|
493
|
-
sort: 'score-asc',
|
|
494
|
-
filter: 'all',
|
|
495
|
-
theme: initialTheme(),
|
|
496
|
-
connection: 'connecting',
|
|
497
|
-
routeBySlug: {}
|
|
498
|
-
};
|
|
499
|
-
|
|
500
|
-
function initialTheme() {
|
|
501
|
-
try {
|
|
502
|
-
var stored = localStorage.getItem('svelte-vitals-theme');
|
|
503
|
-
if (stored === 'dark' || stored === 'light') return stored;
|
|
504
|
-
} catch (e) {}
|
|
505
|
-
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
506
|
-
}
|
|
507
|
-
function applyTheme() { document.documentElement.setAttribute('data-theme', state.theme); }
|
|
508
|
-
function toggleTheme() {
|
|
509
|
-
state.theme = state.theme === 'dark' ? 'light' : 'dark';
|
|
510
|
-
try { localStorage.setItem('svelte-vitals-theme', state.theme); } catch (e) {}
|
|
511
|
-
applyTheme();
|
|
512
|
-
renderTopbar();
|
|
513
|
-
}
|
|
514
|
-
function toggleSidebar() {
|
|
515
|
-
var sb = document.getElementById('dv-sidebar');
|
|
516
|
-
if (sb) sb.classList.toggle('open');
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
function brandEl() {
|
|
520
|
-
var el = h('button', { type: 'button', class: 'dv-brand', 'aria-label': 'Go to Overview', onclick: function () { selectItem('overview'); } }, []);
|
|
521
|
-
el.innerHTML = WORDMARK_SVG;
|
|
522
|
-
return el;
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
function renderTopbar() {
|
|
526
|
-
var s = state.snapshot;
|
|
527
|
-
var findings = s.report.routes.reduce(function (n, r) { return n + r.issues.length; }, 0) + s.report.siteIssues.length;
|
|
528
|
-
var kids = [
|
|
529
|
-
h('button', { type: 'button', class: 'dv-menu-toggle', 'aria-label': 'Toggle route list', onclick: toggleSidebar, text: '\u2261' }, []),
|
|
530
|
-
brandEl(),
|
|
531
|
-
h('div', { class: 'dv-meta' }, [
|
|
532
|
-
h('span', { text: 'v' + s.meta.version }, []),
|
|
533
|
-
s.meta.coreVersion ? h('span', { title: '@svelte-vitals/core version', text: 'core v' + s.meta.coreVersion }, []) : null,
|
|
534
|
-
h('span', { text: s.report.routes.length + ' routes' }, []),
|
|
535
|
-
h('span', { text: findings + ' findings' }, [])
|
|
536
|
-
].filter(Boolean)),
|
|
537
|
-
h('div', { class: 'dv-status' }, [
|
|
538
|
-
s.analyzing ? h('span', { class: 'dv-analyzing', text: 'Analyzing\u2026' }, []) : null,
|
|
539
|
-
h('span', { class: 'dv-conn dv-conn-' + state.connection, title: state.connection }, []),
|
|
540
|
-
h('button', { type: 'button', class: 'dv-theme-toggle', 'aria-label': 'Toggle dark mode', onclick: toggleTheme, text: state.theme === 'dark' ? '\u2600' : '\u263E' }, [])
|
|
541
|
-
].filter(Boolean))
|
|
542
|
-
];
|
|
543
|
-
mount('dv-topbar', h('div', { class: 'dv-topbar-inner' }, kids));
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
function slugify(route) {
|
|
547
|
-
return 'route-' + route.replace(/[^a-zA-Z0-9]+/g, '-').replace(/^-|-$/g, '').toLowerCase();
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
function matchesSearch(route, q) {
|
|
551
|
-
if (!q) return true;
|
|
552
|
-
q = q.toLowerCase();
|
|
553
|
-
if (route.route.toLowerCase().indexOf(q) !== -1) return true;
|
|
554
|
-
return route.issues.some(function (iss) {
|
|
555
|
-
return (iss.id + ' ' + iss.title + ' ' + (iss.location || '')).toLowerCase().indexOf(q) !== -1;
|
|
556
|
-
});
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
function sortedRoutes() {
|
|
560
|
-
var s = state.snapshot;
|
|
561
|
-
var q = state.search.trim();
|
|
562
|
-
var list = s.report.routes.filter(function (r) { return matchesSearch(r, q); }).slice();
|
|
563
|
-
var sort = state.sort;
|
|
564
|
-
if (sort === 'score-asc') list.sort(function (a, b) { return a.score - b.score; });
|
|
565
|
-
else if (sort === 'score-desc') list.sort(function (a, b) { return b.score - a.score; });
|
|
566
|
-
else if (sort === 'alpha') list.sort(function (a, b) { return a.route.localeCompare(b.route); });
|
|
567
|
-
else if (sort === 'most-findings') list.sort(function (a, b) { return b.issues.length - a.issues.length; });
|
|
568
|
-
return list;
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
function renderNavItem(label, key, route, active) {
|
|
572
|
-
var kids = [h('span', { class: 'dv-nav-label', text: label }, [])];
|
|
573
|
-
if (route) {
|
|
574
|
-
var band = scoreBand(route.score);
|
|
575
|
-
var crit = route.issues.filter(function (i) { return i.severity === 'critical'; }).length;
|
|
576
|
-
var warn = route.issues.filter(function (i) { return i.severity === 'warning'; }).length;
|
|
577
|
-
var info = route.issues.filter(function (i) { return i.severity === 'info'; }).length;
|
|
578
|
-
var summary = [];
|
|
579
|
-
if (crit) summary.push(crit + ' critical');
|
|
580
|
-
if (warn) summary.push(warn + ' warning' + (warn > 1 ? 's' : ''));
|
|
581
|
-
if (info) summary.push(info + ' info');
|
|
582
|
-
var badge = state.snapshot.badges[route.route];
|
|
583
|
-
kids.push(h('span', { class: 'dv-nav-meta' }, [
|
|
584
|
-
badge ? h('span', { class: 'dv-badge dv-badge-' + badge, text: badge }, []) : null,
|
|
585
|
-
h('span', { class: 'dv-nav-score', style: 'color:' + BAND_COLOR[band], text: String(route.score) }, []),
|
|
586
|
-
h('span', { class: 'dv-nav-sum', text: summary.length ? summary.join(' \xB7 ') : 'no issues' }, [])
|
|
587
|
-
].filter(Boolean)));
|
|
588
|
-
}
|
|
589
|
-
return h('div', {
|
|
590
|
-
class: 'dv-nav-item' + (active ? ' active' : ''),
|
|
591
|
-
role: 'option',
|
|
592
|
-
'aria-selected': active ? 'true' : 'false',
|
|
593
|
-
tabindex: '0',
|
|
594
|
-
onclick: function () { selectItem(key); },
|
|
595
|
-
onkeydown: function (e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); selectItem(key); } }
|
|
596
|
-
}, kids);
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
function selectItem(key) {
|
|
600
|
-
state.selected = key;
|
|
601
|
-
location.hash = key === 'overview' ? 'overview' : 'route/' + slugify(key);
|
|
602
|
-
var sb = document.getElementById('dv-sidebar');
|
|
603
|
-
if (sb) sb.classList.remove('open');
|
|
604
|
-
renderSidebar();
|
|
605
|
-
renderDetail();
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
function renderSidebar() {
|
|
609
|
-
var s = state.snapshot;
|
|
610
|
-
state.routeBySlug = {};
|
|
611
|
-
|
|
612
|
-
// mount() clears and rebuilds the whole sidebar, including the <input> itself, so
|
|
613
|
-
// a naive re-render on every keystroke drops focus and cursor position \u2014 capture
|
|
614
|
-
// them before rebuilding and restore them on the freshly-created input afterward.
|
|
615
|
-
var prevSearch = document.querySelector('.dv-search');
|
|
616
|
-
var hadFocus = !!prevSearch && document.activeElement === prevSearch;
|
|
617
|
-
var selStart = hadFocus ? prevSearch.selectionStart : null;
|
|
618
|
-
var selEnd = hadFocus ? prevSearch.selectionEnd : null;
|
|
619
|
-
|
|
620
|
-
var searchInput = h('input', {
|
|
621
|
-
type: 'search',
|
|
622
|
-
class: 'dv-search',
|
|
623
|
-
placeholder: 'Search routes or rules\u2026',
|
|
624
|
-
value: state.search,
|
|
625
|
-
oninput: function (e) { state.search = e.target.value; renderSidebar(); }
|
|
626
|
-
}, []);
|
|
627
|
-
|
|
628
|
-
var sortSelect = h('select', { class: 'dv-sort', 'aria-label': 'Sort routes', onchange: function (e) { state.sort = e.target.value; renderSidebar(); } }, [
|
|
629
|
-
h('option', { value: 'score-asc', selected: state.sort === 'score-asc' || undefined, text: 'Score (worst first)' }, []),
|
|
630
|
-
h('option', { value: 'score-desc', selected: state.sort === 'score-desc' || undefined, text: 'Score (best first)' }, []),
|
|
631
|
-
h('option', { value: 'alpha', selected: state.sort === 'alpha' || undefined, text: 'Alphabetical' }, []),
|
|
632
|
-
h('option', { value: 'most-findings', selected: state.sort === 'most-findings' || undefined, text: 'Most findings' }, [])
|
|
633
|
-
]);
|
|
634
|
-
|
|
635
|
-
var items = [renderNavItem('Overview', 'overview', null, state.selected === 'overview')];
|
|
636
|
-
sortedRoutes().forEach(function (r) {
|
|
637
|
-
var slug = slugify(r.route);
|
|
638
|
-
state.routeBySlug[slug] = r.route;
|
|
639
|
-
items.push(renderNavItem(r.route, r.route, r, state.selected === r.route));
|
|
640
|
-
});
|
|
641
|
-
|
|
642
|
-
var nav = h('div', { class: 'dv-nav', role: 'listbox', 'aria-label': 'Routes' }, items);
|
|
643
|
-
mount('dv-sidebar', h('div', { class: 'dv-sidebar-inner' }, [searchInput, sortSelect, nav]));
|
|
644
|
-
|
|
645
|
-
if (hadFocus) {
|
|
646
|
-
searchInput.focus();
|
|
647
|
-
if (selStart !== null && searchInput.setSelectionRange) {
|
|
648
|
-
searchInput.setSelectionRange(selStart, selEnd);
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
function renderFilterChips(categories) {
|
|
654
|
-
var chip = function (filter, label) {
|
|
655
|
-
return h('button', {
|
|
656
|
-
type: 'button', class: 'dv-chip', 'aria-pressed': state.filter === filter ? 'true' : 'false',
|
|
657
|
-
onclick: function () { state.filter = filter; renderDetail(); },
|
|
658
|
-
text: label
|
|
659
|
-
}, []);
|
|
660
|
-
};
|
|
661
|
-
var catChips = Object.keys(categories).map(function (cat) {
|
|
662
|
-
var name = cat === 'seo' ? 'SEO' : cat.charAt(0).toUpperCase() + cat.slice(1);
|
|
663
|
-
return chip(cat, name);
|
|
664
|
-
});
|
|
665
|
-
return h('div', { class: 'dv-filters', role: 'group', 'aria-label': 'Filter findings' },
|
|
666
|
-
[chip('all', 'All'), chip('critical', 'Critical'), chip('warning', 'Warning'), chip('info', 'Info')].concat(catChips));
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
function passesFilter(issue) {
|
|
670
|
-
var f = state.filter;
|
|
671
|
-
return f === 'all' || issue.severity === f || issue.category === f;
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
var SEVERITY_ORDER = { critical: 0, warning: 1, info: 2 };
|
|
675
|
-
|
|
676
|
-
function renderFinding(issue, route, promptRoute) {
|
|
677
|
-
var kids = [
|
|
678
|
-
h('div', { class: 'dv-f-head' }, [
|
|
679
|
-
h('span', { class: 'dv-ruleid', text: issue.id }, []),
|
|
680
|
-
h('span', { class: 'dv-f-title', text: issue.title }, []),
|
|
681
|
-
h('span', { class: 'dv-sev-tag dv-sev-' + issue.severity, text: issue.severity }, [])
|
|
682
|
-
])
|
|
683
|
-
];
|
|
684
|
-
if (route) {
|
|
685
|
-
kids.push(h('button', { type: 'button', class: 'dv-f-route', onclick: function () { selectItem(route); }, text: route }, []));
|
|
686
|
-
}
|
|
687
|
-
if (issue.location) {
|
|
688
|
-
kids.push(h('p', { class: 'dv-f-loc', text: issue.location + (issue.line !== undefined ? ':' + issue.line : '') }, []));
|
|
689
|
-
}
|
|
690
|
-
if (issue.recommendation) {
|
|
691
|
-
kids.push(h('p', { class: 'dv-f-rec', text: issue.recommendation }, []));
|
|
692
|
-
}
|
|
693
|
-
if (issue.fix && issue.fix.snippet) {
|
|
694
|
-
kids.push(h('div', { class: 'dv-fix' }, [h('div', { class: 'dv-fix-label', text: 'fix' }, []), renderFixSnippet(issue.fix)]));
|
|
695
|
-
}
|
|
696
|
-
if (issue.docsUrl) {
|
|
697
|
-
kids.push(h('a', { class: 'dv-f-link', href: issue.docsUrl, text: 'Learn more' }, []));
|
|
698
|
-
}
|
|
699
|
-
kids.push(renderAiPrompt(issue, promptRoute !== undefined ? promptRoute : route));
|
|
700
|
-
return h('article', { class: 'dv-finding dv-finding-' + issue.severity }, kids);
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
function renderGauge(score) {
|
|
704
|
-
var band = scoreBand(score);
|
|
705
|
-
var svgNs = 'http://www.w3.org/2000/svg';
|
|
706
|
-
var C = 2 * Math.PI * 58;
|
|
707
|
-
var offset = (C * (1 - score / 100)).toFixed(1);
|
|
708
|
-
var svg = document.createElementNS(svgNs, 'svg');
|
|
709
|
-
svg.setAttribute('width', '132');
|
|
710
|
-
svg.setAttribute('height', '132');
|
|
711
|
-
svg.setAttribute('viewBox', '0 0 132 132');
|
|
712
|
-
var bg = document.createElementNS(svgNs, 'circle');
|
|
713
|
-
bg.setAttribute('cx', '66'); bg.setAttribute('cy', '66'); bg.setAttribute('r', '58');
|
|
714
|
-
bg.setAttribute('fill', 'none'); bg.setAttribute('class', 'dv-gauge-track'); bg.setAttribute('stroke-width', '11');
|
|
715
|
-
var arc = document.createElementNS(svgNs, 'circle');
|
|
716
|
-
arc.setAttribute('cx', '66'); arc.setAttribute('cy', '66'); arc.setAttribute('r', '58');
|
|
717
|
-
arc.setAttribute('fill', 'none'); arc.setAttribute('stroke', BAND_COLOR[band]); arc.setAttribute('stroke-width', '11');
|
|
718
|
-
arc.setAttribute('stroke-linecap', 'round');
|
|
719
|
-
arc.setAttribute('stroke-dasharray', C.toFixed(1));
|
|
720
|
-
arc.setAttribute('stroke-dashoffset', offset);
|
|
721
|
-
svg.appendChild(bg);
|
|
722
|
-
svg.appendChild(arc);
|
|
723
|
-
var wrap = h('div', { class: 'dv-gauge' }, [h('div', { class: 'dv-gauge-num' }, [h('strong', { text: String(score) }, []), h('span', { text: 'Health' }, [])])]);
|
|
724
|
-
wrap.insertBefore(svg, wrap.firstChild);
|
|
725
|
-
return wrap;
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
function renderOverview(s) {
|
|
729
|
-
var gauge = renderGauge(s.report.score);
|
|
730
|
-
var cats = Object.keys(s.report.categories).map(function (cat) {
|
|
731
|
-
var c = s.report.categories[cat];
|
|
732
|
-
var band = scoreBand(c.score);
|
|
733
|
-
var weight = s.report.weights[cat];
|
|
734
|
-
var name = cat === 'seo' ? 'SEO' : cat.charAt(0).toUpperCase() + cat.slice(1);
|
|
735
|
-
return h('div', { class: 'dv-cat' }, [
|
|
736
|
-
h('div', { class: 'dv-cat-top' }, [
|
|
737
|
-
h('span', { text: name + (weight !== undefined ? ' (weight ' + weight + ')' : '') }, []),
|
|
738
|
-
h('span', { style: 'color:' + BAND_COLOR[band], text: String(c.score) }, [])
|
|
739
|
-
]),
|
|
740
|
-
h('div', { class: 'dv-bar' }, [h('i', { style: 'width:' + c.score + '%;background:' + BAND_COLOR[band] }, [])])
|
|
741
|
-
]);
|
|
742
|
-
});
|
|
743
|
-
var chips = renderFilterChips(s.report.categories);
|
|
744
|
-
var totalCount = s.report.routes.reduce(function (n, r) { return n + r.issues.length; }, 0) + s.report.siteIssues.length;
|
|
745
|
-
var entries = [];
|
|
746
|
-
s.report.routes.forEach(function (r) {
|
|
747
|
-
r.issues.forEach(function (issue) { entries.push({ issue: issue, route: r.route }); });
|
|
748
|
-
});
|
|
749
|
-
s.report.siteIssues.forEach(function (issue) { entries.push({ issue: issue, route: null }); });
|
|
750
|
-
entries = entries.filter(function (e) { return passesFilter(e.issue); });
|
|
751
|
-
entries.sort(function (a, b) {
|
|
752
|
-
var sd = SEVERITY_ORDER[a.issue.severity] - SEVERITY_ORDER[b.issue.severity];
|
|
753
|
-
if (sd !== 0) return sd;
|
|
754
|
-
return (a.route || '').localeCompare(b.route || '');
|
|
755
|
-
});
|
|
756
|
-
var body = entries.length
|
|
757
|
-
? entries.map(function (e) { return renderFinding(e.issue, e.route); })
|
|
758
|
-
: [h('p', { class: 'dv-empty', text: totalCount ? 'No issues match the current filter.' : 'No issues found \u2014 nice work!' }, [])];
|
|
759
|
-
var findings = h('section', { class: 'dv-section' }, [h('h2', { text: 'Findings' }, [])].concat(body));
|
|
760
|
-
return h('div', { class: 'dv-overview' }, [gauge, h('div', { class: 'dv-cats' }, cats), chips, findings].filter(Boolean));
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
function renderRouteDetail(route) {
|
|
764
|
-
var badge = state.snapshot.badges[route.route];
|
|
765
|
-
var band = scoreBand(route.score);
|
|
766
|
-
var header = h('div', { class: 'dv-detail-header' }, [
|
|
767
|
-
h('span', { class: 'dv-route-path', text: route.route }, []),
|
|
768
|
-
badge ? h('span', { class: 'dv-badge dv-badge-' + badge, text: badge }, []) : null,
|
|
769
|
-
h('span', { class: 'dv-score-chip', style: 'color:' + BAND_COLOR[band], text: String(route.score) }, [])
|
|
770
|
-
].filter(Boolean));
|
|
771
|
-
var chips = renderFilterChips(state.snapshot.report.categories);
|
|
772
|
-
var findings = route.issues.filter(passesFilter);
|
|
773
|
-
var body = findings.length
|
|
774
|
-
? findings.map(function (issue) { return renderFinding(issue, undefined, route.route); })
|
|
775
|
-
: [h('p', { class: 'dv-empty', text: 'No issues match the current filter.' }, [])];
|
|
776
|
-
return h('div', { class: 'dv-route-detail' }, [header, chips].concat(body));
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
function renderDetail() {
|
|
780
|
-
var s = state.snapshot;
|
|
781
|
-
if (state.selected === 'overview') {
|
|
782
|
-
mount('dv-detail', renderOverview(s));
|
|
783
|
-
return;
|
|
784
|
-
}
|
|
785
|
-
var route = s.report.routes.filter(function (r) { return r.route === state.selected; })[0];
|
|
786
|
-
if (!route) {
|
|
787
|
-
state.selected = 'overview';
|
|
788
|
-
mount('dv-detail', renderOverview(s));
|
|
789
|
-
return;
|
|
790
|
-
}
|
|
791
|
-
mount('dv-detail', renderRouteDetail(route));
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
function renderAll() {
|
|
795
|
-
renderTopbar();
|
|
796
|
-
renderSidebar();
|
|
797
|
-
renderDetail();
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
function restoreSelectionFromHash() {
|
|
801
|
-
var raw = location.hash.replace(/^#/, '');
|
|
802
|
-
if (!raw || raw === 'overview') { state.selected = 'overview'; return; }
|
|
803
|
-
var m = /^route\\/(.+)$/.exec(raw);
|
|
804
|
-
if (m && state.routeBySlug[m[1]]) state.selected = state.routeBySlug[m[1]];
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
function fetchSnapshot() {
|
|
808
|
-
fetch('/__svelte-vitals/data.json').then(function (r) { return r.json(); }).then(function (data) {
|
|
809
|
-
if (state.snapshot && data.sequence <= state.snapshot.sequence) return;
|
|
810
|
-
state.snapshot = data;
|
|
811
|
-
renderAll();
|
|
812
|
-
}).catch(function () {});
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
function boot() {
|
|
816
|
-
var raw = document.getElementById('svelte-vitals-data');
|
|
817
|
-
state.snapshot = JSON.parse(raw.textContent);
|
|
818
|
-
applyTheme();
|
|
819
|
-
renderSidebar(); // populates routeBySlug before the hash can be trusted
|
|
820
|
-
restoreSelectionFromHash();
|
|
821
|
-
renderAll();
|
|
822
|
-
|
|
823
|
-
window.addEventListener('hashchange', function () {
|
|
824
|
-
restoreSelectionFromHash();
|
|
825
|
-
renderSidebar();
|
|
826
|
-
renderDetail();
|
|
827
|
-
});
|
|
828
|
-
|
|
829
|
-
if (typeof EventSource !== 'undefined') {
|
|
830
|
-
var es = new EventSource('/__svelte-vitals/events');
|
|
831
|
-
es.addEventListener('open', function () { state.connection = 'connected'; renderTopbar(); fetchSnapshot(); });
|
|
832
|
-
es.addEventListener('update', fetchSnapshot);
|
|
833
|
-
es.addEventListener('error', function () { state.connection = 'reconnecting'; renderTopbar(); });
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
boot();
|
|
838
|
-
})();
|
|
839
|
-
`;
|
|
840
|
-
|
|
841
|
-
// src/ui/dashboard.ts
|
|
842
|
-
function embedJson(value) {
|
|
843
|
-
return JSON.stringify(value).replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
844
|
-
}
|
|
845
|
-
function renderDashboardShell(snapshot) {
|
|
846
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>svelte-vitals dashboard</title><style>${DASHBOARD_STYLE}</style></head><body><div class="dv-app" id="dv-app"><header class="dv-topbar" id="dv-topbar"></header><nav class="dv-sidebar" id="dv-sidebar"></nav><main class="dv-detail" id="dv-detail"></main></div><script type="application/json" id="svelte-vitals-data">${embedJson(snapshot)}</script><script>${DASHBOARD_SCRIPT}</script></body></html>`;
|
|
847
|
-
}
|
|
848
|
-
|
|
849
221
|
// src/ui/middleware.ts
|
|
850
222
|
var SEVERITIES = /* @__PURE__ */ new Set(["critical", "warning", "info"]);
|
|
851
223
|
var CATEGORIES = /* @__PURE__ */ new Set(["seo", "performance", "correctness", "security", "architecture"]);
|
|
@@ -923,7 +295,7 @@ function installUiMiddleware(server, config, version, store, coreVersion) {
|
|
|
923
295
|
return;
|
|
924
296
|
}
|
|
925
297
|
try {
|
|
926
|
-
const html =
|
|
298
|
+
const html = renderAppShell(buildSnapshot(store, config, { version, coreVersion }));
|
|
927
299
|
res.setHeader("Content-Type", "text/html");
|
|
928
300
|
res.end(html);
|
|
929
301
|
} catch {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@svelte-vitals/vite",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.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,8 +42,8 @@
|
|
|
42
42
|
"esm-env": "^1.2.2",
|
|
43
43
|
"node-html-parser": "^9.0.0",
|
|
44
44
|
"tinyglobby": "^0.2.17",
|
|
45
|
-
"svelte-vitals": "0.
|
|
46
|
-
"
|
|
45
|
+
"@svelte-vitals/core": "0.26.0",
|
|
46
|
+
"svelte-vitals": "0.29.0"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"@sveltejs/kit": "^2.69.2",
|