@svelte-vitals/core 0.14.0 → 0.15.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/index.d.ts +36 -2
- package/dist/index.js +223 -32
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -100,7 +100,7 @@ interface Runtime {
|
|
|
100
100
|
* these, so rules never need to know which mode produced them.
|
|
101
101
|
*/
|
|
102
102
|
interface HeadTag {
|
|
103
|
-
kind: 'title' | 'meta' | 'link' | 'jsonld';
|
|
103
|
+
kind: 'title' | 'meta' | 'link' | 'jsonld' | 'script';
|
|
104
104
|
/** <meta name="...">. */
|
|
105
105
|
name?: string;
|
|
106
106
|
/** <meta property="..."> (e.g. og:image). */
|
|
@@ -121,6 +121,10 @@ interface HeadTag {
|
|
|
121
121
|
text?: string;
|
|
122
122
|
/** Literal `hreflang` of a `<link rel="alternate">` (e.g. 'en', 'en-US', 'x-default'). Undefined when dynamic/absent. */
|
|
123
123
|
hreflang?: string;
|
|
124
|
+
/** Literal href (link) / src (script) URL when static — used for third-party origin analysis (PERF008). */
|
|
125
|
+
href?: string;
|
|
126
|
+
/** True for a render-blocking `<script src>` in <head> (no defer/async/module) (PERF007). */
|
|
127
|
+
blocking?: boolean;
|
|
124
128
|
/** Where this tag was set relative to the route. Never 'none' (absence = no tag). */
|
|
125
129
|
presence: Exclude<Presence, 'none'>;
|
|
126
130
|
/** Whether the tag's value is static/dynamic/absent (design §4). */
|
|
@@ -160,6 +164,10 @@ interface ImageInfo {
|
|
|
160
164
|
hasLoading: boolean;
|
|
161
165
|
/** True when the <img> has an `alt` attribute at all (incl. empty `alt=""` decorative; SEO025). */
|
|
162
166
|
hasAlt: boolean;
|
|
167
|
+
/** True when the <img> has a literal `loading="lazy"` (PERF005). Dynamic/spread → false. */
|
|
168
|
+
lazy: boolean;
|
|
169
|
+
/** True when the <img> has a `srcset` attribute (PERF006). */
|
|
170
|
+
hasSrcset: boolean;
|
|
163
171
|
/** 1-based source line, or 0 if unknown. */
|
|
164
172
|
line: number;
|
|
165
173
|
/** Source file the <img> came from. */
|
|
@@ -267,10 +275,36 @@ declare const seo009HtmlLang: Rule;
|
|
|
267
275
|
|
|
268
276
|
declare const perf001ImageDimensions: Rule;
|
|
269
277
|
declare const perf002ImageLoading: Rule;
|
|
278
|
+
declare const perf006ResponsiveImage: Rule;
|
|
270
279
|
|
|
271
280
|
declare const perf003PreloadAs: Rule;
|
|
272
281
|
declare const perf004FontPreloadCrossorigin: Rule;
|
|
273
282
|
|
|
283
|
+
/**
|
|
284
|
+
* PERF005 — LCP image not lazy-loaded. Lazy-loading the largest contentful paint
|
|
285
|
+
* image delays it. Static analysis approximates the LCP as the first <img> in
|
|
286
|
+
* document order for the route; if that image is loading="lazy", flag it.
|
|
287
|
+
* CLI/static only (the rendered provider does not collect <img>).
|
|
288
|
+
*/
|
|
289
|
+
declare const perf005LcpImage: Rule;
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* PERF007 — Render-blocking <script> in <head>. A <script src> without
|
|
293
|
+
* defer/async/type=module blocks the parser. SvelteKit's own scripts are
|
|
294
|
+
* module/deferred, so this catches hand-added blocking scripts — in app.html
|
|
295
|
+
* (rendered mode) or in <svelte:head> (static mode). A head with no <script>
|
|
296
|
+
* emits nothing (no signal), like the image rules.
|
|
297
|
+
*/
|
|
298
|
+
declare const perf007RenderBlockingScript: Rule;
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* PERF008 — Preconnect for third-party origins. A resource from a well-known
|
|
302
|
+
* third-party origin (e.g. Google Fonts) without a preconnect/dns-prefetch pays a
|
|
303
|
+
* connection-setup round-trip. Opt-in by construction: only origins in the
|
|
304
|
+
* allowlist are checked; routes referencing none emit nothing.
|
|
305
|
+
*/
|
|
306
|
+
declare const perf008Preconnect: Rule;
|
|
307
|
+
|
|
274
308
|
declare const seo010Indexability: Rule;
|
|
275
309
|
declare const seo011TwitterCard: Rule;
|
|
276
310
|
declare const seo012OgDescription: Rule;
|
|
@@ -527,4 +561,4 @@ declare function selectRules(rules: Rule[], config: Config): Rule[];
|
|
|
527
561
|
/** Apply per-rule severity overrides to results (design §6). */
|
|
528
562
|
declare function applyRuleSeverities(results: Result[], config: Config): Result[];
|
|
529
563
|
|
|
530
|
-
export { BAND_COLOR, type Category, type Classification, type Config, type ConsoleReportOptions, type Detection, type Fix, type HeadProvider, type HeadTag, type HeadingInfo, type HealthResult, type ImageInfo, type JsonReport, type Presence, type Project, ROBOTS_SOURCE_PATHS, type ResolvedHead, type ResolvedHeadings, type ResolvedImages, type Result, type Rule, type RuleContext, type RuleInfo, type RuleSetting, type Runtime, SITEMAP_SOURCE_PATHS, type Scope, type ScoreModel, type ScoreOptions, type ScoreResult, type Severity, type Summary, type TreatDynamicAs, type Value, allRules, applyRuleSeverities, buildHtmlDocument, buildJsonReport, classify, computeHealth, computeScore, defaultConfig, defaultProject, defineConfig, docsUrlFor, effectiveSeverity, escapeHtml, explainRule, formatAgentReport, formatConsoleReport, formatGithubReport, formatHtmlReport, formatJsonReport, formatSarifReport, hasFailureAtOrAbove, headTagRule, imageRule, isPenalized, linkRule, perf001ImageDimensions, perf002ImageLoading, perf003PreloadAs, perf004FontPreloadCrossorigin, runRules, safeHref, scoreBand, scoresByCategory, selectRules, seo001Title, seo002Description, seo003Canonical, seo004OgImage, seo005OgTitle, seo006Robots, seo007Sitemap, seo008JsonLd, seo009HtmlLang, seo010Indexability, seo011TwitterCard, seo012OgDescription, seo013OgUrl, seo014Viewport, seo015SitemapInRobots, seo016JsonLdValidity, seo017DeprecatedType, seo018RelativeUrl, seo019DateFormat, seo020Placeholder, seo021RequiredProps, seo022TitleLength, seo023DescriptionLength, seo024Charset, seo025ImageAlt, seo026Hreflang, seo027Heading, summarize };
|
|
564
|
+
export { BAND_COLOR, type Category, type Classification, type Config, type ConsoleReportOptions, type Detection, type Fix, type HeadProvider, type HeadTag, type HeadingInfo, type HealthResult, type ImageInfo, type JsonReport, type Presence, type Project, ROBOTS_SOURCE_PATHS, type ResolvedHead, type ResolvedHeadings, type ResolvedImages, type Result, type Rule, type RuleContext, type RuleInfo, type RuleSetting, type Runtime, SITEMAP_SOURCE_PATHS, type Scope, type ScoreModel, type ScoreOptions, type ScoreResult, type Severity, type Summary, type TreatDynamicAs, type Value, allRules, applyRuleSeverities, buildHtmlDocument, buildJsonReport, classify, computeHealth, computeScore, defaultConfig, defaultProject, defineConfig, docsUrlFor, effectiveSeverity, escapeHtml, explainRule, formatAgentReport, formatConsoleReport, formatGithubReport, formatHtmlReport, formatJsonReport, formatSarifReport, hasFailureAtOrAbove, headTagRule, imageRule, isPenalized, linkRule, perf001ImageDimensions, perf002ImageLoading, perf003PreloadAs, perf004FontPreloadCrossorigin, perf005LcpImage, perf006ResponsiveImage, perf007RenderBlockingScript, perf008Preconnect, runRules, safeHref, scoreBand, scoresByCategory, selectRules, seo001Title, seo002Description, seo003Canonical, seo004OgImage, seo005OgTitle, seo006Robots, seo007Sitemap, seo008JsonLd, seo009HtmlLang, seo010Indexability, seo011TwitterCard, seo012OgDescription, seo013OgUrl, seo014Viewport, seo015SitemapInRobots, seo016JsonLdValidity, seo017DeprecatedType, seo018RelativeUrl, seo019DateFormat, seo020Placeholder, seo021RequiredProps, seo022TitleLength, seo023DescriptionLength, seo024Charset, seo025ImageAlt, seo026Hreflang, seo027Heading, summarize };
|
package/dist/index.js
CHANGED
|
@@ -94,7 +94,7 @@ function detect(head, match) {
|
|
|
94
94
|
return tag ? { presence: tag.presence, value: tag.value } : { presence: "none", value: "absent" };
|
|
95
95
|
}
|
|
96
96
|
function headTagRule(opts) {
|
|
97
|
-
const
|
|
97
|
+
const docsUrl6 = docsUrlFor(opts.id);
|
|
98
98
|
return {
|
|
99
99
|
id: opts.id,
|
|
100
100
|
title: opts.title,
|
|
@@ -117,7 +117,7 @@ function headTagRule(opts) {
|
|
|
117
117
|
location: head.file,
|
|
118
118
|
message,
|
|
119
119
|
recommendation: opts.recommendation,
|
|
120
|
-
docsUrl:
|
|
120
|
+
docsUrl: docsUrl6,
|
|
121
121
|
// Copy per finding: opts.fix is a rule-level template shared across all
|
|
122
122
|
// results this rule emits; a fresh object keeps findings independent.
|
|
123
123
|
...opts.fix ? { fix: { ...opts.fix } } : {}
|
|
@@ -296,7 +296,7 @@ var seo009HtmlLang = {
|
|
|
296
296
|
|
|
297
297
|
// src/rules/perf/image-rule.ts
|
|
298
298
|
function imageRule(opts) {
|
|
299
|
-
const
|
|
299
|
+
const docsUrl6 = docsUrlFor(opts.id);
|
|
300
300
|
const category = opts.category ?? "performance";
|
|
301
301
|
return {
|
|
302
302
|
id: opts.id,
|
|
@@ -320,7 +320,7 @@ function imageRule(opts) {
|
|
|
320
320
|
route: route.route,
|
|
321
321
|
message: opts.label,
|
|
322
322
|
recommendation: opts.recommendation,
|
|
323
|
-
docsUrl:
|
|
323
|
+
docsUrl: docsUrl6
|
|
324
324
|
});
|
|
325
325
|
continue;
|
|
326
326
|
}
|
|
@@ -335,7 +335,7 @@ function imageRule(opts) {
|
|
|
335
335
|
...img.line > 0 ? { line: img.line } : {},
|
|
336
336
|
message: `Missing ${opts.label}`,
|
|
337
337
|
recommendation: opts.recommendation,
|
|
338
|
-
docsUrl:
|
|
338
|
+
docsUrl: docsUrl6,
|
|
339
339
|
...opts.fix ? { fix: { ...opts.fix } } : {}
|
|
340
340
|
});
|
|
341
341
|
}
|
|
@@ -374,10 +374,24 @@ var perf002ImageLoading = imageRule({
|
|
|
374
374
|
},
|
|
375
375
|
ok: (img) => img.hasLoading
|
|
376
376
|
});
|
|
377
|
+
var perf006ResponsiveImage = imageRule({
|
|
378
|
+
id: "PERF006",
|
|
379
|
+
title: "Responsive image",
|
|
380
|
+
severity: "info",
|
|
381
|
+
label: "<img> srcset",
|
|
382
|
+
recommendation: "Provide a srcset (and sizes) so the browser can pick a right-sized image per viewport.",
|
|
383
|
+
rationale: "An <img> without srcset ships one fixed-size asset to every device, wasting bytes on small screens. Static analysis cannot measure intended display size, so this is advisory.",
|
|
384
|
+
fix: {
|
|
385
|
+
description: "Add a srcset (and sizes) to the <img> for responsive delivery.",
|
|
386
|
+
snippet: '<img src="/hero.jpg" srcset="/hero-800.jpg 800w, /hero-1600.jpg 1600w" sizes="100vw" width="1600" height="900" alt="\u2026" />',
|
|
387
|
+
lang: "svelte"
|
|
388
|
+
},
|
|
389
|
+
ok: (img) => img.hasSrcset
|
|
390
|
+
});
|
|
377
391
|
|
|
378
392
|
// src/rules/perf/link-rule.ts
|
|
379
393
|
function linkRule(opts) {
|
|
380
|
-
const
|
|
394
|
+
const docsUrl6 = docsUrlFor(opts.id);
|
|
381
395
|
return {
|
|
382
396
|
id: opts.id,
|
|
383
397
|
title: opts.title,
|
|
@@ -401,7 +415,7 @@ function linkRule(opts) {
|
|
|
401
415
|
route: head.route,
|
|
402
416
|
message: opts.label,
|
|
403
417
|
recommendation: opts.recommendation,
|
|
404
|
-
docsUrl:
|
|
418
|
+
docsUrl: docsUrl6
|
|
405
419
|
});
|
|
406
420
|
continue;
|
|
407
421
|
}
|
|
@@ -418,7 +432,7 @@ function linkRule(opts) {
|
|
|
418
432
|
location: tag.file ?? head.file,
|
|
419
433
|
message: `Missing ${opts.label}`,
|
|
420
434
|
recommendation: opts.recommendation,
|
|
421
|
-
docsUrl:
|
|
435
|
+
docsUrl: docsUrl6,
|
|
422
436
|
...opts.fix ? { fix: { ...opts.fix } } : {}
|
|
423
437
|
});
|
|
424
438
|
}
|
|
@@ -460,6 +474,175 @@ var perf004FontPreloadCrossorigin = linkRule({
|
|
|
460
474
|
ok: (t) => t.hasCrossorigin === true
|
|
461
475
|
});
|
|
462
476
|
|
|
477
|
+
// src/rules/perf/perf005-lcp-image.ts
|
|
478
|
+
var docsUrl = docsUrlFor("PERF005");
|
|
479
|
+
var recommendation = 'Remove loading="lazy" from the LCP/first image and consider fetchpriority="high" so it loads as early as possible.';
|
|
480
|
+
var perf005LcpImage = {
|
|
481
|
+
id: "PERF005",
|
|
482
|
+
title: "LCP image eager loading",
|
|
483
|
+
category: "performance",
|
|
484
|
+
severity: "warning",
|
|
485
|
+
scope: "route",
|
|
486
|
+
rationale: "Lazy-loading the LCP (first/above-the-fold) image delays the largest paint and hurts Core Web Vitals. The first image is the best static proxy for the LCP candidate.",
|
|
487
|
+
fix: {
|
|
488
|
+
description: 'Remove loading="lazy" from the first/LCP image; consider fetchpriority="high".',
|
|
489
|
+
snippet: '<img src="/hero.jpg" width="1200" height="630" fetchpriority="high" alt="\u2026" />',
|
|
490
|
+
lang: "svelte"
|
|
491
|
+
},
|
|
492
|
+
async check(ctx) {
|
|
493
|
+
const out = [];
|
|
494
|
+
for (const route of ctx.images ?? []) {
|
|
495
|
+
const first = route.images[0];
|
|
496
|
+
if (!first) continue;
|
|
497
|
+
out.push(
|
|
498
|
+
first.lazy ? {
|
|
499
|
+
id: "PERF005",
|
|
500
|
+
category: "performance",
|
|
501
|
+
severity: "warning",
|
|
502
|
+
detection: { presence: "none", value: "absent" },
|
|
503
|
+
route: route.route,
|
|
504
|
+
location: first.file,
|
|
505
|
+
...first.line > 0 ? { line: first.line } : {},
|
|
506
|
+
message: 'First image (likely LCP) is loading="lazy"',
|
|
507
|
+
recommendation,
|
|
508
|
+
docsUrl,
|
|
509
|
+
fix: { ...perf005LcpImage.fix }
|
|
510
|
+
} : {
|
|
511
|
+
id: "PERF005",
|
|
512
|
+
category: "performance",
|
|
513
|
+
severity: "warning",
|
|
514
|
+
detection: { presence: "own", value: "static" },
|
|
515
|
+
route: route.route,
|
|
516
|
+
message: "LCP image eager loading",
|
|
517
|
+
recommendation,
|
|
518
|
+
docsUrl
|
|
519
|
+
}
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
return out;
|
|
523
|
+
}
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
// src/rules/perf/perf007-render-blocking.ts
|
|
527
|
+
var docsUrl2 = docsUrlFor("PERF007");
|
|
528
|
+
var recommendation2 = 'Add defer (or type="module"), or async, to the <script> so it does not block HTML parsing.';
|
|
529
|
+
var perf007RenderBlockingScript = {
|
|
530
|
+
id: "PERF007",
|
|
531
|
+
title: "Render-blocking script",
|
|
532
|
+
category: "performance",
|
|
533
|
+
severity: "warning",
|
|
534
|
+
scope: "route",
|
|
535
|
+
rationale: 'A synchronous <script src> in <head> blocks HTML parsing until it downloads and runs, delaying first paint. defer, async, or type="module" avoids the block.',
|
|
536
|
+
fix: {
|
|
537
|
+
description: 'Add defer (or type="module") / async to the head <script>.',
|
|
538
|
+
snippet: '<script src="/analytics.js" defer></script>',
|
|
539
|
+
lang: "html"
|
|
540
|
+
},
|
|
541
|
+
async check(ctx) {
|
|
542
|
+
const out = [];
|
|
543
|
+
for (const head of ctx.heads) {
|
|
544
|
+
const scripts = head.tags.filter((t) => t.kind === "script");
|
|
545
|
+
if (scripts.length === 0) continue;
|
|
546
|
+
const blocking = scripts.filter((t) => t.blocking);
|
|
547
|
+
if (blocking.length > 0) {
|
|
548
|
+
for (const tag of blocking) {
|
|
549
|
+
out.push({
|
|
550
|
+
id: "PERF007",
|
|
551
|
+
category: "performance",
|
|
552
|
+
severity: "warning",
|
|
553
|
+
detection: { presence: "none", value: "absent" },
|
|
554
|
+
route: head.route,
|
|
555
|
+
// location is a source path (the URL stays in the message), per the rule-engine convention.
|
|
556
|
+
location: tag.file ?? head.file,
|
|
557
|
+
message: `Render-blocking <script>${tag.href ? ` (${tag.href})` : ""} in <head>`,
|
|
558
|
+
recommendation: recommendation2,
|
|
559
|
+
docsUrl: docsUrl2,
|
|
560
|
+
fix: { ...perf007RenderBlockingScript.fix }
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
} else {
|
|
564
|
+
out.push({
|
|
565
|
+
id: "PERF007",
|
|
566
|
+
category: "performance",
|
|
567
|
+
severity: "warning",
|
|
568
|
+
detection: { presence: "own", value: "static" },
|
|
569
|
+
route: head.route,
|
|
570
|
+
message: "No render-blocking scripts",
|
|
571
|
+
recommendation: recommendation2,
|
|
572
|
+
docsUrl: docsUrl2
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
return out;
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
// src/rules/perf/perf008-preconnect.ts
|
|
581
|
+
var docsUrl3 = docsUrlFor("PERF008");
|
|
582
|
+
var recommendation3 = 'Add <link rel="preconnect"> (or dns-prefetch) for the third-party origin so the connection is set up early.';
|
|
583
|
+
var THIRD_PARTY_ORIGINS = /* @__PURE__ */ new Set(["fonts.googleapis.com", "fonts.gstatic.com"]);
|
|
584
|
+
function hostOf(href) {
|
|
585
|
+
const m = /^(?:https?:)?\/\/([^/?#]+)/i.exec(href);
|
|
586
|
+
return m ? m[1].toLowerCase() : void 0;
|
|
587
|
+
}
|
|
588
|
+
var perf008Preconnect = {
|
|
589
|
+
id: "PERF008",
|
|
590
|
+
title: "Preconnect third-party origin",
|
|
591
|
+
category: "performance",
|
|
592
|
+
severity: "info",
|
|
593
|
+
scope: "route",
|
|
594
|
+
rationale: "Connecting to a third-party origin (DNS + TCP + TLS) is costly; a preconnect/dns-prefetch hint starts it early so the resource arrives sooner.",
|
|
595
|
+
fix: {
|
|
596
|
+
description: "Add a preconnect hint for the third-party origin.",
|
|
597
|
+
snippet: '<link rel="preconnect" href="https://fonts.googleapis.com" />',
|
|
598
|
+
lang: "html"
|
|
599
|
+
},
|
|
600
|
+
async check(ctx) {
|
|
601
|
+
const out = [];
|
|
602
|
+
for (const head of ctx.heads) {
|
|
603
|
+
const referenced = /* @__PURE__ */ new Map();
|
|
604
|
+
const covered = /* @__PURE__ */ new Set();
|
|
605
|
+
for (const tag of head.tags) {
|
|
606
|
+
if (tag.kind !== "link" && tag.kind !== "script" || typeof tag.href !== "string") continue;
|
|
607
|
+
const host = hostOf(tag.href);
|
|
608
|
+
if (!host || !THIRD_PARTY_ORIGINS.has(host)) continue;
|
|
609
|
+
if (tag.kind === "link" && (tag.rel === "preconnect" || tag.rel === "dns-prefetch")) covered.add(host);
|
|
610
|
+
else if (!referenced.has(host)) referenced.set(host, tag.file);
|
|
611
|
+
}
|
|
612
|
+
if (referenced.size === 0) continue;
|
|
613
|
+
const missing = [...referenced].filter(([host]) => !covered.has(host));
|
|
614
|
+
if (missing.length === 0) {
|
|
615
|
+
out.push({
|
|
616
|
+
id: "PERF008",
|
|
617
|
+
category: "performance",
|
|
618
|
+
severity: "info",
|
|
619
|
+
detection: { presence: "own", value: "static" },
|
|
620
|
+
route: head.route,
|
|
621
|
+
message: "Third-party origins are preconnected",
|
|
622
|
+
recommendation: recommendation3,
|
|
623
|
+
docsUrl: docsUrl3
|
|
624
|
+
});
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
for (const [host, file] of missing) {
|
|
628
|
+
out.push({
|
|
629
|
+
id: "PERF008",
|
|
630
|
+
category: "performance",
|
|
631
|
+
severity: "info",
|
|
632
|
+
detection: { presence: "none", value: "absent" },
|
|
633
|
+
route: head.route,
|
|
634
|
+
location: file ?? head.file,
|
|
635
|
+
message: `Third-party origin ${host} used without a preconnect`,
|
|
636
|
+
recommendation: recommendation3,
|
|
637
|
+
docsUrl: docsUrl3,
|
|
638
|
+
fix: { ...perf008Preconnect.fix }
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
return out;
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
|
|
463
646
|
// src/rules/seo/seo010-015.ts
|
|
464
647
|
var SEO010_FIX = {
|
|
465
648
|
description: 'If this route should be indexed, drop noindex from its <meta name="robots">.',
|
|
@@ -475,7 +658,7 @@ var seo010Indexability = {
|
|
|
475
658
|
rationale: "A noindex directive removes the page from search results; an accidental noindex on a public route silently deindexes it.",
|
|
476
659
|
fix: SEO010_FIX,
|
|
477
660
|
async check(ctx) {
|
|
478
|
-
const
|
|
661
|
+
const docsUrl6 = docsUrlFor("SEO010");
|
|
479
662
|
const out = [];
|
|
480
663
|
for (const head of ctx.heads) {
|
|
481
664
|
const noindexed = head.tags.some((t) => t.kind === "meta" && t.name === "robots" && t.noindex === true);
|
|
@@ -490,7 +673,7 @@ var seo010Indexability = {
|
|
|
490
673
|
location: head.file,
|
|
491
674
|
message: "Route is noindex \u2014 verify this is intentional",
|
|
492
675
|
recommendation: 'If this route should be indexed, remove noindex from its <meta name="robots">.',
|
|
493
|
-
docsUrl:
|
|
676
|
+
docsUrl: docsUrl6,
|
|
494
677
|
fix: { ...SEO010_FIX }
|
|
495
678
|
});
|
|
496
679
|
}
|
|
@@ -745,7 +928,7 @@ var seo016JsonLdValidity = {
|
|
|
745
928
|
lang: "svelte"
|
|
746
929
|
},
|
|
747
930
|
async check(ctx) {
|
|
748
|
-
const
|
|
931
|
+
const docsUrl6 = docsUrlFor("SEO016");
|
|
749
932
|
const out = [];
|
|
750
933
|
for (const head of ctx.heads) {
|
|
751
934
|
for (const tag of jsonldTags(head)) {
|
|
@@ -764,7 +947,7 @@ var seo016JsonLdValidity = {
|
|
|
764
947
|
location: head.file,
|
|
765
948
|
message: problem,
|
|
766
949
|
recommendation: "Make the JSON-LD valid JSON with both @context and @type.",
|
|
767
|
-
docsUrl:
|
|
950
|
+
docsUrl: docsUrl6,
|
|
768
951
|
fix: { ...seo016JsonLdValidity.fix }
|
|
769
952
|
} : {
|
|
770
953
|
id: "SEO016",
|
|
@@ -774,7 +957,7 @@ var seo016JsonLdValidity = {
|
|
|
774
957
|
route: head.route,
|
|
775
958
|
message: "JSON-LD validity",
|
|
776
959
|
recommendation: "Make the JSON-LD valid JSON with both @context and @type.",
|
|
777
|
-
docsUrl:
|
|
960
|
+
docsUrl: docsUrl6
|
|
778
961
|
}
|
|
779
962
|
);
|
|
780
963
|
}
|
|
@@ -783,7 +966,7 @@ var seo016JsonLdValidity = {
|
|
|
783
966
|
}
|
|
784
967
|
};
|
|
785
968
|
function jsonldRule(opts) {
|
|
786
|
-
const
|
|
969
|
+
const docsUrl6 = docsUrlFor(opts.id);
|
|
787
970
|
return {
|
|
788
971
|
id: opts.id,
|
|
789
972
|
title: opts.title,
|
|
@@ -811,7 +994,7 @@ function jsonldRule(opts) {
|
|
|
811
994
|
location: head.file,
|
|
812
995
|
message: problem,
|
|
813
996
|
recommendation: opts.recommendation,
|
|
814
|
-
docsUrl:
|
|
997
|
+
docsUrl: docsUrl6,
|
|
815
998
|
...opts.fix ? { fix: { ...opts.fix } } : {}
|
|
816
999
|
} : {
|
|
817
1000
|
id: opts.id,
|
|
@@ -821,7 +1004,7 @@ function jsonldRule(opts) {
|
|
|
821
1004
|
route: head.route,
|
|
822
1005
|
message: opts.label,
|
|
823
1006
|
recommendation: opts.recommendation,
|
|
824
|
-
docsUrl:
|
|
1007
|
+
docsUrl: docsUrl6
|
|
825
1008
|
}
|
|
826
1009
|
);
|
|
827
1010
|
}
|
|
@@ -920,7 +1103,7 @@ function visibleLength(s) {
|
|
|
920
1103
|
|
|
921
1104
|
// src/rules/seo/seo022-023.ts
|
|
922
1105
|
function lengthRule(opts) {
|
|
923
|
-
const
|
|
1106
|
+
const docsUrl6 = docsUrlFor(opts.id);
|
|
924
1107
|
return {
|
|
925
1108
|
id: opts.id,
|
|
926
1109
|
title: opts.title,
|
|
@@ -947,7 +1130,7 @@ function lengthRule(opts) {
|
|
|
947
1130
|
location: tag.file ?? head.file,
|
|
948
1131
|
message: problem,
|
|
949
1132
|
recommendation: opts.recommendation,
|
|
950
|
-
docsUrl:
|
|
1133
|
+
docsUrl: docsUrl6
|
|
951
1134
|
} : {
|
|
952
1135
|
id: opts.id,
|
|
953
1136
|
category: "seo",
|
|
@@ -956,7 +1139,7 @@ function lengthRule(opts) {
|
|
|
956
1139
|
route: head.route,
|
|
957
1140
|
message: opts.label,
|
|
958
1141
|
recommendation: opts.recommendation,
|
|
959
|
-
docsUrl:
|
|
1142
|
+
docsUrl: docsUrl6
|
|
960
1143
|
}
|
|
961
1144
|
);
|
|
962
1145
|
}
|
|
@@ -1022,8 +1205,8 @@ var seo025ImageAlt = imageRule({
|
|
|
1022
1205
|
});
|
|
1023
1206
|
|
|
1024
1207
|
// src/rules/seo/seo026-hreflang.ts
|
|
1025
|
-
var
|
|
1026
|
-
var
|
|
1208
|
+
var docsUrl4 = docsUrlFor("SEO026");
|
|
1209
|
+
var recommendation4 = 'Use valid hreflang codes (e.g. "en", "en-US", "x-default") and include an x-default when you have multiple language alternates.';
|
|
1027
1210
|
var HREFLANG_RE = /^[a-z]{2,3}(-[a-z]{4})?(-([a-z]{2}|\d{3}))?$/i;
|
|
1028
1211
|
function isValidHreflang(v) {
|
|
1029
1212
|
return v.toLowerCase() === "x-default" || HREFLANG_RE.test(v);
|
|
@@ -1061,8 +1244,8 @@ var seo026Hreflang = {
|
|
|
1061
1244
|
route: head.route,
|
|
1062
1245
|
location,
|
|
1063
1246
|
message: problem,
|
|
1064
|
-
recommendation,
|
|
1065
|
-
docsUrl
|
|
1247
|
+
recommendation: recommendation4,
|
|
1248
|
+
docsUrl: docsUrl4
|
|
1066
1249
|
} : {
|
|
1067
1250
|
id: "SEO026",
|
|
1068
1251
|
category: "seo",
|
|
@@ -1070,8 +1253,8 @@ var seo026Hreflang = {
|
|
|
1070
1253
|
detection: PASS,
|
|
1071
1254
|
route: head.route,
|
|
1072
1255
|
message: "hreflang",
|
|
1073
|
-
recommendation,
|
|
1074
|
-
docsUrl
|
|
1256
|
+
recommendation: recommendation4,
|
|
1257
|
+
docsUrl: docsUrl4
|
|
1075
1258
|
}
|
|
1076
1259
|
);
|
|
1077
1260
|
}
|
|
@@ -1080,8 +1263,8 @@ var seo026Hreflang = {
|
|
|
1080
1263
|
};
|
|
1081
1264
|
|
|
1082
1265
|
// src/rules/seo/seo027-heading.ts
|
|
1083
|
-
var
|
|
1084
|
-
var
|
|
1266
|
+
var docsUrl5 = docsUrlFor("SEO027");
|
|
1267
|
+
var recommendation5 = "Use exactly one <h1> per page for its main topic; demote extra top-level headings to <h2>+.";
|
|
1085
1268
|
var seo027Heading = {
|
|
1086
1269
|
id: "SEO027",
|
|
1087
1270
|
title: "Heading hierarchy",
|
|
@@ -1113,8 +1296,8 @@ var seo027Heading = {
|
|
|
1113
1296
|
route: route.route,
|
|
1114
1297
|
...where,
|
|
1115
1298
|
message: problem,
|
|
1116
|
-
recommendation:
|
|
1117
|
-
docsUrl:
|
|
1299
|
+
recommendation: recommendation5,
|
|
1300
|
+
docsUrl: docsUrl5
|
|
1118
1301
|
} : {
|
|
1119
1302
|
id: "SEO027",
|
|
1120
1303
|
category: "seo",
|
|
@@ -1122,8 +1305,8 @@ var seo027Heading = {
|
|
|
1122
1305
|
detection: PASS,
|
|
1123
1306
|
route: route.route,
|
|
1124
1307
|
message: "Heading hierarchy",
|
|
1125
|
-
recommendation:
|
|
1126
|
-
docsUrl:
|
|
1308
|
+
recommendation: recommendation5,
|
|
1309
|
+
docsUrl: docsUrl5
|
|
1127
1310
|
}
|
|
1128
1311
|
);
|
|
1129
1312
|
}
|
|
@@ -1163,7 +1346,11 @@ var allRules = [
|
|
|
1163
1346
|
seo024Charset,
|
|
1164
1347
|
seo025ImageAlt,
|
|
1165
1348
|
seo026Hreflang,
|
|
1166
|
-
seo027Heading
|
|
1349
|
+
seo027Heading,
|
|
1350
|
+
perf005LcpImage,
|
|
1351
|
+
perf006ResponsiveImage,
|
|
1352
|
+
perf007RenderBlockingScript,
|
|
1353
|
+
perf008Preconnect
|
|
1167
1354
|
];
|
|
1168
1355
|
function explainRule(id) {
|
|
1169
1356
|
const target = id.toUpperCase();
|
|
@@ -1776,6 +1963,10 @@ export {
|
|
|
1776
1963
|
perf002ImageLoading,
|
|
1777
1964
|
perf003PreloadAs,
|
|
1778
1965
|
perf004FontPreloadCrossorigin,
|
|
1966
|
+
perf005LcpImage,
|
|
1967
|
+
perf006ResponsiveImage,
|
|
1968
|
+
perf007RenderBlockingScript,
|
|
1969
|
+
perf008Preconnect,
|
|
1779
1970
|
runRules,
|
|
1780
1971
|
safeHref,
|
|
1781
1972
|
scoreBand,
|