@svelte-vitals/vite 0.27.1 → 0.29.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.
@@ -1,5 +1,5 @@
1
1
  // src/providers/rendered/parse-html.ts
2
- import { parse, HTMLElement } from "node-html-parser";
2
+ import { parse } from "node-html-parser";
3
3
  function attrValue(v) {
4
4
  return v !== void 0 && v.trim().length > 0 ? "static" : "absent";
5
5
  }
@@ -85,17 +85,7 @@ function parseHtmlHead(html) {
85
85
  const htmlEl = root.querySelector("html");
86
86
  const lang = htmlEl?.getAttribute("lang");
87
87
  const htmlLang = lang === void 0 || lang === null ? { presence: "none", value: "absent" } : { presence: "own", value: attrValue(lang) };
88
- const headings = [];
89
- const collectHeadings = (el) => {
90
- for (const child of el.childNodes) {
91
- if (child instanceof HTMLElement) {
92
- const m = /^h([1-6])$/i.exec(child.rawTagName ?? "");
93
- if (m) headings.push(Number(m[1]));
94
- collectHeadings(child);
95
- }
96
- }
97
- };
98
- collectHeadings(root.querySelector("body") ?? root);
88
+ const headings = (root.querySelector("body") ?? root).querySelectorAll("h1,h2,h3,h4,h5,h6").map((el) => Number(el.rawTagName[1]));
99
89
  const images = (root.querySelector("body") ?? root).querySelectorAll("img").map((img) => ({
100
90
  hasWidth: img.hasAttribute("width"),
101
91
  hasHeight: img.hasAttribute("height"),
@@ -118,12 +108,7 @@ function isLoopbackOrigin(origin) {
118
108
  }
119
109
  }
120
110
  function isLoopbackHost(host) {
121
- if (host === void 0) return false;
122
- try {
123
- return isLoopbackOrigin(`http://${host}`);
124
- } catch {
125
- return false;
126
- }
111
+ return host !== void 0 && isLoopbackOrigin(`http://${host}`);
127
112
  }
128
113
 
129
114
  export {
@@ -8,7 +8,6 @@ interface SvelteVitalsHookOptions {
8
8
  /** Per-rule overrides keyed by rule id, e.g. `{ 'seo/json-ld': 'off' }`. Mirrors the plugin option. */
9
9
  rules?: Record<string, RuleSetting>;
10
10
  }
11
-
12
11
  /**
13
12
  * SvelteKit `handle` that analyzes each visited page's rendered `<head>`, in dev only,
14
13
  * and (when the live dashboard is enabled) feeds the results in — upgrading that
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  isLoopbackOrigin,
3
3
  parseHtmlHead
4
- } from "../chunk-HURGA7UH.js";
4
+ } from "../chunk-QEIZ5U4M.js";
5
5
 
6
6
  // src/hooks/handle.ts
7
7
  import { DEV } from "esm-env";
@@ -9,20 +9,14 @@ import {
9
9
  allRules,
10
10
  applyRuleSeverities,
11
11
  defineConfig,
12
+ effectiveSeverity,
13
+ isPenalized,
12
14
  runRules,
13
15
  selectRules
14
16
  } from "@svelte-vitals/core";
15
-
16
- // src/hooks/format.ts
17
- import { isPenalized, effectiveSeverity } from "@svelte-vitals/core";
18
- function penalized(results, config) {
19
- return results.filter((r) => isPenalized(r.detection, config.treatDynamicAs));
20
- }
21
17
  function findingSignature(results, config) {
22
- return penalized(results, config).map((r) => `${r.id}:${effectiveSeverity(r, config)}:${r.detection.presence}:${r.detection.value}`).sort().join("|");
18
+ return results.filter((r) => isPenalized(r.detection, config.treatDynamicAs)).map((r) => `${r.id}:${effectiveSeverity(r, config)}:${r.detection.presence}:${r.detection.value}`).sort().join("|");
23
19
  }
24
-
25
- // src/hooks/handle.ts
26
20
  async function postIngest(origin, route, results) {
27
21
  if (!isLoopbackOrigin(origin)) {
28
22
  if (globalThis.process?.env?.SVELTE_VITALS_DEBUG) {
@@ -50,10 +44,8 @@ async function analyzeAndIngest(html, route, origin, rules, config, lastSignatur
50
44
  ];
51
45
  const images = [{ route, images: imgs.map((img) => ({ ...img, file: route })) }];
52
46
  const project = { hasRobotsTxt: true, hasSitemap: true, htmlLang };
53
- const results = applyRuleSeverities(
54
- await runRules(rules, { heads: [head], headings, images, project, config }),
55
- config
56
- );
47
+ const { results: ruleResults } = await runRules(rules, { heads: [head], headings, images, project, config });
48
+ const results = applyRuleSeverities(ruleResults, config);
57
49
  const signature = findingSignature(results, config);
58
50
  if (lastSignature.get(route) === signature) return;
59
51
  lastSignature.set(route, signature);
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  isLoopbackHost,
3
3
  isLoopbackOrigin,
4
4
  parseHtmlHead
5
- } from "./chunk-HURGA7UH.js";
5
+ } from "./chunk-QEIZ5U4M.js";
6
6
 
7
7
  // src/plugin.ts
8
8
  import { existsSync } from "fs";
@@ -11,12 +11,11 @@ import { join as join4, isAbsolute, dirname, relative as relative3, basename, se
11
11
  import {
12
12
  CATEGORIES as CATEGORIES2,
13
13
  defaultConfig,
14
- defineConfig as defineConfig2,
15
14
  resolveRuleOptions,
16
15
  shouldSkipRangeCheck,
17
16
  validateRuleSetting
18
17
  } from "@svelte-vitals/core";
19
- import { findUnknownRuleIds, knownRuleIds, loadConfigFile as loadConfigFile2, ruleOptionsSpec } from "svelte-vitals";
18
+ import { findUnknownRuleIds, knownRuleIds, ruleOptionsSpec } from "svelte-vitals";
20
19
 
21
20
  // src/analyze.ts
22
21
  import {
@@ -187,13 +186,10 @@ function readCoreVersion() {
187
186
  }
188
187
 
189
188
  // src/analyze.ts
190
- async function analyze(prerenderPagesDir, cwd, options, extraProjectFacts) {
191
- const loaded = await loadConfigFile(cwd);
192
- const fileConfig = loaded?.config;
193
- const warnings = loaded?.warnings ?? [];
189
+ function mergeConfig(options, fileConfig) {
194
190
  const weights = options.weights ?? fileConfig?.weights;
195
191
  const overrides = options.overrides ?? fileConfig?.overrides;
196
- const config = defineConfig({
192
+ return defineConfig({
197
193
  treatDynamicAs: options.treatDynamicAs ?? fileConfig?.treatDynamicAs ?? "pass",
198
194
  metaComponents: options.metaComponents ?? fileConfig?.metaComponents ?? [],
199
195
  rules: options.rules ?? fileConfig?.rules ?? {},
@@ -201,6 +197,14 @@ async function analyze(prerenderPagesDir, cwd, options, extraProjectFacts) {
201
197
  ...weights !== void 0 ? { weights } : {},
202
198
  ...overrides !== void 0 ? { overrides } : {}
203
199
  });
200
+ }
201
+ async function resolveConfig(cwd, options) {
202
+ const loaded = await loadConfigFile(cwd);
203
+ const config = mergeConfig(options, loaded?.config);
204
+ return { config, warnings: loaded?.warnings ?? [] };
205
+ }
206
+ async function analyze(prerenderPagesDir, cwd, options, extraProjectFacts, resolved) {
207
+ const { config, warnings } = resolved ?? await resolveConfig(cwd, options);
204
208
  const { heads, headings, images, htmlLang } = await collectRenderedHeads(prerenderPagesDir);
205
209
  const project = {
206
210
  ...await collectRenderedProject(cwd, htmlLang),
@@ -210,22 +214,17 @@ async function analyze(prerenderPagesDir, cwd, options, extraProjectFacts) {
210
214
  const kitModules = await collectKitModuleFacts(cwd, project.kitAliases);
211
215
  const sourceFiles = await collectSourceFiles(cwd);
212
216
  const selected = selectRules(allRules, config);
213
- const results = applyOverrides(
214
- applyRuleSeverities(
215
- await runRules(selected, {
216
- heads,
217
- headings,
218
- images,
219
- project,
220
- components,
221
- config,
222
- kitModules,
223
- sourceFiles
224
- }),
225
- config
226
- ),
227
- config
228
- );
217
+ const { results: rawResults, examined } = await runRules(selected, {
218
+ heads,
219
+ headings,
220
+ images,
221
+ project,
222
+ components,
223
+ config,
224
+ kitModules,
225
+ sourceFiles
226
+ });
227
+ const results = applyOverrides(applyRuleSeverities(rawResults, config), config);
229
228
  const { score } = computeScore(results, config);
230
229
  const summary = summarize(results, config);
231
230
  const failed = hasFailureAtOrAbove(summary, config.failOn);
@@ -236,7 +235,8 @@ Scanned ${components.length} component(s) under src/ for Correctness/Security/Ar
236
235
  results,
237
236
  config,
238
237
  { version: readPackageVersion() },
239
- selected.map((r) => r.id)
238
+ selected.map((r) => r.id),
239
+ examined
240
240
  );
241
241
  return {
242
242
  score,
@@ -269,7 +269,7 @@ async function resolveMinifyDisabled(minify, configFile, root) {
269
269
  }
270
270
 
271
271
  // src/ui/middleware.ts
272
- import { renderAppShell } from "@svelte-vitals/core";
272
+ import { CATEGORIES, renderAppShell } from "@svelte-vitals/core";
273
273
 
274
274
  // src/ui/snapshot.ts
275
275
  import { buildJsonReport, safeHref } from "@svelte-vitals/core";
@@ -301,7 +301,7 @@ function buildSnapshot(store, config, meta) {
301
301
 
302
302
  // src/ui/middleware.ts
303
303
  var SEVERITIES = /* @__PURE__ */ new Set(["critical", "warning", "info"]);
304
- var CATEGORIES = /* @__PURE__ */ new Set(["seo", "performance", "correctness", "security", "architecture"]);
304
+ var CATEGORY_SET = new Set(CATEGORIES);
305
305
  function isOptionalString(v) {
306
306
  return v === void 0 || typeof v === "string";
307
307
  }
@@ -310,7 +310,7 @@ function isResultLike(x) {
310
310
  const r = x;
311
311
  const d = r.detection;
312
312
  const f = r.fix;
313
- return typeof r.id === "string" && typeof r.message === "string" && typeof r.severity === "string" && SEVERITIES.has(r.severity) && typeof d === "object" && d !== null && typeof d.presence === "string" && typeof d.value === "string" && (r.category === void 0 || typeof r.category === "string" && CATEGORIES.has(r.category)) && isOptionalString(r.location) && isOptionalString(r.recommendation) && isOptionalString(r.docsUrl) && (f === void 0 || typeof f === "object" && f !== null && typeof f.description === "string" && isOptionalString(f.snippet) && isOptionalString(f.lang)) && (r.line === void 0 || typeof r.line === "number") && isOptionalString(r.route);
313
+ return typeof r.id === "string" && typeof r.message === "string" && typeof r.severity === "string" && SEVERITIES.has(r.severity) && typeof d === "object" && d !== null && typeof d.presence === "string" && typeof d.value === "string" && (r.category === void 0 || typeof r.category === "string" && CATEGORY_SET.has(r.category)) && isOptionalString(r.location) && isOptionalString(r.recommendation) && isOptionalString(r.docsUrl) && (f === void 0 || typeof f === "object" && f !== null && typeof f.description === "string" && isOptionalString(f.snippet) && isOptionalString(f.lang)) && (r.line === void 0 || typeof r.line === "number") && isOptionalString(r.route);
314
314
  }
315
315
  function installUiMiddleware(server, config, version, store, coreVersion) {
316
316
  const clients = /* @__PURE__ */ new Set();
@@ -616,10 +616,17 @@ function svelteVitals(options = {}) {
616
616
  const pagesDir = options.prerenderDir ? options.prerenderDir : join4(root, DEFAULT_PRERENDER_DIR);
617
617
  const resolved = isAbsolute(pagesDir) ? pagesDir : join4(root, pagesDir);
618
618
  if (!existsSync(resolved)) return;
619
+ const resolvedConfig = await resolveConfig(root, options);
619
620
  let result;
620
621
  try {
621
622
  const viteMinifyDisabled = minifyFlag ? await resolveMinifyDisabled(minifyFlag.minify, minifyFlag.configFile, root) : void 0;
622
- result = await analyze(resolved, root, options, viteMinifyDisabled ? { viteMinifyDisabled } : void 0);
623
+ result = await analyze(
624
+ resolved,
625
+ root,
626
+ options,
627
+ viteMinifyDisabled ? { viteMinifyDisabled } : void 0,
628
+ resolvedConfig
629
+ );
623
630
  } catch (err) {
624
631
  console.warn(`svelte-vitals: skipped \u2014 analysis failed: ${err instanceof Error ? err.message : String(err)}`);
625
632
  return;
@@ -647,17 +654,18 @@ function svelteVitals(options = {}) {
647
654
  async configureServer(server) {
648
655
  process.env.SVELTE_VITALS_UI = "1";
649
656
  const uiRoot = options.cwd ?? server.config.root;
650
- const loaded = await loadConfigFile2(uiRoot);
651
- const fileConfig = loaded?.config;
652
- for (const w of loaded?.warnings ?? []) console.warn(`svelte-vitals: ${w}`);
653
- const weights = options.weights ?? fileConfig?.weights;
654
- const config = defineConfig2({
655
- treatDynamicAs: options.treatDynamicAs ?? fileConfig?.treatDynamicAs ?? "pass",
656
- metaComponents: options.metaComponents ?? fileConfig?.metaComponents ?? [],
657
- rules: options.rules ?? fileConfig?.rules ?? {},
658
- failOn: options.failOn ?? fileConfig?.failOn ?? "critical",
659
- ...weights !== void 0 ? { weights } : {}
660
- });
657
+ let config;
658
+ let warnings;
659
+ try {
660
+ ({ config, warnings } = await resolveConfig(uiRoot, options));
661
+ } catch (err) {
662
+ console.warn(
663
+ `svelte-vitals: config file invalid \u2014 dashboard using plugin options/defaults: ${err instanceof Error ? err.message : String(err)}`
664
+ );
665
+ config = mergeConfig(options, void 0);
666
+ warnings = [];
667
+ }
668
+ for (const w of warnings) console.warn(`svelte-vitals: ${w}`);
661
669
  const store = createStore();
662
670
  const runner = createAnalysisRunner({
663
671
  root: uiRoot,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svelte-vitals/vite",
3
- "version": "0.27.1",
3
+ "version": "0.29.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.1",
44
44
  "tinyglobby": "^0.2.17",
45
- "@svelte-vitals/core": "0.36.1",
46
- "svelte-vitals": "0.42.0"
45
+ "svelte-vitals": "0.44.0",
46
+ "@svelte-vitals/core": "0.38.0"
47
47
  },
48
48
  "peerDependencies": {
49
49
  "@sveltejs/kit": "^2.70.2",