unhead 3.0.5 → 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/plugins.d.mts +4 -31
- package/dist/plugins.d.ts +4 -31
- package/dist/plugins.mjs +35 -199
- package/dist/shared/unhead.Dl_lRDXb.d.mts +38 -0
- package/dist/shared/unhead.Dl_lRDXb.d.ts +38 -0
- package/dist/shared/unhead.ebqUBTt1.mjs +513 -0
- package/dist/stream/client.d.mts +22 -3
- package/dist/stream/client.d.ts +22 -3
- package/dist/stream/server.d.mts +6 -3
- package/dist/stream/server.d.ts +6 -3
- 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/validate.d.mts +236 -0
- package/dist/validate.d.ts +236 -0
- package/dist/validate.mjs +49 -0
- package/package.json +18 -3
package/dist/plugins.d.mts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { n as HeadPluginInput, U as Unhead, o as HeadPluginOptions } from './shared/unhead.BoZ-Ul8T.mjs';
|
|
2
|
+
import { V as ValidationRuleId, R as RulesConfig } from './shared/unhead.Dl_lRDXb.mjs';
|
|
3
|
+
export { a as RuleConfig, b as RuleSeverity, c as ValidationRuleOptions } from './shared/unhead.Dl_lRDXb.mjs';
|
|
2
4
|
import { v as HeadTag } from './shared/unhead.DeoGMp34.mjs';
|
|
3
5
|
import 'hookable';
|
|
4
6
|
|
|
@@ -115,35 +117,6 @@ declare const SafeInputPlugin: HeadPluginInput;
|
|
|
115
117
|
|
|
116
118
|
declare const TemplateParamsPlugin: HeadPluginInput;
|
|
117
119
|
|
|
118
|
-
type RuleSeverity = 'warn' | 'info' | 'off';
|
|
119
|
-
type ValidationRuleId = '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' | 'og-image-missing-dimensions' | 'og-missing-description' | 'og-missing-title' | 'possible-typo' | 'preconnect-missing-crossorigin' | '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';
|
|
120
|
-
interface ValidationRuleOptions {
|
|
121
|
-
'charset-not-early': {
|
|
122
|
-
maxPosition: number;
|
|
123
|
-
};
|
|
124
|
-
'inline-script-size': {
|
|
125
|
-
maxKB: number;
|
|
126
|
-
};
|
|
127
|
-
'inline-style-size': {
|
|
128
|
-
maxKB: number;
|
|
129
|
-
};
|
|
130
|
-
'meta-beyond-1mb': {
|
|
131
|
-
maxBytes: number;
|
|
132
|
-
};
|
|
133
|
-
'too-many-fetchpriority-high': {
|
|
134
|
-
max: number;
|
|
135
|
-
};
|
|
136
|
-
'too-many-preloads': {
|
|
137
|
-
max: number;
|
|
138
|
-
};
|
|
139
|
-
'too-many-preconnects': {
|
|
140
|
-
max: number;
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
type RuleConfig<Id extends ValidationRuleId> = Id extends keyof ValidationRuleOptions ? RuleSeverity | [severity: RuleSeverity, options: ValidationRuleOptions[Id]] : RuleSeverity;
|
|
144
|
-
type RulesConfig = {
|
|
145
|
-
[K in ValidationRuleId]?: RuleConfig<K>;
|
|
146
|
-
};
|
|
147
120
|
interface HeadValidationRule {
|
|
148
121
|
id: ValidationRuleId;
|
|
149
122
|
message: string;
|
|
@@ -178,5 +151,5 @@ interface ValidatePluginOptions {
|
|
|
178
151
|
}
|
|
179
152
|
declare function ValidatePlugin(options?: ValidatePluginOptions): HeadPluginInput;
|
|
180
153
|
|
|
181
|
-
export { AliasSortingPlugin, CanonicalPlugin, FlatMetaPlugin, InferSeoMetaPlugin, MinifyPlugin, PromisesPlugin, SafeInputPlugin, TemplateParamsPlugin, ValidatePlugin, defineHeadPlugin };
|
|
182
|
-
export type { CanonicalPluginOptions, HeadValidationRule, MinifyPluginOptions,
|
|
154
|
+
export { AliasSortingPlugin, CanonicalPlugin, FlatMetaPlugin, InferSeoMetaPlugin, MinifyPlugin, PromisesPlugin, RulesConfig, SafeInputPlugin, TemplateParamsPlugin, ValidatePlugin, ValidationRuleId, defineHeadPlugin };
|
|
155
|
+
export type { CanonicalPluginOptions, HeadValidationRule, MinifyPluginOptions, ValidatePluginOptions };
|
package/dist/plugins.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { n as HeadPluginInput, U as Unhead, o as HeadPluginOptions } from './shared/unhead.gui9LmZS.js';
|
|
2
|
+
import { V as ValidationRuleId, R as RulesConfig } from './shared/unhead.Dl_lRDXb.js';
|
|
3
|
+
export { a as RuleConfig, b as RuleSeverity, c as ValidationRuleOptions } from './shared/unhead.Dl_lRDXb.js';
|
|
2
4
|
import { v as HeadTag } from './shared/unhead.DeoGMp34.js';
|
|
3
5
|
import 'hookable';
|
|
4
6
|
|
|
@@ -115,35 +117,6 @@ declare const SafeInputPlugin: HeadPluginInput;
|
|
|
115
117
|
|
|
116
118
|
declare const TemplateParamsPlugin: HeadPluginInput;
|
|
117
119
|
|
|
118
|
-
type RuleSeverity = 'warn' | 'info' | 'off';
|
|
119
|
-
type ValidationRuleId = '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' | 'og-image-missing-dimensions' | 'og-missing-description' | 'og-missing-title' | 'possible-typo' | 'preconnect-missing-crossorigin' | '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';
|
|
120
|
-
interface ValidationRuleOptions {
|
|
121
|
-
'charset-not-early': {
|
|
122
|
-
maxPosition: number;
|
|
123
|
-
};
|
|
124
|
-
'inline-script-size': {
|
|
125
|
-
maxKB: number;
|
|
126
|
-
};
|
|
127
|
-
'inline-style-size': {
|
|
128
|
-
maxKB: number;
|
|
129
|
-
};
|
|
130
|
-
'meta-beyond-1mb': {
|
|
131
|
-
maxBytes: number;
|
|
132
|
-
};
|
|
133
|
-
'too-many-fetchpriority-high': {
|
|
134
|
-
max: number;
|
|
135
|
-
};
|
|
136
|
-
'too-many-preloads': {
|
|
137
|
-
max: number;
|
|
138
|
-
};
|
|
139
|
-
'too-many-preconnects': {
|
|
140
|
-
max: number;
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
type RuleConfig<Id extends ValidationRuleId> = Id extends keyof ValidationRuleOptions ? RuleSeverity | [severity: RuleSeverity, options: ValidationRuleOptions[Id]] : RuleSeverity;
|
|
144
|
-
type RulesConfig = {
|
|
145
|
-
[K in ValidationRuleId]?: RuleConfig<K>;
|
|
146
|
-
};
|
|
147
120
|
interface HeadValidationRule {
|
|
148
121
|
id: ValidationRuleId;
|
|
149
122
|
message: string;
|
|
@@ -178,5 +151,5 @@ interface ValidatePluginOptions {
|
|
|
178
151
|
}
|
|
179
152
|
declare function ValidatePlugin(options?: ValidatePluginOptions): HeadPluginInput;
|
|
180
153
|
|
|
181
|
-
export { AliasSortingPlugin, CanonicalPlugin, FlatMetaPlugin, InferSeoMetaPlugin, MinifyPlugin, PromisesPlugin, SafeInputPlugin, TemplateParamsPlugin, ValidatePlugin, defineHeadPlugin };
|
|
182
|
-
export type { CanonicalPluginOptions, HeadValidationRule, MinifyPluginOptions,
|
|
154
|
+
export { AliasSortingPlugin, CanonicalPlugin, FlatMetaPlugin, InferSeoMetaPlugin, MinifyPlugin, PromisesPlugin, RulesConfig, SafeInputPlugin, TemplateParamsPlugin, ValidatePlugin, ValidationRuleId, defineHeadPlugin };
|
|
155
|
+
export type { CanonicalPluginOptions, HeadValidationRule, MinifyPluginOptions, ValidatePluginOptions };
|
package/dist/plugins.mjs
CHANGED
|
@@ -2,6 +2,7 @@ export { A as AliasSortingPlugin, P as PromisesPlugin, T as TemplateParamsPlugin
|
|
|
2
2
|
import { d as defineHeadPlugin } from './shared/unhead.CUXLLRtV.mjs';
|
|
3
3
|
export { F as FlatMetaPlugin, S as SafeInputPlugin } from './shared/unhead.A4ndrqqe.mjs';
|
|
4
4
|
import { minifyJS, minifyCSS, minifyJSON } from './minify.mjs';
|
|
5
|
+
import { t as tagInputFromRuntime, o as tagPredicates, U as URL_META_KEYS, q as titleInputFromRuntime, h as headInputPredicates } from './shared/unhead.ebqUBTt1.mjs';
|
|
5
6
|
import './shared/unhead.BGFxPGPQ.mjs';
|
|
6
7
|
import './shared/unhead.JWUDzlIs.mjs';
|
|
7
8
|
import './shared/unhead.fg-0ge_u.mjs';
|
|
@@ -201,155 +202,25 @@ function MinifyPlugin(options) {
|
|
|
201
202
|
};
|
|
202
203
|
}
|
|
203
204
|
|
|
204
|
-
const URL_META_KEYS = /* @__PURE__ */ new Set([
|
|
205
|
-
"og:url",
|
|
206
|
-
"og:image",
|
|
207
|
-
"og:image:url",
|
|
208
|
-
"og:image:secure_url",
|
|
209
|
-
"og:video",
|
|
210
|
-
"og:video:url",
|
|
211
|
-
"og:video:secure_url",
|
|
212
|
-
"og:audio",
|
|
213
|
-
"og:audio:url",
|
|
214
|
-
"og:audio:secure_url",
|
|
215
|
-
"twitter:image",
|
|
216
|
-
"twitter:image:src",
|
|
217
|
-
"twitter:player",
|
|
218
|
-
"twitter:player:stream"
|
|
219
|
-
]);
|
|
220
|
-
const KNOWN_META_PROPERTIES = /* @__PURE__ */ new Set([
|
|
221
|
-
"article:author",
|
|
222
|
-
"article:expiration_time",
|
|
223
|
-
"article:modified_time",
|
|
224
|
-
"article:published_time",
|
|
225
|
-
"article:section",
|
|
226
|
-
"article:tag",
|
|
227
|
-
"book:author",
|
|
228
|
-
"book:isbn",
|
|
229
|
-
"book:release_date",
|
|
230
|
-
"book:tag",
|
|
231
|
-
"fb:app_id",
|
|
232
|
-
"og:audio",
|
|
233
|
-
"og:audio:secure_url",
|
|
234
|
-
"og:audio:type",
|
|
235
|
-
"og:audio:url",
|
|
236
|
-
"og:description",
|
|
237
|
-
"og:determiner",
|
|
238
|
-
"og:image",
|
|
239
|
-
"og:image:height",
|
|
240
|
-
"og:image:secure_url",
|
|
241
|
-
"og:image:type",
|
|
242
|
-
"og:image:url",
|
|
243
|
-
"og:image:width",
|
|
244
|
-
"og:locale",
|
|
245
|
-
"og:locale:alternate",
|
|
246
|
-
"og:site_name",
|
|
247
|
-
"og:title",
|
|
248
|
-
"og:type",
|
|
249
|
-
"og:url",
|
|
250
|
-
"og:video",
|
|
251
|
-
"og:video:height",
|
|
252
|
-
"og:video:secure_url",
|
|
253
|
-
"og:video:type",
|
|
254
|
-
"og:video:url",
|
|
255
|
-
"og:video:width",
|
|
256
|
-
"profile:first_name",
|
|
257
|
-
"profile:gender",
|
|
258
|
-
"profile:last_name",
|
|
259
|
-
"profile:username"
|
|
260
|
-
]);
|
|
261
|
-
const KNOWN_META_NAMES = /* @__PURE__ */ new Set([
|
|
262
|
-
"apple-itunes-app",
|
|
263
|
-
"apple-mobile-web-app-capable",
|
|
264
|
-
"apple-mobile-web-app-status-bar-style",
|
|
265
|
-
"apple-mobile-web-app-title",
|
|
266
|
-
"application-name",
|
|
267
|
-
"author",
|
|
268
|
-
"color-scheme",
|
|
269
|
-
"creator",
|
|
270
|
-
"description",
|
|
271
|
-
"fb:app_id",
|
|
272
|
-
"fediverse:creator",
|
|
273
|
-
"format-detection",
|
|
274
|
-
"generator",
|
|
275
|
-
"google-site-verification",
|
|
276
|
-
"google",
|
|
277
|
-
"googlebot",
|
|
278
|
-
"keywords",
|
|
279
|
-
"mobile-web-app-capable",
|
|
280
|
-
"msapplication-Config",
|
|
281
|
-
"msapplication-TileColor",
|
|
282
|
-
"msapplication-TileImage",
|
|
283
|
-
"publisher",
|
|
284
|
-
"rating",
|
|
285
|
-
"referrer",
|
|
286
|
-
"robots",
|
|
287
|
-
"theme-color",
|
|
288
|
-
"viewport",
|
|
289
|
-
"twitter:app:id:googleplay",
|
|
290
|
-
"twitter:app:id:ipad",
|
|
291
|
-
"twitter:app:id:iphone",
|
|
292
|
-
"twitter:app:name:googleplay",
|
|
293
|
-
"twitter:app:name:ipad",
|
|
294
|
-
"twitter:app:name:iphone",
|
|
295
|
-
"twitter:app:url:googleplay",
|
|
296
|
-
"twitter:app:url:ipad",
|
|
297
|
-
"twitter:app:url:iphone",
|
|
298
|
-
"twitter:card",
|
|
299
|
-
"twitter:creator",
|
|
300
|
-
"twitter:creator:id",
|
|
301
|
-
"twitter:data:1",
|
|
302
|
-
"twitter:data:2",
|
|
303
|
-
"twitter:description",
|
|
304
|
-
"twitter:image",
|
|
305
|
-
"twitter:image:alt",
|
|
306
|
-
"twitter:label:1",
|
|
307
|
-
"twitter:label:2",
|
|
308
|
-
"twitter:player",
|
|
309
|
-
"twitter:player:height",
|
|
310
|
-
"twitter:player:stream",
|
|
311
|
-
"twitter:player:width",
|
|
312
|
-
"twitter:site",
|
|
313
|
-
"twitter:site:id",
|
|
314
|
-
"twitter:title"
|
|
315
|
-
]);
|
|
316
205
|
const TEMPLATE_PARAM_RE = /%\w+(?:\.\w+)?%/;
|
|
317
|
-
const MAX_SCALE_RE = /maximum-scale\s*=\s*1(?:\.0?)?(?:\s|,|$)/i;
|
|
318
|
-
const USER_SCALABLE_NO_RE = /user-scalable\s*=\s*no(?:\s|,|$)/i;
|
|
319
|
-
const NUMERIC_RE = /^\d+$/;
|
|
320
|
-
const OG_PREFIX_RE = /^(?:og|article|book|profile|fb):/;
|
|
321
|
-
const HTML_CHARS_RE = /[<>]/;
|
|
322
206
|
const AT_PREFIX_RE = /^at\s+/;
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
let best;
|
|
341
|
-
let bestDist = threshold + 1;
|
|
342
|
-
for (const known of knownSet) {
|
|
343
|
-
if (Math.abs(known.length - value.length) > threshold)
|
|
344
|
-
continue;
|
|
345
|
-
const dist = levenshtein(value, known);
|
|
346
|
-
if (dist < bestDist) {
|
|
347
|
-
bestDist = dist;
|
|
348
|
-
best = known;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
return best;
|
|
352
|
-
}
|
|
207
|
+
const PREDICATE_SEVERITY = {
|
|
208
|
+
"defer-on-module-script": "info",
|
|
209
|
+
"deprecated-prop-body": "warn",
|
|
210
|
+
"deprecated-prop-children": "warn",
|
|
211
|
+
"deprecated-prop-hid-vmid": "warn",
|
|
212
|
+
"empty-meta-content": "warn",
|
|
213
|
+
"html-in-title": "warn",
|
|
214
|
+
"non-absolute-canonical": "warn",
|
|
215
|
+
"numeric-tag-priority": "info",
|
|
216
|
+
"possible-typo": "warn",
|
|
217
|
+
"preload-font-crossorigin": "warn",
|
|
218
|
+
"preload-missing-as": "warn",
|
|
219
|
+
"robots-conflict": "warn",
|
|
220
|
+
"script-src-with-content": "warn",
|
|
221
|
+
"twitter-handle-missing-at": "warn",
|
|
222
|
+
"viewport-user-scalable": "info"
|
|
223
|
+
};
|
|
353
224
|
function isAbsoluteUrl(url) {
|
|
354
225
|
return url.startsWith("http://") || url.startsWith("https://");
|
|
355
226
|
}
|
|
@@ -432,7 +303,7 @@ function ValidatePlugin(options = {}) {
|
|
|
432
303
|
if (tag.tag === "title")
|
|
433
304
|
hasTitle = true;
|
|
434
305
|
if (tag.tag === "meta") {
|
|
435
|
-
const key = tag.props.property || tag.props.name;
|
|
306
|
+
const key = tag.props.property || (tag.props.name ? String(tag.props.name).toLowerCase() : void 0);
|
|
436
307
|
if (key) {
|
|
437
308
|
metaByKey.set(key, tag);
|
|
438
309
|
if (key.startsWith("og:"))
|
|
@@ -446,12 +317,19 @@ function ValidatePlugin(options = {}) {
|
|
|
446
317
|
if (tag.tag === "link" && tag.props.rel === "canonical")
|
|
447
318
|
canonicalHref = tag.props.href;
|
|
448
319
|
}
|
|
320
|
+
function emitFromPredicates(diagnostics, tag) {
|
|
321
|
+
for (const diag of diagnostics) {
|
|
322
|
+
const sev = PREDICATE_SEVERITY[diag.ruleId] ?? "warn";
|
|
323
|
+
report(diag.ruleId, diag.message, sev, tag);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
449
326
|
for (const tag of tags) {
|
|
450
327
|
const { props } = tag;
|
|
451
|
-
const metaKey = props.property || props.name;
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
328
|
+
const metaKey = props.property || (props.name ? String(props.name).toLowerCase() : void 0);
|
|
329
|
+
const tagInput = tagInputFromRuntime(tag);
|
|
330
|
+
if (tagInput) {
|
|
331
|
+
for (const predicate of Object.values(tagPredicates))
|
|
332
|
+
emitFromPredicates(predicate(tagInput), tag);
|
|
455
333
|
}
|
|
456
334
|
if (tag.tag === "meta" && metaKey && URL_META_KEYS.has(metaKey)) {
|
|
457
335
|
const content = String(props.content ?? "");
|
|
@@ -460,57 +338,23 @@ function ValidatePlugin(options = {}) {
|
|
|
460
338
|
}
|
|
461
339
|
if (tag.tag === "meta" && metaKey) {
|
|
462
340
|
const content = String(props.content ?? "");
|
|
463
|
-
if ("content" in props && content === "")
|
|
464
|
-
report("empty-meta-content", `Meta tag "${metaKey}" has empty content.`, "warn", tag);
|
|
465
341
|
if (content && TEMPLATE_PARAM_RE.test(content))
|
|
466
342
|
report("unresolved-template-param", `Unresolved template param in ${metaKey}: "${content}".`, "warn", tag);
|
|
467
|
-
if (metaKey === "robots" && content) {
|
|
468
|
-
const directives = content.toLowerCase().split(",").map((d) => d.trim());
|
|
469
|
-
if (directives.includes("index") && directives.includes("noindex"))
|
|
470
|
-
report("robots-conflict", `Robots meta has conflicting "index" and "noindex" directives.`, "warn", tag);
|
|
471
|
-
if (directives.includes("follow") && directives.includes("nofollow"))
|
|
472
|
-
report("robots-conflict", `Robots meta has conflicting "follow" and "nofollow" directives.`, "warn", tag);
|
|
473
|
-
}
|
|
474
|
-
if (metaKey === "viewport" && content) {
|
|
475
|
-
if (USER_SCALABLE_NO_RE.test(content))
|
|
476
|
-
report("viewport-user-scalable", `viewport has "user-scalable=no" which prevents zooming and harms accessibility.`, "info", tag);
|
|
477
|
-
if (MAX_SCALE_RE.test(content))
|
|
478
|
-
report("viewport-user-scalable", `viewport "maximum-scale=1" limits zooming and may harm accessibility.`, "info", tag);
|
|
479
|
-
}
|
|
480
|
-
if ((metaKey === "twitter:site" || metaKey === "twitter:creator") && content && !content.startsWith("@") && !NUMERIC_RE.test(content))
|
|
481
|
-
report("twitter-handle-missing-at", `${metaKey} should start with "@", received "${content}".`, "warn", tag);
|
|
482
|
-
if (props.property && !KNOWN_META_PROPERTIES.has(props.property) && OG_PREFIX_RE.test(props.property)) {
|
|
483
|
-
const suggestion = findClosestMatch(props.property, KNOWN_META_PROPERTIES);
|
|
484
|
-
if (suggestion)
|
|
485
|
-
report("possible-typo", `Unknown meta property "${props.property}". Did you mean "${suggestion}"?`, "warn", tag);
|
|
486
|
-
}
|
|
487
|
-
if (props.name && !KNOWN_META_NAMES.has(props.name) && (props.name.startsWith("twitter:") || props.name.startsWith("fediverse:") || !props.name.includes(":"))) {
|
|
488
|
-
const suggestion = findClosestMatch(props.name, KNOWN_META_NAMES);
|
|
489
|
-
if (suggestion)
|
|
490
|
-
report("possible-typo", `Unknown meta name "${props.name}". Did you mean "${suggestion}"?`, "warn", tag);
|
|
491
|
-
}
|
|
492
343
|
}
|
|
493
344
|
if (tag.tag === "title") {
|
|
345
|
+
const titleInput = titleInputFromRuntime(tag);
|
|
346
|
+
if (titleInput) {
|
|
347
|
+
for (const diag of headInputPredicates["no-html-in-title"](titleInput))
|
|
348
|
+
report(diag.ruleId, diag.message, "warn", tag);
|
|
349
|
+
}
|
|
494
350
|
const text = tag.textContent || "";
|
|
495
|
-
if (HTML_CHARS_RE.test(text))
|
|
496
|
-
report("html-in-title", `Title contains HTML characters which will be escaped, not rendered: "${text}".`, "warn", tag);
|
|
497
351
|
if (TEMPLATE_PARAM_RE.test(text))
|
|
498
352
|
report("unresolved-template-param", `Unresolved template param in title: "${text}".`, "warn", tag);
|
|
499
353
|
if (!text.trim())
|
|
500
354
|
report("empty-title", `Title tag is empty. If using titleTemplate, ensure it produces output.`, "warn", tag);
|
|
501
355
|
}
|
|
502
|
-
if (tag.tag === "link" && props.rel === "preload") {
|
|
503
|
-
if (props.as === "font" && !("crossorigin" in props))
|
|
504
|
-
report("preload-font-crossorigin", `Font preload requires "crossorigin" attribute \u2014 without it the font will be fetched twice.`, "warn", tag);
|
|
505
|
-
if (!props.as)
|
|
506
|
-
report("preload-missing-as", `Preload link is missing the required "as" attribute.`, "warn", tag);
|
|
507
|
-
}
|
|
508
|
-
if (tag.tag === "script" && props.src && (tag.innerHTML || tag.textContent))
|
|
509
|
-
report("script-src-with-content", `Script has both "src" and inline content \u2014 the browser will ignore the inline content.`, "warn", tag);
|
|
510
356
|
if (tag.tag === "script" && props.src && !props.async && !props.defer && props.type !== "module" && (!tag.tagPosition || tag.tagPosition === "head"))
|
|
511
357
|
report("render-blocking-script", `Script "${props.src}" is render-blocking. Add "async", "defer", or use type="module" to avoid blocking the critical rendering path.`, "warn", tag);
|
|
512
|
-
if (tag.tag === "script" && props.type === "module" && props.defer)
|
|
513
|
-
report("defer-on-module-script", `"defer" is redundant on module scripts. Modules are deferred by default.`, "info", tag);
|
|
514
358
|
if (tag.tag === "link" && props.rel === "preload" && props.fetchpriority === "low" && props.as !== "script")
|
|
515
359
|
report("preload-fetchpriority-conflict", `Preload with fetchpriority="low" is contradictory \u2014 preload signals critical, low priority contradicts that.`, "warn", tag);
|
|
516
360
|
if (tag.tag === "style" && (tag.innerHTML || tag.textContent)) {
|
|
@@ -602,14 +446,6 @@ function ValidatePlugin(options = {}) {
|
|
|
602
446
|
}
|
|
603
447
|
}
|
|
604
448
|
}
|
|
605
|
-
for (const tag of tags) {
|
|
606
|
-
if ("children" in tag.props)
|
|
607
|
-
report("deprecated-prop-children", `"children" was removed in v3. Use "innerHTML" instead.`, "warn", tag);
|
|
608
|
-
if ("hid" in tag.props || "vmid" in tag.props)
|
|
609
|
-
report("deprecated-prop-hid-vmid", `"${"hid" in tag.props ? "hid" : "vmid"}" was removed in v3. Use "key" instead.`, "warn", tag);
|
|
610
|
-
if (tag.props.body === true)
|
|
611
|
-
report("deprecated-prop-body", `"body: true" was removed in v3. Use "tagPosition: 'bodyClose'" instead.`, "warn", tag);
|
|
612
|
-
}
|
|
613
449
|
const { max: maxHighPriority } = resolveOptions(ruleConfig, "too-many-fetchpriority-high", { max: 2 });
|
|
614
450
|
const highPriorityCount = tags.filter((t) => t.props.fetchpriority === "high").length;
|
|
615
451
|
if (highPriorityCount > maxHighPriority)
|
|
@@ -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 };
|