@svelte-vitals/vite 0.36.3 → 0.36.4

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.
@@ -1,4 +1,4 @@
1
- import { i as toOccurrenceMap, o as parseHtmlHead, t as isLoopbackOrigin } from "../loopback-CE47i4VD.js";
1
+ import { i as toOccurrenceMap, r as parseHtmlHead, t as isLoopbackOrigin } from "../loopback-D9pGJXl6.js";
2
2
  import { defineConfig } from "@svelte-vitals/core";
3
3
  import { allRules, defaultProject, effectiveSeverity, formatFailedRuleWarning, isPenalized, runAnalysis, selectRules, terminalSafe } from "@svelte-vitals/core/internal";
4
4
  import { DEV } from "esm-env";
@@ -8,24 +8,37 @@ function findingSignature(results, config) {
8
8
  return results.filter((r) => isPenalized(r.detection, config.treatDynamicAs)).map((r) => `${r.id}:${effectiveSeverity(r, config)}:${r.detection.presence}:${r.detection.value}`).sort().join("|");
9
9
  }
10
10
  const warn = (line) => console.warn(terminalSafe(line));
11
+ /** A dashboard that never answers must not block the route's later ingests forever. */
12
+ const INGEST_TIMEOUT_MS = 1e4;
13
+ /** True only when the dashboard acknowledged the POST. */
11
14
  async function postIngest(origin, route, results, failedRuleIds) {
12
15
  if (!isLoopbackOrigin(origin)) {
13
16
  if (globalThis.process?.env?.SVELTE_VITALS_DEBUG) warn(`svelte-vitals: live UI ingest skipped for non-loopback origin ${origin} — open the dashboard via localhost`);
14
- return;
17
+ return false;
15
18
  }
19
+ const controller = new AbortController();
20
+ const timer = setTimeout(() => controller.abort(), INGEST_TIMEOUT_MS);
16
21
  try {
17
- await fetch(`${origin}/__svelte-vitals/ingest`, {
22
+ const res = await fetch(`${origin}/__svelte-vitals/ingest`, {
18
23
  method: "POST",
19
24
  headers: { "content-type": "application/json" },
20
25
  body: JSON.stringify({
21
26
  route,
22
27
  results,
23
28
  failedRuleIds
24
- })
29
+ }),
30
+ signal: controller.signal
25
31
  });
26
- } catch {}
32
+ if (!res.ok && globalThis.process?.env?.SVELTE_VITALS_DEBUG) warn(`svelte-vitals: live UI ingest for ${route} rejected with HTTP ${res.status}`);
33
+ return res.ok;
34
+ } catch (err) {
35
+ if (globalThis.process?.env?.SVELTE_VITALS_DEBUG) warn(`svelte-vitals: live UI ingest for ${route} failed: ${err instanceof Error ? err.message : String(err)}`);
36
+ return false;
37
+ } finally {
38
+ clearTimeout(timer);
39
+ }
27
40
  }
28
- async function analyzeAndIngest(html, route, origin, rules, config, lastSignature) {
41
+ async function analyzeAndIngest(html, route, origin, rules, config, queued, inflight) {
29
42
  try {
30
43
  const { tags, headings: levels, images: imgs, landmarks, nestedLandmarks, ids, idRefs } = parseHtmlHead(html);
31
44
  const head = {
@@ -76,9 +89,19 @@ async function analyzeAndIngest(html, route, origin, rules, config, lastSignatur
76
89
  }, /* @__PURE__ */ new Map());
77
90
  if (failedRules.length > 0 && globalThis.process?.env?.SVELTE_VITALS_DEBUG) for (const f of failedRules) warn(formatFailedRuleWarning(f));
78
91
  const signature = `${findingSignature(results, config)}|failed:${[...failedRuleIds].sort().join(",")}`;
79
- if (lastSignature.get(route) === signature) return;
80
- lastSignature.set(route, signature);
81
- if (globalThis.process?.env?.SVELTE_VITALS_UI) postIngest(origin, route, results, failedRuleIds);
92
+ if (queued.get(route)?.signature === signature) return;
93
+ if (!globalThis.process?.env?.SVELTE_VITALS_UI) return;
94
+ const entry = { signature };
95
+ queued.set(route, entry);
96
+ const next = (inflight.get(route) ?? Promise.resolve()).then(async () => {
97
+ if (!await postIngest(origin, route, results, failedRuleIds) && queued.get(route) === entry) queued.delete(route);
98
+ });
99
+ inflight.set(route, next);
100
+ try {
101
+ await next;
102
+ } finally {
103
+ if (inflight.get(route) === next) inflight.delete(route);
104
+ }
82
105
  } catch (err) {
83
106
  if (globalThis.process?.env?.SVELTE_VITALS_DEBUG) warn(`svelte-vitals: dev analysis failed: ${err instanceof Error ? err.message : String(err)}`);
84
107
  }
@@ -96,12 +119,13 @@ function svelteVitalsHandle(options = {}) {
96
119
  rules: options.rules ?? {}
97
120
  });
98
121
  const rules = selectRules(allRules, config).filter((r) => r.scope === "route" && !r.crossRoute);
99
- const lastSignature = /* @__PURE__ */ new Map();
122
+ const queued = /* @__PURE__ */ new Map();
123
+ const inflight = /* @__PURE__ */ new Map();
100
124
  return ({ event, resolve }) => {
101
125
  let buffer = "";
102
126
  return resolve(event, { transformPageChunk: ({ html, done }) => {
103
127
  buffer += html;
104
- if (done && event.route.id != null) analyzeAndIngest(buffer, event.route.id, event.url.origin, rules, config, lastSignature);
128
+ if (done && event.route.id != null) analyzeAndIngest(buffer, event.route.id, event.url.origin, rules, config, queued, inflight);
105
129
  return html;
106
130
  } });
107
131
  };
package/dist/index.js CHANGED
@@ -1,10 +1,88 @@
1
- import { a as globFiles, n as isSameOrigin, r as collectRenderedHeads, t as isLoopbackOrigin } from "./loopback-CE47i4VD.js";
1
+ import { i as toOccurrenceMap, n as isSameOrigin, r as parseHtmlHead, t as isLoopbackOrigin } from "./loopback-D9pGJXl6.js";
2
2
  import { existsSync, readFileSync } from "node:fs";
3
3
  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
- 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, withReadLimit } from "@svelte-vitals/core/internal";
7
- import { CONFIG_FILENAMES, analyzeProject, findUnknownRuleIds, knownRuleIds, loadConfigFile, registryTag, ruleOptionsSpec } from "svelte-vitals";
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";
8
+ //#region src/providers/rendered/collect.ts
9
+ /** Map a prerendered HTML path (relative to pages/, POSIX) to its route. */
10
+ function deriveRouteFromHtmlPath(relPath) {
11
+ let p = relPath.replace(/\\/g, "/").replace(/\.html$/, "");
12
+ if (p === "index") return "/";
13
+ if (p.endsWith("/index")) p = p.slice(0, -6);
14
+ return "/" + p;
15
+ }
16
+ /** Read every prerendered HTML page under `prerenderPagesDir` into ResolvedHead[]. */
17
+ async function collectRenderedHeads(prerenderPagesDir) {
18
+ const rt = createNodeRuntime();
19
+ const files = (await rt.glob("**/*.html", prerenderPagesDir)).sort();
20
+ const parsedFiles = await Promise.all(files.map(async (rel) => ({
21
+ rel,
22
+ parsed: parseHtmlHead(await rt.readFile(rt.join(prerenderPagesDir, rel)))
23
+ })));
24
+ const heads = [];
25
+ const headings = [];
26
+ const images = [];
27
+ const a11y = [];
28
+ let htmlLang = {
29
+ presence: "none",
30
+ value: "absent"
31
+ };
32
+ for (const { rel, parsed } of parsedFiles) {
33
+ if (htmlLang.presence === "none" && parsed.htmlLang.presence === "own") htmlLang = parsed.htmlLang;
34
+ const route = deriveRouteFromHtmlPath(rel);
35
+ heads.push({
36
+ route,
37
+ source: "rendered",
38
+ tags: parsed.tags,
39
+ file: rel
40
+ });
41
+ headings.push({
42
+ route,
43
+ headings: parsed.headings.map((level) => ({
44
+ level,
45
+ line: 0,
46
+ file: rel
47
+ }))
48
+ });
49
+ images.push({
50
+ route,
51
+ images: parsed.images.map((img) => ({
52
+ ...img,
53
+ file: rel
54
+ }))
55
+ });
56
+ a11y.push({
57
+ route,
58
+ landmarks: toOccurrenceMap(parsed.landmarks, rel),
59
+ nestedLandmarks: parsed.nestedLandmarks.map((n) => ({
60
+ ...n,
61
+ file: rel,
62
+ line: 0
63
+ })),
64
+ ids: toOccurrenceMap(parsed.ids, rel),
65
+ idRefs: parsed.idRefs.map((r) => ({
66
+ ...r,
67
+ file: rel,
68
+ line: 0
69
+ })),
70
+ idCandidates: [...new Set(parsed.ids)],
71
+ fullyResolved: true,
72
+ elementTags: parsed.elementTags,
73
+ elementsClosed: true,
74
+ file: rel
75
+ });
76
+ }
77
+ return {
78
+ heads,
79
+ headings,
80
+ images,
81
+ a11y,
82
+ htmlLang
83
+ };
84
+ }
85
+ //#endregion
8
86
  //#region src/providers/rendered/project.ts
9
87
  async function exists(path) {
10
88
  try {
@@ -61,25 +139,7 @@ async function collectRenderedProject(cwd, htmlLang) {
61
139
  }
62
140
  //#endregion
63
141
  //#region src/providers/source/components.ts
64
- /**
65
- * Node-backed Runtime adapter (design §8). vite always runs in Node, so no
66
- * swappable runtime is needed here — this just satisfies the interface the
67
- * shared core implementation expects.
68
- */
69
- const boundedRead = withReadLimit((path) => readFile(path, "utf8"));
70
- const nodeRuntime = {
71
- readFile: (path) => boundedRead(path),
72
- async exists(path) {
73
- try {
74
- await access(path);
75
- return true;
76
- } catch {
77
- return false;
78
- }
79
- },
80
- glob: (pattern, cwd) => globFiles(pattern, cwd),
81
- join: (...parts) => join(...parts)
82
- };
142
+ const nodeRuntime = createNodeRuntime();
83
143
  /**
84
144
  * Scan every `.svelte` component under `src/` for Correctness/Security/Architecture/
85
145
  * Bundle-Performance/Accessibility facts (build mode only). Implementation lives in
@@ -330,6 +390,7 @@ function isResultLike(x) {
330
390
  */
331
391
  function installUiMiddleware(server, config, version, store, coreVersion, getStaticFailedRuleIds) {
332
392
  const clients = /* @__PURE__ */ new Set();
393
+ const currentConfig = () => typeof config === "function" ? config() : config;
333
394
  store.subscribe(() => {
334
395
  for (const res of clients) try {
335
396
  res.write("event: update\ndata: {}\n\n");
@@ -396,7 +457,7 @@ function installUiMiddleware(server, config, version, store, coreVersion, getSta
396
457
  }
397
458
  if (url.startsWith("/data.json")) {
398
459
  try {
399
- const snapshot = buildSnapshot(store, config, {
460
+ const snapshot = buildSnapshot(store, currentConfig(), {
400
461
  version,
401
462
  coreVersion
402
463
  }, getStaticFailedRuleIds?.());
@@ -409,7 +470,7 @@ function installUiMiddleware(server, config, version, store, coreVersion, getSta
409
470
  return;
410
471
  }
411
472
  try {
412
- const html = renderAppShell(buildSnapshot(store, config, {
473
+ const html = renderAppShell(buildSnapshot(store, currentConfig(), {
413
474
  version,
414
475
  coreVersion
415
476
  }, getStaticFailedRuleIds?.()));
@@ -456,6 +517,12 @@ function composeBadges(staticResults, liveByRoute) {
456
517
  for (const route of liveByRoute.keys()) badges[route] = "measured";
457
518
  return badges;
458
519
  }
520
+ /**
521
+ * In-memory findings store for the dev UI. Owned by the ui plugin, shared between the
522
+ * analysis runner (writes the static, whole-project layer) and the ingest middleware
523
+ * (writes the live, per-route layer from rendered pages). `snapshot()`/`badges()` expose
524
+ * the merged view per the design doc (2026-07-08-dev-dashboard-whole-project-design.md §2).
525
+ */
459
526
  function createStore() {
460
527
  let staticResults = [];
461
528
  const liveByRoute = /* @__PURE__ */ new Map();
@@ -468,6 +535,11 @@ function createStore() {
468
535
  for (const fn of subs) fn();
469
536
  }
470
537
  return {
538
+ /**
539
+ * Replace a route's live findings (route stamped onto results missing one) and notify
540
+ * subscribers. `failedRuleIds` replaces that route's live-layer failed-rule set — an
541
+ * omitted or empty array clears it, so a route re-analyzed with no failures recovers.
542
+ */
471
543
  set(route, results, failedRuleIds) {
472
544
  liveByRoute.set(route, results.map((r) => r.route ? r : {
473
545
  ...r,
@@ -481,6 +553,7 @@ function createStore() {
481
553
  staticResults = results;
482
554
  notify();
483
555
  },
556
+ /** Whether a whole-project analysis run is in flight; participates in subscribe/notify like a findings change. */
484
557
  setAnalyzing(next) {
485
558
  analyzing = next;
486
559
  notify();
@@ -488,15 +561,19 @@ function createStore() {
488
561
  isAnalyzing() {
489
562
  return analyzing;
490
563
  },
564
+ /** Composed findings across both layers — feed straight into buildJsonReport. */
491
565
  snapshot() {
492
566
  return composeSnapshot(staticResults, liveByRoute);
493
567
  },
568
+ /** Per-route provenance for the dashboard's badges: 'measured' (live) or 'static'. */
494
569
  badges() {
495
570
  return composeBadges(staticResults, liveByRoute);
496
571
  },
572
+ /** Union of failed rule ids across every live (ingested) route, for `withFailedRulesOff`. */
497
573
  failedRuleIds() {
498
574
  return [...new Set([...liveFailedByRoute.values()].flat())].sort();
499
575
  },
576
+ /** Bumped once per notify() — lets consumers discard stale fetches. */
500
577
  sequence() {
501
578
  return seq;
502
579
  },
@@ -521,12 +598,13 @@ function createAnalysisRunner(opts) {
521
598
  let running = false;
522
599
  let pending = false;
523
600
  let timer;
601
+ let lastWarningsKey;
524
602
  async function runOnce() {
525
603
  if (stopped) return;
526
604
  running = true;
527
605
  opts.onStatusChange?.(true);
528
606
  try {
529
- const { results, failedRuleIds } = await analyze({
607
+ const result = await analyze({
530
608
  cwd: opts.root,
531
609
  treatDynamicAs: opts.treatDynamicAs,
532
610
  metaComponents: opts.metaComponents,
@@ -534,8 +612,13 @@ function createAnalysisRunner(opts) {
534
612
  failOn: opts.failOn,
535
613
  parseCache
536
614
  });
615
+ const { results, failedRuleIds } = result;
537
616
  if (!stopped) if (failedRuleIds !== void 0) opts.onResults(results, failedRuleIds);
538
617
  else opts.onResults(results);
618
+ const warnings = result.warnings ?? [];
619
+ const key = warnings.join("\n");
620
+ if (!stopped && warnings.length > 0 && key !== lastWarningsKey) opts.onWarnings?.(warnings);
621
+ lastWarningsKey = key;
539
622
  } catch (err) {
540
623
  if (!stopped) opts.onError(err);
541
624
  } finally {
@@ -548,10 +631,12 @@ function createAnalysisRunner(opts) {
548
631
  }
549
632
  }
550
633
  return {
634
+ /** Kick off the first whole-project analysis. Fire-and-forget — never blocks the caller. */
551
635
  start() {
552
636
  if (stopped) return;
553
637
  runOnce();
554
638
  },
639
+ /** A relevant source file changed; schedule a debounced re-analysis. */
555
640
  notifyChange(file) {
556
641
  if (stopped) return;
557
642
  const rel = relative(opts.root, file).split(sep).join("/");
@@ -736,15 +821,23 @@ function svelteVitals(options = {}) {
736
821
  process.env.SVELTE_VITALS_UI = "1";
737
822
  const uiRoot = options.cwd ?? server.config.root;
738
823
  let config;
739
- let warnings;
740
- try {
741
- ({config, warnings} = await resolveConfig(uiRoot, options));
742
- } catch (err) {
743
- warn(`svelte-vitals: config file invalid — dashboard using plugin options/defaults: ${err instanceof Error ? err.message : String(err)}`);
744
- config = mergeConfig(options, void 0);
745
- warnings = [];
746
- }
747
- for (const w of warnings) warn(`svelte-vitals: ${w}`);
824
+ let printedConfigWarnings = /* @__PURE__ */ new Set();
825
+ let configGeneration = 0;
826
+ const applyConfig = async () => {
827
+ const generation = ++configGeneration;
828
+ try {
829
+ const resolved = await resolveConfig(uiRoot, options);
830
+ if (generation !== configGeneration) return;
831
+ config = resolved.config;
832
+ printedConfigWarnings = new Set(resolved.warnings);
833
+ for (const w of resolved.warnings) warn(`svelte-vitals: ${w}`);
834
+ } catch (err) {
835
+ if (generation !== configGeneration) return;
836
+ warn(`svelte-vitals: config file invalid — dashboard using ${config ? "the previous config" : "plugin options/defaults"}: ${err instanceof Error ? err.message : String(err)}`);
837
+ config ??= mergeConfig(options, void 0);
838
+ }
839
+ };
840
+ await applyConfig();
748
841
  const store = createStore();
749
842
  let staticFailedRuleIds = [];
750
843
  const runner = createAnalysisRunner({
@@ -758,17 +851,28 @@ function svelteVitals(options = {}) {
758
851
  staticFailedRuleIds = failedRuleIds ?? [];
759
852
  },
760
853
  onError: (err) => warn(`svelte-vitals: dev analysis failed: ${err instanceof Error ? err.message : String(err)}`),
854
+ onWarnings: (warnings) => {
855
+ for (const w of warnings) if (!printedConfigWarnings.has(w)) warn(`svelte-vitals: ${w}`);
856
+ },
761
857
  onStatusChange: (analyzing) => store.setAnalyzing(analyzing)
762
858
  });
763
859
  runner.start();
860
+ let configTimer;
764
861
  server.watcher?.on("all", (_event, file) => {
765
- if (isRelevant(file, uiRoot)) runner.notifyChange(file);
862
+ if (!isRelevant(file, uiRoot)) return;
863
+ if (CONFIG_FILENAMES.includes(basename(file))) {
864
+ clearTimeout(configTimer);
865
+ configTimer = setTimeout(() => void applyConfig().then(() => runner.notifyChange(file)), 500);
866
+ return;
867
+ }
868
+ runner.notifyChange(file);
766
869
  });
767
870
  server.httpServer?.once("close", () => {
768
871
  delete process.env.SVELTE_VITALS_UI;
872
+ clearTimeout(configTimer);
769
873
  runner.stop();
770
874
  });
771
- installUiMiddleware(server, config, readPackageVersion(), store, readCoreVersion(), () => staticFailedRuleIds);
875
+ installUiMiddleware(server, () => config, readPackageVersion(), store, readCoreVersion(), () => staticFailedRuleIds);
772
876
  if (typeof server.printUrls === "function") {
773
877
  const printUrls = server.printUrls.bind(server);
774
878
  server.printUrls = () => {
@@ -1,5 +1,3 @@
1
- import { glob, readFile, stat } from "node:fs/promises";
2
- import { join, relative, sep } from "node:path";
3
1
  import { ASIDE_DEMOTING_TAGS, IDREF_ATTRS, NAMING_ATTRS, SECTIONING_TAGS, decodeFragmentId, isClassicScriptType, isTopFragment, resolveLandmark, splitTokens, stripTextDirective } from "@svelte-vitals/core/internal";
4
2
  import { parse } from "node-html-parser";
5
3
  //#region src/providers/rendered/parse-html.ts
@@ -180,34 +178,6 @@ function parseHtmlHead(html) {
180
178
  ...collectA11y(root)
181
179
  };
182
180
  }
183
- //#endregion
184
- //#region src/glob.ts
185
- /**
186
- * `node:fs` glob constrained to the contract callers rely on: files only (fs.glob also matches
187
- * directories), cwd-relative POSIX-separated paths on every platform (fs.glob returns platform
188
- * separators), dotfiles excluded (fs.glob's default). A symlink's Dirent never reports isFile(),
189
- * so symlinks are stat'd and kept when they point at a file; symlinked *directories* are not
190
- * traversed — fs.glob never descends into them. Duplicated in the CLI package — core purity
191
- * (no `node:` imports in core) rules out the shared home.
192
- */
193
- async function globFiles(pattern, cwd) {
194
- const out = [];
195
- for await (const entry of glob(pattern, {
196
- cwd,
197
- withFileTypes: true
198
- })) {
199
- const path = join(entry.parentPath, entry.name);
200
- if (!entry.isFile()) {
201
- if (!entry.isSymbolicLink()) continue;
202
- if (!(await stat(path).catch(() => null))?.isFile()) continue;
203
- }
204
- const rel = relative(cwd, path);
205
- out.push(sep === "/" ? rel : rel.split(sep).join("/"));
206
- }
207
- return out;
208
- }
209
- //#endregion
210
- //#region src/providers/rendered/collect.ts
211
181
  /** Group raw occurrence keys (one entry per hit, in document order) by key, `file` attached, `line: 0` (rendered mode does not track source lines). */
212
182
  function toOccurrenceMap(keys, file) {
213
183
  const out = Object.create(null);
@@ -217,81 +187,6 @@ function toOccurrenceMap(keys, file) {
217
187
  });
218
188
  return out;
219
189
  }
220
- /** Map a prerendered HTML path (relative to pages/, POSIX) to its route. */
221
- function deriveRouteFromHtmlPath(relPath) {
222
- let p = relPath.replace(/\\/g, "/").replace(/\.html$/, "");
223
- if (p === "index") return "/";
224
- if (p.endsWith("/index")) p = p.slice(0, -6);
225
- return "/" + p;
226
- }
227
- /** Read every prerendered HTML page under `prerenderPagesDir` into ResolvedHead[]. */
228
- async function collectRenderedHeads(prerenderPagesDir) {
229
- const files = (await globFiles("**/*.html", prerenderPagesDir)).sort();
230
- const parsedFiles = await Promise.all(files.map(async (rel) => ({
231
- rel,
232
- parsed: parseHtmlHead(await readFile(join(prerenderPagesDir, rel), "utf8"))
233
- })));
234
- const heads = [];
235
- const headings = [];
236
- const images = [];
237
- const a11y = [];
238
- let htmlLang = {
239
- presence: "none",
240
- value: "absent"
241
- };
242
- for (const { rel, parsed } of parsedFiles) {
243
- if (htmlLang.presence === "none" && parsed.htmlLang.presence === "own") htmlLang = parsed.htmlLang;
244
- const route = deriveRouteFromHtmlPath(rel);
245
- heads.push({
246
- route,
247
- source: "rendered",
248
- tags: parsed.tags,
249
- file: rel
250
- });
251
- headings.push({
252
- route,
253
- headings: parsed.headings.map((level) => ({
254
- level,
255
- line: 0,
256
- file: rel
257
- }))
258
- });
259
- images.push({
260
- route,
261
- images: parsed.images.map((img) => ({
262
- ...img,
263
- file: rel
264
- }))
265
- });
266
- a11y.push({
267
- route,
268
- landmarks: toOccurrenceMap(parsed.landmarks, rel),
269
- nestedLandmarks: parsed.nestedLandmarks.map((n) => ({
270
- ...n,
271
- file: rel,
272
- line: 0
273
- })),
274
- ids: toOccurrenceMap(parsed.ids, rel),
275
- idRefs: parsed.idRefs.map((r) => ({
276
- ...r,
277
- file: rel,
278
- line: 0
279
- })),
280
- idCandidates: [...new Set(parsed.ids)],
281
- fullyResolved: true,
282
- elementTags: parsed.elementTags,
283
- elementsClosed: true,
284
- file: rel
285
- });
286
- }
287
- return {
288
- heads,
289
- headings,
290
- images,
291
- a11y,
292
- htmlLang
293
- };
294
- }
295
190
  //#endregion
296
191
  //#region src/loopback.ts
297
192
  /** Only the local dev server hosts the ingest endpoint, so never POST off-box. */
@@ -325,4 +220,4 @@ function effectivePort(url) {
325
220
  return url.port || (url.protocol === "https:" ? "443" : "80");
326
221
  }
327
222
  //#endregion
328
- export { globFiles as a, toOccurrenceMap as i, isSameOrigin as n, parseHtmlHead as o, collectRenderedHeads as r, isLoopbackOrigin as t };
223
+ export { toOccurrenceMap as i, isSameOrigin as n, parseHtmlHead as r, isLoopbackOrigin as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svelte-vitals/vite",
3
- "version": "0.36.3",
3
+ "version": "0.36.4",
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.2"
44
44
  },
45
45
  "peerDependencies": {
46
- "@svelte-vitals/core": "^0.51.1",
46
+ "@svelte-vitals/core": "^0.51.2",
47
47
  "@sveltejs/kit": "^2.70.3",
48
- "svelte-vitals": "^0.54.3",
48
+ "svelte-vitals": "^0.54.4",
49
49
  "vite": "^8.2.2"
50
50
  },
51
51
  "peerDependenciesMeta": {
@@ -54,12 +54,12 @@
54
54
  }
55
55
  },
56
56
  "devDependencies": {
57
- "@svelte-vitals/core": "0.51.1",
57
+ "@svelte-vitals/core": "0.51.2",
58
58
  "@sveltejs/kit": "^2.70.3",
59
59
  "@types/node": "^24.13.3",
60
- "happy-dom": "^20.12.0",
60
+ "happy-dom": "^20.13.2",
61
61
  "svelte": "^5.57.0",
62
- "svelte-vitals": "0.54.3",
62
+ "svelte-vitals": "0.54.4",
63
63
  "vite": "^8.2.2"
64
64
  },
65
65
  "scripts": {