@svelte-vitals/vite 0.36.1 → 0.36.3
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/hooks/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { i as
|
|
1
|
+
import { i as toOccurrenceMap, o as parseHtmlHead, t as isLoopbackOrigin } from "../loopback-CE47i4VD.js";
|
|
2
2
|
import { defineConfig } from "@svelte-vitals/core";
|
|
3
|
-
import { allRules,
|
|
3
|
+
import { allRules, defaultProject, effectiveSeverity, formatFailedRuleWarning, isPenalized, runAnalysis, selectRules, terminalSafe } from "@svelte-vitals/core/internal";
|
|
4
4
|
import { DEV } from "esm-env";
|
|
5
5
|
//#region src/hooks/handle.ts
|
|
6
6
|
/** Stable signature of a route's penalized findings, so ingest is skipped when a repeat visit finds nothing new. */
|
|
@@ -66,16 +66,14 @@ async function analyzeAndIngest(html, route, origin, rules, config, lastSignatur
|
|
|
66
66
|
idCandidates: [...new Set(ids)],
|
|
67
67
|
fullyResolved: true
|
|
68
68
|
}];
|
|
69
|
-
const { results
|
|
69
|
+
const { results, failedRules, failedRuleIds } = await runAnalysis(rules, {
|
|
70
70
|
heads: [head],
|
|
71
71
|
headings,
|
|
72
72
|
images,
|
|
73
73
|
a11y,
|
|
74
74
|
project: defaultProject,
|
|
75
75
|
config
|
|
76
|
-
});
|
|
77
|
-
const results = applyRuleSeverities(ruleResults, config);
|
|
78
|
-
const failedRuleIds = failedRules.map((f) => f.id);
|
|
76
|
+
}, /* @__PURE__ */ new Map());
|
|
79
77
|
if (failedRules.length > 0 && globalThis.process?.env?.SVELTE_VITALS_DEBUG) for (const f of failedRules) warn(formatFailedRuleWarning(f));
|
|
80
78
|
const signature = `${findingSignature(results, config)}|failed:${[...failedRuleIds].sort().join(",")}`;
|
|
81
79
|
if (lastSignature.get(route) === signature) return;
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { n as collectRenderedHeads, t as isLoopbackOrigin } from "./loopback-
|
|
1
|
+
import { a as globFiles, n as isSameOrigin, r as collectRenderedHeads, t as isLoopbackOrigin } from "./loopback-CE47i4VD.js";
|
|
2
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import { access, mkdir, readFile, 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,
|
|
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, renderAppShell, resolveKitAliases, resolveKitPathsBase, resolveRuleOptions, runAnalysis, safeHref, selectRules, shouldSkipRangeCheck, skippedFileWarnings, summarize, terminalSafe, unknownDirectiveIds, validateRuleSetting, withFailedRulesOff, withReadLimit } from "@svelte-vitals/core/internal";
|
|
7
7
|
import { CONFIG_FILENAMES, analyzeProject, findUnknownRuleIds, knownRuleIds, loadConfigFile, registryTag, ruleOptionsSpec } from "svelte-vitals";
|
|
8
|
-
import { glob } from "tinyglobby";
|
|
9
8
|
//#region src/providers/rendered/project.ts
|
|
10
9
|
async function exists(path) {
|
|
11
10
|
try {
|
|
@@ -78,10 +77,7 @@ const nodeRuntime = {
|
|
|
78
77
|
return false;
|
|
79
78
|
}
|
|
80
79
|
},
|
|
81
|
-
glob: (pattern, cwd) =>
|
|
82
|
-
cwd,
|
|
83
|
-
dot: false
|
|
84
|
-
}),
|
|
80
|
+
glob: (pattern, cwd) => globFiles(pattern, cwd),
|
|
85
81
|
join: (...parts) => join(...parts)
|
|
86
82
|
};
|
|
87
83
|
/**
|
|
@@ -185,7 +181,13 @@ async function analyze(prerenderPagesDir, cwd, options, extraProjectFacts, resol
|
|
|
185
181
|
const kitModules = await collectKitModuleFacts$1(cwd, project.kitAliases);
|
|
186
182
|
const selected = selectRules(allRules, config);
|
|
187
183
|
if (selected.some((r) => r.id === "a11y/unverified-id-ref")) warnings.push("a11y/unverified-id-ref has no effect in rendered mode — the prerendered document is always fully resolved.");
|
|
188
|
-
const
|
|
184
|
+
const directives = /* @__PURE__ */ new Map();
|
|
185
|
+
addFactsDirectives(directives, {
|
|
186
|
+
components,
|
|
187
|
+
kitModules,
|
|
188
|
+
viteMinifyDisabled: project.viteMinifyDisabled
|
|
189
|
+
});
|
|
190
|
+
const { results, examined, failedRules, scoringConfig } = await runAnalysis(selected, {
|
|
189
191
|
heads,
|
|
190
192
|
headings,
|
|
191
193
|
images,
|
|
@@ -195,16 +197,10 @@ async function analyze(prerenderPagesDir, cwd, options, extraProjectFacts, resol
|
|
|
195
197
|
config,
|
|
196
198
|
kitModules,
|
|
197
199
|
sourceFiles
|
|
198
|
-
});
|
|
199
|
-
const directives = /* @__PURE__ */ new Map();
|
|
200
|
-
for (const c of components) directives.set(c.file, c.suppressions ?? []);
|
|
201
|
-
for (const m of kitModules) directives.set(m.file, m.suppressions ?? []);
|
|
202
|
-
if (project.viteMinifyDisabled?.file) directives.set(project.viteMinifyDisabled.file, project.viteMinifyDisabled.suppressions ?? []);
|
|
203
|
-
const results = applyInlineDirectives(applyOverrides(applyRuleSeverities(rawResults, config), config), directives, selected, config);
|
|
200
|
+
}, directives);
|
|
204
201
|
warnings.push(...skippedFileWarnings([...components, ...kitModules]));
|
|
205
202
|
warnings.push(...unknownDirectiveIds(directives, allRules));
|
|
206
203
|
for (const f of failedRules) warnings.push(formatFailedRuleWarning(f));
|
|
207
|
-
const scoringConfig = withFailedRulesOff(config, failedRules.map((f) => f.id));
|
|
208
204
|
const { score } = computeScore(results, scoringConfig);
|
|
209
205
|
const summary = summarize(results, scoringConfig);
|
|
210
206
|
const failed = hasFailureAtOrAbove(summary, scoringConfig.failOn);
|
|
@@ -308,6 +304,7 @@ const SEVERITIES = /* @__PURE__ */ new Set([
|
|
|
308
304
|
"info"
|
|
309
305
|
]);
|
|
310
306
|
const CATEGORY_SET = new Set(CATEGORIES);
|
|
307
|
+
const INGEST_BODY_LIMIT = 4194304;
|
|
311
308
|
function isOptionalString(v) {
|
|
312
309
|
return v === void 0 || typeof v === "string";
|
|
313
310
|
}
|
|
@@ -348,7 +345,8 @@ function installUiMiddleware(server, config, version, store, coreVersion, getSta
|
|
|
348
345
|
const url = req.url ?? "/";
|
|
349
346
|
const origin = req.headers.origin;
|
|
350
347
|
const host = req.headers.host;
|
|
351
|
-
|
|
348
|
+
const isTls = Boolean(req.socket?.encrypted);
|
|
349
|
+
if (host === void 0 || !isLoopbackOrigin(`http://${host}`) || typeof origin === "string" && !isSameOrigin(origin, host, isTls)) {
|
|
352
350
|
req.resume();
|
|
353
351
|
res.statusCode = 403;
|
|
354
352
|
res.end("svelte-vitals dev UI is only available from localhost");
|
|
@@ -356,8 +354,23 @@ function installUiMiddleware(server, config, version, store, coreVersion, getSta
|
|
|
356
354
|
}
|
|
357
355
|
if (req.method === "POST" && url.startsWith("/ingest")) {
|
|
358
356
|
const chunks = [];
|
|
359
|
-
|
|
357
|
+
let received = 0;
|
|
358
|
+
let tooLarge = false;
|
|
359
|
+
req.on("data", (c) => {
|
|
360
|
+
if (tooLarge) return;
|
|
361
|
+
received += c.length;
|
|
362
|
+
if (received > INGEST_BODY_LIMIT) {
|
|
363
|
+
tooLarge = true;
|
|
364
|
+
chunks.length = 0;
|
|
365
|
+
res.statusCode = 413;
|
|
366
|
+
res.end();
|
|
367
|
+
req.destroy();
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
chunks.push(c);
|
|
371
|
+
});
|
|
360
372
|
req.on("end", () => {
|
|
373
|
+
if (tooLarge) return;
|
|
361
374
|
try {
|
|
362
375
|
const { route, results, failedRuleIds } = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
363
376
|
if (typeof route === "string" && Array.isArray(results)) {
|
|
@@ -1,48 +1,11 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { IDREF_ATTRS,
|
|
4
|
-
import { glob } from "tinyglobby";
|
|
1
|
+
import { glob, readFile, stat } from "node:fs/promises";
|
|
2
|
+
import { join, relative, sep } from "node:path";
|
|
3
|
+
import { ASIDE_DEMOTING_TAGS, IDREF_ATTRS, NAMING_ATTRS, SECTIONING_TAGS, decodeFragmentId, isClassicScriptType, isTopFragment, resolveLandmark, splitTokens, stripTextDirective } from "@svelte-vitals/core/internal";
|
|
5
4
|
import { parse } from "node-html-parser";
|
|
6
5
|
//#region src/providers/rendered/parse-html.ts
|
|
7
6
|
function attrValue(v) {
|
|
8
7
|
return v !== void 0 && v.trim().length > 0 ? "static" : "absent";
|
|
9
8
|
}
|
|
10
|
-
const JS_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
11
|
-
"application/ecmascript",
|
|
12
|
-
"application/javascript",
|
|
13
|
-
"application/x-ecmascript",
|
|
14
|
-
"application/x-javascript",
|
|
15
|
-
"text/ecmascript",
|
|
16
|
-
"text/javascript",
|
|
17
|
-
"text/javascript1.0",
|
|
18
|
-
"text/javascript1.1",
|
|
19
|
-
"text/javascript1.2",
|
|
20
|
-
"text/javascript1.3",
|
|
21
|
-
"text/javascript1.4",
|
|
22
|
-
"text/javascript1.5",
|
|
23
|
-
"text/jscript",
|
|
24
|
-
"text/livescript",
|
|
25
|
-
"text/x-ecmascript",
|
|
26
|
-
"text/x-javascript"
|
|
27
|
-
]);
|
|
28
|
-
function isClassicScriptType(type) {
|
|
29
|
-
if (type === void 0) return true;
|
|
30
|
-
const normalized = type.trim().toLowerCase();
|
|
31
|
-
return normalized === "" || JS_MIME_TYPES.has(normalized);
|
|
32
|
-
}
|
|
33
|
-
const SECTIONING_TAGS = /* @__PURE__ */ new Set([
|
|
34
|
-
"article",
|
|
35
|
-
"aside",
|
|
36
|
-
"main",
|
|
37
|
-
"nav",
|
|
38
|
-
"section"
|
|
39
|
-
]);
|
|
40
|
-
const ASIDE_DEMOTING_TAGS = /* @__PURE__ */ new Set([
|
|
41
|
-
"article",
|
|
42
|
-
"aside",
|
|
43
|
-
"nav",
|
|
44
|
-
"section"
|
|
45
|
-
]);
|
|
46
9
|
/** Whole-document scan (not body-scoped: app.html shell ids/refs are real occurrences too). */
|
|
47
10
|
function collectA11y(root) {
|
|
48
11
|
const landmarks = [];
|
|
@@ -54,16 +17,13 @@ function collectA11y(root) {
|
|
|
54
17
|
const tag = el.rawTagName?.toLowerCase();
|
|
55
18
|
if (tag && ctx.inBody) elementTags.add(tag);
|
|
56
19
|
const roleAttr = el.getAttribute("role");
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const named = ["aria-label", "aria-labelledby"].some((a) => (el.getAttribute(a) ?? "").trim().length > 0);
|
|
65
|
-
landmark = ctx.asideDemoting === 0 || named ? "complementary" : void 0;
|
|
66
|
-
}
|
|
20
|
+
const landmark = resolveLandmark({
|
|
21
|
+
tag,
|
|
22
|
+
roleTokens: roleAttr !== void 0 ? splitTokens(roleAttr) : void 0,
|
|
23
|
+
named: tag === "aside" && NAMING_ATTRS.some((a) => (el.getAttribute(a) ?? "").trim().length > 0),
|
|
24
|
+
insideSectioning: ctx.sectioning > 0,
|
|
25
|
+
insideAsideDemoting: ctx.asideDemoting > 0
|
|
26
|
+
});
|
|
67
27
|
if (landmark) {
|
|
68
28
|
landmarks.push(landmark);
|
|
69
29
|
const within = ctx.landmarks.at(-1);
|
|
@@ -221,6 +181,32 @@ function parseHtmlHead(html) {
|
|
|
221
181
|
};
|
|
222
182
|
}
|
|
223
183
|
//#endregion
|
|
184
|
+
//#region src/glob.ts
|
|
185
|
+
/**
|
|
186
|
+
* `node:fs` glob constrained to the contract callers rely on: files only (fs.glob also matches
|
|
187
|
+
* directories), cwd-relative POSIX-separated paths on every platform (fs.glob returns platform
|
|
188
|
+
* separators), dotfiles excluded (fs.glob's default). A symlink's Dirent never reports isFile(),
|
|
189
|
+
* so symlinks are stat'd and kept when they point at a file; symlinked *directories* are not
|
|
190
|
+
* traversed — fs.glob never descends into them. Duplicated in the CLI package — core purity
|
|
191
|
+
* (no `node:` imports in core) rules out the shared home.
|
|
192
|
+
*/
|
|
193
|
+
async function globFiles(pattern, cwd) {
|
|
194
|
+
const out = [];
|
|
195
|
+
for await (const entry of glob(pattern, {
|
|
196
|
+
cwd,
|
|
197
|
+
withFileTypes: true
|
|
198
|
+
})) {
|
|
199
|
+
const path = join(entry.parentPath, entry.name);
|
|
200
|
+
if (!entry.isFile()) {
|
|
201
|
+
if (!entry.isSymbolicLink()) continue;
|
|
202
|
+
if (!(await stat(path).catch(() => null))?.isFile()) continue;
|
|
203
|
+
}
|
|
204
|
+
const rel = relative(cwd, path);
|
|
205
|
+
out.push(sep === "/" ? rel : rel.split(sep).join("/"));
|
|
206
|
+
}
|
|
207
|
+
return out;
|
|
208
|
+
}
|
|
209
|
+
//#endregion
|
|
224
210
|
//#region src/providers/rendered/collect.ts
|
|
225
211
|
/** Group raw occurrence keys (one entry per hit, in document order) by key, `file` attached, `line: 0` (rendered mode does not track source lines). */
|
|
226
212
|
function toOccurrenceMap(keys, file) {
|
|
@@ -240,7 +226,7 @@ function deriveRouteFromHtmlPath(relPath) {
|
|
|
240
226
|
}
|
|
241
227
|
/** Read every prerendered HTML page under `prerenderPagesDir` into ResolvedHead[]. */
|
|
242
228
|
async function collectRenderedHeads(prerenderPagesDir) {
|
|
243
|
-
const files = (await
|
|
229
|
+
const files = (await globFiles("**/*.html", prerenderPagesDir)).sort();
|
|
244
230
|
const parsedFiles = await Promise.all(files.map(async (rel) => ({
|
|
245
231
|
rel,
|
|
246
232
|
parsed: parseHtmlHead(await readFile(join(prerenderPagesDir, rel), "utf8"))
|
|
@@ -317,5 +303,26 @@ function isLoopbackOrigin(origin) {
|
|
|
317
303
|
return false;
|
|
318
304
|
}
|
|
319
305
|
}
|
|
306
|
+
/**
|
|
307
|
+
* Whether `origin` (an Origin header value) names exactly the server this request reached —
|
|
308
|
+
* `host` is the Host header, `hostname[:port]`, and `secure` is whether the connection is TLS.
|
|
309
|
+
* Compared as hostname + effective port: each side's omitted port resolves to its own scheme's
|
|
310
|
+
* default (the Origin's from its URL scheme, the Host's from `secure`), so `https://localhost`
|
|
311
|
+
* never matches a plain-http server on :80 and `https://localhost:443` does match a TLS server
|
|
312
|
+
* whose Host is `localhost:443`. The Origin's scheme is otherwise not compared: under
|
|
313
|
+
* `server.https` the Origin is https while the Host header is unchanged.
|
|
314
|
+
*/
|
|
315
|
+
function isSameOrigin(origin, host, secure = false) {
|
|
316
|
+
try {
|
|
317
|
+
const o = new URL(origin);
|
|
318
|
+
const h = new URL(`${secure ? "https" : "http"}://${host}`);
|
|
319
|
+
return o.hostname === h.hostname && effectivePort(o) === effectivePort(h);
|
|
320
|
+
} catch {
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function effectivePort(url) {
|
|
325
|
+
return url.port || (url.protocol === "https:" ? "443" : "80");
|
|
326
|
+
}
|
|
320
327
|
//#endregion
|
|
321
|
-
export {
|
|
328
|
+
export { globFiles as a, toOccurrenceMap as i, isSameOrigin as n, parseHtmlHead as o, collectRenderedHeads as r, isLoopbackOrigin as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@svelte-vitals/vite",
|
|
3
|
-
"version": "0.36.
|
|
3
|
+
"version": "0.36.3",
|
|
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",
|
|
@@ -40,13 +40,12 @@
|
|
|
40
40
|
],
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"esm-env": "^1.2.2",
|
|
43
|
-
"node-html-parser": "^9.0.
|
|
44
|
-
"tinyglobby": "^0.2.17"
|
|
43
|
+
"node-html-parser": "^9.0.2"
|
|
45
44
|
},
|
|
46
45
|
"peerDependencies": {
|
|
47
|
-
"@svelte-vitals/core": "^0.
|
|
46
|
+
"@svelte-vitals/core": "^0.51.1",
|
|
48
47
|
"@sveltejs/kit": "^2.70.3",
|
|
49
|
-
"svelte-vitals": "^0.54.
|
|
48
|
+
"svelte-vitals": "^0.54.3",
|
|
50
49
|
"vite": "^8.2.2"
|
|
51
50
|
},
|
|
52
51
|
"peerDependenciesMeta": {
|
|
@@ -55,12 +54,12 @@
|
|
|
55
54
|
}
|
|
56
55
|
},
|
|
57
56
|
"devDependencies": {
|
|
58
|
-
"@svelte-vitals/core": "0.
|
|
57
|
+
"@svelte-vitals/core": "0.51.1",
|
|
59
58
|
"@sveltejs/kit": "^2.70.3",
|
|
60
59
|
"@types/node": "^24.13.3",
|
|
61
|
-
"happy-dom": "^20.
|
|
62
|
-
"svelte": "^5.
|
|
63
|
-
"svelte-vitals": "0.54.
|
|
60
|
+
"happy-dom": "^20.12.0",
|
|
61
|
+
"svelte": "^5.57.0",
|
|
62
|
+
"svelte-vitals": "0.54.3",
|
|
64
63
|
"vite": "^8.2.2"
|
|
65
64
|
},
|
|
66
65
|
"scripts": {
|