@svelte-vitals/vite 0.5.0 → 0.7.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.
- package/dist/chunk-MICKONGB.js +89 -0
- package/dist/hooks/index.js +6 -3
- package/dist/index.js +27 -7
- package/package.json +2 -2
- package/dist/chunk-AHRJ6LY3.js +0 -51
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// src/providers/rendered/parse-html.ts
|
|
2
|
+
import { parse, HTMLElement } 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) {
|
|
12
|
+
const text = title.text;
|
|
13
|
+
tags.push({
|
|
14
|
+
kind: "title",
|
|
15
|
+
presence: "own",
|
|
16
|
+
value: attrValue(text),
|
|
17
|
+
...text && text.trim().length > 0 ? { text } : {}
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
for (const meta of head.querySelectorAll("meta")) {
|
|
21
|
+
const name = meta.getAttribute("name");
|
|
22
|
+
const property = meta.getAttribute("property");
|
|
23
|
+
const charset = meta.getAttribute("charset");
|
|
24
|
+
if (charset != null) {
|
|
25
|
+
tags.push({ kind: "meta", name: "charset", presence: "own", value: attrValue(charset) });
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (!name && !property) continue;
|
|
29
|
+
const content = name === "robots" ? meta.getAttribute("content") : null;
|
|
30
|
+
const noindex = content != null && /(^|[\s,])(noindex|none)([\s,]|$)/i.test(content);
|
|
31
|
+
const descText = name === "description" ? meta.getAttribute("content") : null;
|
|
32
|
+
tags.push({
|
|
33
|
+
kind: "meta",
|
|
34
|
+
...name ? { name } : {},
|
|
35
|
+
...property ? { property } : {},
|
|
36
|
+
presence: "own",
|
|
37
|
+
value: attrValue(meta.getAttribute("content")),
|
|
38
|
+
...noindex ? { noindex: true } : {},
|
|
39
|
+
...descText && descText.trim().length > 0 ? { text: descText } : {}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
for (const link of head.querySelectorAll("link")) {
|
|
43
|
+
const rel = link.getAttribute("rel");
|
|
44
|
+
if (!rel) continue;
|
|
45
|
+
const asAttr = link.getAttribute("as");
|
|
46
|
+
const hasCrossorigin = link.hasAttribute("crossorigin");
|
|
47
|
+
const hreflang = link.getAttribute("hreflang");
|
|
48
|
+
tags.push({
|
|
49
|
+
kind: "link",
|
|
50
|
+
rel,
|
|
51
|
+
presence: "own",
|
|
52
|
+
value: attrValue(link.getAttribute("href")),
|
|
53
|
+
...asAttr != null ? { hasAs: true, as: asAttr } : {},
|
|
54
|
+
...hasCrossorigin ? { hasCrossorigin: true } : {},
|
|
55
|
+
// Keep a literal empty hreflang="" (present-but-invalid) so SEO026 can flag it.
|
|
56
|
+
...hreflang != null ? { hreflang } : {}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
for (const script of head.querySelectorAll("script")) {
|
|
60
|
+
if (script.getAttribute("type") === "application/ld+json") {
|
|
61
|
+
const raw = script.rawText;
|
|
62
|
+
tags.push({
|
|
63
|
+
kind: "jsonld",
|
|
64
|
+
presence: "own",
|
|
65
|
+
value: attrValue(raw),
|
|
66
|
+
...raw && raw.trim().length > 0 ? { jsonld: raw } : {}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const htmlEl = root.querySelector("html");
|
|
71
|
+
const lang = htmlEl?.getAttribute("lang");
|
|
72
|
+
const htmlLang = lang === void 0 || lang === null ? { presence: "none", value: "absent" } : { presence: "own", value: attrValue(lang) };
|
|
73
|
+
const headings = [];
|
|
74
|
+
const collectHeadings = (el) => {
|
|
75
|
+
for (const child of el.childNodes) {
|
|
76
|
+
if (child instanceof HTMLElement) {
|
|
77
|
+
const m = /^h([1-6])$/i.exec(child.rawTagName ?? "");
|
|
78
|
+
if (m) headings.push(Number(m[1]));
|
|
79
|
+
collectHeadings(child);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
collectHeadings(root.querySelector("body") ?? root);
|
|
84
|
+
return { tags, htmlLang, headings };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export {
|
|
88
|
+
parseHtmlHead
|
|
89
|
+
};
|
package/dist/hooks/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
parseHtmlHead
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-MICKONGB.js";
|
|
4
4
|
|
|
5
5
|
// src/hooks/handle.ts
|
|
6
6
|
import { DEV } from "esm-env";
|
|
@@ -63,10 +63,13 @@ async function postIngest(origin, route, results) {
|
|
|
63
63
|
}
|
|
64
64
|
async function analyzeAndWarn(html, route, origin, rules, config, lastSignature) {
|
|
65
65
|
try {
|
|
66
|
-
const { tags, htmlLang } = parseHtmlHead(html);
|
|
66
|
+
const { tags, htmlLang, headings: levels } = parseHtmlHead(html);
|
|
67
67
|
const head = { route, source: "rendered", tags, file: route };
|
|
68
|
+
const headings = [
|
|
69
|
+
{ route, headings: levels.map((level) => ({ level, line: 0, file: route })) }
|
|
70
|
+
];
|
|
68
71
|
const project = { hasRobotsTxt: true, hasSitemap: true, htmlLang };
|
|
69
|
-
const results = applyRuleSeverities(await runRules(rules, { heads: [head], project, config }), config);
|
|
72
|
+
const results = applyRuleSeverities(await runRules(rules, { heads: [head], headings, project, config }), config);
|
|
70
73
|
const signature = findingSignature(results, config);
|
|
71
74
|
if (lastSignature.get(route) === signature) return;
|
|
72
75
|
lastSignature.set(route, signature);
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
parseHtmlHead
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-MICKONGB.js";
|
|
4
4
|
|
|
5
5
|
// src/plugin.ts
|
|
6
6
|
import { existsSync } from "fs";
|
|
@@ -38,21 +38,28 @@ async function collectRenderedHeads(prerenderPagesDir) {
|
|
|
38
38
|
files.map(async (rel) => ({ rel, parsed: parseHtmlHead(await readFile(join(prerenderPagesDir, rel), "utf8")) }))
|
|
39
39
|
);
|
|
40
40
|
const heads = [];
|
|
41
|
+
const headings = [];
|
|
41
42
|
let htmlLang = { presence: "none", value: "absent" };
|
|
42
43
|
for (const { rel, parsed } of parsedFiles) {
|
|
43
44
|
if (htmlLang.presence === "none" && parsed.htmlLang.presence === "own") htmlLang = parsed.htmlLang;
|
|
45
|
+
const route = deriveRouteFromHtmlPath(rel);
|
|
44
46
|
heads.push({
|
|
45
|
-
route
|
|
47
|
+
route,
|
|
46
48
|
source: "rendered",
|
|
47
49
|
tags: parsed.tags,
|
|
48
50
|
file: rel
|
|
49
51
|
});
|
|
52
|
+
headings.push({
|
|
53
|
+
route,
|
|
54
|
+
// Rendered mode does not track source lines (line 0 = unknown); file is the HTML path.
|
|
55
|
+
headings: parsed.headings.map((level) => ({ level, line: 0, file: rel }))
|
|
56
|
+
});
|
|
50
57
|
}
|
|
51
|
-
return { heads, htmlLang };
|
|
58
|
+
return { heads, headings, htmlLang };
|
|
52
59
|
}
|
|
53
60
|
|
|
54
61
|
// src/providers/rendered/project.ts
|
|
55
|
-
import { access } from "fs/promises";
|
|
62
|
+
import { access, readFile as readFile2 } from "fs/promises";
|
|
56
63
|
import { join as join2 } from "path";
|
|
57
64
|
import { ROBOTS_SOURCE_PATHS, SITEMAP_SOURCE_PATHS } from "@svelte-vitals/core";
|
|
58
65
|
async function exists(path) {
|
|
@@ -67,12 +74,25 @@ async function existsAny(cwd, paths) {
|
|
|
67
74
|
const found = await Promise.all(paths.map((p) => exists(join2(cwd, p))));
|
|
68
75
|
return found.some(Boolean);
|
|
69
76
|
}
|
|
77
|
+
async function robotsRefsSitemap(cwd) {
|
|
78
|
+
try {
|
|
79
|
+
return /^\s*sitemap:/im.test(await readFile2(join2(cwd, "static/robots.txt"), "utf8"));
|
|
80
|
+
} catch {
|
|
81
|
+
return void 0;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
70
84
|
async function collectRenderedProject(cwd, htmlLang) {
|
|
71
85
|
const [hasRobotsTxt, hasSitemap] = await Promise.all([
|
|
72
86
|
existsAny(cwd, ROBOTS_SOURCE_PATHS),
|
|
73
87
|
existsAny(cwd, SITEMAP_SOURCE_PATHS)
|
|
74
88
|
]);
|
|
75
|
-
|
|
89
|
+
const robotsReferencesSitemap = await robotsRefsSitemap(cwd);
|
|
90
|
+
return {
|
|
91
|
+
hasRobotsTxt,
|
|
92
|
+
hasSitemap,
|
|
93
|
+
htmlLang,
|
|
94
|
+
...robotsReferencesSitemap !== void 0 ? { robotsReferencesSitemap } : {}
|
|
95
|
+
};
|
|
76
96
|
}
|
|
77
97
|
|
|
78
98
|
// src/version.ts
|
|
@@ -94,10 +114,10 @@ async function analyze(prerenderPagesDir, cwd, options) {
|
|
|
94
114
|
rules: options.rules ?? {},
|
|
95
115
|
failOn: options.failOn ?? "critical"
|
|
96
116
|
});
|
|
97
|
-
const { heads, htmlLang } = await collectRenderedHeads(prerenderPagesDir);
|
|
117
|
+
const { heads, headings, htmlLang } = await collectRenderedHeads(prerenderPagesDir);
|
|
98
118
|
const project = await collectRenderedProject(cwd, htmlLang);
|
|
99
119
|
const results = applyRuleSeverities(
|
|
100
|
-
await runRules(selectRules(allRules, config), { heads, project, config }),
|
|
120
|
+
await runRules(selectRules(allRules, config), { heads, headings, project, config }),
|
|
101
121
|
config
|
|
102
122
|
);
|
|
103
123
|
const { score } = computeScore(results, config);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@svelte-vitals/vite",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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,7 +42,7 @@
|
|
|
42
42
|
"esm-env": "^1.2.2",
|
|
43
43
|
"node-html-parser": "^6.1.13",
|
|
44
44
|
"tinyglobby": "^0.2.17",
|
|
45
|
-
"@svelte-vitals/core": "0.
|
|
45
|
+
"@svelte-vitals/core": "0.14.0"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"@sveltejs/kit": "^2.0.0",
|
package/dist/chunk-AHRJ6LY3.js
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
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
|
-
const asAttr = link.getAttribute("as");
|
|
28
|
-
const hasCrossorigin = link.hasAttribute("crossorigin");
|
|
29
|
-
tags.push({
|
|
30
|
-
kind: "link",
|
|
31
|
-
rel,
|
|
32
|
-
presence: "own",
|
|
33
|
-
value: attrValue(link.getAttribute("href")),
|
|
34
|
-
...asAttr != null ? { hasAs: true, as: asAttr } : {},
|
|
35
|
-
...hasCrossorigin ? { hasCrossorigin: true } : {}
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
for (const script of head.querySelectorAll("script")) {
|
|
39
|
-
if (script.getAttribute("type") === "application/ld+json") {
|
|
40
|
-
tags.push({ kind: "jsonld", presence: "own", value: attrValue(script.text) });
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
const htmlEl = root.querySelector("html");
|
|
44
|
-
const lang = htmlEl?.getAttribute("lang");
|
|
45
|
-
const htmlLang = lang === void 0 || lang === null ? { presence: "none", value: "absent" } : { presence: "own", value: attrValue(lang) };
|
|
46
|
-
return { tags, htmlLang };
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export {
|
|
50
|
-
parseHtmlHead
|
|
51
|
-
};
|