feature: vor und nachteile

This commit is contained in:
Hikyu
2026-05-31 13:54:31 +02:00
parent 552a947a1c
commit e3dad17446
23270 changed files with 3024794 additions and 200 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) Harlan Wilton <harlan@harlanzw.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+16
View File
@@ -0,0 +1,16 @@
# `@unhead/schema`
Typescript definitions for document `<head>`.
## Installation
```bash
npm install --save-dev @unhead/schema
# Using yarn
yarn add --dev @unhead/schema
```
## Types
See [head.ts](./src/head.ts) for the full list of types.
+675
View File
@@ -0,0 +1,675 @@
'use strict';
const packrup = require('packrup');
function asArray(value) {
return Array.isArray(value) ? value : [value];
}
const SelfClosingTags = /* @__PURE__ */ new Set(["meta", "link", "base"]);
const TagsWithInnerContent = /* @__PURE__ */ new Set(["title", "titleTemplate", "script", "style", "noscript"]);
const HasElementTags = /* @__PURE__ */ new Set([
"base",
"meta",
"link",
"style",
"script",
"noscript"
]);
const ValidHeadTags = /* @__PURE__ */ new Set([
"title",
"titleTemplate",
"templateParams",
"base",
"htmlAttrs",
"bodyAttrs",
"meta",
"link",
"style",
"script",
"noscript"
]);
const UniqueTags = /* @__PURE__ */ new Set(["base", "title", "titleTemplate", "bodyAttrs", "htmlAttrs", "templateParams"]);
const TagConfigKeys = /* @__PURE__ */ new Set(["tagPosition", "tagPriority", "tagDuplicateStrategy", "children", "innerHTML", "textContent", "processTemplateParams"]);
const IsBrowser = typeof window !== "undefined";
const composableNames = [
"getActiveHead",
"useHead",
"useSeoMeta",
"useHeadSafe",
"useServerHead",
"useServerSeoMeta",
"useServerHeadSafe"
];
function defineHeadPlugin(plugin) {
return plugin;
}
function hashCode(s) {
let h = 9;
for (let i = 0; i < s.length; )
h = Math.imul(h ^ s.charCodeAt(i++), 9 ** 9);
return ((h ^ h >>> 9) + 65536).toString(16).substring(1, 8).toLowerCase();
}
function hashTag(tag) {
if (tag._h) {
return tag._h;
}
if (tag._d) {
return hashCode(tag._d);
}
let content = `${tag.tag}:${tag.textContent || tag.innerHTML || ""}:`;
for (const key in tag.props) {
content += `${key}:${String(tag.props[key])},`;
}
return hashCode(content);
}
const p = (p2) => ({ keyValue: p2, metaKey: "property" });
const k = (p2) => ({ keyValue: p2 });
const MetaPackingSchema = {
appleItunesApp: {
unpack: {
entrySeparator: ", ",
resolve({ key, value }) {
return `${fixKeyCase(key)}=${value}`;
}
}
},
articleExpirationTime: p("article:expiration_time"),
articleModifiedTime: p("article:modified_time"),
articlePublishedTime: p("article:published_time"),
bookReleaseDate: p("book:release_date"),
charset: {
metaKey: "charset"
},
contentSecurityPolicy: {
unpack: {
entrySeparator: "; ",
resolve({ key, value }) {
return `${fixKeyCase(key)} ${value}`;
}
},
metaKey: "http-equiv"
},
contentType: {
metaKey: "http-equiv"
},
defaultStyle: {
metaKey: "http-equiv"
},
fbAppId: p("fb:app_id"),
msapplicationConfig: k("msapplication-Config"),
msapplicationTileColor: k("msapplication-TileColor"),
msapplicationTileImage: k("msapplication-TileImage"),
ogAudioSecureUrl: p("og:audio:secure_url"),
ogAudioUrl: p("og:audio"),
ogImageSecureUrl: p("og:image:secure_url"),
ogImageUrl: p("og:image"),
ogSiteName: p("og:site_name"),
ogVideoSecureUrl: p("og:video:secure_url"),
ogVideoUrl: p("og:video"),
profileFirstName: p("profile:first_name"),
profileLastName: p("profile:last_name"),
profileUsername: p("profile:username"),
refresh: {
metaKey: "http-equiv",
unpack: {
entrySeparator: ";",
resolve({ key, value }) {
if (key === "seconds")
return `${value}`;
}
}
},
robots: {
unpack: {
entrySeparator: ", ",
resolve({ key, value }) {
if (typeof value === "boolean")
return `${fixKeyCase(key)}`;
else
return `${fixKeyCase(key)}:${value}`;
}
}
},
xUaCompatible: {
metaKey: "http-equiv"
}
};
const openGraphNamespaces = /* @__PURE__ */ new Set([
"og",
"book",
"article",
"profile"
]);
function resolveMetaKeyType(key) {
const fKey = fixKeyCase(key);
const prefixIndex = fKey.indexOf(":");
if (openGraphNamespaces.has(fKey.substring(0, prefixIndex)))
return "property";
return MetaPackingSchema[key]?.metaKey || "name";
}
function resolveMetaKeyValue(key) {
return MetaPackingSchema[key]?.keyValue || fixKeyCase(key);
}
function fixKeyCase(key) {
const updated = key.replace(/([A-Z])/g, "-$1").toLowerCase();
const prefixIndex = updated.indexOf("-");
const fKey = updated.substring(0, prefixIndex);
if (fKey === "twitter" || openGraphNamespaces.has(fKey))
return key.replace(/([A-Z])/g, ":$1").toLowerCase();
return updated;
}
function changeKeyCasingDeep(input) {
if (Array.isArray(input)) {
return input.map((entry) => changeKeyCasingDeep(entry));
}
if (typeof input !== "object" || Array.isArray(input))
return input;
const output = {};
for (const key in input) {
if (!Object.prototype.hasOwnProperty.call(input, key)) {
continue;
}
output[fixKeyCase(key)] = changeKeyCasingDeep(input[key]);
}
return output;
}
function resolvePackedMetaObjectValue(value, key) {
const definition = MetaPackingSchema[key];
if (key === "refresh")
return `${value.seconds};url=${value.url}`;
return packrup.unpackToString(
changeKeyCasingDeep(value),
{
keyValueSeparator: "=",
entrySeparator: ", ",
resolve({ value: value2, key: key2 }) {
if (value2 === null)
return "";
if (typeof value2 === "boolean")
return `${key2}`;
},
...definition?.unpack
}
);
}
const ObjectArrayEntries = /* @__PURE__ */ new Set(["og:image", "og:video", "og:audio", "twitter:image"]);
function sanitize(input) {
const out = {};
for (const k2 in input) {
if (!Object.prototype.hasOwnProperty.call(input, k2)) {
continue;
}
const v = input[k2];
if (String(v) !== "false" && k2)
out[k2] = v;
}
return out;
}
function handleObjectEntry(key, v) {
const value = sanitize(v);
const fKey = fixKeyCase(key);
const attr = resolveMetaKeyType(fKey);
if (ObjectArrayEntries.has(fKey)) {
const input = {};
for (const k2 in value) {
if (!Object.prototype.hasOwnProperty.call(value, k2)) {
continue;
}
input[`${key}${k2 === "url" ? "" : `${k2[0].toUpperCase()}${k2.slice(1)}`}`] = value[k2];
}
return unpackMeta(input).sort((a, b) => (a[attr]?.length || 0) - (b[attr]?.length || 0));
}
return [{ [attr]: fKey, ...value }];
}
function unpackMeta(input) {
const extras = [];
const primitives = {};
for (const key in input) {
if (!Object.prototype.hasOwnProperty.call(input, key)) {
continue;
}
const value = input[key];
if (!Array.isArray(value)) {
if (typeof value === "object" && value) {
if (ObjectArrayEntries.has(fixKeyCase(key))) {
extras.push(...handleObjectEntry(key, value));
continue;
}
primitives[key] = sanitize(value);
} else {
primitives[key] = value;
}
continue;
}
for (const v of value) {
extras.push(...typeof v === "string" ? unpackMeta({ [key]: v }) : handleObjectEntry(key, v));
}
}
const meta = packrup.unpackToArray(primitives, {
key({ key }) {
return resolveMetaKeyType(key);
},
value({ key }) {
return key === "charset" ? "charset" : "content";
},
resolveKeyData({ key }) {
return resolveMetaKeyValue(key);
},
resolveValueData({ value, key }) {
if (value === null)
return "_null";
if (typeof value === "object")
return resolvePackedMetaObjectValue(value, key);
return typeof value === "number" ? value.toString() : value;
}
});
return [...extras, ...meta].map((m) => {
if (m.content === "_null")
m.content = null;
return m;
});
}
function packMeta(inputs) {
const mappedPackingSchema = Object.entries(MetaPackingSchema).map(([key, value]) => [key, value.keyValue]);
return packrup.packArray(inputs, {
key: ["name", "property", "httpEquiv", "http-equiv", "charset"],
value: ["content", "charset"],
resolveKey(k2) {
let key = mappedPackingSchema.filter((sk) => sk[1] === k2)?.[0]?.[0] || k2;
const replacer = (_, letter) => letter?.toUpperCase();
key = key.replace(/:([a-z])/g, replacer).replace(/-([a-z])/g, replacer);
return key;
}
});
}
function thenable(val, thenFn) {
if (val instanceof Promise) {
return val.then(thenFn);
}
return thenFn(val);
}
function normaliseTag(tagName, input, e, normalizedProps) {
const props = normalizedProps || normaliseProps(
// explicitly check for an object
// @ts-expect-error untyped
typeof input === "object" && typeof input !== "function" && !(input instanceof Promise) ? { ...input } : { [tagName === "script" || tagName === "noscript" || tagName === "style" ? "innerHTML" : "textContent"]: input },
tagName === "templateParams" || tagName === "titleTemplate"
);
if (props instanceof Promise) {
return props.then((val) => normaliseTag(tagName, input, e, val));
}
const tag = {
tag: tagName,
props
};
for (const k of TagConfigKeys) {
const val = tag.props[k] !== void 0 ? tag.props[k] : e[k];
if (val !== void 0) {
if (!(k === "innerHTML" || k === "textContent" || k === "children") || TagsWithInnerContent.has(tag.tag)) {
tag[k === "children" ? "innerHTML" : k] = val;
}
delete tag.props[k];
}
}
if (tag.props.body) {
tag.tagPosition = "bodyClose";
delete tag.props.body;
}
if (tag.tag === "script") {
if (typeof tag.innerHTML === "object") {
tag.innerHTML = JSON.stringify(tag.innerHTML);
tag.props.type = tag.props.type || "application/json";
}
}
return Array.isArray(tag.props.content) ? tag.props.content.map((v) => ({ ...tag, props: { ...tag.props, content: v } })) : tag;
}
function normaliseStyleClassProps(key, v) {
const sep = key === "class" ? " " : ";";
if (v && typeof v === "object" && !Array.isArray(v)) {
v = Object.entries(v).filter(([, v2]) => v2).map(([k, v2]) => key === "style" ? `${k}:${v2}` : k);
}
return String(Array.isArray(v) ? v.join(sep) : v)?.split(sep).filter((c) => Boolean(c.trim())).join(sep);
}
function nestedNormaliseProps(props, virtual, keys, startIndex) {
for (let i = startIndex; i < keys.length; i += 1) {
const k = keys[i];
if (k === "class" || k === "style") {
props[k] = normaliseStyleClassProps(k, props[k]);
continue;
}
if (props[k] instanceof Promise) {
return props[k].then((val) => {
props[k] = val;
return nestedNormaliseProps(props, virtual, keys, i);
});
}
if (!virtual && !TagConfigKeys.has(k)) {
const v = String(props[k]);
const isDataKey = k.startsWith("data-");
if (v === "true" || v === "") {
props[k] = isDataKey ? "true" : true;
} else if (!props[k]) {
if (isDataKey && v === "false")
props[k] = "false";
else
delete props[k];
}
}
}
}
function normaliseProps(props, virtual = false) {
const resolvedProps = nestedNormaliseProps(props, virtual, Object.keys(props), 0);
if (resolvedProps instanceof Promise) {
return resolvedProps.then(() => props);
}
return props;
}
const TagEntityBits = 10;
function nestedNormaliseEntryTags(headTags, tagPromises, startIndex) {
for (let i = startIndex; i < tagPromises.length; i += 1) {
const tags = tagPromises[i];
if (tags instanceof Promise) {
return tags.then((val) => {
tagPromises[i] = val;
return nestedNormaliseEntryTags(headTags, tagPromises, i);
});
}
if (Array.isArray(tags)) {
headTags.push(...tags);
} else {
headTags.push(tags);
}
}
}
function normaliseEntryTags(e) {
const tagPromises = [];
const input = e.resolvedInput;
for (const k in input) {
if (!Object.prototype.hasOwnProperty.call(input, k)) {
continue;
}
const v = input[k];
if (v === void 0 || !ValidHeadTags.has(k)) {
continue;
}
if (Array.isArray(v)) {
for (const props of v) {
tagPromises.push(normaliseTag(k, props, e));
}
continue;
}
tagPromises.push(normaliseTag(k, v, e));
}
if (tagPromises.length === 0) {
return [];
}
const headTags = [];
return thenable(nestedNormaliseEntryTags(headTags, tagPromises, 0), () => headTags.map((t, i) => {
t._e = e._i;
e.mode && (t._m = e.mode);
t._p = (e._i << TagEntityBits) + i;
return t;
}));
}
const WhitelistAttributes = {
htmlAttrs: ["id", "class", "lang", "dir"],
bodyAttrs: ["id", "class"],
meta: ["id", "name", "property", "charset", "content"],
noscript: ["id", "textContent"],
script: ["id", "type", "textContent"],
link: ["id", "color", "crossorigin", "fetchpriority", "href", "hreflang", "imagesrcset", "imagesizes", "integrity", "media", "referrerpolicy", "rel", "sizes", "type"]
};
function acceptDataAttrs(value) {
const filtered = {};
Object.keys(value || {}).filter((a) => a.startsWith("data-")).forEach((a) => {
filtered[a] = value[a];
});
return filtered;
}
function whitelistSafeInput(input) {
const filtered = {};
Object.keys(input).forEach((key) => {
const tagValue = input[key];
if (!tagValue)
return;
switch (key) {
// always safe
case "title":
case "titleTemplate":
case "templateParams":
filtered[key] = tagValue;
break;
case "htmlAttrs":
case "bodyAttrs":
filtered[key] = acceptDataAttrs(tagValue);
WhitelistAttributes[key].forEach((a) => {
if (tagValue[a])
filtered[key][a] = tagValue[a];
});
break;
// meta is safe, except for http-equiv
case "meta":
if (Array.isArray(tagValue)) {
filtered[key] = tagValue.map((meta) => {
const safeMeta = acceptDataAttrs(meta);
WhitelistAttributes.meta.forEach((key2) => {
if (meta[key2])
safeMeta[key2] = meta[key2];
});
return safeMeta;
}).filter((meta) => Object.keys(meta).length > 0);
}
break;
// link tags we don't allow stylesheets, scripts, preloading, prerendering, prefetching, etc
case "link":
if (Array.isArray(tagValue)) {
filtered[key] = tagValue.map((meta) => {
const link = acceptDataAttrs(meta);
WhitelistAttributes.link.forEach((key2) => {
const val = meta[key2];
if (key2 === "rel" && (val === "stylesheet" || val === "canonical" || val === "modulepreload" || val === "prerender" || val === "preload" || val === "prefetch"))
return;
if (key2 === "href") {
if (val.includes("javascript:") || val.includes("data:"))
return;
link[key2] = val;
} else if (val) {
link[key2] = val;
}
});
return link;
}).filter((link) => Object.keys(link).length > 1 && !!link.rel);
}
break;
case "noscript":
if (Array.isArray(tagValue)) {
filtered[key] = tagValue.map((meta) => {
const noscript = acceptDataAttrs(meta);
WhitelistAttributes.noscript.forEach((key2) => {
if (meta[key2])
noscript[key2] = meta[key2];
});
return noscript;
}).filter((meta) => Object.keys(meta).length > 0);
}
break;
// we only allow JSON in scripts
case "script":
if (Array.isArray(tagValue)) {
filtered[key] = tagValue.map((script) => {
const safeScript = acceptDataAttrs(script);
WhitelistAttributes.script.forEach((s) => {
if (script[s]) {
if (s === "textContent") {
try {
const jsonVal = typeof script[s] === "string" ? JSON.parse(script[s]) : script[s];
safeScript[s] = JSON.stringify(jsonVal, null, 0);
} catch (e) {
}
} else {
safeScript[s] = script[s];
}
}
});
return safeScript;
}).filter((meta) => Object.keys(meta).length > 0);
}
break;
}
});
return filtered;
}
const NetworkEvents = /* @__PURE__ */ new Set(["onload", "onerror", "onabort", "onprogress", "onloadstart"]);
const ScriptNetworkEvents = /* @__PURE__ */ new Set(["onload", "onerror"]);
const TAG_WEIGHTS = {
// tags
base: -10,
title: 10
};
const TAG_ALIASES = {
// relative scores to their default values
critical: -80,
high: -10,
low: 20
};
function tagWeight(tag) {
const priority = tag.tagPriority;
if (typeof priority === "number")
return priority;
let weight = 100;
if (tag.tag === "meta") {
if (tag.props["http-equiv"] === "content-security-policy")
weight = -30;
else if (tag.props.charset)
weight = -20;
else if (tag.props.name === "viewport")
weight = -15;
} else if (tag.tag === "link" && tag.props.rel === "preconnect") {
weight = 20;
} else if (tag.tag in TAG_WEIGHTS) {
weight = TAG_WEIGHTS[tag.tag];
}
if (priority && priority in TAG_ALIASES) {
return weight + TAG_ALIASES[priority];
}
return weight;
}
const SortModifiers = [{ prefix: "before:", offset: -1 }, { prefix: "after:", offset: 1 }];
const allowedMetaProperties = ["name", "property", "http-equiv"];
function tagDedupeKey(tag) {
const { props, tag: tagName } = tag;
if (UniqueTags.has(tagName))
return tagName;
if (tagName === "link" && props.rel === "canonical")
return "canonical";
if (props.charset)
return "charset";
if (props.id) {
return `${tagName}:id:${props.id}`;
}
for (const n of allowedMetaProperties) {
if (props[n] !== void 0) {
return `${tagName}:${n}:${props[n]}`;
}
}
return false;
}
const sepSub = "%separator";
function sub(p, token, isJson = false) {
let val;
if (token === "s" || token === "pageTitle") {
val = p.pageTitle;
} else if (token.includes(".")) {
const dotIndex = token.indexOf(".");
val = p[token.substring(0, dotIndex)]?.[token.substring(dotIndex + 1)];
} else {
val = p[token];
}
if (val !== void 0) {
return isJson ? (val || "").replace(/"/g, '\\"') : val || "";
}
return void 0;
}
const sepSubRe = new RegExp(`${sepSub}(?:\\s*${sepSub})*`, "g");
function processTemplateParams(s, p, sep, isJson = false) {
if (typeof s !== "string" || !s.includes("%"))
return s;
let decoded = s;
try {
decoded = decodeURI(s);
} catch {
}
const tokens = decoded.match(/%\w+(?:\.\w+)?/g);
if (!tokens) {
return s;
}
const hasSepSub = s.includes(sepSub);
s = s.replace(/%\w+(?:\.\w+)?/g, (token) => {
if (token === sepSub || !tokens.includes(token)) {
return token;
}
const re = sub(p, token.slice(1), isJson);
return re !== void 0 ? re : token;
}).trim();
if (hasSepSub) {
if (s.endsWith(sepSub))
s = s.slice(0, -sepSub.length);
if (s.startsWith(sepSub))
s = s.slice(sepSub.length);
s = s.replace(sepSubRe, sep).trim();
}
return s;
}
function resolveTitleTemplate(template, title) {
if (template == null)
return title || null;
if (typeof template === "function")
return template(title);
return template;
}
exports.HasElementTags = HasElementTags;
exports.IsBrowser = IsBrowser;
exports.NetworkEvents = NetworkEvents;
exports.ScriptNetworkEvents = ScriptNetworkEvents;
exports.SelfClosingTags = SelfClosingTags;
exports.SortModifiers = SortModifiers;
exports.TAG_ALIASES = TAG_ALIASES;
exports.TAG_WEIGHTS = TAG_WEIGHTS;
exports.TagConfigKeys = TagConfigKeys;
exports.TagEntityBits = TagEntityBits;
exports.TagsWithInnerContent = TagsWithInnerContent;
exports.UniqueTags = UniqueTags;
exports.ValidHeadTags = ValidHeadTags;
exports.asArray = asArray;
exports.composableNames = composableNames;
exports.defineHeadPlugin = defineHeadPlugin;
exports.hashCode = hashCode;
exports.hashTag = hashTag;
exports.normaliseEntryTags = normaliseEntryTags;
exports.normaliseProps = normaliseProps;
exports.normaliseStyleClassProps = normaliseStyleClassProps;
exports.normaliseTag = normaliseTag;
exports.packMeta = packMeta;
exports.processTemplateParams = processTemplateParams;
exports.resolveMetaKeyType = resolveMetaKeyType;
exports.resolveMetaKeyValue = resolveMetaKeyValue;
exports.resolvePackedMetaObjectValue = resolvePackedMetaObjectValue;
exports.resolveTitleTemplate = resolveTitleTemplate;
exports.tagDedupeKey = tagDedupeKey;
exports.tagWeight = tagWeight;
exports.thenable = thenable;
exports.unpackMeta = unpackMeta;
exports.whitelistSafeInput = whitelistSafeInput;
+69
View File
@@ -0,0 +1,69 @@
import { HeadPluginInput, HeadTag, BaseMeta, MetaFlatInput, Head, HeadEntry, MaybeArray, HeadSafe, TemplateParams } from '@unhead/schema';
type Arrayable<T> = T | Array<T>;
declare function asArray<T>(value: Arrayable<T>): T[];
declare const SelfClosingTags: Set<string>;
declare const TagsWithInnerContent: Set<string>;
declare const HasElementTags: Set<string>;
declare const ValidHeadTags: Set<string>;
declare const UniqueTags: Set<string>;
declare const TagConfigKeys: Set<string>;
declare const IsBrowser: boolean;
declare const composableNames: string[];
declare function defineHeadPlugin(plugin: HeadPluginInput): HeadPluginInput;
declare function hashCode(s: string): string;
declare function hashTag(tag: HeadTag): string;
declare function resolveMetaKeyType(key: string): keyof BaseMeta;
declare function resolveMetaKeyValue(key: string): string;
declare function resolvePackedMetaObjectValue(value: string, key: string): string;
/**
* Converts a flat meta object into an array of meta entries.
* @param input
*/
declare function unpackMeta<T extends MetaFlatInput>(input: T): Required<Head>['meta'];
/**
* Convert an array of meta entries to a flat object.
* @param inputs
*/
declare function packMeta<T extends Required<Head>['meta']>(inputs: T): MetaFlatInput;
type Thenable<T> = Promise<T> | T;
declare function thenable<T, R>(val: T, thenFn: (val: Awaited<T>) => R): Promise<R> | R;
declare function normaliseTag<T extends HeadTag>(tagName: T['tag'], input: HeadTag['props'] | string, e: HeadEntry<T>, normalizedProps?: HeadTag['props']): Thenable<T | T[]>;
declare function normaliseStyleClassProps<T extends 'class' | 'style'>(key: T, v: Required<Required<Head>['htmlAttrs']['class']> | Required<Required<Head>['htmlAttrs']['style']>): string;
declare function normaliseProps<T extends HeadTag>(props: T['props'], virtual?: boolean): Thenable<T['props']>;
declare const TagEntityBits = 10;
declare function normaliseEntryTags<T extends object = Head>(e: HeadEntry<T>): Thenable<HeadTag[]>;
declare function whitelistSafeInput(input: Record<string, MaybeArray<Record<string, string>>>): HeadSafe;
declare const NetworkEvents: Set<string>;
declare const ScriptNetworkEvents: Set<string>;
declare const TAG_WEIGHTS: {
readonly base: -10;
readonly title: 10;
};
declare const TAG_ALIASES: {
readonly critical: -80;
readonly high: -10;
readonly low: 20;
};
declare function tagWeight<T extends HeadTag>(tag: T): number;
declare const SortModifiers: {
prefix: string;
offset: number;
}[];
declare function tagDedupeKey<T extends HeadTag>(tag: T): string | false;
declare function processTemplateParams(s: string, p: TemplateParams, sep: string, isJson?: boolean): string;
declare function resolveTitleTemplate(template: string | ((title?: string) => string | null) | null, title?: string): string | null;
export { type Arrayable, HasElementTags, IsBrowser, NetworkEvents, ScriptNetworkEvents, SelfClosingTags, SortModifiers, TAG_ALIASES, TAG_WEIGHTS, TagConfigKeys, TagEntityBits, TagsWithInnerContent, type Thenable, UniqueTags, ValidHeadTags, asArray, composableNames, defineHeadPlugin, hashCode, hashTag, normaliseEntryTags, normaliseProps, normaliseStyleClassProps, normaliseTag, packMeta, processTemplateParams, resolveMetaKeyType, resolveMetaKeyValue, resolvePackedMetaObjectValue, resolveTitleTemplate, tagDedupeKey, tagWeight, thenable, unpackMeta, whitelistSafeInput };
+69
View File
@@ -0,0 +1,69 @@
import { HeadPluginInput, HeadTag, BaseMeta, MetaFlatInput, Head, HeadEntry, MaybeArray, HeadSafe, TemplateParams } from '@unhead/schema';
type Arrayable<T> = T | Array<T>;
declare function asArray<T>(value: Arrayable<T>): T[];
declare const SelfClosingTags: Set<string>;
declare const TagsWithInnerContent: Set<string>;
declare const HasElementTags: Set<string>;
declare const ValidHeadTags: Set<string>;
declare const UniqueTags: Set<string>;
declare const TagConfigKeys: Set<string>;
declare const IsBrowser: boolean;
declare const composableNames: string[];
declare function defineHeadPlugin(plugin: HeadPluginInput): HeadPluginInput;
declare function hashCode(s: string): string;
declare function hashTag(tag: HeadTag): string;
declare function resolveMetaKeyType(key: string): keyof BaseMeta;
declare function resolveMetaKeyValue(key: string): string;
declare function resolvePackedMetaObjectValue(value: string, key: string): string;
/**
* Converts a flat meta object into an array of meta entries.
* @param input
*/
declare function unpackMeta<T extends MetaFlatInput>(input: T): Required<Head>['meta'];
/**
* Convert an array of meta entries to a flat object.
* @param inputs
*/
declare function packMeta<T extends Required<Head>['meta']>(inputs: T): MetaFlatInput;
type Thenable<T> = Promise<T> | T;
declare function thenable<T, R>(val: T, thenFn: (val: Awaited<T>) => R): Promise<R> | R;
declare function normaliseTag<T extends HeadTag>(tagName: T['tag'], input: HeadTag['props'] | string, e: HeadEntry<T>, normalizedProps?: HeadTag['props']): Thenable<T | T[]>;
declare function normaliseStyleClassProps<T extends 'class' | 'style'>(key: T, v: Required<Required<Head>['htmlAttrs']['class']> | Required<Required<Head>['htmlAttrs']['style']>): string;
declare function normaliseProps<T extends HeadTag>(props: T['props'], virtual?: boolean): Thenable<T['props']>;
declare const TagEntityBits = 10;
declare function normaliseEntryTags<T extends object = Head>(e: HeadEntry<T>): Thenable<HeadTag[]>;
declare function whitelistSafeInput(input: Record<string, MaybeArray<Record<string, string>>>): HeadSafe;
declare const NetworkEvents: Set<string>;
declare const ScriptNetworkEvents: Set<string>;
declare const TAG_WEIGHTS: {
readonly base: -10;
readonly title: 10;
};
declare const TAG_ALIASES: {
readonly critical: -80;
readonly high: -10;
readonly low: 20;
};
declare function tagWeight<T extends HeadTag>(tag: T): number;
declare const SortModifiers: {
prefix: string;
offset: number;
}[];
declare function tagDedupeKey<T extends HeadTag>(tag: T): string | false;
declare function processTemplateParams(s: string, p: TemplateParams, sep: string, isJson?: boolean): string;
declare function resolveTitleTemplate(template: string | ((title?: string) => string | null) | null, title?: string): string | null;
export { type Arrayable, HasElementTags, IsBrowser, NetworkEvents, ScriptNetworkEvents, SelfClosingTags, SortModifiers, TAG_ALIASES, TAG_WEIGHTS, TagConfigKeys, TagEntityBits, TagsWithInnerContent, type Thenable, UniqueTags, ValidHeadTags, asArray, composableNames, defineHeadPlugin, hashCode, hashTag, normaliseEntryTags, normaliseProps, normaliseStyleClassProps, normaliseTag, packMeta, processTemplateParams, resolveMetaKeyType, resolveMetaKeyValue, resolvePackedMetaObjectValue, resolveTitleTemplate, tagDedupeKey, tagWeight, thenable, unpackMeta, whitelistSafeInput };
+69
View File
@@ -0,0 +1,69 @@
import { HeadPluginInput, HeadTag, BaseMeta, MetaFlatInput, Head, HeadEntry, MaybeArray, HeadSafe, TemplateParams } from '@unhead/schema';
type Arrayable<T> = T | Array<T>;
declare function asArray<T>(value: Arrayable<T>): T[];
declare const SelfClosingTags: Set<string>;
declare const TagsWithInnerContent: Set<string>;
declare const HasElementTags: Set<string>;
declare const ValidHeadTags: Set<string>;
declare const UniqueTags: Set<string>;
declare const TagConfigKeys: Set<string>;
declare const IsBrowser: boolean;
declare const composableNames: string[];
declare function defineHeadPlugin(plugin: HeadPluginInput): HeadPluginInput;
declare function hashCode(s: string): string;
declare function hashTag(tag: HeadTag): string;
declare function resolveMetaKeyType(key: string): keyof BaseMeta;
declare function resolveMetaKeyValue(key: string): string;
declare function resolvePackedMetaObjectValue(value: string, key: string): string;
/**
* Converts a flat meta object into an array of meta entries.
* @param input
*/
declare function unpackMeta<T extends MetaFlatInput>(input: T): Required<Head>['meta'];
/**
* Convert an array of meta entries to a flat object.
* @param inputs
*/
declare function packMeta<T extends Required<Head>['meta']>(inputs: T): MetaFlatInput;
type Thenable<T> = Promise<T> | T;
declare function thenable<T, R>(val: T, thenFn: (val: Awaited<T>) => R): Promise<R> | R;
declare function normaliseTag<T extends HeadTag>(tagName: T['tag'], input: HeadTag['props'] | string, e: HeadEntry<T>, normalizedProps?: HeadTag['props']): Thenable<T | T[]>;
declare function normaliseStyleClassProps<T extends 'class' | 'style'>(key: T, v: Required<Required<Head>['htmlAttrs']['class']> | Required<Required<Head>['htmlAttrs']['style']>): string;
declare function normaliseProps<T extends HeadTag>(props: T['props'], virtual?: boolean): Thenable<T['props']>;
declare const TagEntityBits = 10;
declare function normaliseEntryTags<T extends object = Head>(e: HeadEntry<T>): Thenable<HeadTag[]>;
declare function whitelistSafeInput(input: Record<string, MaybeArray<Record<string, string>>>): HeadSafe;
declare const NetworkEvents: Set<string>;
declare const ScriptNetworkEvents: Set<string>;
declare const TAG_WEIGHTS: {
readonly base: -10;
readonly title: 10;
};
declare const TAG_ALIASES: {
readonly critical: -80;
readonly high: -10;
readonly low: 20;
};
declare function tagWeight<T extends HeadTag>(tag: T): number;
declare const SortModifiers: {
prefix: string;
offset: number;
}[];
declare function tagDedupeKey<T extends HeadTag>(tag: T): string | false;
declare function processTemplateParams(s: string, p: TemplateParams, sep: string, isJson?: boolean): string;
declare function resolveTitleTemplate(template: string | ((title?: string) => string | null) | null, title?: string): string | null;
export { type Arrayable, HasElementTags, IsBrowser, NetworkEvents, ScriptNetworkEvents, SelfClosingTags, SortModifiers, TAG_ALIASES, TAG_WEIGHTS, TagConfigKeys, TagEntityBits, TagsWithInnerContent, type Thenable, UniqueTags, ValidHeadTags, asArray, composableNames, defineHeadPlugin, hashCode, hashTag, normaliseEntryTags, normaliseProps, normaliseStyleClassProps, normaliseTag, packMeta, processTemplateParams, resolveMetaKeyType, resolveMetaKeyValue, resolvePackedMetaObjectValue, resolveTitleTemplate, tagDedupeKey, tagWeight, thenable, unpackMeta, whitelistSafeInput };
+641
View File
@@ -0,0 +1,641 @@
import { unpackToString, unpackToArray, packArray } from 'packrup';
function asArray(value) {
return Array.isArray(value) ? value : [value];
}
const SelfClosingTags = /* @__PURE__ */ new Set(["meta", "link", "base"]);
const TagsWithInnerContent = /* @__PURE__ */ new Set(["title", "titleTemplate", "script", "style", "noscript"]);
const HasElementTags = /* @__PURE__ */ new Set([
"base",
"meta",
"link",
"style",
"script",
"noscript"
]);
const ValidHeadTags = /* @__PURE__ */ new Set([
"title",
"titleTemplate",
"templateParams",
"base",
"htmlAttrs",
"bodyAttrs",
"meta",
"link",
"style",
"script",
"noscript"
]);
const UniqueTags = /* @__PURE__ */ new Set(["base", "title", "titleTemplate", "bodyAttrs", "htmlAttrs", "templateParams"]);
const TagConfigKeys = /* @__PURE__ */ new Set(["tagPosition", "tagPriority", "tagDuplicateStrategy", "children", "innerHTML", "textContent", "processTemplateParams"]);
const IsBrowser = typeof window !== "undefined";
const composableNames = [
"getActiveHead",
"useHead",
"useSeoMeta",
"useHeadSafe",
"useServerHead",
"useServerSeoMeta",
"useServerHeadSafe"
];
function defineHeadPlugin(plugin) {
return plugin;
}
function hashCode(s) {
let h = 9;
for (let i = 0; i < s.length; )
h = Math.imul(h ^ s.charCodeAt(i++), 9 ** 9);
return ((h ^ h >>> 9) + 65536).toString(16).substring(1, 8).toLowerCase();
}
function hashTag(tag) {
if (tag._h) {
return tag._h;
}
if (tag._d) {
return hashCode(tag._d);
}
let content = `${tag.tag}:${tag.textContent || tag.innerHTML || ""}:`;
for (const key in tag.props) {
content += `${key}:${String(tag.props[key])},`;
}
return hashCode(content);
}
const p = (p2) => ({ keyValue: p2, metaKey: "property" });
const k = (p2) => ({ keyValue: p2 });
const MetaPackingSchema = {
appleItunesApp: {
unpack: {
entrySeparator: ", ",
resolve({ key, value }) {
return `${fixKeyCase(key)}=${value}`;
}
}
},
articleExpirationTime: p("article:expiration_time"),
articleModifiedTime: p("article:modified_time"),
articlePublishedTime: p("article:published_time"),
bookReleaseDate: p("book:release_date"),
charset: {
metaKey: "charset"
},
contentSecurityPolicy: {
unpack: {
entrySeparator: "; ",
resolve({ key, value }) {
return `${fixKeyCase(key)} ${value}`;
}
},
metaKey: "http-equiv"
},
contentType: {
metaKey: "http-equiv"
},
defaultStyle: {
metaKey: "http-equiv"
},
fbAppId: p("fb:app_id"),
msapplicationConfig: k("msapplication-Config"),
msapplicationTileColor: k("msapplication-TileColor"),
msapplicationTileImage: k("msapplication-TileImage"),
ogAudioSecureUrl: p("og:audio:secure_url"),
ogAudioUrl: p("og:audio"),
ogImageSecureUrl: p("og:image:secure_url"),
ogImageUrl: p("og:image"),
ogSiteName: p("og:site_name"),
ogVideoSecureUrl: p("og:video:secure_url"),
ogVideoUrl: p("og:video"),
profileFirstName: p("profile:first_name"),
profileLastName: p("profile:last_name"),
profileUsername: p("profile:username"),
refresh: {
metaKey: "http-equiv",
unpack: {
entrySeparator: ";",
resolve({ key, value }) {
if (key === "seconds")
return `${value}`;
}
}
},
robots: {
unpack: {
entrySeparator: ", ",
resolve({ key, value }) {
if (typeof value === "boolean")
return `${fixKeyCase(key)}`;
else
return `${fixKeyCase(key)}:${value}`;
}
}
},
xUaCompatible: {
metaKey: "http-equiv"
}
};
const openGraphNamespaces = /* @__PURE__ */ new Set([
"og",
"book",
"article",
"profile"
]);
function resolveMetaKeyType(key) {
const fKey = fixKeyCase(key);
const prefixIndex = fKey.indexOf(":");
if (openGraphNamespaces.has(fKey.substring(0, prefixIndex)))
return "property";
return MetaPackingSchema[key]?.metaKey || "name";
}
function resolveMetaKeyValue(key) {
return MetaPackingSchema[key]?.keyValue || fixKeyCase(key);
}
function fixKeyCase(key) {
const updated = key.replace(/([A-Z])/g, "-$1").toLowerCase();
const prefixIndex = updated.indexOf("-");
const fKey = updated.substring(0, prefixIndex);
if (fKey === "twitter" || openGraphNamespaces.has(fKey))
return key.replace(/([A-Z])/g, ":$1").toLowerCase();
return updated;
}
function changeKeyCasingDeep(input) {
if (Array.isArray(input)) {
return input.map((entry) => changeKeyCasingDeep(entry));
}
if (typeof input !== "object" || Array.isArray(input))
return input;
const output = {};
for (const key in input) {
if (!Object.prototype.hasOwnProperty.call(input, key)) {
continue;
}
output[fixKeyCase(key)] = changeKeyCasingDeep(input[key]);
}
return output;
}
function resolvePackedMetaObjectValue(value, key) {
const definition = MetaPackingSchema[key];
if (key === "refresh")
return `${value.seconds};url=${value.url}`;
return unpackToString(
changeKeyCasingDeep(value),
{
keyValueSeparator: "=",
entrySeparator: ", ",
resolve({ value: value2, key: key2 }) {
if (value2 === null)
return "";
if (typeof value2 === "boolean")
return `${key2}`;
},
...definition?.unpack
}
);
}
const ObjectArrayEntries = /* @__PURE__ */ new Set(["og:image", "og:video", "og:audio", "twitter:image"]);
function sanitize(input) {
const out = {};
for (const k2 in input) {
if (!Object.prototype.hasOwnProperty.call(input, k2)) {
continue;
}
const v = input[k2];
if (String(v) !== "false" && k2)
out[k2] = v;
}
return out;
}
function handleObjectEntry(key, v) {
const value = sanitize(v);
const fKey = fixKeyCase(key);
const attr = resolveMetaKeyType(fKey);
if (ObjectArrayEntries.has(fKey)) {
const input = {};
for (const k2 in value) {
if (!Object.prototype.hasOwnProperty.call(value, k2)) {
continue;
}
input[`${key}${k2 === "url" ? "" : `${k2[0].toUpperCase()}${k2.slice(1)}`}`] = value[k2];
}
return unpackMeta(input).sort((a, b) => (a[attr]?.length || 0) - (b[attr]?.length || 0));
}
return [{ [attr]: fKey, ...value }];
}
function unpackMeta(input) {
const extras = [];
const primitives = {};
for (const key in input) {
if (!Object.prototype.hasOwnProperty.call(input, key)) {
continue;
}
const value = input[key];
if (!Array.isArray(value)) {
if (typeof value === "object" && value) {
if (ObjectArrayEntries.has(fixKeyCase(key))) {
extras.push(...handleObjectEntry(key, value));
continue;
}
primitives[key] = sanitize(value);
} else {
primitives[key] = value;
}
continue;
}
for (const v of value) {
extras.push(...typeof v === "string" ? unpackMeta({ [key]: v }) : handleObjectEntry(key, v));
}
}
const meta = unpackToArray(primitives, {
key({ key }) {
return resolveMetaKeyType(key);
},
value({ key }) {
return key === "charset" ? "charset" : "content";
},
resolveKeyData({ key }) {
return resolveMetaKeyValue(key);
},
resolveValueData({ value, key }) {
if (value === null)
return "_null";
if (typeof value === "object")
return resolvePackedMetaObjectValue(value, key);
return typeof value === "number" ? value.toString() : value;
}
});
return [...extras, ...meta].map((m) => {
if (m.content === "_null")
m.content = null;
return m;
});
}
function packMeta(inputs) {
const mappedPackingSchema = Object.entries(MetaPackingSchema).map(([key, value]) => [key, value.keyValue]);
return packArray(inputs, {
key: ["name", "property", "httpEquiv", "http-equiv", "charset"],
value: ["content", "charset"],
resolveKey(k2) {
let key = mappedPackingSchema.filter((sk) => sk[1] === k2)?.[0]?.[0] || k2;
const replacer = (_, letter) => letter?.toUpperCase();
key = key.replace(/:([a-z])/g, replacer).replace(/-([a-z])/g, replacer);
return key;
}
});
}
function thenable(val, thenFn) {
if (val instanceof Promise) {
return val.then(thenFn);
}
return thenFn(val);
}
function normaliseTag(tagName, input, e, normalizedProps) {
const props = normalizedProps || normaliseProps(
// explicitly check for an object
// @ts-expect-error untyped
typeof input === "object" && typeof input !== "function" && !(input instanceof Promise) ? { ...input } : { [tagName === "script" || tagName === "noscript" || tagName === "style" ? "innerHTML" : "textContent"]: input },
tagName === "templateParams" || tagName === "titleTemplate"
);
if (props instanceof Promise) {
return props.then((val) => normaliseTag(tagName, input, e, val));
}
const tag = {
tag: tagName,
props
};
for (const k of TagConfigKeys) {
const val = tag.props[k] !== void 0 ? tag.props[k] : e[k];
if (val !== void 0) {
if (!(k === "innerHTML" || k === "textContent" || k === "children") || TagsWithInnerContent.has(tag.tag)) {
tag[k === "children" ? "innerHTML" : k] = val;
}
delete tag.props[k];
}
}
if (tag.props.body) {
tag.tagPosition = "bodyClose";
delete tag.props.body;
}
if (tag.tag === "script") {
if (typeof tag.innerHTML === "object") {
tag.innerHTML = JSON.stringify(tag.innerHTML);
tag.props.type = tag.props.type || "application/json";
}
}
return Array.isArray(tag.props.content) ? tag.props.content.map((v) => ({ ...tag, props: { ...tag.props, content: v } })) : tag;
}
function normaliseStyleClassProps(key, v) {
const sep = key === "class" ? " " : ";";
if (v && typeof v === "object" && !Array.isArray(v)) {
v = Object.entries(v).filter(([, v2]) => v2).map(([k, v2]) => key === "style" ? `${k}:${v2}` : k);
}
return String(Array.isArray(v) ? v.join(sep) : v)?.split(sep).filter((c) => Boolean(c.trim())).join(sep);
}
function nestedNormaliseProps(props, virtual, keys, startIndex) {
for (let i = startIndex; i < keys.length; i += 1) {
const k = keys[i];
if (k === "class" || k === "style") {
props[k] = normaliseStyleClassProps(k, props[k]);
continue;
}
if (props[k] instanceof Promise) {
return props[k].then((val) => {
props[k] = val;
return nestedNormaliseProps(props, virtual, keys, i);
});
}
if (!virtual && !TagConfigKeys.has(k)) {
const v = String(props[k]);
const isDataKey = k.startsWith("data-");
if (v === "true" || v === "") {
props[k] = isDataKey ? "true" : true;
} else if (!props[k]) {
if (isDataKey && v === "false")
props[k] = "false";
else
delete props[k];
}
}
}
}
function normaliseProps(props, virtual = false) {
const resolvedProps = nestedNormaliseProps(props, virtual, Object.keys(props), 0);
if (resolvedProps instanceof Promise) {
return resolvedProps.then(() => props);
}
return props;
}
const TagEntityBits = 10;
function nestedNormaliseEntryTags(headTags, tagPromises, startIndex) {
for (let i = startIndex; i < tagPromises.length; i += 1) {
const tags = tagPromises[i];
if (tags instanceof Promise) {
return tags.then((val) => {
tagPromises[i] = val;
return nestedNormaliseEntryTags(headTags, tagPromises, i);
});
}
if (Array.isArray(tags)) {
headTags.push(...tags);
} else {
headTags.push(tags);
}
}
}
function normaliseEntryTags(e) {
const tagPromises = [];
const input = e.resolvedInput;
for (const k in input) {
if (!Object.prototype.hasOwnProperty.call(input, k)) {
continue;
}
const v = input[k];
if (v === void 0 || !ValidHeadTags.has(k)) {
continue;
}
if (Array.isArray(v)) {
for (const props of v) {
tagPromises.push(normaliseTag(k, props, e));
}
continue;
}
tagPromises.push(normaliseTag(k, v, e));
}
if (tagPromises.length === 0) {
return [];
}
const headTags = [];
return thenable(nestedNormaliseEntryTags(headTags, tagPromises, 0), () => headTags.map((t, i) => {
t._e = e._i;
e.mode && (t._m = e.mode);
t._p = (e._i << TagEntityBits) + i;
return t;
}));
}
const WhitelistAttributes = {
htmlAttrs: ["id", "class", "lang", "dir"],
bodyAttrs: ["id", "class"],
meta: ["id", "name", "property", "charset", "content"],
noscript: ["id", "textContent"],
script: ["id", "type", "textContent"],
link: ["id", "color", "crossorigin", "fetchpriority", "href", "hreflang", "imagesrcset", "imagesizes", "integrity", "media", "referrerpolicy", "rel", "sizes", "type"]
};
function acceptDataAttrs(value) {
const filtered = {};
Object.keys(value || {}).filter((a) => a.startsWith("data-")).forEach((a) => {
filtered[a] = value[a];
});
return filtered;
}
function whitelistSafeInput(input) {
const filtered = {};
Object.keys(input).forEach((key) => {
const tagValue = input[key];
if (!tagValue)
return;
switch (key) {
// always safe
case "title":
case "titleTemplate":
case "templateParams":
filtered[key] = tagValue;
break;
case "htmlAttrs":
case "bodyAttrs":
filtered[key] = acceptDataAttrs(tagValue);
WhitelistAttributes[key].forEach((a) => {
if (tagValue[a])
filtered[key][a] = tagValue[a];
});
break;
// meta is safe, except for http-equiv
case "meta":
if (Array.isArray(tagValue)) {
filtered[key] = tagValue.map((meta) => {
const safeMeta = acceptDataAttrs(meta);
WhitelistAttributes.meta.forEach((key2) => {
if (meta[key2])
safeMeta[key2] = meta[key2];
});
return safeMeta;
}).filter((meta) => Object.keys(meta).length > 0);
}
break;
// link tags we don't allow stylesheets, scripts, preloading, prerendering, prefetching, etc
case "link":
if (Array.isArray(tagValue)) {
filtered[key] = tagValue.map((meta) => {
const link = acceptDataAttrs(meta);
WhitelistAttributes.link.forEach((key2) => {
const val = meta[key2];
if (key2 === "rel" && (val === "stylesheet" || val === "canonical" || val === "modulepreload" || val === "prerender" || val === "preload" || val === "prefetch"))
return;
if (key2 === "href") {
if (val.includes("javascript:") || val.includes("data:"))
return;
link[key2] = val;
} else if (val) {
link[key2] = val;
}
});
return link;
}).filter((link) => Object.keys(link).length > 1 && !!link.rel);
}
break;
case "noscript":
if (Array.isArray(tagValue)) {
filtered[key] = tagValue.map((meta) => {
const noscript = acceptDataAttrs(meta);
WhitelistAttributes.noscript.forEach((key2) => {
if (meta[key2])
noscript[key2] = meta[key2];
});
return noscript;
}).filter((meta) => Object.keys(meta).length > 0);
}
break;
// we only allow JSON in scripts
case "script":
if (Array.isArray(tagValue)) {
filtered[key] = tagValue.map((script) => {
const safeScript = acceptDataAttrs(script);
WhitelistAttributes.script.forEach((s) => {
if (script[s]) {
if (s === "textContent") {
try {
const jsonVal = typeof script[s] === "string" ? JSON.parse(script[s]) : script[s];
safeScript[s] = JSON.stringify(jsonVal, null, 0);
} catch (e) {
}
} else {
safeScript[s] = script[s];
}
}
});
return safeScript;
}).filter((meta) => Object.keys(meta).length > 0);
}
break;
}
});
return filtered;
}
const NetworkEvents = /* @__PURE__ */ new Set(["onload", "onerror", "onabort", "onprogress", "onloadstart"]);
const ScriptNetworkEvents = /* @__PURE__ */ new Set(["onload", "onerror"]);
const TAG_WEIGHTS = {
// tags
base: -10,
title: 10
};
const TAG_ALIASES = {
// relative scores to their default values
critical: -80,
high: -10,
low: 20
};
function tagWeight(tag) {
const priority = tag.tagPriority;
if (typeof priority === "number")
return priority;
let weight = 100;
if (tag.tag === "meta") {
if (tag.props["http-equiv"] === "content-security-policy")
weight = -30;
else if (tag.props.charset)
weight = -20;
else if (tag.props.name === "viewport")
weight = -15;
} else if (tag.tag === "link" && tag.props.rel === "preconnect") {
weight = 20;
} else if (tag.tag in TAG_WEIGHTS) {
weight = TAG_WEIGHTS[tag.tag];
}
if (priority && priority in TAG_ALIASES) {
return weight + TAG_ALIASES[priority];
}
return weight;
}
const SortModifiers = [{ prefix: "before:", offset: -1 }, { prefix: "after:", offset: 1 }];
const allowedMetaProperties = ["name", "property", "http-equiv"];
function tagDedupeKey(tag) {
const { props, tag: tagName } = tag;
if (UniqueTags.has(tagName))
return tagName;
if (tagName === "link" && props.rel === "canonical")
return "canonical";
if (props.charset)
return "charset";
if (props.id) {
return `${tagName}:id:${props.id}`;
}
for (const n of allowedMetaProperties) {
if (props[n] !== void 0) {
return `${tagName}:${n}:${props[n]}`;
}
}
return false;
}
const sepSub = "%separator";
function sub(p, token, isJson = false) {
let val;
if (token === "s" || token === "pageTitle") {
val = p.pageTitle;
} else if (token.includes(".")) {
const dotIndex = token.indexOf(".");
val = p[token.substring(0, dotIndex)]?.[token.substring(dotIndex + 1)];
} else {
val = p[token];
}
if (val !== void 0) {
return isJson ? (val || "").replace(/"/g, '\\"') : val || "";
}
return void 0;
}
const sepSubRe = new RegExp(`${sepSub}(?:\\s*${sepSub})*`, "g");
function processTemplateParams(s, p, sep, isJson = false) {
if (typeof s !== "string" || !s.includes("%"))
return s;
let decoded = s;
try {
decoded = decodeURI(s);
} catch {
}
const tokens = decoded.match(/%\w+(?:\.\w+)?/g);
if (!tokens) {
return s;
}
const hasSepSub = s.includes(sepSub);
s = s.replace(/%\w+(?:\.\w+)?/g, (token) => {
if (token === sepSub || !tokens.includes(token)) {
return token;
}
const re = sub(p, token.slice(1), isJson);
return re !== void 0 ? re : token;
}).trim();
if (hasSepSub) {
if (s.endsWith(sepSub))
s = s.slice(0, -sepSub.length);
if (s.startsWith(sepSub))
s = s.slice(sepSub.length);
s = s.replace(sepSubRe, sep).trim();
}
return s;
}
function resolveTitleTemplate(template, title) {
if (template == null)
return title || null;
if (typeof template === "function")
return template(title);
return template;
}
export { HasElementTags, IsBrowser, NetworkEvents, ScriptNetworkEvents, SelfClosingTags, SortModifiers, TAG_ALIASES, TAG_WEIGHTS, TagConfigKeys, TagEntityBits, TagsWithInnerContent, UniqueTags, ValidHeadTags, asArray, composableNames, defineHeadPlugin, hashCode, hashTag, normaliseEntryTags, normaliseProps, normaliseStyleClassProps, normaliseTag, packMeta, processTemplateParams, resolveMetaKeyType, resolveMetaKeyValue, resolvePackedMetaObjectValue, resolveTitleTemplate, tagDedupeKey, tagWeight, thenable, unpackMeta, whitelistSafeInput };
+45
View File
@@ -0,0 +1,45 @@
{
"name": "@unhead/shared",
"type": "module",
"version": "1.11.20",
"author": "Harlan Wilton <harlan@harlanzw.com>",
"license": "MIT",
"funding": "https://github.com/sponsors/harlan-zw",
"homepage": "https://unhead.unjs.io",
"repository": {
"type": "git",
"url": "git+https://github.com/unjs/unhead.git",
"directory": "packages/schema"
},
"bugs": {
"url": "https://github.com/unjs/unhead/issues"
},
"keywords": [
"head",
"meta tags",
"types"
],
"sideEffects": false,
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
}
},
"main": "dist/index.cjs",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"dependencies": {
"packrup": "^0.1.2",
"@unhead/schema": "1.11.20"
},
"scripts": {
"build": "unbuild .",
"stub": "unbuild . --stub",
"export:sizes": "npx export-size . -r"
}
}