@svelte-vitals/vite 0.54.6 → 0.54.7

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,6 +34,8 @@ 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
+ A `svelte-vitals.config.*` and a `svelte-vitals-suppressions.json` in the project root are read automatically (explicit options win over the config file). Only source-scan suppression entries apply; route-level entries are skipped on every plugin surface (rendered route findings anchor to the built HTML) and the plugin says how many.
38
+
37
39
  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
40
 
39
41
  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.
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import { access, mkdir, readFile, writeFile } from "node:fs/promises";
4
4
  import { basename, dirname, isAbsolute, join, relative, sep } from "node:path";
5
5
  import { CATEGORIES, defineConfig } from "@svelte-vitals/core";
6
6
  import { ROBOTS_SOURCE_PATHS, SITEMAP_SOURCE_PATHS, SVELTE_CONFIG_FILES, VITE_CONFIG_FILES, addFactsDirectives, allRules, buildJsonReport, collectComponentFacts, collectKitModuleFacts, collectSourceFiles, collectSuppressions, computeScore, defaultConfig, findMinifyDisabled, formatConsoleReport, formatFailedRuleWarning, formatJsonReport, hasFailureAtOrAbove, renderAppShell, resolveKitAliases, resolveKitPathsBase, resolveRuleOptions, runAnalysis, safeHref, selectRules, shouldSkipRangeCheck, skippedFileWarnings, summarize, terminalSafe, unknownDirectiveIds, validateRuleSetting, withFailedRulesOff } from "@svelte-vitals/core/internal";
7
- import { CONFIG_FILENAMES, analyzeProject, createNodeRuntime, findUnknownRuleIds, knownRuleIds, loadConfigFile, registryTag, ruleOptionsSpec } from "svelte-vitals";
7
+ import { CONFIG_FILENAMES, SUPPRESSIONS_FILE, analyzeProject, applySuppressions, createNodeRuntime, findUnknownRuleIds, knownRuleIds, loadConfigFile, loadSuppressions, registryTag, ruleOptionsSpec } from "svelte-vitals";
8
8
  //#region src/providers/rendered/collect.ts
9
9
  /** Map a prerendered HTML path (relative to pages/, POSIX) to its route. */
10
10
  function deriveRouteFromHtmlPath(relPath) {
@@ -157,6 +157,29 @@ function collectSourceFiles$1(root) {
157
157
  return collectSourceFiles(nodeRuntime, root);
158
158
  }
159
159
  //#endregion
160
+ //#region src/suppressions.ts
161
+ /**
162
+ * Apply `svelte-vitals-suppressions.json` the way every plugin surface must: source-scan entries
163
+ * only. A route-level entry (route starting with `/`) is written by the CLI against
164
+ * `src/routes/…/+page.svelte`, while the build gate's rendered findings anchor to the built HTML
165
+ * and the dashboard's live layer to the route itself — the gate could never match it, and the
166
+ * dashboard's static layer would drop it only until the route was browsed and the live result
167
+ * re-surfaced it. Skipping them everywhere keeps Health identical across surfaces; the caller
168
+ * gets one line per fact to print. Stale entries are not counted: pruning stays with the CLI.
169
+ */
170
+ function applySourceSuppressions(results, entries, config) {
171
+ const notices = [];
172
+ const source = entries.filter((e) => !e.route?.startsWith("/"));
173
+ const routeLevel = entries.length - source.length;
174
+ const applied = applySuppressions(results, source, config);
175
+ if (applied.suppressed > 0) notices.push(`${applied.suppressed} finding(s) suppressed by ${SUPPRESSIONS_FILE}.`);
176
+ if (routeLevel > 0) notices.push(`${routeLevel} route-level ${routeLevel === 1 ? "entry does" : "entries do"} not apply to the plugin (${SUPPRESSIONS_FILE}) — rendered route findings anchor to the built HTML; use \`overrides\` for those.`);
177
+ return {
178
+ results: applied.results,
179
+ notices
180
+ };
181
+ }
182
+ //#endregion
160
183
  //#region src/version.ts
161
184
  /** Read this package's version from its own package.json so the report never drifts. */
162
185
  function readPackageVersion() {
@@ -261,11 +284,13 @@ function ssrDisabledRouteMatcher(kitModules) {
261
284
  /**
262
285
  * Collect prerendered heads + project facts + component facts, run the core pipeline, and
263
286
  * format reports. Config precedence: see `resolveConfig`. Pass `resolved` when the caller
264
- * already resolved config itself (build mode resolves it outside `analyze`'s try/catch so a
265
- * config-file validation error fails the build instead of being caught as an analysis error).
287
+ * already resolved config and loaded `svelte-vitals-suppressions.json` itself (build mode does
288
+ * both outside `analyze`'s try/catch so a validation error fails the build instead of being
289
+ * caught as an analysis error); `suppressions: undefined` means the file does not exist.
266
290
  */
267
291
  async function analyze(prerenderPagesDir, cwd, options, extraProjectFacts, resolved) {
268
292
  const { config, warnings } = resolved ?? await resolveConfig(cwd, options);
293
+ const suppressions = resolved ? resolved.suppressions : loadSuppressions(cwd);
269
294
  const [rendered, components, sourceFiles] = await Promise.all([
270
295
  collectRenderedHeads(prerenderPagesDir),
271
296
  collectComponentFacts$1(cwd),
@@ -296,7 +321,7 @@ async function analyze(prerenderPagesDir, cwd, options, extraProjectFacts, resol
296
321
  kitModules,
297
322
  viteMinifyDisabled: project.viteMinifyDisabled
298
323
  });
299
- const { results, examined, failedRules, scoringConfig } = await runAnalysis(selected, {
324
+ const analysis = await runAnalysis(selected, {
300
325
  heads,
301
326
  headings,
302
327
  images,
@@ -307,6 +332,13 @@ async function analyze(prerenderPagesDir, cwd, options, extraProjectFacts, resol
307
332
  kitModules,
308
333
  sourceFiles
309
334
  }, directives);
335
+ const { examined, failedRules, scoringConfig } = analysis;
336
+ let results = analysis.results;
337
+ if (suppressions !== void 0) {
338
+ const applied = applySourceSuppressions(results, suppressions, config);
339
+ results = applied.results;
340
+ warnings.push(...applied.notices);
341
+ }
310
342
  warnings.push(...skippedFileWarnings([...components, ...kitModules]));
311
343
  warnings.push(...unknownDirectiveIds(directives, allRules));
312
344
  for (const f of failedRules) warnings.push(formatFailedRuleWarning(f));
@@ -315,12 +347,14 @@ async function analyze(prerenderPagesDir, cwd, options, extraProjectFacts, resol
315
347
  const failed = hasFailureAtOrAbove(summary, scoringConfig.failOn);
316
348
  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.
317
349
  Scanned ${components.length} component(s) under src/ for Correctness/Security/Architecture/Accessibility/Bundle findings.`;
350
+ const consoleReport = formatConsoleReport(results, scoringConfig, { mode: "rendered / plugin" }) + "\n" + coverageNote + "\n";
351
+ const jsonReport = formatJsonReport(results, scoringConfig, { version: readPackageVersion() }, selected.map((r) => r.id), examined);
318
352
  return {
319
353
  score,
320
354
  summary,
321
355
  results,
322
- consoleReport: formatConsoleReport(results, scoringConfig, { mode: "rendered / plugin" }) + "\n" + coverageNote + "\n",
323
- jsonReport: formatJsonReport(results, scoringConfig, { version: readPackageVersion() }, selected.map((r) => r.id), examined),
356
+ consoleReport,
357
+ jsonReport,
324
358
  routeCount: rendered.heads.length,
325
359
  failed,
326
360
  failOn: scoringConfig.failOn,
@@ -635,6 +669,26 @@ function createStore() {
635
669
  //#endregion
636
670
  //#region src/ui/analysis.ts
637
671
  /**
672
+ * Apply `svelte-vitals-suppressions.json` to the whole-project layer, same entries as the build
673
+ * gate (see `applySourceSuppressions`). A malformed file is a warning here, not an error: dev
674
+ * must keep serving. Returns the results unchanged when the file is absent or `config` is
675
+ * unknown (an injected `analyze` that returns no config).
676
+ */
677
+ function suppress(root, results, config, warnings) {
678
+ if (!config) return results;
679
+ let entries;
680
+ try {
681
+ entries = loadSuppressions(root);
682
+ } catch (err) {
683
+ warnings.push(`${err instanceof Error ? err.message : String(err)} — ${SUPPRESSIONS_FILE} ignored.`);
684
+ return results;
685
+ }
686
+ if (entries === void 0) return results;
687
+ const applied = applySourceSuppressions(results, entries, config);
688
+ warnings.push(...applied.notices);
689
+ return applied.results;
690
+ }
691
+ /**
638
692
  * Owns the dev-dashboard's whole-project analysis lifecycle: an async run at startup,
639
693
  * a debounced re-run on source changes, and coalescing so a change that arrives mid-run
640
694
  * produces exactly one follow-up run rather than one per change (design doc §1).
@@ -661,12 +715,13 @@ function createAnalysisRunner(opts) {
661
715
  failOn: opts.failOn,
662
716
  parseCache
663
717
  });
664
- const { results, failedRuleIds } = result;
718
+ const { failedRuleIds } = result;
719
+ const warnings = [...result.warnings ?? []];
720
+ const results = suppress(opts.root, result.results, result.config, warnings);
665
721
  if (!stopped) {
666
722
  if (failedRuleIds !== void 0) opts.onResults(results, failedRuleIds);
667
723
  else opts.onResults(results);
668
724
  }
669
- const warnings = result.warnings ?? [];
670
725
  const key = warnings.join("\n");
671
726
  if (!stopped && warnings.length > 0 && key !== lastWarningsKey) opts.onWarnings?.(warnings);
672
727
  lastWarningsKey = key;
@@ -716,6 +771,7 @@ const CONFIG_BASENAMES = /* @__PURE__ */ new Set([
716
771
  "svelte.config.js",
717
772
  "svelte.config.ts",
718
773
  ...CONFIG_FILENAMES,
774
+ SUPPRESSIONS_FILE,
719
775
  "vite.config.js",
720
776
  "vite.config.mjs",
721
777
  "vite.config.ts",
@@ -841,7 +897,10 @@ function svelteVitals(options = {}) {
841
897
  const pagesDir = options.prerenderDir ? options.prerenderDir : join(root, DEFAULT_PRERENDER_DIR);
842
898
  const resolved = isAbsolute(pagesDir) ? pagesDir : join(root, pagesDir);
843
899
  if (!existsSync(resolved)) return;
844
- const resolvedConfig = await resolveConfig(root, options);
900
+ const resolvedConfig = {
901
+ ...await resolveConfig(root, options),
902
+ suppressions: loadSuppressions(root)
903
+ };
845
904
  let result;
846
905
  try {
847
906
  const viteMinifyDisabled = await resolveMinifyDisabled(minify, configFile, root);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svelte-vitals/vite",
3
- "version": "0.54.6",
3
+ "version": "0.54.7",
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",
@@ -43,9 +43,9 @@
43
43
  "node-html-parser": "^9.0.4"
44
44
  },
45
45
  "peerDependencies": {
46
- "@svelte-vitals/core": "^0.54.6",
46
+ "@svelte-vitals/core": "^0.54.7",
47
47
  "@sveltejs/kit": "^2.70.3",
48
- "svelte-vitals": "^0.54.6",
48
+ "svelte-vitals": "^0.54.7",
49
49
  "vite": "^8.3.0"
50
50
  },
51
51
  "peerDependenciesMeta": {
@@ -54,12 +54,12 @@
54
54
  }
55
55
  },
56
56
  "devDependencies": {
57
- "@svelte-vitals/core": "0.54.6",
57
+ "@svelte-vitals/core": "0.54.7",
58
58
  "@sveltejs/kit": "^2.70.3",
59
59
  "@types/node": "^24.13.4",
60
60
  "happy-dom": "^20.14.3",
61
61
  "svelte": "^5.57.0",
62
- "svelte-vitals": "0.54.6",
62
+ "svelte-vitals": "0.54.7",
63
63
  "vite": "^8.3.0"
64
64
  },
65
65
  "scripts": {