svelte-vitals 0.5.2 → 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/bin.js +1 -1
- package/dist/{chunk-LQPB2WAL.js → chunk-DVIKLRTP.js} +152 -33
- package/dist/index.d.ts +4 -0
- package/dist/index.js +1 -1
- package/package.json +2 -2
package/dist/bin.js
CHANGED
|
@@ -164,6 +164,13 @@ function attrValue(attributes, name) {
|
|
|
164
164
|
if (v && v.type === "ExpressionTag") return "dynamic";
|
|
165
165
|
return "absent";
|
|
166
166
|
}
|
|
167
|
+
function lineOf(source, offset) {
|
|
168
|
+
if (typeof offset !== "number" || offset < 0) return 0;
|
|
169
|
+
let line = 1;
|
|
170
|
+
const end = Math.min(offset, source.length);
|
|
171
|
+
for (let i = 0; i < end; i++) if (source[i] === "\n") line++;
|
|
172
|
+
return line;
|
|
173
|
+
}
|
|
167
174
|
function findAttr(attributes, name) {
|
|
168
175
|
if (!Array.isArray(attributes)) return void 0;
|
|
169
176
|
return attributes.find((a) => a?.type === "Attribute" && a.name === name);
|
|
@@ -231,16 +238,39 @@ function collectComponents(node, acc) {
|
|
|
231
238
|
if (key in node) collectComponents(node[key], acc);
|
|
232
239
|
}
|
|
233
240
|
}
|
|
241
|
+
function collectImages(node, source, acc) {
|
|
242
|
+
if (Array.isArray(node)) {
|
|
243
|
+
for (const child of node) collectImages(child, source, acc);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (!node || typeof node !== "object") return;
|
|
247
|
+
if (node.type === "RegularElement" && node.name === "img") {
|
|
248
|
+
const attrs = node.attributes ?? [];
|
|
249
|
+
const hasSpread = attrs.some((a) => a?.type === "SpreadAttribute");
|
|
250
|
+
acc.push({
|
|
251
|
+
hasWidth: hasSpread || Boolean(findAttr(attrs, "width")),
|
|
252
|
+
hasHeight: hasSpread || Boolean(findAttr(attrs, "height")),
|
|
253
|
+
hasLoading: hasSpread || Boolean(findAttr(attrs, "loading")),
|
|
254
|
+
line: lineOf(source, node.start)
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
for (const key of CHILD_NODE_KEYS) {
|
|
258
|
+
if (key in node) collectImages(node[key], source, acc);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
234
261
|
function parseFile(source, filename) {
|
|
235
262
|
const ast = parse(source, { modern: true, filename });
|
|
236
263
|
const heads = [];
|
|
237
264
|
collectSvelteHeads(ast.fragment ?? ast, heads);
|
|
238
265
|
const components = [];
|
|
239
266
|
collectComponents(ast.fragment ?? ast, components);
|
|
267
|
+
const images = [];
|
|
268
|
+
collectImages(ast.fragment ?? ast, source, images);
|
|
240
269
|
return {
|
|
241
270
|
headTags: heads.flatMap(tagsFromHead),
|
|
242
271
|
components,
|
|
243
|
-
imports: collectImports(ast)
|
|
272
|
+
imports: collectImports(ast),
|
|
273
|
+
images
|
|
244
274
|
};
|
|
245
275
|
}
|
|
246
276
|
|
|
@@ -403,9 +433,13 @@ async function resolveRoute(rt, cwd, pageRel, config) {
|
|
|
403
433
|
const composed = /* @__PURE__ */ new Map();
|
|
404
434
|
let broadOwn = false;
|
|
405
435
|
let broadInherited = false;
|
|
436
|
+
const images = [];
|
|
406
437
|
for (const { rel, isPage } of files) {
|
|
407
438
|
const source = await rt.readFile(rt.join(cwd, rel));
|
|
408
439
|
const parsed = parseFile(source, rel);
|
|
440
|
+
for (const img of parsed.images) {
|
|
441
|
+
images.push({ ...img, file: rel });
|
|
442
|
+
}
|
|
409
443
|
const resolved = await resolveFileTags(rt, cwd, rel, parsed, config, MAX_DEPTH, /* @__PURE__ */ new Set([rel]));
|
|
410
444
|
for (const tag of resolved.tags) {
|
|
411
445
|
composed.set(tagKey(tag), { ...tag, presence: isPage ? "own" : "inherited", file: rel });
|
|
@@ -422,20 +456,119 @@ async function resolveRoute(rt, cwd, pageRel, config) {
|
|
|
422
456
|
if (!composed.has(key)) composed.set(key, { ...tag, presence });
|
|
423
457
|
}
|
|
424
458
|
}
|
|
459
|
+
const route = deriveRoute(pageRel);
|
|
425
460
|
return {
|
|
426
|
-
route:
|
|
427
|
-
|
|
428
|
-
tags: [...composed.values()],
|
|
429
|
-
file: pageRel
|
|
461
|
+
head: { route, source: "static", tags: [...composed.values()], file: pageRel },
|
|
462
|
+
images: { route, images }
|
|
430
463
|
};
|
|
431
464
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
465
|
+
async function collectRoutes(rt, cwd, config = defaultConfig) {
|
|
466
|
+
const pages = await enumerateRoutePages(rt, cwd);
|
|
467
|
+
const facts = await Promise.all(pages.map((page) => resolveRoute(rt, cwd, page, config)));
|
|
468
|
+
return {
|
|
469
|
+
heads: facts.map((f) => f.head),
|
|
470
|
+
images: facts.map((f) => f.images)
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// src/providers/source/a11y.ts
|
|
475
|
+
import { compile } from "svelte/compiler";
|
|
476
|
+
import { defaultConfig as defaultConfig2 } from "@svelte-vitals/core";
|
|
477
|
+
|
|
478
|
+
// src/rules-config.ts
|
|
479
|
+
import { allRules } from "@svelte-vitals/core";
|
|
480
|
+
var KNOWN_IDS = new Set(allRules.map((r) => r.id));
|
|
481
|
+
var A11Y_CODE_PREFIX = "a11y_";
|
|
482
|
+
var A11Y_CATEGORY_KEY = "a11y_category";
|
|
483
|
+
function findUnknownRuleIds(ids) {
|
|
484
|
+
return [
|
|
485
|
+
...new Set(ids.filter((id) => !KNOWN_IDS.has(id) && !(id.startsWith(A11Y_CODE_PREFIX) && id !== A11Y_CATEGORY_KEY)))
|
|
486
|
+
];
|
|
487
|
+
}
|
|
488
|
+
function knownRuleIds() {
|
|
489
|
+
return [...KNOWN_IDS].sort();
|
|
490
|
+
}
|
|
491
|
+
function buildRulesConfig(allow, ignore) {
|
|
492
|
+
const rules = {};
|
|
493
|
+
if (allow.length > 0) {
|
|
494
|
+
for (const r of allRules) if (!allow.includes(r.id)) rules[r.id] = "off";
|
|
495
|
+
if (!allow.some((id) => id.startsWith(A11Y_CODE_PREFIX))) rules[A11Y_CATEGORY_KEY] = "off";
|
|
437
496
|
}
|
|
438
|
-
|
|
497
|
+
for (const id of ignore) rules[id] = "off";
|
|
498
|
+
return rules;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// src/providers/source/a11y.ts
|
|
502
|
+
function firstLine(message) {
|
|
503
|
+
return message.split("\n")[0] ?? message;
|
|
504
|
+
}
|
|
505
|
+
function fileA11y(source, rel) {
|
|
506
|
+
let warnings;
|
|
507
|
+
try {
|
|
508
|
+
({ warnings } = compile(source, { generate: false, filename: rel }));
|
|
509
|
+
} catch {
|
|
510
|
+
return { ok: false, warnings: [] };
|
|
511
|
+
}
|
|
512
|
+
const out = [];
|
|
513
|
+
for (const w of warnings) {
|
|
514
|
+
if (w.code.startsWith(A11Y_CODE_PREFIX)) {
|
|
515
|
+
out.push({
|
|
516
|
+
code: w.code,
|
|
517
|
+
message: firstLine(w.message),
|
|
518
|
+
line: w.start?.line ?? 0
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return { ok: true, warnings: out };
|
|
523
|
+
}
|
|
524
|
+
async function collectA11y(rt, cwd, config = defaultConfig2) {
|
|
525
|
+
if (config.rules[A11Y_CATEGORY_KEY] === "off") return [];
|
|
526
|
+
const pages = await enumerateRoutePages(rt, cwd);
|
|
527
|
+
const cache = /* @__PURE__ */ new Map();
|
|
528
|
+
const results = [];
|
|
529
|
+
for (const page of pages) {
|
|
530
|
+
const files = await chainFiles(rt, cwd, page);
|
|
531
|
+
const route = deriveRoute(page);
|
|
532
|
+
const fails = [];
|
|
533
|
+
let compiledAll = true;
|
|
534
|
+
for (const { rel } of files) {
|
|
535
|
+
let entry = cache.get(rel);
|
|
536
|
+
if (!entry) {
|
|
537
|
+
const source = await rt.readFile(rt.join(cwd, rel));
|
|
538
|
+
entry = fileA11y(source, rel);
|
|
539
|
+
cache.set(rel, entry);
|
|
540
|
+
}
|
|
541
|
+
if (!entry.ok) compiledAll = false;
|
|
542
|
+
for (const w of entry.warnings) {
|
|
543
|
+
if (config.rules[w.code] === "off") continue;
|
|
544
|
+
fails.push({
|
|
545
|
+
id: w.code,
|
|
546
|
+
category: "a11y",
|
|
547
|
+
severity: "warning",
|
|
548
|
+
detection: { presence: "none", value: "absent" },
|
|
549
|
+
route,
|
|
550
|
+
location: rel,
|
|
551
|
+
...w.line > 0 ? { line: w.line } : {},
|
|
552
|
+
message: w.message,
|
|
553
|
+
docsUrl: `https://svelte.dev/e/${w.code}`
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
if (fails.length > 0) {
|
|
558
|
+
results.push(...fails);
|
|
559
|
+
} else if (compiledAll) {
|
|
560
|
+
results.push({
|
|
561
|
+
id: "a11y",
|
|
562
|
+
category: "a11y",
|
|
563
|
+
severity: "warning",
|
|
564
|
+
detection: { presence: "own", value: "static" },
|
|
565
|
+
route,
|
|
566
|
+
message: "Accessibility"
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
return results;
|
|
571
|
+
}
|
|
439
572
|
|
|
440
573
|
// src/version.ts
|
|
441
574
|
import { readFileSync } from "fs";
|
|
@@ -477,24 +610,6 @@ function isAutoDetectedGithub(explicit, env = process.env) {
|
|
|
477
610
|
return !explicit && !isReporterName(env.SVELTE_VITALS_REPORTER) && !isAgentEnv(env) && isGithubActionsEnv(env);
|
|
478
611
|
}
|
|
479
612
|
|
|
480
|
-
// src/rules-config.ts
|
|
481
|
-
import { allRules } from "@svelte-vitals/core";
|
|
482
|
-
var KNOWN_IDS = new Set(allRules.map((r) => r.id));
|
|
483
|
-
function findUnknownRuleIds(ids) {
|
|
484
|
-
return [...new Set(ids.filter((id) => !KNOWN_IDS.has(id)))];
|
|
485
|
-
}
|
|
486
|
-
function knownRuleIds() {
|
|
487
|
-
return [...KNOWN_IDS].sort();
|
|
488
|
-
}
|
|
489
|
-
function buildRulesConfig(allow, ignore) {
|
|
490
|
-
const rules = {};
|
|
491
|
-
if (allow.length > 0) {
|
|
492
|
-
for (const r of allRules) if (!allow.includes(r.id)) rules[r.id] = "off";
|
|
493
|
-
}
|
|
494
|
-
for (const id of ignore) rules[id] = "off";
|
|
495
|
-
return rules;
|
|
496
|
-
}
|
|
497
|
-
|
|
498
613
|
// src/index.ts
|
|
499
614
|
function routeMatcher(glob) {
|
|
500
615
|
if (!glob) return () => true;
|
|
@@ -513,10 +628,14 @@ async function analyzeProject(opts = {}) {
|
|
|
513
628
|
});
|
|
514
629
|
await detectProject(rt, cwd);
|
|
515
630
|
const matches = routeMatcher(opts.route);
|
|
516
|
-
const
|
|
631
|
+
const collected = await collectRoutes(rt, cwd, config);
|
|
632
|
+
const heads = collected.heads.filter((h) => matches(h.route));
|
|
633
|
+
const images = collected.images.filter((i) => matches(i.route));
|
|
517
634
|
const project = await collectProjectFacts(rt, cwd);
|
|
518
635
|
const rules = selectRules(allRules2, config);
|
|
519
|
-
const
|
|
636
|
+
const a11y = (await collectA11y(rt, cwd, config)).filter((r) => r.route === void 0 || matches(r.route));
|
|
637
|
+
const ruleResults = await runRules(rules, { heads, images, project, config });
|
|
638
|
+
const results = applyRuleSeverities([...ruleResults, ...a11y], config);
|
|
520
639
|
return { results, config, version: readPackageVersion() };
|
|
521
640
|
}
|
|
522
641
|
async function run(opts = {}) {
|
|
@@ -576,11 +695,11 @@ async function run(opts = {}) {
|
|
|
576
695
|
|
|
577
696
|
export {
|
|
578
697
|
ProjectError,
|
|
579
|
-
readPackageVersion,
|
|
580
|
-
isReporterName,
|
|
581
698
|
findUnknownRuleIds,
|
|
582
699
|
knownRuleIds,
|
|
583
700
|
buildRulesConfig,
|
|
701
|
+
readPackageVersion,
|
|
702
|
+
isReporterName,
|
|
584
703
|
routeMatcher,
|
|
585
704
|
analyzeProject,
|
|
586
705
|
run
|
package/dist/index.d.ts
CHANGED
|
@@ -16,6 +16,10 @@ declare function knownRuleIds(): string[];
|
|
|
16
16
|
* (--ignore). An allow-list disables every rule not listed; deny always wins.
|
|
17
17
|
* Callers should reject unknown ids first (see findUnknownRuleIds) so a typo in
|
|
18
18
|
* --rules can't silently disable every rule.
|
|
19
|
+
*
|
|
20
|
+
* When the allow-list is non-empty and contains no `a11y_*` entries, the
|
|
21
|
+
* `A11Y_CATEGORY_KEY` sentinel is set to `'off'`; `collectA11y` checks this key
|
|
22
|
+
* to suppress the entire Accessibility category.
|
|
19
23
|
*/
|
|
20
24
|
declare function buildRulesConfig(allow: string[], ignore: string[]): Record<string, RuleSetting>;
|
|
21
25
|
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelte-vitals",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "A SvelteKit SEO checker — not a runtime Web Vitals reporter. Static analysis of your routes' head metadata.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"mri": "^1.2.0",
|
|
43
43
|
"svelte": "^5.56.3",
|
|
44
44
|
"tinyglobby": "^0.2.17",
|
|
45
|
-
"@svelte-vitals/core": "0.
|
|
45
|
+
"@svelte-vitals/core": "0.8.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/node": "^24.7.0"
|