@svelte-vitals/vite 0.1.1 → 0.2.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.
@@ -0,0 +1,42 @@
1
+ // src/providers/rendered/parse-html.ts
2
+ import { parse } from "node-html-parser";
3
+ function attrValue(v) {
4
+ return v !== void 0 && v.trim().length > 0 ? "static" : "absent";
5
+ }
6
+ function parseHtmlHead(html) {
7
+ const root = parse(html);
8
+ const head = root.querySelector("head") ?? root;
9
+ const tags = [];
10
+ const title = head.querySelector("title");
11
+ if (title) tags.push({ kind: "title", presence: "own", value: attrValue(title.text) });
12
+ for (const meta of head.querySelectorAll("meta")) {
13
+ const name = meta.getAttribute("name");
14
+ const property = meta.getAttribute("property");
15
+ if (!name && !property) continue;
16
+ tags.push({
17
+ kind: "meta",
18
+ ...name ? { name } : {},
19
+ ...property ? { property } : {},
20
+ presence: "own",
21
+ value: attrValue(meta.getAttribute("content"))
22
+ });
23
+ }
24
+ for (const link of head.querySelectorAll("link")) {
25
+ const rel = link.getAttribute("rel");
26
+ if (!rel) continue;
27
+ tags.push({ kind: "link", rel, presence: "own", value: attrValue(link.getAttribute("href")) });
28
+ }
29
+ for (const script of head.querySelectorAll("script")) {
30
+ if (script.getAttribute("type") === "application/ld+json") {
31
+ tags.push({ kind: "jsonld", presence: "own", value: attrValue(script.text) });
32
+ }
33
+ }
34
+ const htmlEl = root.querySelector("html");
35
+ const lang = htmlEl?.getAttribute("lang");
36
+ const htmlLang = lang === void 0 || lang === null ? { presence: "none", value: "absent" } : { presence: "own", value: attrValue(lang) };
37
+ return { tags, htmlLang };
38
+ }
39
+
40
+ export {
41
+ parseHtmlHead
42
+ };
@@ -0,0 +1,18 @@
1
+ import { Handle } from '@sveltejs/kit';
2
+ import { RuleSetting } from '@svelte-vitals/core';
3
+
4
+ /** Options for the dev-time SvelteKit handle. A focused subset of the plugin options. */
5
+ interface SvelteVitalsHookOptions {
6
+ /** Component names treated as meta sources (design §11 layer 4). Mirrors the plugin option. */
7
+ metaComponents?: string[];
8
+ /** Per-rule overrides keyed by rule id, e.g. `{ SEO008: 'off' }`. Mirrors the plugin option. */
9
+ rules?: Record<string, RuleSetting>;
10
+ }
11
+
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())`.
15
+ */
16
+ declare function svelteVitalsHandle(options?: SvelteVitalsHookOptions): Handle;
17
+
18
+ export { type SvelteVitalsHookOptions, svelteVitalsHandle };
@@ -0,0 +1,76 @@
1
+ import {
2
+ parseHtmlHead
3
+ } from "../chunk-3MTVPSNS.js";
4
+
5
+ // src/hooks/handle.ts
6
+ import { DEV } from "esm-env";
7
+ import {
8
+ allRules,
9
+ applyRuleSeverities,
10
+ defineConfig,
11
+ runRules,
12
+ selectRules
13
+ } from "@svelte-vitals/core";
14
+
15
+ // src/hooks/format.ts
16
+ import { isPenalized, effectiveSeverity } from "@svelte-vitals/core";
17
+ var GLYPH = { critical: "\u2717", warning: "\u26A0", info: "\xB7" };
18
+ var RANK = { critical: 0, warning: 1, info: 2 };
19
+ function penalized(results, config) {
20
+ return results.filter((r) => isPenalized(r.detection, config.treatDynamicAs));
21
+ }
22
+ function formatDevReport(route, results, config) {
23
+ const failing = penalized(results, config).sort(
24
+ (a, b) => RANK[effectiveSeverity(a, config)] - RANK[effectiveSeverity(b, config)] || a.id.localeCompare(b.id)
25
+ );
26
+ if (failing.length === 0) return "";
27
+ const lines = [`[svelte-vitals] ${route}`];
28
+ for (const r of failing) {
29
+ lines.push(` ${GLYPH[effectiveSeverity(r, config)]} ${r.id} ${r.message}`);
30
+ }
31
+ return lines.join("\n");
32
+ }
33
+ function findingSignature(results, config) {
34
+ return penalized(results, config).map((r) => `${r.id}:${effectiveSeverity(r, config)}:${r.detection.presence}:${r.detection.value}`).sort().join("|");
35
+ }
36
+
37
+ // src/hooks/handle.ts
38
+ async function analyzeAndWarn(html, route, rules, config, lastSignature) {
39
+ try {
40
+ const { tags, htmlLang } = parseHtmlHead(html);
41
+ const head = { route, source: "rendered", tags, file: route };
42
+ const project = { hasRobotsTxt: true, hasSitemap: true, htmlLang };
43
+ const results = applyRuleSeverities(await runRules(rules, { heads: [head], project, config }), config);
44
+ const signature = findingSignature(results, config);
45
+ if (lastSignature.get(route) === signature) return;
46
+ lastSignature.set(route, signature);
47
+ const report = formatDevReport(route, results, config);
48
+ if (report) console.warn(report);
49
+ } catch (err) {
50
+ if (globalThis.process?.env?.SVELTE_VITALS_DEBUG) {
51
+ console.warn("[svelte-vitals] dev analysis failed:", err);
52
+ }
53
+ }
54
+ }
55
+ function svelteVitalsHandle(options = {}) {
56
+ if (!DEV) return ({ event, resolve }) => resolve(event);
57
+ const config = defineConfig({
58
+ metaComponents: options.metaComponents ?? [],
59
+ rules: options.rules ?? {}
60
+ });
61
+ const rules = selectRules(allRules, config);
62
+ const lastSignature = /* @__PURE__ */ new Map();
63
+ return ({ event, resolve }) => {
64
+ let buffer = "";
65
+ return resolve(event, {
66
+ transformPageChunk: ({ html, done }) => {
67
+ buffer += html;
68
+ if (done) void analyzeAndWarn(buffer, event.route.id ?? event.url.pathname, rules, config, lastSignature);
69
+ return html;
70
+ }
71
+ });
72
+ };
73
+ }
74
+ export {
75
+ svelteVitalsHandle
76
+ };
package/dist/index.js CHANGED
@@ -1,3 +1,7 @@
1
+ import {
2
+ parseHtmlHead
3
+ } from "./chunk-3MTVPSNS.js";
4
+
1
5
  // src/plugin.ts
2
6
  import { existsSync } from "fs";
3
7
  import { writeFile, mkdir } from "fs/promises";
@@ -21,47 +25,6 @@ import {
21
25
  import { readFile } from "fs/promises";
22
26
  import { join } from "path";
23
27
  import { glob } from "tinyglobby";
24
-
25
- // src/providers/rendered/parse-html.ts
26
- import { parse } from "node-html-parser";
27
- function attrValue(v) {
28
- return v !== void 0 && v.trim().length > 0 ? "static" : "absent";
29
- }
30
- function parseHtmlHead(html) {
31
- const root = parse(html);
32
- const head = root.querySelector("head") ?? root;
33
- const tags = [];
34
- const title = head.querySelector("title");
35
- if (title) tags.push({ kind: "title", presence: "own", value: attrValue(title.text) });
36
- for (const meta of head.querySelectorAll("meta")) {
37
- const name = meta.getAttribute("name");
38
- const property = meta.getAttribute("property");
39
- if (!name && !property) continue;
40
- tags.push({
41
- kind: "meta",
42
- ...name ? { name } : {},
43
- ...property ? { property } : {},
44
- presence: "own",
45
- value: attrValue(meta.getAttribute("content"))
46
- });
47
- }
48
- for (const link of head.querySelectorAll("link")) {
49
- const rel = link.getAttribute("rel");
50
- if (!rel) continue;
51
- tags.push({ kind: "link", rel, presence: "own", value: attrValue(link.getAttribute("href")) });
52
- }
53
- for (const script of head.querySelectorAll("script")) {
54
- if (script.getAttribute("type") === "application/ld+json") {
55
- tags.push({ kind: "jsonld", presence: "own", value: attrValue(script.text) });
56
- }
57
- }
58
- const htmlEl = root.querySelector("html");
59
- const lang = htmlEl?.getAttribute("lang");
60
- const htmlLang = lang === void 0 || lang === null ? { presence: "none", value: "absent" } : { presence: "own", value: attrValue(lang) };
61
- return { tags, htmlLang };
62
- }
63
-
64
- // src/providers/rendered/collect.ts
65
28
  function deriveRouteFromHtmlPath(relPath) {
66
29
  let p = relPath.replace(/\\/g, "/").replace(/\.html$/, "");
67
30
  if (p === "index") return "/";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svelte-vitals/vite",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Vite/SvelteKit plugin for svelte-vitals — analyzes prerendered HTML during vite build.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -25,6 +25,10 @@
25
25
  ".": {
26
26
  "types": "./dist/index.d.ts",
27
27
  "import": "./dist/index.js"
28
+ },
29
+ "./hooks": {
30
+ "types": "./dist/hooks/index.d.ts",
31
+ "import": "./dist/hooks/index.js"
28
32
  }
29
33
  },
30
34
  "main": "./dist/index.js",
@@ -33,15 +37,24 @@
33
37
  "dist"
34
38
  ],
35
39
  "dependencies": {
40
+ "esm-env": "^1.2.2",
36
41
  "node-html-parser": "^6.1.13",
37
42
  "tinyglobby": "^0.2.17",
38
- "@svelte-vitals/core": "0.4.0"
43
+ "@svelte-vitals/core": "0.5.0"
39
44
  },
40
45
  "peerDependencies": {
46
+ "@sveltejs/kit": "^2.0.0",
41
47
  "vite": "^8.0.16"
42
48
  },
49
+ "peerDependenciesMeta": {
50
+ "@sveltejs/kit": {
51
+ "optional": true
52
+ }
53
+ },
43
54
  "devDependencies": {
55
+ "@sveltejs/kit": "^2.0.0",
44
56
  "@types/node": "^24.7.0",
57
+ "svelte": "^5.56.3",
45
58
  "vite": "^8.0.16"
46
59
  },
47
60
  "scripts": {