unhead 3.0.4 → 3.1.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/client.d.mts +5 -5
- package/dist/client.d.ts +5 -5
- package/dist/index.d.mts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/legacy.d.mts +3 -3
- package/dist/legacy.d.ts +3 -3
- package/dist/parser.d.mts +1 -1
- package/dist/parser.d.ts +1 -1
- package/dist/plugins.d.mts +6 -33
- package/dist/plugins.d.ts +6 -33
- package/dist/plugins.mjs +35 -199
- package/dist/scripts.d.mts +4 -4
- package/dist/scripts.d.ts +4 -4
- package/dist/server.d.mts +4 -4
- package/dist/server.d.ts +4 -4
- package/dist/shared/{unhead.CHsGwxB0.d.ts → unhead.-D8hRpkn.d.ts} +2 -2
- package/dist/shared/{unhead.BfINO8Du.d.mts → unhead.B2jfOxG1.d.mts} +2 -2
- package/dist/shared/{unhead.D91IvQ_J.d.mts → unhead.B7bBMqva.d.mts} +1 -1
- package/dist/shared/{unhead.DCXRrsx_.d.ts → unhead.BRwJvCZb.d.ts} +2 -2
- package/dist/shared/{unhead.LatFr5s0.d.mts → unhead.BX9134DF.d.mts} +1 -1
- package/dist/shared/{unhead.m4TuRTvV.d.mts → unhead.BoZ-Ul8T.d.mts} +1 -1
- package/dist/shared/{unhead.DvqMLXpO.d.mts → unhead.ClE7lB2Z.d.mts} +1 -1
- package/dist/shared/{unhead.DoIxTwSF.d.ts → unhead.CqVawd25.d.ts} +1 -1
- package/dist/shared/{unhead.Bbk6Q9rK.d.mts → unhead.Cv5yrrUd.d.mts} +2 -2
- package/dist/shared/{unhead.CGloe9Qc.d.mts → unhead.Dc5Pn4qI.d.mts} +2 -2
- package/dist/shared/{unhead.9aHQrEvB.d.ts → unhead.DdarjSpi.d.ts} +2 -2
- package/dist/shared/{unhead.BcynQ3qJ.d.mts → unhead.DeoGMp34.d.mts} +1 -1
- package/dist/shared/{unhead.BcynQ3qJ.d.ts → unhead.DeoGMp34.d.ts} +1 -1
- package/dist/shared/unhead.Dl_lRDXb.d.mts +38 -0
- package/dist/shared/unhead.Dl_lRDXb.d.ts +38 -0
- package/dist/shared/{unhead.Ogog_XUX.d.ts → unhead.cXt2Dgt5.d.ts} +1 -1
- package/dist/shared/unhead.ebqUBTt1.mjs +513 -0
- package/dist/shared/{unhead.BmdY3z1V.d.ts → unhead.gui9LmZS.d.ts} +1 -1
- package/dist/shared/{unhead.CvqH1vcy.d.ts → unhead.q49lHHFN.d.ts} +1 -1
- package/dist/stream/client.d.mts +24 -5
- package/dist/stream/client.d.ts +24 -5
- package/dist/stream/server.d.mts +9 -6
- package/dist/stream/server.d.ts +9 -6
- package/dist/stream/server.mjs +15 -4
- package/dist/stream/unplugin.d.mts +81 -0
- package/dist/stream/unplugin.d.ts +81 -0
- package/dist/stream/unplugin.mjs +166 -0
- package/dist/stream/vite.d.mts +19 -27
- package/dist/stream/vite.d.ts +19 -27
- package/dist/stream/vite.mjs +6 -78
- package/dist/types.d.mts +6 -6
- package/dist/types.d.ts +6 -6
- package/dist/utils.d.mts +2 -2
- package/dist/utils.d.ts +2 -2
- package/dist/validate.d.mts +236 -0
- package/dist/validate.d.ts +236 -0
- package/dist/validate.mjs +49 -0
- package/package.json +20 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { HookableCore } from 'hookable';
|
|
2
|
-
import { U as Unhead, C as ClientHeadHooks, c as CreateClientHeadOptions } from './unhead.
|
|
3
|
-
import {
|
|
2
|
+
import { U as Unhead, C as ClientHeadHooks, c as CreateClientHeadOptions } from './unhead.BoZ-Ul8T.mjs';
|
|
3
|
+
import { aw as ResolvableHead } from './unhead.DeoGMp34.mjs';
|
|
4
4
|
|
|
5
5
|
interface ClientUnhead<T = ResolvableHead> extends Unhead<T, boolean> {
|
|
6
6
|
hooks: HookableCore<ClientHeadHooks>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { p as HeadRenderer, d as CreateHeadOptions, U as Unhead } from './unhead.
|
|
2
|
-
import {
|
|
1
|
+
import { p as HeadRenderer, d as CreateHeadOptions, U as Unhead } from './unhead.BoZ-Ul8T.mjs';
|
|
2
|
+
import { aw as ResolvableHead } from './unhead.DeoGMp34.mjs';
|
|
3
3
|
|
|
4
4
|
declare function createUnhead<T = ResolvableHead, R = unknown>(renderer: HeadRenderer<R>, resolvedOptions?: CreateHeadOptions): Unhead<T, R>;
|
|
5
5
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { HookableCore } from 'hookable';
|
|
2
|
-
import { U as Unhead, s as SSRHeadPayload, v as ServerHeadHooks, e as CreateServerHeadOptions } from './unhead.
|
|
3
|
-
import {
|
|
2
|
+
import { U as Unhead, s as SSRHeadPayload, v as ServerHeadHooks, e as CreateServerHeadOptions } from './unhead.gui9LmZS.js';
|
|
3
|
+
import { aw as ResolvableHead } from './unhead.DeoGMp34.js';
|
|
4
4
|
|
|
5
5
|
interface ServerUnhead<T = ResolvableHead> extends Unhead<T, SSRHeadPayload> {
|
|
6
6
|
hooks: HookableCore<ServerHeadHooks>;
|
|
@@ -2942,4 +2942,4 @@ interface HeadTag extends TagPriority, TagPosition, ResolvesDuplicates, HasTempl
|
|
|
2942
2942
|
}
|
|
2943
2943
|
type HeadTagKeys = (keyof HeadTag)[];
|
|
2944
2944
|
|
|
2945
|
-
export type {
|
|
2945
|
+
export type { MaskIconLink as $, AlternateFeedLink as A, Booleanable as B, CanonicalLink as C, DataKeys as D, ExpectLink as E, FaviconLink as F, GenericLink as G, HasTemplateParams as H, HubLink as I, IconLink as J, ImportMapConfig as K, ImportMapScript as L, InferLink as M, InferScript as N, InlineModuleScript as O, InlineScript as P, InnerContent as Q, InnerContentVal as R, InternalTagKey as S, JsonLdScript as T, KnownLinkRel as U, KnownScriptType as V, LicenseLink as W, Link as X, LinkBase as Y, LinkHttpEvents as Z, ManifestLink as _, AlternateLanguageLink as a, TemplateParamsAugmentations as a$, MaybeArray as a0, MaybeEventFnHandlers as a1, MeLink as a2, Meta as a3, MetaBase as a4, MetaFlat as a5, MetaGeneric as a6, MetaNames as a7, MetaProperties as a8, ModuleScript as a9, ResolvableNoscript as aA, ResolvableProperties as aB, ResolvableScript as aC, ResolvableStyle as aD, ResolvableTemplateParams as aE, ResolvableTitle as aF, ResolvableTitleTemplate as aG, ResolvableUnion as aH, ResolvableValue as aI, ResolvesDuplicates as aJ, SchemaAugmentations as aK, Script as aL, ScriptBase as aM, ScriptHttpEvents as aN, SearchLink as aO, SerializableHead as aP, SitemapLink as aQ, SpeculationRules as aR, SpeculationRulesScript as aS, StringInnerContent as aT, Stringable as aU, StylesheetLink as aV, TagKey as aW, TagPosition as aX, TagPriority as aY, TagUserProperties as aZ, TemplateParams as a_, ModulepreloadLink as aa, NameMeta as ab, Never as ac, NextLink as ad, PingbackLink as ae, PreconnectLink as af, PrefetchLink as ag, PreloadFontLink as ah, PreloadImageLink as ai, PreloadLink as aj, PreloadLinkBase as ak, PreloadOtherLink as al, PreloadScriptLink as am, PreloadStyleLink as an, PrerenderLink as ao, PrevLink as ap, PrivacyPolicyLink as aq, ProcessesTemplateParams as ar, PropertyMeta as as, RawInput as at, ResolvableBase as au, ResolvableBodyAttributes as av, ResolvableHead as aw, ResolvableHtmlAttributes as ax, ResolvableLink as ay, ResolvableMeta as az, AlternateLink as b, TermsOfServiceLink as b0, UnheadBodyAttributesWithoutEvents as b1, UnheadHtmlAttributes as b2, UnheadMeta as b3, UseHeadInput as b4, UseSeoMetaInput as b5, ValidTagPositions as b6, WebmentionLink as b7, AlternateMediaLink as c, AlternateStylesheetLink as d, AmpHtmlLink as e, AppleTouchIconLink as f, AppleTouchStartupImageLink as g, ApplicationJsonScript as h, Arrayable as i, AuthorLink as j, BareAlternateLink as k, BodyAttributesWithoutEvents as l, BodyEvents as m, CharsetMeta as n, CompressionDictionaryLink as o, DeepReadonly as p, DeepResolvableProperties as q, DnsPrefetchLink as r, ExternalScript as s, GenericScript as t, GlobalAttributes as u, HeadTag as v, HeadTagKeys as w, HelpLink as x, HttpEquivMeta as y, HttpEventAttributes as z };
|
|
@@ -2942,4 +2942,4 @@ interface HeadTag extends TagPriority, TagPosition, ResolvesDuplicates, HasTempl
|
|
|
2942
2942
|
}
|
|
2943
2943
|
type HeadTagKeys = (keyof HeadTag)[];
|
|
2944
2944
|
|
|
2945
|
-
export type {
|
|
2945
|
+
export type { MaskIconLink as $, AlternateFeedLink as A, Booleanable as B, CanonicalLink as C, DataKeys as D, ExpectLink as E, FaviconLink as F, GenericLink as G, HasTemplateParams as H, HubLink as I, IconLink as J, ImportMapConfig as K, ImportMapScript as L, InferLink as M, InferScript as N, InlineModuleScript as O, InlineScript as P, InnerContent as Q, InnerContentVal as R, InternalTagKey as S, JsonLdScript as T, KnownLinkRel as U, KnownScriptType as V, LicenseLink as W, Link as X, LinkBase as Y, LinkHttpEvents as Z, ManifestLink as _, AlternateLanguageLink as a, TemplateParamsAugmentations as a$, MaybeArray as a0, MaybeEventFnHandlers as a1, MeLink as a2, Meta as a3, MetaBase as a4, MetaFlat as a5, MetaGeneric as a6, MetaNames as a7, MetaProperties as a8, ModuleScript as a9, ResolvableNoscript as aA, ResolvableProperties as aB, ResolvableScript as aC, ResolvableStyle as aD, ResolvableTemplateParams as aE, ResolvableTitle as aF, ResolvableTitleTemplate as aG, ResolvableUnion as aH, ResolvableValue as aI, ResolvesDuplicates as aJ, SchemaAugmentations as aK, Script as aL, ScriptBase as aM, ScriptHttpEvents as aN, SearchLink as aO, SerializableHead as aP, SitemapLink as aQ, SpeculationRules as aR, SpeculationRulesScript as aS, StringInnerContent as aT, Stringable as aU, StylesheetLink as aV, TagKey as aW, TagPosition as aX, TagPriority as aY, TagUserProperties as aZ, TemplateParams as a_, ModulepreloadLink as aa, NameMeta as ab, Never as ac, NextLink as ad, PingbackLink as ae, PreconnectLink as af, PrefetchLink as ag, PreloadFontLink as ah, PreloadImageLink as ai, PreloadLink as aj, PreloadLinkBase as ak, PreloadOtherLink as al, PreloadScriptLink as am, PreloadStyleLink as an, PrerenderLink as ao, PrevLink as ap, PrivacyPolicyLink as aq, ProcessesTemplateParams as ar, PropertyMeta as as, RawInput as at, ResolvableBase as au, ResolvableBodyAttributes as av, ResolvableHead as aw, ResolvableHtmlAttributes as ax, ResolvableLink as ay, ResolvableMeta as az, AlternateLink as b, TermsOfServiceLink as b0, UnheadBodyAttributesWithoutEvents as b1, UnheadHtmlAttributes as b2, UnheadMeta as b3, UseHeadInput as b4, UseSeoMetaInput as b5, ValidTagPositions as b6, WebmentionLink as b7, AlternateMediaLink as c, AlternateStylesheetLink as d, AmpHtmlLink as e, AppleTouchIconLink as f, AppleTouchStartupImageLink as g, ApplicationJsonScript as h, Arrayable as i, AuthorLink as j, BareAlternateLink as k, BodyAttributesWithoutEvents as l, BodyEvents as m, CharsetMeta as n, CompressionDictionaryLink as o, DeepReadonly as p, DeepResolvableProperties as q, DnsPrefetchLink as r, ExternalScript as s, GenericScript as t, GlobalAttributes as u, HeadTag as v, HeadTagKeys as w, HelpLink as x, HttpEquivMeta as y, HttpEventAttributes as z };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
type RuleSeverity = 'warn' | 'info' | 'off';
|
|
2
|
+
/**
|
|
3
|
+
* Canonical list of every validation rule ID. Mirrored by the `ValidationRuleId`
|
|
4
|
+
* union below; both are kept in sync by hand. Consumers (devtools UI, eslint
|
|
5
|
+
* plugin docs) can iterate this array to enumerate all rules at runtime.
|
|
6
|
+
*/
|
|
7
|
+
declare const VALIDATION_RULE_IDS: readonly ["canonical-og-url-mismatch", "charset-not-early", "defer-on-module-script", "deprecated-option-mode", "deprecated-prop-body", "deprecated-prop-children", "deprecated-prop-hid-vmid", "duplicate-resource-hint", "empty-meta-content", "empty-title", "html-in-title", "inline-script-size", "inline-style-size", "meta-beyond-1mb", "missing-alias-sorting-plugin", "missing-description", "missing-template-params-plugin", "missing-title", "non-absolute-canonical", "non-absolute-og-url", "numeric-tag-priority", "og-image-missing-dimensions", "og-missing-description", "og-missing-title", "possible-typo", "preconnect-missing-crossorigin", "prefer-define-helpers", "prefetch-preload-conflict", "preload-async-defer-conflict", "preload-fetchpriority-conflict", "preload-font-crossorigin", "preload-missing-as", "preload-not-modulepreload", "redundant-dns-prefetch", "render-blocking-script", "robots-conflict", "script-src-with-content", "too-many-fetchpriority-high", "too-many-preconnects", "too-many-preloads", "twitter-handle-missing-at", "unresolved-template-param", "viewport-user-scalable"];
|
|
8
|
+
type ValidationRuleId = typeof VALIDATION_RULE_IDS[number];
|
|
9
|
+
interface ValidationRuleOptions {
|
|
10
|
+
'charset-not-early': {
|
|
11
|
+
maxPosition: number;
|
|
12
|
+
};
|
|
13
|
+
'inline-script-size': {
|
|
14
|
+
maxKB: number;
|
|
15
|
+
};
|
|
16
|
+
'inline-style-size': {
|
|
17
|
+
maxKB: number;
|
|
18
|
+
};
|
|
19
|
+
'meta-beyond-1mb': {
|
|
20
|
+
maxBytes: number;
|
|
21
|
+
};
|
|
22
|
+
'too-many-fetchpriority-high': {
|
|
23
|
+
max: number;
|
|
24
|
+
};
|
|
25
|
+
'too-many-preloads': {
|
|
26
|
+
max: number;
|
|
27
|
+
};
|
|
28
|
+
'too-many-preconnects': {
|
|
29
|
+
max: number;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
type RuleConfig<Id extends ValidationRuleId> = Id extends keyof ValidationRuleOptions ? RuleSeverity | [severity: RuleSeverity, options: ValidationRuleOptions[Id]] : RuleSeverity;
|
|
33
|
+
type RulesConfig = {
|
|
34
|
+
[K in ValidationRuleId]?: RuleConfig<K>;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export { VALIDATION_RULE_IDS as d };
|
|
38
|
+
export type { RulesConfig as R, ValidationRuleId as V, RuleConfig as a, RuleSeverity as b, ValidationRuleOptions as c };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
type RuleSeverity = 'warn' | 'info' | 'off';
|
|
2
|
+
/**
|
|
3
|
+
* Canonical list of every validation rule ID. Mirrored by the `ValidationRuleId`
|
|
4
|
+
* union below; both are kept in sync by hand. Consumers (devtools UI, eslint
|
|
5
|
+
* plugin docs) can iterate this array to enumerate all rules at runtime.
|
|
6
|
+
*/
|
|
7
|
+
declare const VALIDATION_RULE_IDS: readonly ["canonical-og-url-mismatch", "charset-not-early", "defer-on-module-script", "deprecated-option-mode", "deprecated-prop-body", "deprecated-prop-children", "deprecated-prop-hid-vmid", "duplicate-resource-hint", "empty-meta-content", "empty-title", "html-in-title", "inline-script-size", "inline-style-size", "meta-beyond-1mb", "missing-alias-sorting-plugin", "missing-description", "missing-template-params-plugin", "missing-title", "non-absolute-canonical", "non-absolute-og-url", "numeric-tag-priority", "og-image-missing-dimensions", "og-missing-description", "og-missing-title", "possible-typo", "preconnect-missing-crossorigin", "prefer-define-helpers", "prefetch-preload-conflict", "preload-async-defer-conflict", "preload-fetchpriority-conflict", "preload-font-crossorigin", "preload-missing-as", "preload-not-modulepreload", "redundant-dns-prefetch", "render-blocking-script", "robots-conflict", "script-src-with-content", "too-many-fetchpriority-high", "too-many-preconnects", "too-many-preloads", "twitter-handle-missing-at", "unresolved-template-param", "viewport-user-scalable"];
|
|
8
|
+
type ValidationRuleId = typeof VALIDATION_RULE_IDS[number];
|
|
9
|
+
interface ValidationRuleOptions {
|
|
10
|
+
'charset-not-early': {
|
|
11
|
+
maxPosition: number;
|
|
12
|
+
};
|
|
13
|
+
'inline-script-size': {
|
|
14
|
+
maxKB: number;
|
|
15
|
+
};
|
|
16
|
+
'inline-style-size': {
|
|
17
|
+
maxKB: number;
|
|
18
|
+
};
|
|
19
|
+
'meta-beyond-1mb': {
|
|
20
|
+
maxBytes: number;
|
|
21
|
+
};
|
|
22
|
+
'too-many-fetchpriority-high': {
|
|
23
|
+
max: number;
|
|
24
|
+
};
|
|
25
|
+
'too-many-preloads': {
|
|
26
|
+
max: number;
|
|
27
|
+
};
|
|
28
|
+
'too-many-preconnects': {
|
|
29
|
+
max: number;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
type RuleConfig<Id extends ValidationRuleId> = Id extends keyof ValidationRuleOptions ? RuleSeverity | [severity: RuleSeverity, options: ValidationRuleOptions[Id]] : RuleSeverity;
|
|
33
|
+
type RulesConfig = {
|
|
34
|
+
[K in ValidationRuleId]?: RuleConfig<K>;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export { VALIDATION_RULE_IDS as d };
|
|
38
|
+
export type { RulesConfig as R, ValidationRuleId as V, RuleConfig as a, RuleSeverity as b, ValidationRuleOptions as c };
|
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
const URL_META_KEYS = /* @__PURE__ */ new Set([
|
|
2
|
+
"og:url",
|
|
3
|
+
"og:image",
|
|
4
|
+
"og:image:url",
|
|
5
|
+
"og:image:secure_url",
|
|
6
|
+
"og:video",
|
|
7
|
+
"og:video:url",
|
|
8
|
+
"og:video:secure_url",
|
|
9
|
+
"og:audio",
|
|
10
|
+
"og:audio:url",
|
|
11
|
+
"og:audio:secure_url",
|
|
12
|
+
"twitter:image",
|
|
13
|
+
"twitter:image:src",
|
|
14
|
+
"twitter:player",
|
|
15
|
+
"twitter:player:stream"
|
|
16
|
+
]);
|
|
17
|
+
const KNOWN_META_PROPERTIES = /* @__PURE__ */ new Set([
|
|
18
|
+
"article:author",
|
|
19
|
+
"article:expiration_time",
|
|
20
|
+
"article:modified_time",
|
|
21
|
+
"article:published_time",
|
|
22
|
+
"article:section",
|
|
23
|
+
"article:tag",
|
|
24
|
+
"book:author",
|
|
25
|
+
"book:isbn",
|
|
26
|
+
"book:release_date",
|
|
27
|
+
"book:tag",
|
|
28
|
+
"fb:app_id",
|
|
29
|
+
"og:audio",
|
|
30
|
+
"og:audio:secure_url",
|
|
31
|
+
"og:audio:type",
|
|
32
|
+
"og:audio:url",
|
|
33
|
+
"og:description",
|
|
34
|
+
"og:determiner",
|
|
35
|
+
"og:image",
|
|
36
|
+
"og:image:alt",
|
|
37
|
+
"og:image:height",
|
|
38
|
+
"og:image:secure_url",
|
|
39
|
+
"og:image:type",
|
|
40
|
+
"og:image:url",
|
|
41
|
+
"og:image:width",
|
|
42
|
+
"og:locale",
|
|
43
|
+
"og:locale:alternate",
|
|
44
|
+
"og:site_name",
|
|
45
|
+
"og:title",
|
|
46
|
+
"og:type",
|
|
47
|
+
"og:url",
|
|
48
|
+
"og:video",
|
|
49
|
+
"og:video:alt",
|
|
50
|
+
"og:video:duration",
|
|
51
|
+
"og:video:height",
|
|
52
|
+
"og:video:secure_url",
|
|
53
|
+
"og:video:type",
|
|
54
|
+
"og:video:url",
|
|
55
|
+
"og:video:width",
|
|
56
|
+
"profile:first_name",
|
|
57
|
+
"profile:gender",
|
|
58
|
+
"profile:last_name",
|
|
59
|
+
"profile:username"
|
|
60
|
+
]);
|
|
61
|
+
const KNOWN_META_NAMES = /* @__PURE__ */ new Set([
|
|
62
|
+
"apple-itunes-app",
|
|
63
|
+
"apple-mobile-web-app-capable",
|
|
64
|
+
"apple-mobile-web-app-status-bar-style",
|
|
65
|
+
"apple-mobile-web-app-title",
|
|
66
|
+
"application-name",
|
|
67
|
+
"author",
|
|
68
|
+
"color-scheme",
|
|
69
|
+
"creator",
|
|
70
|
+
"description",
|
|
71
|
+
"fb:app_id",
|
|
72
|
+
"fediverse:creator",
|
|
73
|
+
"format-detection",
|
|
74
|
+
"generator",
|
|
75
|
+
"google-site-verification",
|
|
76
|
+
"google",
|
|
77
|
+
"googlebot",
|
|
78
|
+
"keywords",
|
|
79
|
+
"mobile-web-app-capable",
|
|
80
|
+
"msapplication-config",
|
|
81
|
+
"msapplication-tilecolor",
|
|
82
|
+
"msapplication-tileimage",
|
|
83
|
+
"publisher",
|
|
84
|
+
"rating",
|
|
85
|
+
"referrer",
|
|
86
|
+
"robots",
|
|
87
|
+
"theme-color",
|
|
88
|
+
"viewport",
|
|
89
|
+
"twitter:app:id:googleplay",
|
|
90
|
+
"twitter:app:id:ipad",
|
|
91
|
+
"twitter:app:id:iphone",
|
|
92
|
+
"twitter:app:name:googleplay",
|
|
93
|
+
"twitter:app:name:ipad",
|
|
94
|
+
"twitter:app:name:iphone",
|
|
95
|
+
"twitter:app:url:googleplay",
|
|
96
|
+
"twitter:app:url:ipad",
|
|
97
|
+
"twitter:app:url:iphone",
|
|
98
|
+
"twitter:card",
|
|
99
|
+
"twitter:creator",
|
|
100
|
+
"twitter:creator:id",
|
|
101
|
+
"twitter:data:1",
|
|
102
|
+
"twitter:data:2",
|
|
103
|
+
"twitter:description",
|
|
104
|
+
"twitter:image",
|
|
105
|
+
"twitter:image:alt",
|
|
106
|
+
"twitter:label:1",
|
|
107
|
+
"twitter:label:2",
|
|
108
|
+
"twitter:player",
|
|
109
|
+
"twitter:player:height",
|
|
110
|
+
"twitter:player:stream",
|
|
111
|
+
"twitter:player:width",
|
|
112
|
+
"twitter:site",
|
|
113
|
+
"twitter:site:id",
|
|
114
|
+
"twitter:title"
|
|
115
|
+
]);
|
|
116
|
+
const TAG_PRIORITY_ALIASES = ["critical", "high", "low"];
|
|
117
|
+
const DEPRECATED_PROPS = {
|
|
118
|
+
children: { replacement: "innerHTML", ruleId: "deprecated-prop-children" },
|
|
119
|
+
hid: { replacement: "key", ruleId: "deprecated-prop-hid-vmid" },
|
|
120
|
+
vmid: { replacement: "key", ruleId: "deprecated-prop-hid-vmid" },
|
|
121
|
+
body: { replacement: "tagPosition: 'bodyClose'", ruleId: "deprecated-prop-body" }
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
function levenshtein(a, b) {
|
|
125
|
+
const m = a.length;
|
|
126
|
+
const n = b.length;
|
|
127
|
+
const d = Array.from({ length: n + 1 }, (_, i) => i);
|
|
128
|
+
for (let i = 1; i <= m; i++) {
|
|
129
|
+
let prev = i - 1;
|
|
130
|
+
d[0] = i;
|
|
131
|
+
for (let j = 1; j <= n; j++) {
|
|
132
|
+
const tmp = d[j];
|
|
133
|
+
d[j] = a[i - 1] === b[j - 1] ? prev : 1 + Math.min(prev, d[j], d[j - 1]);
|
|
134
|
+
prev = tmp;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return d[n];
|
|
138
|
+
}
|
|
139
|
+
function findClosestMatch(value, knownSet) {
|
|
140
|
+
const threshold = value.length <= 8 ? 2 : 3;
|
|
141
|
+
let best;
|
|
142
|
+
let bestDist = threshold + 1;
|
|
143
|
+
for (const known of knownSet) {
|
|
144
|
+
if (Math.abs(known.length - value.length) > threshold)
|
|
145
|
+
continue;
|
|
146
|
+
const dist = levenshtein(value, known);
|
|
147
|
+
if (dist < bestDist) {
|
|
148
|
+
bestDist = dist;
|
|
149
|
+
best = known;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return best;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const emptyMetaContent = (tag) => {
|
|
156
|
+
if (tag.tagType !== "meta")
|
|
157
|
+
return [];
|
|
158
|
+
if (!tag.keys.has("content"))
|
|
159
|
+
return [];
|
|
160
|
+
if (tag.props.content !== "")
|
|
161
|
+
return [];
|
|
162
|
+
const key = typeof tag.props.name === "string" && tag.props.name || typeof tag.props.property === "string" && tag.props.property || "meta";
|
|
163
|
+
const diag = {
|
|
164
|
+
ruleId: "empty-meta-content",
|
|
165
|
+
message: `Meta tag "${key}" has empty content.`,
|
|
166
|
+
at: { kind: "prop", key: "content" }
|
|
167
|
+
};
|
|
168
|
+
return [diag];
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const noDeprecatedProps = (tag) => {
|
|
172
|
+
const out = [];
|
|
173
|
+
for (const key of tag.keys) {
|
|
174
|
+
if (!(key in DEPRECATED_PROPS))
|
|
175
|
+
continue;
|
|
176
|
+
const { replacement } = DEPRECATED_PROPS[key];
|
|
177
|
+
if (key === "body") {
|
|
178
|
+
if (tag.props.body !== true)
|
|
179
|
+
continue;
|
|
180
|
+
const fix2 = tag.keys.has("tagPosition") ? void 0 : { type: "replace-prop", key: "body", newSource: `tagPosition: 'bodyClose'` };
|
|
181
|
+
out.push({
|
|
182
|
+
ruleId: "deprecated-prop-body",
|
|
183
|
+
message: `"body" was removed in v3 of unhead. Use "${replacement}" instead.`,
|
|
184
|
+
at: { kind: "prop", key },
|
|
185
|
+
fix: fix2
|
|
186
|
+
});
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
const newKey = key === "children" ? "innerHTML" : "key";
|
|
190
|
+
const fix = tag.keys.has(newKey) ? void 0 : { type: "rename-prop", key, newKey };
|
|
191
|
+
out.push({
|
|
192
|
+
ruleId: key === "children" ? "deprecated-prop-children" : "deprecated-prop-hid-vmid",
|
|
193
|
+
message: `"${key}" was removed in v3 of unhead. Use "${replacement}" instead.`,
|
|
194
|
+
at: { kind: "prop", key },
|
|
195
|
+
fix
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
return out;
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const HTML_CHARS_RE = /[<>]/;
|
|
202
|
+
const noHtmlInTitle = (input) => {
|
|
203
|
+
const title = input.props.title;
|
|
204
|
+
if (typeof title !== "string" || !HTML_CHARS_RE.test(title))
|
|
205
|
+
return [];
|
|
206
|
+
const diag = {
|
|
207
|
+
ruleId: "html-in-title",
|
|
208
|
+
message: `Title contains HTML characters which will be escaped, not rendered: "${title}".`,
|
|
209
|
+
at: { kind: "prop-value", key: "title" }
|
|
210
|
+
};
|
|
211
|
+
return [diag];
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const OG_PREFIX_RE = /^(?:og|article|book|profile|fb):/;
|
|
215
|
+
const noUnknownMeta = (tag) => {
|
|
216
|
+
if (tag.tagType !== "meta")
|
|
217
|
+
return [];
|
|
218
|
+
const out = [];
|
|
219
|
+
const property = tag.props.property;
|
|
220
|
+
if (typeof property === "string" && !KNOWN_META_PROPERTIES.has(property) && OG_PREFIX_RE.test(property)) {
|
|
221
|
+
const suggestion = findClosestMatch(property, KNOWN_META_PROPERTIES);
|
|
222
|
+
if (suggestion) {
|
|
223
|
+
out.push({
|
|
224
|
+
ruleId: "possible-typo",
|
|
225
|
+
message: `Unknown meta property "${property}". Did you mean "${suggestion}"?`,
|
|
226
|
+
at: { kind: "prop-value", key: "property" },
|
|
227
|
+
fix: { type: "replace-prop-value", key: "property", newSource: `'${suggestion}'` }
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
const name = tag.props.name;
|
|
232
|
+
if (typeof name === "string") {
|
|
233
|
+
const lower = name.toLowerCase();
|
|
234
|
+
if (!KNOWN_META_NAMES.has(lower) && (lower.startsWith("twitter:") || lower.startsWith("fediverse:") || !lower.includes(":"))) {
|
|
235
|
+
const suggestion = findClosestMatch(lower, KNOWN_META_NAMES);
|
|
236
|
+
if (suggestion) {
|
|
237
|
+
out.push({
|
|
238
|
+
ruleId: "possible-typo",
|
|
239
|
+
message: `Unknown meta name "${name}". Did you mean "${suggestion}"?`,
|
|
240
|
+
at: { kind: "prop-value", key: "name" },
|
|
241
|
+
fix: { type: "replace-prop-value", key: "name", newSource: `'${suggestion}'` }
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return out;
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
function isAbsolute(url) {
|
|
250
|
+
return url.startsWith("http://") || url.startsWith("https://");
|
|
251
|
+
}
|
|
252
|
+
const nonAbsoluteCanonical = (tag) => {
|
|
253
|
+
if (tag.tagType !== "link")
|
|
254
|
+
return [];
|
|
255
|
+
if (tag.props.rel !== "canonical")
|
|
256
|
+
return [];
|
|
257
|
+
const href = tag.props.href;
|
|
258
|
+
if (typeof href !== "string")
|
|
259
|
+
return [];
|
|
260
|
+
if (isAbsolute(href))
|
|
261
|
+
return [];
|
|
262
|
+
const diag = {
|
|
263
|
+
ruleId: "non-absolute-canonical",
|
|
264
|
+
message: `Canonical URL should be absolute, received "${href}".`,
|
|
265
|
+
at: { kind: "prop-value", key: "href" }
|
|
266
|
+
};
|
|
267
|
+
return [diag];
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const ALIASES = ["critical", "high", "low"];
|
|
271
|
+
const numericTagPriority = (tag) => {
|
|
272
|
+
const value = tag.props.tagPriority;
|
|
273
|
+
if (typeof value !== "number")
|
|
274
|
+
return [];
|
|
275
|
+
const diag = {
|
|
276
|
+
ruleId: "numeric-tag-priority",
|
|
277
|
+
message: `Numeric tagPriority (${value}) is brittle. Prefer an alias ('critical' | 'high' | 'low') or 'before:<key>' / 'after:<key>'.`,
|
|
278
|
+
at: { kind: "prop-value", key: "tagPriority" },
|
|
279
|
+
suggestions: ALIASES.map((alias) => ({
|
|
280
|
+
message: `Replace with '${alias}'`,
|
|
281
|
+
fix: { type: "replace-prop-value", key: "tagPriority", newSource: `'${alias}'` }
|
|
282
|
+
}))
|
|
283
|
+
};
|
|
284
|
+
return [diag];
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
const TAG_TO_HELPER = {
|
|
288
|
+
link: "defineLink",
|
|
289
|
+
script: "defineScript"
|
|
290
|
+
};
|
|
291
|
+
const preferDefineHelpers = (tag, ctx) => {
|
|
292
|
+
if (!tag.inArray)
|
|
293
|
+
return [];
|
|
294
|
+
const helper = TAG_TO_HELPER[tag.tagType];
|
|
295
|
+
if (!helper)
|
|
296
|
+
return [];
|
|
297
|
+
const localName = ctx?.importedHelpers?.get(helper);
|
|
298
|
+
const imported = localName !== void 0;
|
|
299
|
+
const fix = { type: "wrap-tag", wrapWith: localName ?? helper };
|
|
300
|
+
const diag = {
|
|
301
|
+
ruleId: "prefer-define-helpers",
|
|
302
|
+
message: `Wrap this ${tag.tagType} entry in \`${helper}()\` so unhead can narrow its type.`,
|
|
303
|
+
fix: imported ? fix : void 0,
|
|
304
|
+
suggestions: imported ? void 0 : [{ message: `Wrap in \`${helper}()\` (you may need to import it).`, fix: { type: "wrap-tag", wrapWith: helper } }]
|
|
305
|
+
};
|
|
306
|
+
return [diag];
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
const preloadMissingAs = (tag) => {
|
|
310
|
+
if (tag.tagType !== "link")
|
|
311
|
+
return [];
|
|
312
|
+
if (tag.props.rel !== "preload")
|
|
313
|
+
return [];
|
|
314
|
+
if (tag.keys.has("as"))
|
|
315
|
+
return [];
|
|
316
|
+
const diag = {
|
|
317
|
+
ruleId: "preload-missing-as",
|
|
318
|
+
message: 'Preload link is missing the required "as" attribute.'
|
|
319
|
+
};
|
|
320
|
+
return [diag];
|
|
321
|
+
};
|
|
322
|
+
const preloadFontCrossorigin = (tag) => {
|
|
323
|
+
if (tag.tagType !== "link")
|
|
324
|
+
return [];
|
|
325
|
+
if (tag.props.rel !== "preload")
|
|
326
|
+
return [];
|
|
327
|
+
if (tag.props.as !== "font")
|
|
328
|
+
return [];
|
|
329
|
+
if (tag.keys.has("crossorigin"))
|
|
330
|
+
return [];
|
|
331
|
+
const diag = {
|
|
332
|
+
ruleId: "preload-font-crossorigin",
|
|
333
|
+
message: 'Font preload requires "crossorigin" \u2014 without it the font will be fetched twice.',
|
|
334
|
+
fix: { type: "insert-after-prop", afterKey: "as", insert: `, crossorigin: 'anonymous'` }
|
|
335
|
+
};
|
|
336
|
+
return [diag];
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
const robotsConflict = (tag) => {
|
|
340
|
+
if (tag.tagType !== "meta")
|
|
341
|
+
return [];
|
|
342
|
+
if (tag.props.name !== "robots")
|
|
343
|
+
return [];
|
|
344
|
+
const content = tag.props.content;
|
|
345
|
+
if (typeof content !== "string")
|
|
346
|
+
return [];
|
|
347
|
+
const directives = content.toLowerCase().split(",").map((d) => d.trim());
|
|
348
|
+
const out = [];
|
|
349
|
+
if (directives.includes("index") && directives.includes("noindex")) {
|
|
350
|
+
out.push({
|
|
351
|
+
ruleId: "robots-conflict",
|
|
352
|
+
message: 'Robots meta has conflicting "index" and "noindex" directives.'
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
if (directives.includes("follow") && directives.includes("nofollow")) {
|
|
356
|
+
out.push({
|
|
357
|
+
ruleId: "robots-conflict",
|
|
358
|
+
message: 'Robots meta has conflicting "follow" and "nofollow" directives.'
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
return out;
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
const deferOnModuleScript = (tag) => {
|
|
365
|
+
if (tag.tagType !== "script")
|
|
366
|
+
return [];
|
|
367
|
+
if (tag.props.type !== "module")
|
|
368
|
+
return [];
|
|
369
|
+
if (tag.props.defer !== true)
|
|
370
|
+
return [];
|
|
371
|
+
const diag = {
|
|
372
|
+
ruleId: "defer-on-module-script",
|
|
373
|
+
message: '"defer" is redundant on module scripts. Modules are deferred by default.',
|
|
374
|
+
at: { kind: "prop", key: "defer" },
|
|
375
|
+
fix: { type: "remove-prop", key: "defer" }
|
|
376
|
+
};
|
|
377
|
+
return [diag];
|
|
378
|
+
};
|
|
379
|
+
const scriptSrcWithContent = (tag) => {
|
|
380
|
+
if (tag.tagType !== "script")
|
|
381
|
+
return [];
|
|
382
|
+
if (typeof tag.props.src !== "string")
|
|
383
|
+
return [];
|
|
384
|
+
const hasInner = tag.keys.has("innerHTML") && tag.props.innerHTML !== "" || tag.keys.has("textContent") && tag.props.textContent !== "";
|
|
385
|
+
if (!hasInner)
|
|
386
|
+
return [];
|
|
387
|
+
const diag = {
|
|
388
|
+
ruleId: "script-src-with-content",
|
|
389
|
+
message: 'Script has both "src" and inline content. The browser will ignore the inline content.'
|
|
390
|
+
};
|
|
391
|
+
return [diag];
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
const NUMERIC_RE = /^\d+$/;
|
|
395
|
+
const twitterHandleMissingAt = (tag) => {
|
|
396
|
+
if (tag.tagType !== "meta")
|
|
397
|
+
return [];
|
|
398
|
+
const name = tag.props.name;
|
|
399
|
+
if (name !== "twitter:site" && name !== "twitter:creator")
|
|
400
|
+
return [];
|
|
401
|
+
const content = tag.props.content;
|
|
402
|
+
if (typeof content !== "string")
|
|
403
|
+
return [];
|
|
404
|
+
if (content.startsWith("@") || NUMERIC_RE.test(content))
|
|
405
|
+
return [];
|
|
406
|
+
const fixedSource = JSON.stringify(`@${content}`);
|
|
407
|
+
const diag = {
|
|
408
|
+
ruleId: "twitter-handle-missing-at",
|
|
409
|
+
message: `${name} should start with "@", received "${content}".`,
|
|
410
|
+
at: { kind: "prop-value", key: "content" },
|
|
411
|
+
fix: { type: "replace-prop-value", key: "content", newSource: fixedSource }
|
|
412
|
+
};
|
|
413
|
+
return [diag];
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
const USER_SCALABLE_NO_RE = /user-scalable\s*=\s*(?:no|0|false)(?:[\s,;]|$)/i;
|
|
417
|
+
const MAX_SCALE_RE = /maximum-scale\s*=\s*1(?:\.\d+)?(?:[\s,;]|$)/i;
|
|
418
|
+
const viewportUserScalable = (tag) => {
|
|
419
|
+
if (tag.tagType !== "meta")
|
|
420
|
+
return [];
|
|
421
|
+
if (tag.props.name !== "viewport")
|
|
422
|
+
return [];
|
|
423
|
+
const content = tag.props.content;
|
|
424
|
+
if (typeof content !== "string")
|
|
425
|
+
return [];
|
|
426
|
+
const out = [];
|
|
427
|
+
if (USER_SCALABLE_NO_RE.test(content)) {
|
|
428
|
+
out.push({
|
|
429
|
+
ruleId: "viewport-user-scalable",
|
|
430
|
+
message: 'viewport "user-scalable=no" prevents zooming and harms accessibility.'
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
if (MAX_SCALE_RE.test(content)) {
|
|
434
|
+
out.push({
|
|
435
|
+
ruleId: "viewport-user-scalable",
|
|
436
|
+
message: 'viewport "maximum-scale=1" limits zooming and may harm accessibility.'
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
return out;
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
const TAG_TYPES = /* @__PURE__ */ new Set(["meta", "link", "script", "noscript", "style"]);
|
|
443
|
+
function tagInputFromRuntime(tag) {
|
|
444
|
+
if (!TAG_TYPES.has(tag.tag))
|
|
445
|
+
return void 0;
|
|
446
|
+
const props = {};
|
|
447
|
+
const keys = /* @__PURE__ */ new Set();
|
|
448
|
+
for (const [k, v] of Object.entries(tag.props)) {
|
|
449
|
+
keys.add(k);
|
|
450
|
+
if (v == null) {
|
|
451
|
+
if (tag.tag === "meta" && k === "content")
|
|
452
|
+
props[k] = "";
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
|
|
456
|
+
if (tag.tag === "meta" && k === "name" && typeof v === "string")
|
|
457
|
+
props[k] = v.toLowerCase();
|
|
458
|
+
else
|
|
459
|
+
props[k] = v;
|
|
460
|
+
} else {
|
|
461
|
+
props[k] = String(v);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
if (tag.tag === "script" || tag.tag === "style" || tag.tag === "noscript") {
|
|
465
|
+
if (tag.innerHTML != null && tag.innerHTML !== "")
|
|
466
|
+
keys.add("innerHTML");
|
|
467
|
+
if (tag.textContent != null && tag.textContent !== "")
|
|
468
|
+
keys.add("textContent");
|
|
469
|
+
}
|
|
470
|
+
if (tag.tagPriority != null) {
|
|
471
|
+
keys.add("tagPriority");
|
|
472
|
+
if (typeof tag.tagPriority === "string" || typeof tag.tagPriority === "number")
|
|
473
|
+
props.tagPriority = tag.tagPriority;
|
|
474
|
+
}
|
|
475
|
+
return {
|
|
476
|
+
tagType: tag.tag,
|
|
477
|
+
props,
|
|
478
|
+
keys
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
function titleInputFromRuntime(titleTag) {
|
|
482
|
+
if (titleTag.tag !== "title")
|
|
483
|
+
return void 0;
|
|
484
|
+
const text = titleTag.textContent ?? titleTag.innerHTML ?? "";
|
|
485
|
+
return {
|
|
486
|
+
callee: "runtime",
|
|
487
|
+
props: { title: text },
|
|
488
|
+
keys: /* @__PURE__ */ new Set(["title"])
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const tagPredicates = {
|
|
493
|
+
"defer-on-module-script": deferOnModuleScript,
|
|
494
|
+
"empty-meta-content": emptyMetaContent,
|
|
495
|
+
"no-deprecated-props": noDeprecatedProps,
|
|
496
|
+
"no-unknown-meta": noUnknownMeta,
|
|
497
|
+
"non-absolute-canonical": nonAbsoluteCanonical,
|
|
498
|
+
"numeric-tag-priority": numericTagPriority,
|
|
499
|
+
"preload-font-crossorigin": preloadFontCrossorigin,
|
|
500
|
+
"preload-missing-as": preloadMissingAs,
|
|
501
|
+
"robots-conflict": robotsConflict,
|
|
502
|
+
"script-src-with-content": scriptSrcWithContent,
|
|
503
|
+
"twitter-handle-missing-at": twitterHandleMissingAt,
|
|
504
|
+
"viewport-user-scalable": viewportUserScalable
|
|
505
|
+
};
|
|
506
|
+
const migrationTagPredicates = {
|
|
507
|
+
"prefer-define-helpers": preferDefineHelpers
|
|
508
|
+
};
|
|
509
|
+
const headInputPredicates = {
|
|
510
|
+
"no-html-in-title": noHtmlInTitle
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
export { DEPRECATED_PROPS as D, KNOWN_META_NAMES as K, TAG_PRIORITY_ALIASES as T, URL_META_KEYS as U, KNOWN_META_PROPERTIES as a, noHtmlInTitle as b, noUnknownMeta as c, deferOnModuleScript as d, emptyMetaContent as e, findClosestMatch as f, nonAbsoluteCanonical as g, headInputPredicates as h, numericTagPriority as i, preloadFontCrossorigin as j, preloadMissingAs as k, levenshtein as l, migrationTagPredicates as m, noDeprecatedProps as n, tagPredicates as o, preferDefineHelpers as p, titleInputFromRuntime as q, robotsConflict as r, scriptSrcWithContent as s, tagInputFromRuntime as t, twitterHandleMissingAt as u, viewportUserScalable as v };
|