@svelte-vitals/vite 0.36.4 → 0.54.6

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/README.md CHANGED
@@ -34,7 +34,7 @@ export default {
34
34
  - `cwd`: project root (defaults to the Vite config root).
35
35
  - `ui`: serve the live dev dashboard (default `true`); pass `false` to keep only the build-time gate.
36
36
 
37
- At build time the HTML check covers prerendered routes only; the source scan applies project-wide. SSR and dynamic routes have no build output, so nothing checks their rendered HTML here. The `svelte-vitals` CLI covers them by source analysis, and browsing them during `vite dev` gets the live dashboard a rendered reading.
37
+ At build time the HTML check covers prerendered routes only; the source scan applies project-wide. Routes that are not prerendered (server-rendered, or dynamic routes without prerendered entries) have no build output, so nothing checks their rendered HTML here. A prerendered route with `ssr = false` has build output, but only the empty app shell, so the HTML check skips it too (a `+layout` with `ssr = false` skips its subtree) and prints one warning listing the skipped routes. The `svelte-vitals` CLI covers them by source analysis, and browsing them during `vite dev` gets the live dashboard a rendered reading.
38
38
 
39
39
  See [Live dashboard](https://oekazuma.github.io/svelte-vitals/guides/dev-dashboard/) and [Plugin mode](https://oekazuma.github.io/svelte-vitals/guides/plugin-mode/) for the full picture.
40
40
 
@@ -14,6 +14,6 @@ interface SvelteVitalsHookOptions {
14
14
  * route's dashboard findings from static (source-only) to `measured` (real rendered
15
15
  * HTML). Add it to `src/hooks.server.ts`, e.g. `sequence(svelteVitalsHandle())`.
16
16
  */
17
- declare function svelteVitalsHandle(options?: SvelteVitalsHookOptions): Handle;
17
+ export declare function svelteVitalsHandle(options?: SvelteVitalsHookOptions): Handle;
18
18
  //#endregion
19
- export { type SvelteVitalsHookOptions, svelteVitalsHandle };
19
+ export type { SvelteVitalsHookOptions };
package/dist/index.d.ts CHANGED
@@ -28,6 +28,6 @@ interface SvelteVitalsOptions {
28
28
  ui?: boolean;
29
29
  }
30
30
  /** svelte-vitals Vite/SvelteKit plugin. */
31
- declare function svelteVitals(options?: SvelteVitalsOptions): Plugin | Plugin[];
31
+ export declare function svelteVitals(options?: SvelteVitalsOptions): Plugin | Plugin[];
32
32
  //#endregion
33
- export { type SvelteVitalsOptions, svelteVitals };
33
+ export type { SvelteVitalsOptions };
package/dist/index.js CHANGED
@@ -222,6 +222,43 @@ async function resolveConfig(cwd, options) {
222
222
  };
223
223
  }
224
224
  /**
225
+ * `ssr` is a page option: only `+page`/`+layout` modules (universal or server) can set it. Same
226
+ * file set as `PAGE_OPTION_FILE_RE` in core's `rules/seo/ssr-disabled.ts`, which reports the fact.
227
+ */
228
+ const PAGE_OPTION_FILE_RE = /^src\/routes\/(.*?)\/?\+(page|layout)(?:\.server)?\.[jt]s$/;
229
+ function escapeRegExp(s) {
230
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
231
+ }
232
+ /** One route directory segment as a pattern over the rendered (concrete) route path, leading `/` included. */
233
+ function segmentPattern(segment) {
234
+ if (/^\[\.\.\.[^\]]+\]$/.test(segment)) return "(?:/.*)?";
235
+ if (/^\[\[[^\]]+\]\]$/.test(segment)) return "(?:/[^/]+)?";
236
+ return "/" + segment.split(/\[[^\]]+\]/).map(escapeRegExp).join("[^/]+");
237
+ }
238
+ /**
239
+ * Routes whose prerendered HTML is the app shell rather than the page: SvelteKit still writes a
240
+ * file for an `ssr = false` route, but its `<head>` and body only exist once the client renders,
241
+ * so every rendered head/heading/landmark check would report the page as empty. A `+layout`
242
+ * covers its whole subtree, a `+page` only its own route; `(group)` directories are not URL
243
+ * segments. Two ceilings: a child route re-enabling `ssr = true` under a disabled layout is not
244
+ * tracked, and Kit's route precedence is not modelled, so a `[param]` route also claims a literal
245
+ * sibling (`/docs/[slug]` matches a prerendered `/docs/intro`) — the warning lists the skipped
246
+ * routes so a wrong skip is visible.
247
+ */
248
+ function ssrDisabledRouteMatcher(kitModules) {
249
+ const patterns = [];
250
+ for (const m of kitModules) {
251
+ const match = m.ssrDisabled ? PAGE_OPTION_FILE_RE.exec(m.file) : null;
252
+ if (!match) continue;
253
+ const path = match[1].split("/").filter((s) => s !== "" && !/^\(.+\)$/.test(s)).map(segmentPattern).join("");
254
+ patterns.push(new RegExp(match[2] === "layout" ? `^${path}(?:/.*)?$` : `^${path}$`));
255
+ }
256
+ return (route) => {
257
+ const r = route === "/" ? "" : route;
258
+ return patterns.some((p) => p.test(r));
259
+ };
260
+ }
261
+ /**
225
262
  * Collect prerendered heads + project facts + component facts, run the core pipeline, and
226
263
  * format reports. Config precedence: see `resolveConfig`. Pass `resolved` when the caller
227
264
  * already resolved config itself (build mode resolves it outside `analyze`'s try/catch so a
@@ -229,17 +266,29 @@ async function resolveConfig(cwd, options) {
229
266
  */
230
267
  async function analyze(prerenderPagesDir, cwd, options, extraProjectFacts, resolved) {
231
268
  const { config, warnings } = resolved ?? await resolveConfig(cwd, options);
232
- const [{ heads, headings, images, a11y, htmlLang }, components, sourceFiles] = await Promise.all([
269
+ const [rendered, components, sourceFiles] = await Promise.all([
233
270
  collectRenderedHeads(prerenderPagesDir),
234
271
  collectComponentFacts$1(cwd),
235
272
  collectSourceFiles$1(cwd)
236
273
  ]);
237
274
  const project = {
238
- ...await collectRenderedProject(cwd, htmlLang),
275
+ ...await collectRenderedProject(cwd, rendered.htmlLang),
239
276
  ...extraProjectFacts
240
277
  };
241
278
  const kitModules = await collectKitModuleFacts$1(cwd, project.kitAliases);
279
+ const isShell = ssrDisabledRouteMatcher(kitModules);
280
+ const keep = (items) => items.filter((i) => !isShell(i.route));
281
+ const heads = keep(rendered.heads);
282
+ const headings = keep(rendered.headings);
283
+ const images = keep(rendered.images);
284
+ const a11y = keep(rendered.a11y);
285
+ const shells = rendered.heads.filter((h) => isShell(h.route)).map((h) => h.route).sort();
242
286
  const selected = selectRules(allRules, config);
287
+ if (shells.length > 0) {
288
+ const shown = shells.slice(0, 10);
289
+ const list = shells.length > shown.length ? `${shown.join(", ")}, … and ${shells.length - shown.length} more` : shown.join(", ");
290
+ warnings.push(`skipped ${shells.length} prerendered route(s) with ssr = false — their HTML is the app shell, not the page; run \`npx svelte-vitals\` to check them from source: ${list}`);
291
+ }
243
292
  if (selected.some((r) => r.id === "a11y/unverified-id-ref")) warnings.push("a11y/unverified-id-ref has no effect in rendered mode — the prerendered document is always fully resolved.");
244
293
  const directives = /* @__PURE__ */ new Map();
245
294
  addFactsDirectives(directives, {
@@ -264,7 +313,7 @@ async function analyze(prerenderPagesDir, cwd, options, extraProjectFacts, resol
264
313
  const { score } = computeScore(results, scoringConfig);
265
314
  const summary = summarize(results, scoringConfig);
266
315
  const failed = hasFailureAtOrAbove(summary, scoringConfig.failOn);
267
- const coverageNote = `Analyzed ${heads.length} prerendered route(s). SSR/dynamic routes are not covered — run \`npx svelte-vitals\` for those.
316
+ const coverageNote = `Analyzed ${heads.length} prerendered route(s)${shells.length > 0 ? ` (skipped ${shells.length} with ssr = false)` : ""}. SSR/dynamic routes are not covered — run \`npx svelte-vitals\` for those.
268
317
  Scanned ${components.length} component(s) under src/ for Correctness/Security/Architecture/Accessibility/Bundle findings.`;
269
318
  return {
270
319
  score,
@@ -272,7 +321,7 @@ Scanned ${components.length} component(s) under src/ for Correctness/Security/Ar
272
321
  results,
273
322
  consoleReport: formatConsoleReport(results, scoringConfig, { mode: "rendered / plugin" }) + "\n" + coverageNote + "\n",
274
323
  jsonReport: formatJsonReport(results, scoringConfig, { version: readPackageVersion() }, selected.map((r) => r.id), examined),
275
- routeCount: heads.length,
324
+ routeCount: rendered.heads.length,
276
325
  failed,
277
326
  failOn: scoringConfig.failOn,
278
327
  warnings
@@ -613,8 +662,10 @@ function createAnalysisRunner(opts) {
613
662
  parseCache
614
663
  });
615
664
  const { results, failedRuleIds } = result;
616
- if (!stopped) if (failedRuleIds !== void 0) opts.onResults(results, failedRuleIds);
617
- else opts.onResults(results);
665
+ if (!stopped) {
666
+ if (failedRuleIds !== void 0) opts.onResults(results, failedRuleIds);
667
+ else opts.onResults(results);
668
+ }
618
669
  const warnings = result.warnings ?? [];
619
670
  const key = warnings.join("\n");
620
671
  if (!stopped && warnings.length > 0 && key !== lastWarningsKey) opts.onWarnings?.(warnings);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svelte-vitals/vite",
3
- "version": "0.36.4",
3
+ "version": "0.54.6",
4
4
  "description": "Vite/SvelteKit plugin for svelte-vitals — analyzes prerendered HTML during vite build, plus a live dev dashboard.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -40,13 +40,13 @@
40
40
  ],
41
41
  "dependencies": {
42
42
  "esm-env": "^1.2.2",
43
- "node-html-parser": "^9.0.2"
43
+ "node-html-parser": "^9.0.4"
44
44
  },
45
45
  "peerDependencies": {
46
- "@svelte-vitals/core": "^0.51.2",
46
+ "@svelte-vitals/core": "^0.54.6",
47
47
  "@sveltejs/kit": "^2.70.3",
48
- "svelte-vitals": "^0.54.4",
49
- "vite": "^8.2.2"
48
+ "svelte-vitals": "^0.54.6",
49
+ "vite": "^8.3.0"
50
50
  },
51
51
  "peerDependenciesMeta": {
52
52
  "@sveltejs/kit": {
@@ -54,13 +54,13 @@
54
54
  }
55
55
  },
56
56
  "devDependencies": {
57
- "@svelte-vitals/core": "0.51.2",
57
+ "@svelte-vitals/core": "0.54.6",
58
58
  "@sveltejs/kit": "^2.70.3",
59
- "@types/node": "^24.13.3",
60
- "happy-dom": "^20.13.2",
59
+ "@types/node": "^24.13.4",
60
+ "happy-dom": "^20.14.3",
61
61
  "svelte": "^5.57.0",
62
- "svelte-vitals": "0.54.4",
63
- "vite": "^8.2.2"
62
+ "svelte-vitals": "0.54.6",
63
+ "vite": "^8.3.0"
64
64
  },
65
65
  "scripts": {
66
66
  "build": "tsdown",