@svelte-vitals/vite 0.30.0 → 0.31.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/README.md +2 -2
- package/dist/{chunk-VLQYXCE6.js → chunk-LTT4HAEL.js} +19 -6
- package/dist/hooks/index.js +3 -3
- package/dist/index.js +31 -24
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Vite/SvelteKit plugin for [svelte-vitals](https://github.com/oekazuma/svelte-vit
|
|
|
7
7
|
- **Build-time gate** — piggybacks on `vite build`, parses the **prerendered HTML** (head metadata, images, headings, landmarks and ids), and runs the same rule engine as the CLI (SEO, Performance, Correctness, Security, Architecture, Accessibility) — library-agnostic, because it inspects the real output. Fails the build when findings reach `failOn`.
|
|
8
8
|
- **Live dev dashboard** — on by default during `vite dev`: a searchable, filterable code-health dashboard at `/__svelte-vitals/`, covering the whole project from startup via static analysis, refined to real rendered values as you browse.
|
|
9
9
|
|
|
10
|
-
> **ESM-only** (Node
|
|
10
|
+
> **ESM-only** (Node 24.16+). Ships ES modules only; `require()` is unsupported by design.
|
|
11
11
|
|
|
12
12
|
## Usage
|
|
13
13
|
|
|
@@ -34,7 +34,7 @@ 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
|
-
|
|
37
|
+
At build time the **HTML** check covers prerendered routes only; the source scan applies project-wide. SSR and dynamic routes have no build output, so nothing checks their rendered HTML here — the `svelte-vitals` CLI covers them by source analysis, and browsing them during `vite dev` gets the live dashboard a rendered reading.
|
|
38
38
|
|
|
39
39
|
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.
|
|
40
40
|
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
// src/providers/rendered/parse-html.ts
|
|
2
2
|
import { parse } from "node-html-parser";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
decodeFragmentId,
|
|
5
|
+
splitTokens,
|
|
6
|
+
isTopFragment,
|
|
7
|
+
stripTextDirective,
|
|
8
|
+
LANDMARK_ROLES,
|
|
9
|
+
IDREF_ATTRS
|
|
10
|
+
} from "@svelte-vitals/core/internal";
|
|
4
11
|
function attrValue(v) {
|
|
5
12
|
return v !== void 0 && v.trim().length > 0 ? "static" : "absent";
|
|
6
13
|
}
|
|
@@ -28,6 +35,7 @@ function isClassicScriptType(type) {
|
|
|
28
35
|
return normalized === "" || JS_MIME_TYPES.has(normalized);
|
|
29
36
|
}
|
|
30
37
|
var SECTIONING_TAGS = /* @__PURE__ */ new Set(["article", "aside", "main", "nav", "section"]);
|
|
38
|
+
var ASIDE_DEMOTING_TAGS = /* @__PURE__ */ new Set(["article", "aside", "nav", "section"]);
|
|
31
39
|
function collectA11y(root) {
|
|
32
40
|
const landmarks = [];
|
|
33
41
|
const nestedLandmarks = [];
|
|
@@ -44,6 +52,9 @@ function collectA11y(root) {
|
|
|
44
52
|
landmark = "main";
|
|
45
53
|
} else if ((tag === "header" || tag === "footer") && ctx.sectioning === 0) {
|
|
46
54
|
landmark = tag === "header" ? "banner" : "contentinfo";
|
|
55
|
+
} else if (tag === "aside") {
|
|
56
|
+
const named = ["aria-label", "aria-labelledby"].some((a) => (el.getAttribute(a) ?? "").trim().length > 0);
|
|
57
|
+
landmark = ctx.asideDemoting === 0 || named ? "complementary" : void 0;
|
|
47
58
|
}
|
|
48
59
|
if (landmark) {
|
|
49
60
|
landmarks.push(landmark);
|
|
@@ -53,8 +64,9 @@ function collectA11y(root) {
|
|
|
53
64
|
const id = el.getAttribute("id");
|
|
54
65
|
if (id && id.trim()) ids.push(id);
|
|
55
66
|
const href = el.getAttribute("href");
|
|
56
|
-
|
|
57
|
-
|
|
67
|
+
const target = href?.startsWith("#") ? stripTextDirective(href.slice(1)) : "";
|
|
68
|
+
if (target) {
|
|
69
|
+
const fragment = decodeFragmentId(target);
|
|
58
70
|
if (!isTopFragment(fragment)) idRefs.push({ id: fragment, attr: "href" });
|
|
59
71
|
}
|
|
60
72
|
for (const attr of IDREF_ATTRS) {
|
|
@@ -63,11 +75,12 @@ function collectA11y(root) {
|
|
|
63
75
|
if (tag === "template") return;
|
|
64
76
|
const nextCtx = {
|
|
65
77
|
sectioning: ctx.sectioning + (SECTIONING_TAGS.has(tag) ? 1 : 0),
|
|
78
|
+
asideDemoting: ctx.asideDemoting + (ASIDE_DEMOTING_TAGS.has(tag) ? 1 : 0),
|
|
66
79
|
landmarks: landmark ? [...ctx.landmarks, landmark] : ctx.landmarks
|
|
67
80
|
};
|
|
68
81
|
for (const child of el.children) walk(child, nextCtx);
|
|
69
82
|
};
|
|
70
|
-
for (const child of root.children) walk(child, { sectioning: 0, landmarks: [] });
|
|
83
|
+
for (const child of root.children) walk(child, { sectioning: 0, asideDemoting: 0, landmarks: [] });
|
|
71
84
|
return { landmarks, nestedLandmarks, ids, idRefs };
|
|
72
85
|
}
|
|
73
86
|
function parseHtmlHead(html) {
|
|
@@ -107,9 +120,9 @@ function parseHtmlHead(html) {
|
|
|
107
120
|
});
|
|
108
121
|
}
|
|
109
122
|
for (const link of head.querySelectorAll("link")) {
|
|
110
|
-
const rel = link.getAttribute("rel");
|
|
123
|
+
const rel = link.getAttribute("rel")?.toLowerCase();
|
|
111
124
|
if (!rel) continue;
|
|
112
|
-
const asAttr = link.getAttribute("as");
|
|
125
|
+
const asAttr = link.getAttribute("as")?.toLowerCase();
|
|
113
126
|
const hasCrossorigin = link.hasAttribute("crossorigin");
|
|
114
127
|
const hreflang = link.getAttribute("hreflang");
|
|
115
128
|
const href = link.getAttribute("href");
|
package/dist/hooks/index.js
CHANGED
|
@@ -2,21 +2,21 @@ import {
|
|
|
2
2
|
isLoopbackOrigin,
|
|
3
3
|
parseHtmlHead,
|
|
4
4
|
toOccurrenceMap
|
|
5
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-LTT4HAEL.js";
|
|
6
6
|
|
|
7
7
|
// src/hooks/handle.ts
|
|
8
8
|
import { DEV } from "esm-env";
|
|
9
|
+
import { defineConfig } from "@svelte-vitals/core";
|
|
9
10
|
import {
|
|
10
11
|
allRules,
|
|
11
12
|
applyRuleSeverities,
|
|
12
|
-
defineConfig,
|
|
13
13
|
effectiveSeverity,
|
|
14
14
|
formatFailedRuleWarning,
|
|
15
15
|
isPenalized,
|
|
16
16
|
runRules,
|
|
17
17
|
selectRules,
|
|
18
18
|
terminalSafe
|
|
19
|
-
} from "@svelte-vitals/core";
|
|
19
|
+
} from "@svelte-vitals/core/internal";
|
|
20
20
|
function findingSignature(results, config) {
|
|
21
21
|
return results.filter((r) => isPenalized(r.detection, config.treatDynamicAs)).map((r) => `${r.id}:${effectiveSeverity(r, config)}:${r.detection.presence}:${r.detection.value}`).sort().join("|");
|
|
22
22
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
import {
|
|
2
2
|
collectRenderedHeads,
|
|
3
3
|
isLoopbackOrigin
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-LTT4HAEL.js";
|
|
5
5
|
|
|
6
6
|
// src/plugin.ts
|
|
7
7
|
import { existsSync } from "fs";
|
|
8
8
|
import { writeFile, mkdir } from "fs/promises";
|
|
9
9
|
import { join as join3, isAbsolute, dirname, relative as relative3, basename, sep as sep2 } from "path";
|
|
10
|
+
import { CATEGORIES as CATEGORIES2 } from "@svelte-vitals/core";
|
|
10
11
|
import {
|
|
11
|
-
CATEGORIES as CATEGORIES2,
|
|
12
12
|
defaultConfig,
|
|
13
13
|
resolveRuleOptions,
|
|
14
14
|
shouldSkipRangeCheck,
|
|
15
15
|
terminalSafe,
|
|
16
16
|
validateRuleSetting
|
|
17
|
-
} from "@svelte-vitals/core";
|
|
18
|
-
import { findUnknownRuleIds, knownRuleIds, ruleOptionsSpec } from "svelte-vitals";
|
|
17
|
+
} from "@svelte-vitals/core/internal";
|
|
18
|
+
import { CONFIG_FILENAMES, findUnknownRuleIds, knownRuleIds, ruleOptionsSpec } from "svelte-vitals";
|
|
19
19
|
|
|
20
20
|
// src/analyze.ts
|
|
21
|
+
import { defineConfig } from "@svelte-vitals/core";
|
|
21
22
|
import {
|
|
22
23
|
allRules,
|
|
23
24
|
selectRules,
|
|
@@ -26,18 +27,19 @@ import {
|
|
|
26
27
|
runRules,
|
|
27
28
|
withFailedRulesOff,
|
|
28
29
|
formatFailedRuleWarning,
|
|
30
|
+
skippedFileWarnings,
|
|
29
31
|
computeScore,
|
|
30
32
|
summarize,
|
|
31
33
|
hasFailureAtOrAbove,
|
|
32
34
|
formatConsoleReport,
|
|
33
|
-
formatJsonReport
|
|
34
|
-
|
|
35
|
-
} from "@svelte-vitals/core";
|
|
35
|
+
formatJsonReport
|
|
36
|
+
} from "@svelte-vitals/core/internal";
|
|
36
37
|
import { loadConfigFile } from "svelte-vitals";
|
|
37
38
|
|
|
38
39
|
// src/providers/rendered/project.ts
|
|
39
40
|
import { access, readFile } from "fs/promises";
|
|
40
41
|
import { join } from "path";
|
|
42
|
+
import "@svelte-vitals/core";
|
|
41
43
|
import {
|
|
42
44
|
ROBOTS_SOURCE_PATHS,
|
|
43
45
|
SITEMAP_SOURCE_PATHS,
|
|
@@ -45,7 +47,7 @@ import {
|
|
|
45
47
|
VITE_CONFIG_FILES,
|
|
46
48
|
resolveKitAliases,
|
|
47
49
|
resolveKitPathsBase
|
|
48
|
-
} from "@svelte-vitals/core";
|
|
50
|
+
} from "@svelte-vitals/core/internal";
|
|
49
51
|
async function exists(path) {
|
|
50
52
|
try {
|
|
51
53
|
await access(path);
|
|
@@ -104,10 +106,12 @@ import { glob } from "tinyglobby";
|
|
|
104
106
|
import {
|
|
105
107
|
collectComponentFacts as collect,
|
|
106
108
|
collectKitModuleFacts as collectKit,
|
|
107
|
-
collectSourceFiles as collectFiles
|
|
108
|
-
|
|
109
|
+
collectSourceFiles as collectFiles,
|
|
110
|
+
withReadLimit
|
|
111
|
+
} from "@svelte-vitals/core/internal";
|
|
112
|
+
var boundedRead = withReadLimit((path) => readFile2(path, "utf8"));
|
|
109
113
|
var nodeRuntime = {
|
|
110
|
-
readFile: (path) =>
|
|
114
|
+
readFile: (path) => boundedRead(path),
|
|
111
115
|
async exists(path) {
|
|
112
116
|
try {
|
|
113
117
|
await access2(path);
|
|
@@ -196,6 +200,7 @@ async function analyze(prerenderPagesDir, cwd, options, extraProjectFacts, resol
|
|
|
196
200
|
sourceFiles
|
|
197
201
|
});
|
|
198
202
|
const results = applyOverrides(applyRuleSeverities(rawResults, config), config);
|
|
203
|
+
warnings.push(...skippedFileWarnings([...components, ...kitModules]));
|
|
199
204
|
for (const f of failedRules) warnings.push(formatFailedRuleWarning(f));
|
|
200
205
|
const scoringConfig = withFailedRulesOff(
|
|
201
206
|
config,
|
|
@@ -230,7 +235,7 @@ Scanned ${components.length} component(s) under src/ for Correctness/Security/Ar
|
|
|
230
235
|
// src/minify-flag.ts
|
|
231
236
|
import { readFile as readFile3 } from "fs/promises";
|
|
232
237
|
import { relative } from "path";
|
|
233
|
-
import { findMinifyDisabled } from "@svelte-vitals/core";
|
|
238
|
+
import { findMinifyDisabled } from "@svelte-vitals/core/internal";
|
|
234
239
|
async function resolveMinifyDisabled(minify, configFile, root) {
|
|
235
240
|
if (minify !== false) return void 0;
|
|
236
241
|
if (!configFile) return {};
|
|
@@ -245,14 +250,12 @@ async function resolveMinifyDisabled(minify, configFile, root) {
|
|
|
245
250
|
}
|
|
246
251
|
|
|
247
252
|
// src/ui/middleware.ts
|
|
248
|
-
import { CATEGORIES
|
|
253
|
+
import { CATEGORIES } from "@svelte-vitals/core";
|
|
254
|
+
import { renderAppShell } from "@svelte-vitals/core/internal";
|
|
249
255
|
|
|
250
256
|
// src/ui/snapshot.ts
|
|
251
|
-
import
|
|
252
|
-
|
|
253
|
-
safeHref,
|
|
254
|
-
withFailedRulesOff as withFailedRulesOff2
|
|
255
|
-
} from "@svelte-vitals/core";
|
|
257
|
+
import "@svelte-vitals/core";
|
|
258
|
+
import { buildJsonReport, safeHref, withFailedRulesOff as withFailedRulesOff2 } from "@svelte-vitals/core/internal";
|
|
256
259
|
function sanitizeDocsUrl(issue) {
|
|
257
260
|
if (issue.docsUrl === void 0) return issue;
|
|
258
261
|
if (safeHref(issue.docsUrl) !== null) return issue;
|
|
@@ -522,9 +525,9 @@ function createAnalysisRunner(opts) {
|
|
|
522
525
|
var CONFIG_BASENAMES = /* @__PURE__ */ new Set([
|
|
523
526
|
"svelte.config.js",
|
|
524
527
|
"svelte.config.ts",
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
+
// The svelte-vitals config names come from the CLI's own loader list, so the watcher
|
|
529
|
+
// can never drift from what analyze() actually loads.
|
|
530
|
+
...CONFIG_FILENAMES,
|
|
528
531
|
"vite.config.js",
|
|
529
532
|
"vite.config.mjs",
|
|
530
533
|
"vite.config.ts",
|
|
@@ -589,14 +592,18 @@ function svelteVitals(options = {}) {
|
|
|
589
592
|
validateRulesOption(options.rules);
|
|
590
593
|
validateOverridesOption(options.overrides, options.rules);
|
|
591
594
|
let root = options.cwd ?? process.cwd();
|
|
592
|
-
let
|
|
595
|
+
let minify;
|
|
596
|
+
let configFile;
|
|
593
597
|
const buildPlugin = {
|
|
594
598
|
name: "svelte-vitals",
|
|
595
599
|
apply: "build",
|
|
596
600
|
enforce: "post",
|
|
601
|
+
config(userConfig) {
|
|
602
|
+
minify = userConfig.build?.minify;
|
|
603
|
+
},
|
|
597
604
|
configResolved(config) {
|
|
598
605
|
if (!options.cwd) root = config.root;
|
|
599
|
-
|
|
606
|
+
configFile = config.configFile;
|
|
600
607
|
},
|
|
601
608
|
async closeBundle() {
|
|
602
609
|
const pagesDir = options.prerenderDir ? options.prerenderDir : join3(root, DEFAULT_PRERENDER_DIR);
|
|
@@ -605,7 +612,7 @@ function svelteVitals(options = {}) {
|
|
|
605
612
|
const resolvedConfig = await resolveConfig(root, options);
|
|
606
613
|
let result;
|
|
607
614
|
try {
|
|
608
|
-
const viteMinifyDisabled =
|
|
615
|
+
const viteMinifyDisabled = await resolveMinifyDisabled(minify, configFile, root);
|
|
609
616
|
result = await analyze(
|
|
610
617
|
resolved,
|
|
611
618
|
root,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@svelte-vitals/vite",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
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",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"homepage": "https://github.com/oekazuma/svelte-vitals#readme",
|
|
24
24
|
"engines": {
|
|
25
|
-
"node": ">=
|
|
25
|
+
"node": ">=24.16.0"
|
|
26
26
|
},
|
|
27
27
|
"sideEffects": false,
|
|
28
28
|
"exports": {
|
|
@@ -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.
|
|
46
|
-
"svelte-vitals": "0.
|
|
45
|
+
"@svelte-vitals/core": "0.44.0",
|
|
46
|
+
"svelte-vitals": "0.48.0"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"@sveltejs/kit": "^2.70.2",
|
|
@@ -58,12 +58,12 @@
|
|
|
58
58
|
"@sveltejs/kit": "^2.70.2",
|
|
59
59
|
"@types/node": "^24.13.3",
|
|
60
60
|
"jsdom": "^30.0.1",
|
|
61
|
-
"svelte": "^5.56.
|
|
61
|
+
"svelte": "^5.56.9",
|
|
62
62
|
"vite": "^8.2.1"
|
|
63
63
|
},
|
|
64
64
|
"scripts": {
|
|
65
65
|
"build": "tsup",
|
|
66
|
-
"bench": "node scripts/bench/bench.
|
|
66
|
+
"bench": "node scripts/bench/bench.js",
|
|
67
67
|
"typecheck": "tsc --noEmit",
|
|
68
68
|
"test": "vitest run"
|
|
69
69
|
}
|