@svelte-vitals/vite 0.13.1 → 0.14.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.
@@ -10,8 +10,10 @@ interface SvelteVitalsHookOptions {
10
10
  }
11
11
 
12
12
  /**
13
- * SvelteKit `handle` that prints SEO warnings for each visited page's rendered `<head>`,
14
- * in dev only. Add it to `src/hooks.server.ts`, e.g. `sequence(svelteVitalsHandle())`.
13
+ * SvelteKit `handle` that analyzes each visited page's rendered `<head>`, in dev only,
14
+ * and (when the live dashboard is enabled) feeds the results in — upgrading that
15
+ * route's dashboard findings from static (source-only) to `measured` (real rendered
16
+ * HTML). Add it to `src/hooks.server.ts`, e.g. `sequence(svelteVitalsHandle())`.
15
17
  */
16
18
  declare function svelteVitalsHandle(options?: SvelteVitalsHookOptions): Handle;
17
19
 
@@ -15,22 +15,9 @@ import {
15
15
 
16
16
  // src/hooks/format.ts
17
17
  import { isPenalized, effectiveSeverity } from "@svelte-vitals/core";
18
- var GLYPH = { critical: "\u2717", warning: "\u26A0", info: "\xB7" };
19
- var RANK = { critical: 0, warning: 1, info: 2 };
20
18
  function penalized(results, config) {
21
19
  return results.filter((r) => isPenalized(r.detection, config.treatDynamicAs));
22
20
  }
23
- function formatDevReport(route, results, config) {
24
- const failing = penalized(results, config).sort(
25
- (a, b) => RANK[effectiveSeverity(a, config)] - RANK[effectiveSeverity(b, config)] || a.id.localeCompare(b.id)
26
- );
27
- if (failing.length === 0) return "";
28
- const lines = [`[svelte-vitals] ${route}`];
29
- for (const r of failing) {
30
- lines.push(` ${GLYPH[effectiveSeverity(r, config)]} ${r.id} ${r.message}`);
31
- }
32
- return lines.join("\n");
33
- }
34
21
  function findingSignature(results, config) {
35
22
  return penalized(results, config).map((r) => `${r.id}:${effectiveSeverity(r, config)}:${r.detection.presence}:${r.detection.value}`).sort().join("|");
36
23
  }
@@ -54,7 +41,7 @@ async function postIngest(origin, route, results) {
54
41
  } catch {
55
42
  }
56
43
  }
57
- async function analyzeAndWarn(html, route, origin, rules, config, lastSignature) {
44
+ async function analyzeAndIngest(html, route, origin, rules, config, lastSignature) {
58
45
  try {
59
46
  const { tags, htmlLang, headings: levels, images: imgs } = parseHtmlHead(html);
60
47
  const head = { route, source: "rendered", tags, file: route };
@@ -70,8 +57,6 @@ async function analyzeAndWarn(html, route, origin, rules, config, lastSignature)
70
57
  const signature = findingSignature(results, config);
71
58
  if (lastSignature.get(route) === signature) return;
72
59
  lastSignature.set(route, signature);
73
- const report = formatDevReport(route, results, config);
74
- if (report) console.warn(report);
75
60
  if (globalThis.process?.env?.SVELTE_VITALS_UI) void postIngest(origin, route, results);
76
61
  } catch (err) {
77
62
  if (globalThis.process?.env?.SVELTE_VITALS_DEBUG) {
@@ -93,7 +78,7 @@ function svelteVitalsHandle(options = {}) {
93
78
  transformPageChunk: ({ html, done }) => {
94
79
  buffer += html;
95
80
  if (done)
96
- void analyzeAndWarn(
81
+ void analyzeAndIngest(
97
82
  buffer,
98
83
  event.route.id ?? event.url.pathname,
99
84
  event.url.origin,
package/dist/index.d.ts CHANGED
@@ -15,7 +15,12 @@ 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). */
18
+ /**
19
+ * Serve a live dashboard at /__svelte-vitals/ during `vite dev` (add
20
+ * svelteVitalsHandle to hooks.server.ts for accurate, per-route `measured` results
21
+ * as you browse — the dashboard still works without it, from whole-project static
22
+ * analysis alone). Default: `true`. Pass `false` to keep only the build-time gate.
23
+ */
19
24
  ui?: boolean;
20
25
  }
21
26
  /** svelte-vitals Vite/SvelteKit plugin. */
package/dist/index.js CHANGED
@@ -210,8 +210,9 @@ body{background:var(--ground);color:var(--ink);font-family:var(--sans);line-heig
210
210
  .dv-app{display:grid;grid-template-rows:auto 1fr;grid-template-columns:280px 1fr;grid-template-areas:"top top" "side main";height:100vh}
211
211
  .dv-topbar{grid-area:top;border-bottom:1px solid var(--line);background:var(--panel)}
212
212
  .dv-topbar-inner{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;padding:12px 20px}
213
- .dv-brand{display:flex;align-items:baseline;gap:8px;font-weight:700;font-size:16px}
214
- .dv-brand .bolt{color:var(--accent)}
213
+ .dv-brand{display:flex;align-items:center;background:none;border:none;padding:0;cursor:pointer;border-radius:6px}
214
+ .dv-brand svg{height:28px;width:auto;display:block}
215
+ .dv-brand:focus-visible{outline:2px solid var(--accent);outline-offset:4px}
215
216
  .dv-meta{font-family:var(--mono);font-size:12px;color:var(--muted);display:flex;gap:12px;flex-wrap:wrap}
216
217
  .dv-status{display:flex;align-items:center;gap:10px}
217
218
  .dv-analyzing{font-size:12px;color:var(--accent);font-weight:600}
@@ -268,6 +269,9 @@ body{background:var(--ground);color:var(--ink);font-family:var(--sans);line-heig
268
269
  .dv-sev-critical{color:var(--poor)}
269
270
  .dv-sev-warning{color:var(--warn)}
270
271
  .dv-sev-info{color:var(--faint)}
272
+ .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}
273
+ .dv-f-route:hover{text-decoration:underline}
274
+ .dv-f-route:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
271
275
  .dv-f-loc{font-family:var(--mono);font-size:12.5px;color:var(--muted);margin:8px 0 0}
272
276
  .dv-f-rec{font-size:14px;margin:10px 0 0}
273
277
  .dv-fix{margin:12px 0 0;background:var(--code-bg);border-radius:8px;overflow:hidden}
@@ -293,6 +297,12 @@ var DASHBOARD_SCRIPT = `
293
297
  var BAND_COLOR = { good: '#2fa968', warn: '#e8a317', poor: '#e5484d' };
294
298
  function scoreBand(score) { return score >= 90 ? 'good' : score >= 50 ? 'warn' : 'poor'; }
295
299
 
300
+ // Same mark as the docs site's hero wordmark (docs/public/wordmark.svg) \u2014 an inline
301
+ // copy, not an <img src>, since the dashboard is a single self-contained HTML response
302
+ // with no other static assets to serve alongside it. Fixed brand colors (not CSS custom
303
+ // properties), matching the docs usage: the wordmark reads the same in both themes.
304
+ 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>';
305
+
296
306
  function h(tag, attrs, kids) {
297
307
  var n = document.createElement(tag);
298
308
  if (attrs) {
@@ -418,12 +428,18 @@ var DASHBOARD_SCRIPT = `
418
428
  if (sb) sb.classList.toggle('open');
419
429
  }
420
430
 
431
+ function brandEl() {
432
+ var el = h('button', { type: 'button', class: 'dv-brand', 'aria-label': 'Go to Overview', onclick: function () { selectItem('overview'); } }, []);
433
+ el.innerHTML = WORDMARK_SVG;
434
+ return el;
435
+ }
436
+
421
437
  function renderTopbar() {
422
438
  var s = state.snapshot;
423
439
  var findings = s.report.routes.reduce(function (n, r) { return n + r.issues.length; }, 0) + s.report.siteIssues.length;
424
440
  var kids = [
425
441
  h('button', { type: 'button', class: 'dv-menu-toggle', 'aria-label': 'Toggle route list', onclick: toggleSidebar, text: '\u2261' }, []),
426
- h('div', { class: 'dv-brand' }, [h('span', { class: 'bolt', text: '\u21AF' }, []), document.createTextNode('svelte-vitals')]),
442
+ brandEl(),
427
443
  h('div', { class: 'dv-meta' }, [
428
444
  h('span', { text: 'v' + s.meta.version }, []),
429
445
  s.meta.coreVersion ? h('span', { title: '@svelte-vitals/core version', text: 'core v' + s.meta.coreVersion }, []) : null,
@@ -567,7 +583,9 @@ var DASHBOARD_SCRIPT = `
567
583
  return f === 'all' || issue.severity === f || issue.category === f;
568
584
  }
569
585
 
570
- function renderFinding(issue) {
586
+ var SEVERITY_ORDER = { critical: 0, warning: 1, info: 2 };
587
+
588
+ function renderFinding(issue, route) {
571
589
  var kids = [
572
590
  h('div', { class: 'dv-f-head' }, [
573
591
  h('span', { class: 'dv-ruleid', text: issue.id }, []),
@@ -575,6 +593,9 @@ var DASHBOARD_SCRIPT = `
575
593
  h('span', { class: 'dv-sev-tag dv-sev-' + issue.severity, text: issue.severity }, [])
576
594
  ])
577
595
  ];
596
+ if (route) {
597
+ kids.push(h('button', { type: 'button', class: 'dv-f-route', onclick: function () { selectItem(route); }, text: route }, []));
598
+ }
578
599
  if (issue.location) {
579
600
  kids.push(h('p', { class: 'dv-f-loc', text: issue.location + (issue.line !== undefined ? ':' + issue.line : '') }, []));
580
601
  }
@@ -631,11 +652,23 @@ var DASHBOARD_SCRIPT = `
631
652
  ]);
632
653
  });
633
654
  var chips = renderFilterChips(s.report.categories);
634
- var siteFindings = s.report.siteIssues.filter(passesFilter);
635
- var siteChecks = s.report.siteIssues.length
636
- ? h('section', { class: 'dv-section' }, [h('h2', { text: 'Site checks' }, [])].concat(siteFindings.map(renderFinding)))
637
- : null;
638
- return h('div', { class: 'dv-overview' }, [gauge, h('div', { class: 'dv-cats' }, cats), chips, siteChecks].filter(Boolean));
655
+ var totalCount = s.report.routes.reduce(function (n, r) { return n + r.issues.length; }, 0) + s.report.siteIssues.length;
656
+ var entries = [];
657
+ s.report.routes.forEach(function (r) {
658
+ r.issues.forEach(function (issue) { entries.push({ issue: issue, route: r.route }); });
659
+ });
660
+ s.report.siteIssues.forEach(function (issue) { entries.push({ issue: issue, route: null }); });
661
+ entries = entries.filter(function (e) { return passesFilter(e.issue); });
662
+ entries.sort(function (a, b) {
663
+ var sd = SEVERITY_ORDER[a.issue.severity] - SEVERITY_ORDER[b.issue.severity];
664
+ if (sd !== 0) return sd;
665
+ return (a.route || '').localeCompare(b.route || '');
666
+ });
667
+ var body = entries.length
668
+ ? entries.map(function (e) { return renderFinding(e.issue, e.route); })
669
+ : [h('p', { class: 'dv-empty', text: totalCount ? 'No issues match the current filter.' : 'No issues found \u2014 nice work!' }, [])];
670
+ var findings = h('section', { class: 'dv-section' }, [h('h2', { text: 'Findings' }, [])].concat(body));
671
+ return h('div', { class: 'dv-overview' }, [gauge, h('div', { class: 'dv-cats' }, cats), chips, findings].filter(Boolean));
639
672
  }
640
673
 
641
674
  function renderRouteDetail(route) {
@@ -648,7 +681,9 @@ var DASHBOARD_SCRIPT = `
648
681
  ].filter(Boolean));
649
682
  var chips = renderFilterChips(state.snapshot.report.categories);
650
683
  var findings = route.issues.filter(passesFilter);
651
- var body = findings.length ? findings.map(renderFinding) : [h('p', { class: 'dv-empty', text: 'No issues match the current filter.' }, [])];
684
+ var body = findings.length
685
+ ? findings.map(function (issue) { return renderFinding(issue); })
686
+ : [h('p', { class: 'dv-empty', text: 'No issues match the current filter.' }, [])];
652
687
  return h('div', { class: 'dv-route-detail' }, [header, chips].concat(body));
653
688
  }
654
689
 
@@ -1004,7 +1039,7 @@ function svelteVitals(options = {}) {
1004
1039
  }
1005
1040
  }
1006
1041
  };
1007
- if (!options.ui) return buildPlugin;
1042
+ if (options.ui === false) return buildPlugin;
1008
1043
  const uiPlugin = {
1009
1044
  name: "svelte-vitals:ui",
1010
1045
  apply: "serve",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svelte-vitals/vite",
3
- "version": "0.13.1",
3
+ "version": "0.14.0",
4
4
  "description": "Vite/SvelteKit plugin for svelte-vitals — analyzes prerendered HTML during vite build.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -43,11 +43,11 @@
43
43
  "node-html-parser": "^9.0.0",
44
44
  "tinyglobby": "^0.2.17",
45
45
  "@svelte-vitals/core": "0.23.0",
46
- "svelte-vitals": "0.23.0"
46
+ "svelte-vitals": "0.24.0"
47
47
  },
48
48
  "peerDependencies": {
49
- "@sveltejs/kit": "^2.69.1",
50
- "vite": "^8.1.3"
49
+ "@sveltejs/kit": "^2.69.2",
50
+ "vite": "^8.1.4"
51
51
  },
52
52
  "peerDependenciesMeta": {
53
53
  "@sveltejs/kit": {
@@ -55,10 +55,10 @@
55
55
  }
56
56
  },
57
57
  "devDependencies": {
58
- "@sveltejs/kit": "^2.69.1",
59
- "@types/node": "^24.13.2",
58
+ "@sveltejs/kit": "^2.69.2",
59
+ "@types/node": "^24.13.3",
60
60
  "svelte": "^5.56.4",
61
- "vite": "^8.1.3"
61
+ "vite": "^8.1.4"
62
62
  },
63
63
  "scripts": {
64
64
  "build": "tsup",