@svelte-vitals/vite 0.55.0 → 0.55.2
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 +1 -1
- package/dist/hooks/index.js +1 -1
- package/dist/index.js +50 -19
- package/dist/{loopback-D9pGJXl6.js → loopback-CAFo1WZs.js} +11 -4
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ export default {
|
|
|
36
36
|
|
|
37
37
|
A `svelte-vitals.config.*` and a `svelte-vitals-suppressions.json` in the project root are read automatically (explicit options win over the config file). Only source-scan suppression entries apply; route-level entries are skipped on every plugin surface (rendered route findings anchor to the built HTML) and the plugin says how many.
|
|
38
38
|
|
|
39
|
-
At build time the HTML check covers prerendered routes only; the source scan applies project-wide. Routes that are not prerendered (server-rendered, or dynamic routes without prerendered entries) have no build output, so nothing checks their rendered HTML here. A prerendered route with `ssr = false` has build output, but only the empty app shell, so the HTML check skips it too (a `+layout` with `ssr = false` skips its subtree) and prints one warning listing the skipped routes. The `svelte-vitals` CLI covers them by source analysis, and browsing them during `vite dev` gets the live dashboard a rendered reading.
|
|
39
|
+
At build time the HTML check covers prerendered routes only; the source scan applies project-wide. Routes that are not prerendered (server-rendered, or dynamic routes without prerendered entries) have no build output, so nothing checks their rendered HTML here. A prerendered route with `ssr = false` has build output, but only the empty app shell, so the HTML check skips it too (a `+layout` with `ssr = false` skips its subtree) and prints one warning listing the skipped routes. The `svelte-vitals` CLI covers them by source analysis, and browsing them during `vite dev` gets the live dashboard a rendered reading. When the build prerenders nothing at all (an SPA served from a fallback page, say), the plugin prints one warning that route analysis was skipped and still runs and gates the source scan; `seo/html-lang`, which reads the rendered `<html>`, is skipped with it.
|
|
40
40
|
|
|
41
41
|
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.
|
|
42
42
|
|
package/dist/hooks/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as toOccurrenceMap, r as parseHtmlHead, t as isLoopbackOrigin } from "../loopback-
|
|
1
|
+
import { i as toOccurrenceMap, r as parseHtmlHead, t as isLoopbackOrigin } from "../loopback-CAFo1WZs.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";
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { i as toOccurrenceMap, n as isSameOrigin, r as parseHtmlHead, t as isLoopbackOrigin } from "./loopback-
|
|
1
|
+
import { i as toOccurrenceMap, n as isSameOrigin, r as parseHtmlHead, t as isLoopbackOrigin } from "./loopback-CAFo1WZs.js";
|
|
2
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
-
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { access, mkdir, readFile, readdir, 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, isPlainObject, renderAppShell, resolveKitAliases, resolveKitPathsBase, resolveRuleOptions, runAnalysis, safeHref, selectRules, shouldSkipRangeCheck, skippedFileWarnings, summarize, terminalSafe, unknownDirectiveIds, validateRuleSetting, withFailedRulesOff, withIndexableOff } from "@svelte-vitals/core/internal";
|
|
7
|
-
import { CONFIG_FILENAMES, SUPPRESSIONS_FILE, analyzeProject, applySuppressions, createNodeRuntime, findUnknownRuleIds, knownRuleIds, loadConfigFile, loadSuppressions, 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, isPlainObject, renderAppShell, resolveKitAliases, resolveKitPathsBase, resolveRuleOptions, runAnalysis, safeHref, selectRules, servesRootFile, shouldSkipRangeCheck, skippedFileWarnings, summarize, terminalSafe, unknownDirectiveIds, validateRuleSetting, withFailedRulesOff, withIndexableOff, withPackageImports, withWorkspacePackages } from "@svelte-vitals/core/internal";
|
|
7
|
+
import { CONFIG_FILENAMES, SUPPRESSIONS_FILE, analyzeProject, applySuppressions, createNodeRuntime, dropConstantListEachBlocks, findUnknownRuleIds, findWorkspacePackages, knownRuleIds, loadConfigFile, loadSuppressions, registryTag, ruleOptionsSpec } from "svelte-vitals";
|
|
8
8
|
//#region src/providers/rendered/collect.ts
|
|
9
9
|
/** Map a prerendered HTML path (relative to pages/, POSIX) to its route. */
|
|
10
10
|
function deriveRouteFromHtmlPath(relPath) {
|
|
@@ -13,14 +13,19 @@ function deriveRouteFromHtmlPath(relPath) {
|
|
|
13
13
|
if (p.endsWith("/index")) p = p.slice(0, -6);
|
|
14
14
|
return "/" + p;
|
|
15
15
|
}
|
|
16
|
+
/** The whole file SvelteKit's prerender writes for a route that redirected — no document of the page. */
|
|
17
|
+
const REDIRECT_STUB = /^<script>location\.href=.*;<\/script><meta http-equiv="refresh" content="0;url=[^"]*">\s*$/s;
|
|
16
18
|
/** Read every prerendered HTML page under `prerenderPagesDir` into ResolvedHead[]. */
|
|
17
19
|
async function collectRenderedHeads(prerenderPagesDir) {
|
|
18
20
|
const rt = createNodeRuntime();
|
|
19
21
|
const files = (await rt.glob("**/*.html", prerenderPagesDir)).sort();
|
|
20
|
-
const parsedFiles = await Promise.all(files.map(async (rel) =>
|
|
21
|
-
rel
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
const parsedFiles = await Promise.all(files.map(async (rel) => {
|
|
23
|
+
const html = await rt.readFile(rt.join(prerenderPagesDir, rel));
|
|
24
|
+
return {
|
|
25
|
+
rel,
|
|
26
|
+
parsed: REDIRECT_STUB.test(html) ? void 0 : parseHtmlHead(html)
|
|
27
|
+
};
|
|
28
|
+
}));
|
|
24
29
|
const heads = [];
|
|
25
30
|
const headings = [];
|
|
26
31
|
const images = [];
|
|
@@ -30,6 +35,7 @@ async function collectRenderedHeads(prerenderPagesDir) {
|
|
|
30
35
|
value: "absent"
|
|
31
36
|
};
|
|
32
37
|
for (const { rel, parsed } of parsedFiles) {
|
|
38
|
+
if (!parsed) continue;
|
|
33
39
|
if (htmlLang.presence === "none" && parsed.htmlLang.presence === "own") htmlLang = parsed.htmlLang;
|
|
34
40
|
const route = deriveRouteFromHtmlPath(rel);
|
|
35
41
|
heads.push({
|
|
@@ -102,6 +108,13 @@ async function robotsRefsSitemap(cwd) {
|
|
|
102
108
|
return;
|
|
103
109
|
}
|
|
104
110
|
}
|
|
111
|
+
async function routeEndpoints(cwd) {
|
|
112
|
+
try {
|
|
113
|
+
return (await readdir(join(cwd, "src/routes"), { recursive: true })).map((e) => `src/routes/${e.split(sep).join("/")}`);
|
|
114
|
+
} catch {
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
}
|
|
105
118
|
/** First existing config candidate with its source — same "only the first is loaded" rule as the CLI provider. */
|
|
106
119
|
async function readFirstConfig(cwd, files) {
|
|
107
120
|
for (const file of files) {
|
|
@@ -119,22 +132,26 @@ async function readFirstConfig(cwd, files) {
|
|
|
119
132
|
}
|
|
120
133
|
/** Project facts for plugin mode: robots/sitemap from source, htmlLang from rendered HTML. */
|
|
121
134
|
async function collectRenderedProject(cwd, htmlLang) {
|
|
122
|
-
const [
|
|
135
|
+
const [hasRobotsFile, hasSitemapFile, endpoints, viteConfig, svelteConfig, packageJson] = await Promise.all([
|
|
123
136
|
existsAny(cwd, ROBOTS_SOURCE_PATHS),
|
|
124
137
|
existsAny(cwd, SITEMAP_SOURCE_PATHS),
|
|
138
|
+
routeEndpoints(cwd),
|
|
125
139
|
readFirstConfig(cwd, VITE_CONFIG_FILES),
|
|
126
|
-
readFirstConfig(cwd, SVELTE_CONFIG_FILES)
|
|
140
|
+
readFirstConfig(cwd, SVELTE_CONFIG_FILES),
|
|
141
|
+
readFirstConfig(cwd, ["package.json"])
|
|
127
142
|
]);
|
|
128
143
|
const robotsReferencesSitemap = await robotsRefsSitemap(cwd);
|
|
129
144
|
const kitPathsBase = resolveKitPathsBase(viteConfig, svelteConfig);
|
|
130
|
-
const kitAliases = resolveKitAliases(viteConfig, svelteConfig);
|
|
145
|
+
const kitAliases = withPackageImports(resolveKitAliases(viteConfig, svelteConfig), packageJson?.source);
|
|
146
|
+
const componentAliases = withWorkspacePackages(kitAliases, await findWorkspacePackages(createNodeRuntime(), cwd, packageJson?.source));
|
|
131
147
|
return {
|
|
132
|
-
hasRobotsTxt,
|
|
133
|
-
hasSitemap,
|
|
148
|
+
hasRobotsTxt: hasRobotsFile || endpoints.some((p) => servesRootFile(p, "robots.txt")),
|
|
149
|
+
hasSitemap: hasSitemapFile || endpoints.some((p) => servesRootFile(p, "sitemap.xml")),
|
|
134
150
|
htmlLang,
|
|
135
151
|
...robotsReferencesSitemap !== void 0 ? { robotsReferencesSitemap } : {},
|
|
136
152
|
...kitPathsBase ? { kitPathsBase } : {},
|
|
137
|
-
...kitAliases ? { kitAliases } : {}
|
|
153
|
+
...kitAliases ? { kitAliases } : {},
|
|
154
|
+
...componentAliases !== kitAliases ? { componentAliases } : {}
|
|
138
155
|
};
|
|
139
156
|
}
|
|
140
157
|
//#endregion
|
|
@@ -148,6 +165,15 @@ const nodeRuntime = createNodeRuntime();
|
|
|
148
165
|
function collectComponentFacts$1(root) {
|
|
149
166
|
return collectComponentFacts(nodeRuntime, root);
|
|
150
167
|
}
|
|
168
|
+
/** `facts` without the `{#each}` blocks over an imported constant list (correctness/each-key, correctness/each-index-key). */
|
|
169
|
+
function dropConstantListEachBlocks$1(root, facts, aliases) {
|
|
170
|
+
return dropConstantListEachBlocks({
|
|
171
|
+
rt: nodeRuntime,
|
|
172
|
+
cwd: root,
|
|
173
|
+
cache: /* @__PURE__ */ new Map(),
|
|
174
|
+
aliases
|
|
175
|
+
}, facts);
|
|
176
|
+
}
|
|
151
177
|
/** Scan SvelteKit route/hooks files for SSR shared-state facts (security/handler-state-write, security/server-module-state, security/shared-state-import; build mode only). */
|
|
152
178
|
function collectKitModuleFacts$1(root, aliases) {
|
|
153
179
|
return collectKitModuleFacts(nodeRuntime, root, aliases);
|
|
@@ -293,7 +319,7 @@ function ssrDisabledRouteMatcher(kitModules) {
|
|
|
293
319
|
async function analyze(prerenderPagesDir, cwd, options, extraProjectFacts, resolved) {
|
|
294
320
|
const { config, warnings } = resolved ?? await resolveConfig(cwd, options);
|
|
295
321
|
const suppressions = resolved ? resolved.suppressions : loadSuppressions(cwd);
|
|
296
|
-
const [rendered,
|
|
322
|
+
const [rendered, componentFacts, sourceFiles] = await Promise.all([
|
|
297
323
|
collectRenderedHeads(prerenderPagesDir),
|
|
298
324
|
collectComponentFacts$1(cwd),
|
|
299
325
|
collectSourceFiles$1(cwd)
|
|
@@ -302,7 +328,7 @@ async function analyze(prerenderPagesDir, cwd, options, extraProjectFacts, resol
|
|
|
302
328
|
...await collectRenderedProject(cwd, rendered.htmlLang),
|
|
303
329
|
...extraProjectFacts
|
|
304
330
|
};
|
|
305
|
-
const kitModules = await collectKitModuleFacts$1(cwd, project.kitAliases);
|
|
331
|
+
const [kitModules, components] = await Promise.all([collectKitModuleFacts$1(cwd, project.kitAliases), dropConstantListEachBlocks$1(cwd, componentFacts, project.componentAliases ?? project.kitAliases)]);
|
|
306
332
|
const isShell = ssrDisabledRouteMatcher(kitModules);
|
|
307
333
|
const keep = (items) => items.filter((i) => !isShell(i.route));
|
|
308
334
|
const heads = keep(rendered.heads);
|
|
@@ -310,7 +336,7 @@ async function analyze(prerenderPagesDir, cwd, options, extraProjectFacts, resol
|
|
|
310
336
|
const images = keep(rendered.images);
|
|
311
337
|
const a11y = keep(rendered.a11y);
|
|
312
338
|
const shells = rendered.heads.filter((h) => isShell(h.route)).map((h) => h.route).sort();
|
|
313
|
-
const selected = selectRules(allRules, config);
|
|
339
|
+
const selected = selectRules(allRules, config).filter((r) => rendered.heads.length > 0 || r.id !== "seo/html-lang");
|
|
314
340
|
if (shells.length > 0) {
|
|
315
341
|
const shown = shells.slice(0, 10);
|
|
316
342
|
const list = shells.length > shown.length ? `${shown.join(", ")}, … and ${shells.length - shown.length} more` : shown.join(", ");
|
|
@@ -898,6 +924,7 @@ function svelteVitals(options = {}) {
|
|
|
898
924
|
let root = options.cwd ?? process.cwd();
|
|
899
925
|
let minify;
|
|
900
926
|
let configFile;
|
|
927
|
+
let ssrBuild = false;
|
|
901
928
|
const buildPlugin = {
|
|
902
929
|
name: "svelte-vitals",
|
|
903
930
|
apply: "build",
|
|
@@ -908,11 +935,12 @@ function svelteVitals(options = {}) {
|
|
|
908
935
|
configResolved(config) {
|
|
909
936
|
if (!options.cwd) root = config.root;
|
|
910
937
|
configFile = config.configFile;
|
|
938
|
+
ssrBuild = Boolean(config.build?.ssr);
|
|
911
939
|
},
|
|
912
940
|
async closeBundle() {
|
|
913
941
|
const pagesDir = options.prerenderDir ? options.prerenderDir : join(root, DEFAULT_PRERENDER_DIR);
|
|
914
942
|
const resolved = isAbsolute(pagesDir) ? pagesDir : join(root, pagesDir);
|
|
915
|
-
if (!existsSync(resolved)) return;
|
|
943
|
+
if (!ssrBuild && !existsSync(resolved)) return;
|
|
916
944
|
const resolvedConfig = {
|
|
917
945
|
...await resolveConfig(root, options),
|
|
918
946
|
suppressions: loadSuppressions(root)
|
|
@@ -926,7 +954,10 @@ function svelteVitals(options = {}) {
|
|
|
926
954
|
return;
|
|
927
955
|
}
|
|
928
956
|
for (const w of result.warnings) warn(`svelte-vitals: ${w}`);
|
|
929
|
-
if (result.routeCount === 0)
|
|
957
|
+
if (result.routeCount === 0) {
|
|
958
|
+
if (!ssrBuild) return;
|
|
959
|
+
warn(`svelte-vitals: no prerendered pages found in ${options.prerenderDir ?? DEFAULT_PRERENDER_DIR} — route analysis skipped; the source scan still ran. Prerender routes to check their HTML, or run \`npx svelte-vitals\` to check every route from source.`);
|
|
960
|
+
}
|
|
930
961
|
if (options.report !== false) {
|
|
931
962
|
const out = options.report === "json" ? result.jsonReport : result.consoleReport;
|
|
932
963
|
console.log(out);
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import { ASIDE_DEMOTING_TAGS, IDREF_ATTRS, NAMING_ATTRS, SECTIONING_TAGS, decodeFragmentId, isClassicScriptType, isTopFragment, resolveLandmark, splitTokens, stripTextDirective } from "@svelte-vitals/core/internal";
|
|
1
|
+
import { ASIDE_DEMOTING_TAGS, IDREF_ATTRS, NAMING_ATTRS, SECTIONING_TAGS, decodeFragmentId, isClassicScriptType, isSvgSrc, isTopFragment, resolveLandmark, splitTokens, stripTextDirective } from "@svelte-vitals/core/internal";
|
|
2
2
|
import { parse } from "node-html-parser";
|
|
3
3
|
//#region src/providers/rendered/parse-html.ts
|
|
4
|
+
/** The <img> is its `<picture>`'s fallback and a sibling `<source>` carries the srcset. */
|
|
5
|
+
function pictureOffersSrcset(img) {
|
|
6
|
+
const parent = img.parentNode;
|
|
7
|
+
return parent?.rawTagName?.toLowerCase() === "picture" && parent.children.some((c) => c.rawTagName.toLowerCase() === "source" && c.hasAttribute("srcset"));
|
|
8
|
+
}
|
|
4
9
|
function attrValue(v) {
|
|
5
10
|
return v !== void 0 && v.trim().length > 0 ? "static" : "absent";
|
|
6
11
|
}
|
|
@@ -84,7 +89,7 @@ function parseHtmlHead(html) {
|
|
|
84
89
|
});
|
|
85
90
|
}
|
|
86
91
|
for (const meta of head.querySelectorAll("meta")) {
|
|
87
|
-
const name = meta.getAttribute("name");
|
|
92
|
+
const name = meta.getAttribute("name")?.toLowerCase();
|
|
88
93
|
const property = meta.getAttribute("property");
|
|
89
94
|
const charset = meta.getAttribute("charset");
|
|
90
95
|
if (charset != null) {
|
|
@@ -131,7 +136,8 @@ function parseHtmlHead(html) {
|
|
|
131
136
|
...href ? { href } : {}
|
|
132
137
|
});
|
|
133
138
|
}
|
|
134
|
-
|
|
139
|
+
const bodyJsonLd = ((head === root ? void 0 : root.querySelector("body"))?.querySelectorAll("script") ?? []).filter((s) => s.getAttribute("type") === "application/ld+json");
|
|
140
|
+
for (const script of [...head.querySelectorAll("script"), ...bodyJsonLd]) {
|
|
135
141
|
const type = script.getAttribute("type");
|
|
136
142
|
if (type === "application/ld+json") {
|
|
137
143
|
const raw = script.rawText;
|
|
@@ -172,7 +178,8 @@ function parseHtmlHead(html) {
|
|
|
172
178
|
hasLoading: img.hasAttribute("loading"),
|
|
173
179
|
hasAlt: img.hasAttribute("alt"),
|
|
174
180
|
lazy: img.getAttribute("loading") === "lazy",
|
|
175
|
-
hasSrcset: img.hasAttribute("srcset"),
|
|
181
|
+
hasSrcset: img.hasAttribute("srcset") || pictureOffersSrcset(img),
|
|
182
|
+
...isSvgSrc(img.getAttribute("src") ?? "") ? { svg: true } : {},
|
|
176
183
|
line: 0
|
|
177
184
|
})),
|
|
178
185
|
...collectA11y(root)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@svelte-vitals/vite",
|
|
3
|
-
"version": "0.55.
|
|
3
|
+
"version": "0.55.2",
|
|
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,10 +43,10 @@
|
|
|
43
43
|
"node-html-parser": "^9.0.4"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
|
-
"@svelte-vitals/core": "^0.55.
|
|
47
|
-
"@sveltejs/kit": "^2.
|
|
48
|
-
"svelte-vitals": "^0.55.
|
|
49
|
-
"vite": "^
|
|
46
|
+
"@svelte-vitals/core": "^0.55.2",
|
|
47
|
+
"@sveltejs/kit": "^2.0.0",
|
|
48
|
+
"svelte-vitals": "^0.55.2",
|
|
49
|
+
"vite": "^5.0.3 || ^6.0.0 || ^7.0.0 || ^8.0.0"
|
|
50
50
|
},
|
|
51
51
|
"peerDependenciesMeta": {
|
|
52
52
|
"@sveltejs/kit": {
|
|
@@ -54,13 +54,13 @@
|
|
|
54
54
|
}
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"@svelte-vitals/core": "0.55.
|
|
57
|
+
"@svelte-vitals/core": "0.55.2",
|
|
58
58
|
"@sveltejs/kit": "^2.70.3",
|
|
59
|
-
"@types/node": "^24.13.
|
|
59
|
+
"@types/node": "^24.13.6",
|
|
60
60
|
"happy-dom": "^20.14.5",
|
|
61
|
-
"svelte": "^5.57.
|
|
62
|
-
"svelte-vitals": "0.55.
|
|
63
|
-
"vite": "^8.3.
|
|
61
|
+
"svelte": "^5.57.1",
|
|
62
|
+
"svelte-vitals": "0.55.2",
|
|
63
|
+
"vite": "^8.3.1"
|
|
64
64
|
},
|
|
65
65
|
"scripts": {
|
|
66
66
|
"build": "tsdown",
|