@svelte-vitals/core 0.11.0 → 0.13.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 +45 -1
- package/dist/index.js +233 -2
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -21,6 +21,8 @@ interface Project {
|
|
|
21
21
|
hasSitemap: boolean;
|
|
22
22
|
/** <html lang> from app.html: presence 'own' when the attribute exists ('none' otherwise); value 'static' if non-empty, 'absent' if empty. */
|
|
23
23
|
htmlLang: Detection;
|
|
24
|
+
/** Whether the static static/robots.txt references a sitemap (`Sitemap:` line). Undefined for a +server endpoint / absent / unreadable. */
|
|
25
|
+
robotsReferencesSitemap?: boolean;
|
|
24
26
|
}
|
|
25
27
|
declare const defaultProject: Project;
|
|
26
28
|
/** A concrete, agent-actionable remediation for a finding (design §10, issue #18). */
|
|
@@ -105,6 +107,14 @@ interface HeadTag {
|
|
|
105
107
|
property?: string;
|
|
106
108
|
/** <link rel="...">. */
|
|
107
109
|
rel?: string;
|
|
110
|
+
/** <link as="..."> keyword (e.g. 'font') when statically literal; undefined when absent or dynamically bound. */
|
|
111
|
+
as?: string;
|
|
112
|
+
/** True when a <link> has an `as` attribute at all (literal or dynamic). Distinguishes "no as" from "dynamic as". */
|
|
113
|
+
hasAs?: boolean;
|
|
114
|
+
/** True when a <link> has a `crossorigin` attribute (presence only; value is irrelevant to the checks). */
|
|
115
|
+
hasCrossorigin?: boolean;
|
|
116
|
+
/** True when a <meta name="robots"> literal content contains `noindex`/`none`. Undefined when dynamic or absent. */
|
|
117
|
+
noindex?: boolean;
|
|
108
118
|
/** Where this tag was set relative to the route. Never 'none' (absence = no tag). */
|
|
109
119
|
presence: Exclude<Presence, 'none'>;
|
|
110
120
|
/** Whether the tag's value is static/dynamic/absent (design §4). */
|
|
@@ -229,6 +239,16 @@ declare const seo009HtmlLang: Rule;
|
|
|
229
239
|
declare const perf001ImageDimensions: Rule;
|
|
230
240
|
declare const perf002ImageLoading: Rule;
|
|
231
241
|
|
|
242
|
+
declare const perf003PreloadAs: Rule;
|
|
243
|
+
declare const perf004FontPreloadCrossorigin: Rule;
|
|
244
|
+
|
|
245
|
+
declare const seo010Indexability: Rule;
|
|
246
|
+
declare const seo011TwitterCard: Rule;
|
|
247
|
+
declare const seo012OgDescription: Rule;
|
|
248
|
+
declare const seo013OgUrl: Rule;
|
|
249
|
+
declare const seo014Viewport: Rule;
|
|
250
|
+
declare const seo015SitemapInRobots: Rule;
|
|
251
|
+
|
|
232
252
|
declare const allRules: Rule[];
|
|
233
253
|
|
|
234
254
|
interface RuleInfo {
|
|
@@ -256,6 +276,13 @@ interface HeadTagRuleOptions {
|
|
|
256
276
|
rationale: string;
|
|
257
277
|
/** Agent-actionable remediation attached to every finding (issue #18). */
|
|
258
278
|
fix?: Fix;
|
|
279
|
+
/**
|
|
280
|
+
* When set, only heads for which this returns true are evaluated; others emit
|
|
281
|
+
* nothing. Use for tags whose canonical location is invisible to a given mode
|
|
282
|
+
* (e.g. viewport lives in app.html → only checkable in rendered mode), so the
|
|
283
|
+
* rule stays silent instead of false-flagging "missing".
|
|
284
|
+
*/
|
|
285
|
+
appliesTo?: (head: ResolvedHead) => boolean;
|
|
259
286
|
}
|
|
260
287
|
/** Build a route-scope rule asserting the presence of a single head tag (design §11). */
|
|
261
288
|
declare function headTagRule(opts: HeadTagRuleOptions): Rule;
|
|
@@ -275,6 +302,23 @@ interface ImageRuleOptions {
|
|
|
275
302
|
/** Build a route-scoped Performance rule that checks each <img> against `ok` (issue #10). */
|
|
276
303
|
declare function imageRule(opts: ImageRuleOptions): Rule;
|
|
277
304
|
|
|
305
|
+
interface LinkRuleOptions {
|
|
306
|
+
id: string;
|
|
307
|
+
title: string;
|
|
308
|
+
severity: Severity;
|
|
309
|
+
/** Noun phrase for messages, e.g. '`as` on a preloaded `<link>`'. */
|
|
310
|
+
label: string;
|
|
311
|
+
recommendation: string;
|
|
312
|
+
rationale: string;
|
|
313
|
+
fix?: Fix;
|
|
314
|
+
/** Which link tags this rule evaluates (e.g. rel === 'preload'). */
|
|
315
|
+
relevant: (tag: HeadTag) => boolean;
|
|
316
|
+
/** Returns true when a relevant link satisfies the rule (passes). */
|
|
317
|
+
ok: (tag: HeadTag) => boolean;
|
|
318
|
+
}
|
|
319
|
+
/** Build a route-scoped Performance rule that checks each relevant <link> in the effective head. */
|
|
320
|
+
declare function linkRule(opts: LinkRuleOptions): Rule;
|
|
321
|
+
|
|
278
322
|
interface Summary {
|
|
279
323
|
critical: number;
|
|
280
324
|
warning: number;
|
|
@@ -411,4 +455,4 @@ declare function selectRules(rules: Rule[], config: Config): Rule[];
|
|
|
411
455
|
/** Apply per-rule severity overrides to results (design §6). */
|
|
412
456
|
declare function applyRuleSeverities(results: Result[], config: Config): Result[];
|
|
413
457
|
|
|
414
|
-
export { BAND_COLOR, type Category, type Classification, type Config, type ConsoleReportOptions, type Detection, type Fix, type HeadProvider, type HeadTag, type HealthResult, type ImageInfo, type JsonReport, type Presence, type Project, ROBOTS_SOURCE_PATHS, type ResolvedHead, 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, perf001ImageDimensions, perf002ImageLoading, runRules, safeHref, scoreBand, scoresByCategory, selectRules, seo001Title, seo002Description, seo003Canonical, seo004OgImage, seo005OgTitle, seo006Robots, seo007Sitemap, seo008JsonLd, seo009HtmlLang, summarize };
|
|
458
|
+
export { BAND_COLOR, type Category, type Classification, type Config, type ConsoleReportOptions, type Detection, type Fix, type HeadProvider, type HeadTag, type HealthResult, type ImageInfo, type JsonReport, type Presence, type Project, ROBOTS_SOURCE_PATHS, type ResolvedHead, 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, summarize };
|
package/dist/index.js
CHANGED
|
@@ -104,7 +104,8 @@ function headTagRule(opts) {
|
|
|
104
104
|
rationale: opts.rationale,
|
|
105
105
|
...opts.fix ? { fix: opts.fix } : {},
|
|
106
106
|
async check(ctx) {
|
|
107
|
-
|
|
107
|
+
const heads = opts.appliesTo ? ctx.heads.filter(opts.appliesTo) : ctx.heads;
|
|
108
|
+
return heads.map((head) => {
|
|
108
109
|
const detection = detect(head, opts.match);
|
|
109
110
|
const message = detection.presence === "none" ? `Missing ${opts.label}` : detection.value === "absent" ? `Empty ${opts.label}` : opts.label;
|
|
110
111
|
return {
|
|
@@ -373,6 +374,219 @@ var perf002ImageLoading = imageRule({
|
|
|
373
374
|
ok: (img) => img.hasLoading
|
|
374
375
|
});
|
|
375
376
|
|
|
377
|
+
// src/rules/perf/link-rule.ts
|
|
378
|
+
function linkRule(opts) {
|
|
379
|
+
const docsUrl = docsUrlFor(opts.id);
|
|
380
|
+
return {
|
|
381
|
+
id: opts.id,
|
|
382
|
+
title: opts.title,
|
|
383
|
+
category: "performance",
|
|
384
|
+
severity: opts.severity,
|
|
385
|
+
scope: "route",
|
|
386
|
+
rationale: opts.rationale,
|
|
387
|
+
...opts.fix ? { fix: opts.fix } : {},
|
|
388
|
+
async check(ctx) {
|
|
389
|
+
const out = [];
|
|
390
|
+
for (const head of ctx.heads) {
|
|
391
|
+
const links = head.tags.filter((t) => t.kind === "link" && opts.relevant(t));
|
|
392
|
+
if (links.length === 0) continue;
|
|
393
|
+
const bad = links.filter((t) => !opts.ok(t));
|
|
394
|
+
if (bad.length === 0) {
|
|
395
|
+
out.push({
|
|
396
|
+
id: opts.id,
|
|
397
|
+
category: "performance",
|
|
398
|
+
severity: opts.severity,
|
|
399
|
+
detection: { presence: "own", value: "static" },
|
|
400
|
+
route: head.route,
|
|
401
|
+
message: opts.label,
|
|
402
|
+
recommendation: opts.recommendation,
|
|
403
|
+
docsUrl
|
|
404
|
+
});
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
for (const tag of bad) {
|
|
408
|
+
out.push({
|
|
409
|
+
id: opts.id,
|
|
410
|
+
category: "performance",
|
|
411
|
+
severity: opts.severity,
|
|
412
|
+
detection: { presence: "none", value: "absent" },
|
|
413
|
+
route: head.route,
|
|
414
|
+
// Point at the file the link actually came from (a layout in static
|
|
415
|
+
// mode); fall back to the route's representative file when the tag
|
|
416
|
+
// carries no file (rendered mode).
|
|
417
|
+
location: tag.file ?? head.file,
|
|
418
|
+
message: `Missing ${opts.label}`,
|
|
419
|
+
recommendation: opts.recommendation,
|
|
420
|
+
docsUrl,
|
|
421
|
+
...opts.fix ? { fix: { ...opts.fix } } : {}
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return out;
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// src/rules/perf/resource-hints.ts
|
|
431
|
+
var perf003PreloadAs = linkRule({
|
|
432
|
+
id: "PERF003",
|
|
433
|
+
title: "Preload missing as",
|
|
434
|
+
severity: "warning",
|
|
435
|
+
label: "`as` on a preloaded `<link>`",
|
|
436
|
+
recommendation: 'Add an `as` attribute to every `<link rel="preload">` so the browser knows the resource type and can prioritize it.',
|
|
437
|
+
rationale: 'A `<link rel="preload">` without an `as` attribute is ignored by the browser (or fetched a second time), wasting the preload.',
|
|
438
|
+
fix: {
|
|
439
|
+
description: "Add an `as` attribute matching the resource type to the preload link.",
|
|
440
|
+
snippet: '<link rel="preload" href="/app.css" as="style" />',
|
|
441
|
+
lang: "html"
|
|
442
|
+
},
|
|
443
|
+
relevant: (t) => t.rel === "preload",
|
|
444
|
+
ok: (t) => t.hasAs === true
|
|
445
|
+
});
|
|
446
|
+
var perf004FontPreloadCrossorigin = linkRule({
|
|
447
|
+
id: "PERF004",
|
|
448
|
+
title: "Font preload missing crossorigin",
|
|
449
|
+
severity: "warning",
|
|
450
|
+
label: "`crossorigin` on a font preload",
|
|
451
|
+
recommendation: 'Add `crossorigin` to `<link rel="preload" as="font">` \u2014 fonts are fetched in CORS mode, so without it the preload fetches a second, unused copy.',
|
|
452
|
+
rationale: "A font preload without `crossorigin` does not match the actual (CORS) font request, so the preloaded file is never used and the font downloads twice.",
|
|
453
|
+
fix: {
|
|
454
|
+
description: "Add the `crossorigin` attribute to the font preload link.",
|
|
455
|
+
snippet: '<link rel="preload" href="/inter.woff2" as="font" type="font/woff2" crossorigin />',
|
|
456
|
+
lang: "html"
|
|
457
|
+
},
|
|
458
|
+
relevant: (t) => t.rel === "preload" && t.as === "font",
|
|
459
|
+
ok: (t) => t.hasCrossorigin === true
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
// src/rules/seo/seo010-015.ts
|
|
463
|
+
var SEO010_FIX = {
|
|
464
|
+
description: 'If this route should be indexed, drop noindex from its <meta name="robots">.',
|
|
465
|
+
snippet: '<svelte:head>\n <meta name="robots" content="index, follow" />\n</svelte:head>',
|
|
466
|
+
lang: "svelte"
|
|
467
|
+
};
|
|
468
|
+
var seo010Indexability = {
|
|
469
|
+
id: "SEO010",
|
|
470
|
+
title: "Indexability",
|
|
471
|
+
category: "seo",
|
|
472
|
+
severity: "info",
|
|
473
|
+
scope: "route",
|
|
474
|
+
rationale: "A noindex directive removes the page from search results; an accidental noindex on a public route silently deindexes it.",
|
|
475
|
+
fix: SEO010_FIX,
|
|
476
|
+
async check(ctx) {
|
|
477
|
+
const docsUrl = docsUrlFor("SEO010");
|
|
478
|
+
const out = [];
|
|
479
|
+
for (const head of ctx.heads) {
|
|
480
|
+
const noindexed = head.tags.some((t) => t.kind === "meta" && t.name === "robots" && t.noindex === true);
|
|
481
|
+
if (!noindexed) continue;
|
|
482
|
+
out.push({
|
|
483
|
+
id: "SEO010",
|
|
484
|
+
category: "seo",
|
|
485
|
+
severity: "info",
|
|
486
|
+
detection: { presence: "none", value: "absent" },
|
|
487
|
+
// surfaced as an issue (isPenalized)
|
|
488
|
+
route: head.route,
|
|
489
|
+
location: head.file,
|
|
490
|
+
message: "Route is noindex \u2014 verify this is intentional",
|
|
491
|
+
recommendation: 'If this route should be indexed, remove noindex from its <meta name="robots">.',
|
|
492
|
+
docsUrl,
|
|
493
|
+
fix: { ...SEO010_FIX }
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
return out;
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
var seo011TwitterCard = headTagRule({
|
|
500
|
+
id: "SEO011",
|
|
501
|
+
title: "Twitter Card",
|
|
502
|
+
severity: "info",
|
|
503
|
+
match: (t) => t.kind === "meta" && t.name === "twitter:card",
|
|
504
|
+
label: '<meta name="twitter:card">',
|
|
505
|
+
recommendation: 'Add <meta name="twitter:card" content="summary_large_image"> so X/Twitter renders a rich card.',
|
|
506
|
+
rationale: "twitter:card selects how the page renders when shared on X/Twitter; without it the platform falls back to a basic link (Open Graph tags are used as fallbacks for the rest).",
|
|
507
|
+
fix: {
|
|
508
|
+
description: "Add a twitter:card meta tag in <svelte:head>.",
|
|
509
|
+
snippet: '<svelte:head>\n <meta name="twitter:card" content="summary_large_image" />\n</svelte:head>',
|
|
510
|
+
lang: "svelte"
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
var seo012OgDescription = headTagRule({
|
|
514
|
+
id: "SEO012",
|
|
515
|
+
title: "Open Graph description",
|
|
516
|
+
severity: "warning",
|
|
517
|
+
match: (t) => t.kind === "meta" && t.property === "og:description",
|
|
518
|
+
label: '<meta property="og:description">',
|
|
519
|
+
recommendation: 'Add <meta property="og:description">, or set openGraph.description on your meta component.',
|
|
520
|
+
rationale: "og:description is the summary shown under the title in social previews; without it platforms guess or show nothing, lowering click-through.",
|
|
521
|
+
fix: {
|
|
522
|
+
description: "Add an og:description meta tag in <svelte:head>.",
|
|
523
|
+
snippet: '<svelte:head>\n <meta property="og:description" content="A concise page summary." />\n</svelte:head>',
|
|
524
|
+
lang: "svelte"
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
var seo013OgUrl = headTagRule({
|
|
528
|
+
id: "SEO013",
|
|
529
|
+
title: "Open Graph URL",
|
|
530
|
+
severity: "info",
|
|
531
|
+
match: (t) => t.kind === "meta" && t.property === "og:url",
|
|
532
|
+
label: '<meta property="og:url">',
|
|
533
|
+
recommendation: 'Add <meta property="og:url"> with the canonical URL, or set openGraph.url on your meta component.',
|
|
534
|
+
rationale: "og:url tells social platforms the canonical address to attribute shares and likes to, consolidating engagement on one URL.",
|
|
535
|
+
fix: {
|
|
536
|
+
description: "Add an og:url meta tag in <svelte:head>.",
|
|
537
|
+
snippet: '<svelte:head>\n <meta property="og:url" content="https://example.com/this-page" />\n</svelte:head>',
|
|
538
|
+
lang: "svelte"
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
var seo014Viewport = headTagRule({
|
|
542
|
+
id: "SEO014",
|
|
543
|
+
title: "Viewport",
|
|
544
|
+
severity: "warning",
|
|
545
|
+
match: (t) => t.kind === "meta" && t.name === "viewport",
|
|
546
|
+
label: '<meta name="viewport">',
|
|
547
|
+
// Viewport canonically lives in app.html, which static (CLI) mode does not
|
|
548
|
+
// resolve into head tags — only evaluate rendered heads so the rule stays
|
|
549
|
+
// silent there instead of false-flagging "missing" on every route.
|
|
550
|
+
appliesTo: (head) => head.source === "rendered",
|
|
551
|
+
recommendation: 'Add <meta name="viewport" content="width=device-width, initial-scale=1"> (usually in app.html).',
|
|
552
|
+
rationale: "Without a viewport meta tag the page is not mobile-responsive, which Google penalizes under mobile-first indexing.",
|
|
553
|
+
fix: {
|
|
554
|
+
description: "Add the viewport meta tag (typically in src/app.html <head>).",
|
|
555
|
+
snippet: '<meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
556
|
+
lang: "html"
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
var SEO015_FIX = {
|
|
560
|
+
description: "Add a Sitemap: line to static/robots.txt.",
|
|
561
|
+
snippet: "User-agent: *\nAllow: /\n\nSitemap: https://example.com/sitemap.xml",
|
|
562
|
+
lang: "text"
|
|
563
|
+
};
|
|
564
|
+
var seo015SitemapInRobots = {
|
|
565
|
+
id: "SEO015",
|
|
566
|
+
title: "Sitemap referenced in robots.txt",
|
|
567
|
+
category: "seo",
|
|
568
|
+
severity: "info",
|
|
569
|
+
scope: "project",
|
|
570
|
+
rationale: "A Sitemap: line in robots.txt helps crawlers discover your sitemap; without it discovery relies on manual submission.",
|
|
571
|
+
fix: SEO015_FIX,
|
|
572
|
+
async check(ctx) {
|
|
573
|
+
const { hasRobotsTxt, hasSitemap, robotsReferencesSitemap } = ctx.project;
|
|
574
|
+
if (!(hasRobotsTxt && hasSitemap && robotsReferencesSitemap === false)) return [];
|
|
575
|
+
return [
|
|
576
|
+
{
|
|
577
|
+
id: "SEO015",
|
|
578
|
+
category: "seo",
|
|
579
|
+
severity: "info",
|
|
580
|
+
detection: { presence: "none", value: "absent" },
|
|
581
|
+
message: "robots.txt does not reference your sitemap",
|
|
582
|
+
recommendation: "Add a Sitemap: line to static/robots.txt pointing at your sitemap.xml.",
|
|
583
|
+
docsUrl: docsUrlFor("SEO015"),
|
|
584
|
+
fix: { ...SEO015_FIX }
|
|
585
|
+
}
|
|
586
|
+
];
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
|
|
376
590
|
// src/rules/index.ts
|
|
377
591
|
var allRules = [
|
|
378
592
|
seo001Title,
|
|
@@ -385,7 +599,15 @@ var allRules = [
|
|
|
385
599
|
seo008JsonLd,
|
|
386
600
|
seo009HtmlLang,
|
|
387
601
|
perf001ImageDimensions,
|
|
388
|
-
perf002ImageLoading
|
|
602
|
+
perf002ImageLoading,
|
|
603
|
+
perf003PreloadAs,
|
|
604
|
+
perf004FontPreloadCrossorigin,
|
|
605
|
+
seo010Indexability,
|
|
606
|
+
seo011TwitterCard,
|
|
607
|
+
seo012OgDescription,
|
|
608
|
+
seo013OgUrl,
|
|
609
|
+
seo014Viewport,
|
|
610
|
+
seo015SitemapInRobots
|
|
389
611
|
];
|
|
390
612
|
function explainRule(id) {
|
|
391
613
|
const target = id.toUpperCase();
|
|
@@ -993,8 +1215,11 @@ export {
|
|
|
993
1215
|
headTagRule,
|
|
994
1216
|
imageRule,
|
|
995
1217
|
isPenalized,
|
|
1218
|
+
linkRule,
|
|
996
1219
|
perf001ImageDimensions,
|
|
997
1220
|
perf002ImageLoading,
|
|
1221
|
+
perf003PreloadAs,
|
|
1222
|
+
perf004FontPreloadCrossorigin,
|
|
998
1223
|
runRules,
|
|
999
1224
|
safeHref,
|
|
1000
1225
|
scoreBand,
|
|
@@ -1009,5 +1234,11 @@ export {
|
|
|
1009
1234
|
seo007Sitemap,
|
|
1010
1235
|
seo008JsonLd,
|
|
1011
1236
|
seo009HtmlLang,
|
|
1237
|
+
seo010Indexability,
|
|
1238
|
+
seo011TwitterCard,
|
|
1239
|
+
seo012OgDescription,
|
|
1240
|
+
seo013OgUrl,
|
|
1241
|
+
seo014Viewport,
|
|
1242
|
+
seo015SitemapInRobots,
|
|
1012
1243
|
summarize
|
|
1013
1244
|
};
|