unhead 1.0.21 → 1.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/index.cjs +654 -512
- package/dist/index.d.ts +143 -30
- package/dist/index.mjs +614 -492
- package/package.json +9 -10
package/dist/index.cjs
CHANGED
|
@@ -2,300 +2,53 @@
|
|
|
2
2
|
|
|
3
3
|
const hookable = require('hookable');
|
|
4
4
|
const dom = require('@unhead/dom');
|
|
5
|
+
const shared = require('@unhead/shared');
|
|
6
|
+
const packrup = require('packrup');
|
|
5
7
|
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return tag;
|
|
25
|
-
}
|
|
26
|
-
tag.props = await normaliseProps({ ...input });
|
|
27
|
-
["children", "innerHtml", "innerHTML"].forEach((key) => {
|
|
28
|
-
if (typeof tag.props[key] !== "undefined") {
|
|
29
|
-
tag.children = tag.props[key];
|
|
30
|
-
if (typeof tag.children === "object")
|
|
31
|
-
tag.children = JSON.stringify(tag.children);
|
|
32
|
-
delete tag.props[key];
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
Object.keys(tag.props).filter((k) => TagConfigKeys.includes(k)).forEach((k) => {
|
|
36
|
-
tag[k] = tag.props[k];
|
|
37
|
-
delete tag.props[k];
|
|
38
|
-
});
|
|
39
|
-
if (typeof tag.props.class === "object" && !Array.isArray(tag.props.class)) {
|
|
40
|
-
tag.props.class = Object.keys(tag.props.class).filter((k) => tag.props.class[k]);
|
|
41
|
-
}
|
|
42
|
-
if (Array.isArray(tag.props.class))
|
|
43
|
-
tag.props.class = tag.props.class.join(" ");
|
|
44
|
-
if (tag.props.content && Array.isArray(tag.props.content)) {
|
|
45
|
-
return tag.props.content.map((v, i) => {
|
|
46
|
-
const newTag = { ...tag, props: { ...tag.props } };
|
|
47
|
-
newTag.props.content = v;
|
|
48
|
-
newTag.key = `${tag.props.name || tag.props.property}:${i}`;
|
|
49
|
-
return newTag;
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
return tag;
|
|
53
|
-
}
|
|
54
|
-
async function normaliseProps(props) {
|
|
55
|
-
for (const k of Object.keys(props)) {
|
|
56
|
-
if (props[k] instanceof Promise) {
|
|
57
|
-
props[k] = await props[k];
|
|
58
|
-
}
|
|
59
|
-
if (String(props[k]) === "true") {
|
|
60
|
-
props[k] = "";
|
|
61
|
-
} else if (String(props[k]) === "false") {
|
|
62
|
-
delete props[k];
|
|
63
|
-
}
|
|
8
|
+
const TAG_WEIGHTS = {
|
|
9
|
+
// aliases
|
|
10
|
+
critical: 2,
|
|
11
|
+
high: 9,
|
|
12
|
+
low: 12,
|
|
13
|
+
// tags
|
|
14
|
+
base: -1,
|
|
15
|
+
title: 1,
|
|
16
|
+
meta: 10
|
|
17
|
+
};
|
|
18
|
+
function tagWeight(tag) {
|
|
19
|
+
if (typeof tag.tagPriority === "number")
|
|
20
|
+
return tag.tagPriority;
|
|
21
|
+
if (tag.tag === "meta") {
|
|
22
|
+
if (tag.props.charset)
|
|
23
|
+
return -2;
|
|
24
|
+
if (tag.props["http-equiv"] === "content-security-policy")
|
|
25
|
+
return 0;
|
|
64
26
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
function unpackToArray(input, options) {
|
|
69
|
-
const unpacked = [];
|
|
70
|
-
const kFn = options.resolveKeyData || ((ctx) => ctx.key);
|
|
71
|
-
const vFn = options.resolveValueData || ((ctx) => ctx.value);
|
|
72
|
-
for (const [k, v] of Object.entries(input)) {
|
|
73
|
-
unpacked.push(...(Array.isArray(v) ? v : [v]).map((i) => {
|
|
74
|
-
const ctx = { key: k, value: i };
|
|
75
|
-
const val = vFn(ctx);
|
|
76
|
-
if (typeof val === "object")
|
|
77
|
-
return unpackToArray(val, options);
|
|
78
|
-
if (Array.isArray(val))
|
|
79
|
-
return val;
|
|
80
|
-
return {
|
|
81
|
-
[typeof options.key === "function" ? options.key(ctx) : options.key]: kFn(ctx),
|
|
82
|
-
[typeof options.value === "function" ? options.value(ctx) : options.value]: val
|
|
83
|
-
};
|
|
84
|
-
}).flat());
|
|
27
|
+
const key = tag.tagPriority || tag.tag;
|
|
28
|
+
if (key in TAG_WEIGHTS) {
|
|
29
|
+
return TAG_WEIGHTS[key];
|
|
85
30
|
}
|
|
86
|
-
return
|
|
31
|
+
return 10;
|
|
87
32
|
}
|
|
88
|
-
|
|
89
|
-
function
|
|
90
|
-
return
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
value2 = value2.replace(new RegExp(options.wrapValue, "g"), `\\${options.wrapValue}`);
|
|
102
|
-
value2 = `${options.wrapValue}${value2}${options.wrapValue}`;
|
|
103
|
-
}
|
|
104
|
-
return `${key}${options.keyValueSeparator || ""}${value2}`;
|
|
105
|
-
}).join(options.entrySeparator || "");
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const MetaPackingSchema = {
|
|
109
|
-
robots: {
|
|
110
|
-
unpack: {
|
|
111
|
-
keyValueSeparator: ":"
|
|
112
|
-
}
|
|
113
|
-
},
|
|
114
|
-
// Pragma directives
|
|
115
|
-
contentSecurityPolicy: {
|
|
116
|
-
unpack: {
|
|
117
|
-
keyValueSeparator: " ",
|
|
118
|
-
entrySeparator: "; "
|
|
119
|
-
},
|
|
120
|
-
metaKey: "http-equiv"
|
|
121
|
-
},
|
|
122
|
-
fbAppId: {
|
|
123
|
-
keyValue: "fb:app_id",
|
|
124
|
-
metaKey: "property"
|
|
125
|
-
},
|
|
126
|
-
msapplicationTileImage: {
|
|
127
|
-
keyValue: "msapplication-TileImage"
|
|
128
|
-
},
|
|
129
|
-
/**
|
|
130
|
-
* Tile colour for windows
|
|
131
|
-
*/
|
|
132
|
-
msapplicationTileColor: {
|
|
133
|
-
keyValue: "msapplication-TileColor"
|
|
134
|
-
},
|
|
135
|
-
/**
|
|
136
|
-
* URL of a config for windows tile.
|
|
137
|
-
*/
|
|
138
|
-
msapplicationConfig: {
|
|
139
|
-
keyValue: "msapplication-Config"
|
|
140
|
-
},
|
|
141
|
-
charset: {
|
|
142
|
-
metaKey: "charset"
|
|
143
|
-
},
|
|
144
|
-
contentType: {
|
|
145
|
-
metaKey: "http-equiv"
|
|
146
|
-
},
|
|
147
|
-
defaultStyle: {
|
|
148
|
-
metaKey: "http-equiv"
|
|
149
|
-
},
|
|
150
|
-
xUaCompatible: {
|
|
151
|
-
metaKey: "http-equiv"
|
|
152
|
-
},
|
|
153
|
-
refresh: {
|
|
154
|
-
metaKey: "http-equiv"
|
|
155
|
-
}
|
|
156
|
-
};
|
|
157
|
-
function resolveMetaKeyType(key) {
|
|
158
|
-
return PropertyPrefixKeys.test(key) ? "property" : MetaPackingSchema[key]?.metaKey || "name";
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const ArrayableInputs = ["Image", "Video", "Audio"];
|
|
162
|
-
function unpackMeta(input) {
|
|
163
|
-
const extras = [];
|
|
164
|
-
ArrayableInputs.forEach((key) => {
|
|
165
|
-
const ogKey = `og:${key.toLowerCase()}`;
|
|
166
|
-
const inputKey = `og${key}`;
|
|
167
|
-
const val = input[inputKey];
|
|
168
|
-
if (typeof val === "object") {
|
|
169
|
-
(Array.isArray(val) ? val : [val]).forEach((entry) => {
|
|
170
|
-
if (!entry)
|
|
171
|
-
return;
|
|
172
|
-
const unpackedEntry = unpackToArray(entry, {
|
|
173
|
-
key: "property",
|
|
174
|
-
value: "content",
|
|
175
|
-
resolveKeyData({ key: key2 }) {
|
|
176
|
-
return fixKeyCase(`${ogKey}${key2 !== "url" ? `:${key2}` : ""}`);
|
|
177
|
-
},
|
|
178
|
-
resolveValueData({ value }) {
|
|
179
|
-
return typeof value === "number" ? value.toString() : value;
|
|
180
|
-
}
|
|
181
|
-
});
|
|
182
|
-
extras.push(
|
|
183
|
-
...unpackedEntry.sort((a, b) => a.property === ogKey ? -1 : b.property === ogKey ? 1 : 0)
|
|
184
|
-
);
|
|
185
|
-
});
|
|
186
|
-
delete input[inputKey];
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
const meta = unpackToArray(input, {
|
|
190
|
-
key({ key }) {
|
|
191
|
-
return resolveMetaKeyType(key);
|
|
192
|
-
},
|
|
193
|
-
value({ key }) {
|
|
194
|
-
return key === "charset" ? "charset" : "content";
|
|
195
|
-
},
|
|
196
|
-
resolveKeyData({ key }) {
|
|
197
|
-
return MetaPackingSchema[key]?.keyValue || fixKeyCase(key);
|
|
198
|
-
},
|
|
199
|
-
resolveValueData({ value, key }) {
|
|
200
|
-
if (value === null)
|
|
201
|
-
return "_null";
|
|
202
|
-
if (typeof value === "object") {
|
|
203
|
-
const definition = MetaPackingSchema[key];
|
|
204
|
-
if (key === "refresh")
|
|
205
|
-
return `${value.seconds};url=${value.url}`;
|
|
206
|
-
return unpackToString(
|
|
207
|
-
changeKeyCasingDeep(value),
|
|
208
|
-
{
|
|
209
|
-
entrySeparator: ", ",
|
|
210
|
-
keyValueSeparator: "=",
|
|
211
|
-
resolve({ value: value2, key: key2 }) {
|
|
212
|
-
if (value2 === null)
|
|
213
|
-
return "";
|
|
214
|
-
if (typeof value2 === "boolean")
|
|
215
|
-
return `${key2}`;
|
|
216
|
-
},
|
|
217
|
-
...definition?.unpack
|
|
33
|
+
const SortModifiers = [{ prefix: "before:", offset: -1 }, { prefix: "after:", offset: 1 }];
|
|
34
|
+
function SortTagsPlugin() {
|
|
35
|
+
return shared.defineHeadPlugin({
|
|
36
|
+
hooks: {
|
|
37
|
+
"tags:resolve": (ctx) => {
|
|
38
|
+
const tagPositionForKey = (key) => ctx.tags.find((tag) => tag._d === key)?._p;
|
|
39
|
+
for (const { prefix, offset } of SortModifiers) {
|
|
40
|
+
for (const tag of ctx.tags.filter((tag2) => typeof tag2.tagPriority === "string" && tag2.tagPriority.startsWith(prefix))) {
|
|
41
|
+
const position = tagPositionForKey(
|
|
42
|
+
tag.tagPriority.replace(prefix, "")
|
|
43
|
+
);
|
|
44
|
+
if (typeof position !== "undefined")
|
|
45
|
+
tag._p = position + offset;
|
|
218
46
|
}
|
|
219
|
-
|
|
47
|
+
}
|
|
48
|
+
ctx.tags.sort((a, b) => a._p - b._p).sort((a, b) => tagWeight(a) - tagWeight(b));
|
|
220
49
|
}
|
|
221
|
-
return typeof value === "number" ? value.toString() : value;
|
|
222
50
|
}
|
|
223
51
|
});
|
|
224
|
-
return [...extras, ...meta].filter((v) => typeof v.content === "undefined" || v.content !== "_null");
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const PropertyPrefixKeys = /^(og|fb)/;
|
|
228
|
-
const ColonPrefixKeys = /^(og|twitter|fb)/;
|
|
229
|
-
function fixKeyCase(key) {
|
|
230
|
-
key = key.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
231
|
-
if (ColonPrefixKeys.test(key)) {
|
|
232
|
-
key = key.replace("secure-url", "secure_url").replace(/-/g, ":");
|
|
233
|
-
}
|
|
234
|
-
return key;
|
|
235
|
-
}
|
|
236
|
-
function changeKeyCasingDeep(input) {
|
|
237
|
-
if (Array.isArray(input)) {
|
|
238
|
-
return input.map((entry) => changeKeyCasingDeep(entry));
|
|
239
|
-
}
|
|
240
|
-
if (typeof input !== "object" || Array.isArray(input))
|
|
241
|
-
return input;
|
|
242
|
-
const output = {};
|
|
243
|
-
for (const [key, value] of Object.entries(input))
|
|
244
|
-
output[fixKeyCase(key)] = changeKeyCasingDeep(value);
|
|
245
|
-
return output;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const tagWeight = (tag) => {
|
|
249
|
-
if (typeof tag.tagPriority === "number")
|
|
250
|
-
return tag.tagPriority;
|
|
251
|
-
switch (tag.tagPriority) {
|
|
252
|
-
case "critical":
|
|
253
|
-
return 2;
|
|
254
|
-
case "high":
|
|
255
|
-
return 9;
|
|
256
|
-
case "low":
|
|
257
|
-
return 12;
|
|
258
|
-
}
|
|
259
|
-
switch (tag.tag) {
|
|
260
|
-
case "base":
|
|
261
|
-
return -1;
|
|
262
|
-
case "title":
|
|
263
|
-
return 1;
|
|
264
|
-
case "meta":
|
|
265
|
-
if (tag.props.charset)
|
|
266
|
-
return -2;
|
|
267
|
-
if (tag.props["http-equiv"] === "content-security-policy")
|
|
268
|
-
return 0;
|
|
269
|
-
return 10;
|
|
270
|
-
default:
|
|
271
|
-
return 10;
|
|
272
|
-
}
|
|
273
|
-
};
|
|
274
|
-
const sortTags = (aTag, bTag) => {
|
|
275
|
-
return tagWeight(aTag) - tagWeight(bTag);
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
const UniqueTags = ["base", "title", "titleTemplate", "bodyAttrs", "htmlAttrs"];
|
|
279
|
-
function tagDedupeKey(tag, fn) {
|
|
280
|
-
const { props, tag: tagName } = tag;
|
|
281
|
-
if (UniqueTags.includes(tagName))
|
|
282
|
-
return tagName;
|
|
283
|
-
if (tagName === "link" && props.rel === "canonical")
|
|
284
|
-
return "canonical";
|
|
285
|
-
if (props.charset)
|
|
286
|
-
return "charset";
|
|
287
|
-
const name = ["id"];
|
|
288
|
-
if (tagName === "meta")
|
|
289
|
-
name.push(...["name", "property", "http-equiv"]);
|
|
290
|
-
for (const n of name) {
|
|
291
|
-
if (typeof props[n] !== "undefined") {
|
|
292
|
-
const val = String(props[n]);
|
|
293
|
-
if (fn && !fn(val))
|
|
294
|
-
return false;
|
|
295
|
-
return `${tagName}:${n}:${val}`;
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
return false;
|
|
299
52
|
}
|
|
300
53
|
|
|
301
54
|
const renderTitleTemplate = (template, title) => {
|
|
@@ -305,127 +58,44 @@ const renderTitleTemplate = (template, title) => {
|
|
|
305
58
|
return template(title);
|
|
306
59
|
return template.replace("%s", title ?? "");
|
|
307
60
|
};
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
const titleIdx = tags.findIndex((i) => i.tag === "title");
|
|
311
|
-
if (titleIdx !== -1 && titleTemplateIdx !== -1) {
|
|
312
|
-
const newTitle = renderTitleTemplate(
|
|
313
|
-
tags[titleTemplateIdx].children,
|
|
314
|
-
tags[titleIdx].children
|
|
315
|
-
);
|
|
316
|
-
if (newTitle !== null) {
|
|
317
|
-
tags[titleIdx].children = newTitle || tags[titleIdx].children;
|
|
318
|
-
} else {
|
|
319
|
-
delete tags[titleIdx];
|
|
320
|
-
}
|
|
321
|
-
} else if (titleTemplateIdx !== -1) {
|
|
322
|
-
const newTitle = renderTitleTemplate(
|
|
323
|
-
tags[titleTemplateIdx].children
|
|
324
|
-
);
|
|
325
|
-
if (newTitle !== null) {
|
|
326
|
-
tags[titleTemplateIdx].children = newTitle;
|
|
327
|
-
tags[titleTemplateIdx].tag = "title";
|
|
328
|
-
titleTemplateIdx = -1;
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
if (titleTemplateIdx !== -1) {
|
|
332
|
-
delete tags[titleTemplateIdx];
|
|
333
|
-
}
|
|
334
|
-
return tags.filter(Boolean);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
const DedupesTagsPlugin = (options) => {
|
|
338
|
-
options = options || {};
|
|
339
|
-
const dedupeKeys = options.dedupeKeys || ["hid", "vmid", "key"];
|
|
340
|
-
return defineHeadPlugin({
|
|
341
|
-
hooks: {
|
|
342
|
-
"tag:normalise": function({ tag }) {
|
|
343
|
-
dedupeKeys.forEach((key) => {
|
|
344
|
-
if (tag.props[key]) {
|
|
345
|
-
tag.key = tag.props[key];
|
|
346
|
-
delete tag.props[key];
|
|
347
|
-
}
|
|
348
|
-
});
|
|
349
|
-
const dedupe = tag.key ? `${tag.tag}:${tag.key}` : tagDedupeKey(tag);
|
|
350
|
-
if (dedupe)
|
|
351
|
-
tag._d = dedupe;
|
|
352
|
-
},
|
|
353
|
-
"tags:resolve": function(ctx) {
|
|
354
|
-
const deduping = {};
|
|
355
|
-
ctx.tags.forEach((tag) => {
|
|
356
|
-
let dedupeKey = tag._d || tag._p;
|
|
357
|
-
const dupedTag = deduping[dedupeKey];
|
|
358
|
-
if (dupedTag) {
|
|
359
|
-
let strategy = tag?.tagDuplicateStrategy;
|
|
360
|
-
if (!strategy && (tag.tag === "htmlAttrs" || tag.tag === "bodyAttrs"))
|
|
361
|
-
strategy = "merge";
|
|
362
|
-
if (strategy === "merge") {
|
|
363
|
-
const oldProps = dupedTag.props;
|
|
364
|
-
["class", "style"].forEach((key) => {
|
|
365
|
-
if (tag.props[key] && oldProps[key]) {
|
|
366
|
-
if (key === "style" && !oldProps[key].endsWith(";"))
|
|
367
|
-
oldProps[key] += ";";
|
|
368
|
-
tag.props[key] = `${oldProps[key]} ${tag.props[key]}`;
|
|
369
|
-
}
|
|
370
|
-
});
|
|
371
|
-
deduping[dedupeKey].props = {
|
|
372
|
-
...oldProps,
|
|
373
|
-
...tag.props
|
|
374
|
-
};
|
|
375
|
-
return;
|
|
376
|
-
} else if (tag._e === dupedTag._e) {
|
|
377
|
-
dedupeKey = tag._d = `${dedupeKey}:${tag._p}`;
|
|
378
|
-
}
|
|
379
|
-
const propCount = Object.keys(tag.props).length;
|
|
380
|
-
if ((propCount === 0 || propCount === 1 && typeof tag.props["data-h-key"] !== "undefined") && !tag.children) {
|
|
381
|
-
delete deduping[dedupeKey];
|
|
382
|
-
return;
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
deduping[dedupeKey] = tag;
|
|
386
|
-
});
|
|
387
|
-
ctx.tags = Object.values(deduping);
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
});
|
|
391
|
-
};
|
|
392
|
-
|
|
393
|
-
const SortTagsPlugin = () => {
|
|
394
|
-
return defineHeadPlugin({
|
|
61
|
+
const TitleTemplatePlugin = () => {
|
|
62
|
+
return shared.defineHeadPlugin({
|
|
395
63
|
hooks: {
|
|
396
64
|
"tags:resolve": (ctx) => {
|
|
397
|
-
const
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
const
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
65
|
+
const { tags } = ctx;
|
|
66
|
+
let titleTemplateIdx = tags.findIndex((i) => i.tag === "titleTemplate");
|
|
67
|
+
const titleIdx = tags.findIndex((i) => i.tag === "title");
|
|
68
|
+
if (titleIdx !== -1 && titleTemplateIdx !== -1) {
|
|
69
|
+
const newTitle = renderTitleTemplate(
|
|
70
|
+
tags[titleTemplateIdx].textContent,
|
|
71
|
+
tags[titleIdx].textContent
|
|
72
|
+
);
|
|
73
|
+
if (newTitle !== null) {
|
|
74
|
+
tags[titleIdx].textContent = newTitle || tags[titleIdx].textContent;
|
|
75
|
+
} else {
|
|
76
|
+
delete tags[titleIdx];
|
|
77
|
+
}
|
|
78
|
+
} else if (titleTemplateIdx !== -1) {
|
|
79
|
+
const newTitle = renderTitleTemplate(
|
|
80
|
+
tags[titleTemplateIdx].textContent
|
|
81
|
+
);
|
|
82
|
+
if (newTitle !== null) {
|
|
83
|
+
tags[titleTemplateIdx].textContent = newTitle;
|
|
84
|
+
tags[titleTemplateIdx].tag = "title";
|
|
85
|
+
titleTemplateIdx = -1;
|
|
409
86
|
}
|
|
410
87
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
};
|
|
416
|
-
|
|
417
|
-
const TitleTemplatePlugin = () => {
|
|
418
|
-
return defineHeadPlugin({
|
|
419
|
-
hooks: {
|
|
420
|
-
"tags:resolve": (ctx) => {
|
|
421
|
-
ctx.tags = resolveTitleTemplateFromTags(ctx.tags);
|
|
88
|
+
if (titleTemplateIdx !== -1) {
|
|
89
|
+
delete tags[titleTemplateIdx];
|
|
90
|
+
}
|
|
91
|
+
ctx.tags = tags.filter(Boolean);
|
|
422
92
|
}
|
|
423
93
|
}
|
|
424
94
|
});
|
|
425
95
|
};
|
|
426
96
|
|
|
427
97
|
const DeprecatedTagAttrPlugin = () => {
|
|
428
|
-
return defineHeadPlugin({
|
|
98
|
+
return shared.defineHeadPlugin({
|
|
429
99
|
hooks: {
|
|
430
100
|
"tag:normalise": function({ tag }) {
|
|
431
101
|
if (typeof tag.props.body !== "undefined") {
|
|
@@ -440,18 +110,21 @@ const DeprecatedTagAttrPlugin = () => {
|
|
|
440
110
|
const IsBrowser = typeof window !== "undefined";
|
|
441
111
|
|
|
442
112
|
const ProvideTagHashPlugin = () => {
|
|
443
|
-
return defineHeadPlugin({
|
|
113
|
+
return shared.defineHeadPlugin({
|
|
444
114
|
hooks: {
|
|
445
115
|
"tag:normalise": (ctx) => {
|
|
446
|
-
const { tag, entry } = ctx;
|
|
116
|
+
const { tag, entry, resolvedOptions } = ctx;
|
|
117
|
+
if (resolvedOptions.experimentalHashHydration === true) {
|
|
118
|
+
tag._h = shared.hashTag(tag);
|
|
119
|
+
}
|
|
447
120
|
const isDynamic = typeof tag.props._dynamic !== "undefined";
|
|
448
|
-
if (!HasElementTags.includes(tag.tag) || !tag.key)
|
|
121
|
+
if (!shared.HasElementTags.includes(tag.tag) || !tag.key)
|
|
449
122
|
return;
|
|
450
|
-
tag._hash = dom.hashCode(JSON.stringify({ tag: tag.tag, key: tag.key }));
|
|
451
123
|
if (IsBrowser || getActiveHead()?.resolvedOptions?.document)
|
|
452
124
|
return;
|
|
453
125
|
if (entry._m === "server" || isDynamic) {
|
|
454
|
-
tag.
|
|
126
|
+
tag._h = tag._h || shared.hashTag(tag);
|
|
127
|
+
tag.props[`data-h-${tag._h}`] = "";
|
|
455
128
|
}
|
|
456
129
|
},
|
|
457
130
|
"tags:resolve": (ctx) => {
|
|
@@ -464,23 +137,7 @@ const ProvideTagHashPlugin = () => {
|
|
|
464
137
|
});
|
|
465
138
|
};
|
|
466
139
|
|
|
467
|
-
const
|
|
468
|
-
return defineHeadPlugin({
|
|
469
|
-
hooks: {
|
|
470
|
-
"entries:updated": function(head) {
|
|
471
|
-
if (typeof options?.document === "undefined" && typeof window === "undefined")
|
|
472
|
-
return;
|
|
473
|
-
let delayFn = options?.delayFn;
|
|
474
|
-
if (!delayFn && typeof requestAnimationFrame !== "undefined")
|
|
475
|
-
delayFn = requestAnimationFrame;
|
|
476
|
-
import('@unhead/dom').then(({ debouncedRenderDOMHead }) => {
|
|
477
|
-
debouncedRenderDOMHead(head, { document: options?.document || window.document, delayFn });
|
|
478
|
-
});
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
});
|
|
482
|
-
};
|
|
483
|
-
|
|
140
|
+
const ValidEventTags = ["script", "link", "bodyAttrs"];
|
|
484
141
|
const EventHandlersPlugin = () => {
|
|
485
142
|
const stripEventHandlers = (mode, tag) => {
|
|
486
143
|
const props = {};
|
|
@@ -496,65 +153,175 @@ const EventHandlersPlugin = () => {
|
|
|
496
153
|
delayedSrc = props.src;
|
|
497
154
|
delete props.src;
|
|
498
155
|
}
|
|
499
|
-
return { props, eventHandlers, delayedSrc };
|
|
156
|
+
return { props, eventHandlers, delayedSrc };
|
|
157
|
+
};
|
|
158
|
+
return shared.defineHeadPlugin({
|
|
159
|
+
hooks: {
|
|
160
|
+
"ssr:render": function(ctx) {
|
|
161
|
+
ctx.tags = ctx.tags.map((tag) => {
|
|
162
|
+
if (!ValidEventTags.includes(tag.tag))
|
|
163
|
+
return tag;
|
|
164
|
+
if (!Object.entries(tag.props).find(([key, value]) => key.startsWith("on") && typeof value === "function"))
|
|
165
|
+
return tag;
|
|
166
|
+
tag.props = stripEventHandlers("ssr", tag).props;
|
|
167
|
+
return tag;
|
|
168
|
+
});
|
|
169
|
+
},
|
|
170
|
+
"dom:beforeRenderTag": function(ctx) {
|
|
171
|
+
if (!ValidEventTags.includes(ctx.tag.tag))
|
|
172
|
+
return;
|
|
173
|
+
if (!Object.entries(ctx.tag.props).find(([key, value]) => key.startsWith("on") && typeof value === "function"))
|
|
174
|
+
return;
|
|
175
|
+
const { props, eventHandlers, delayedSrc } = stripEventHandlers("dom", ctx.tag);
|
|
176
|
+
if (!Object.keys(eventHandlers).length)
|
|
177
|
+
return;
|
|
178
|
+
ctx.tag.props = props;
|
|
179
|
+
ctx.tag._eventHandlers = eventHandlers;
|
|
180
|
+
ctx.tag._delayedSrc = delayedSrc;
|
|
181
|
+
},
|
|
182
|
+
"dom:renderTag": function(ctx) {
|
|
183
|
+
const $el = ctx.$el;
|
|
184
|
+
if (!ctx.tag._eventHandlers || !$el)
|
|
185
|
+
return;
|
|
186
|
+
const $eventListenerTarget = ctx.tag.tag === "bodyAttrs" && typeof window !== "undefined" ? window : $el;
|
|
187
|
+
Object.entries(ctx.tag._eventHandlers).forEach(([k, value]) => {
|
|
188
|
+
const sdeKey = `${ctx.tag._d || ctx.tag._p}:${k}`;
|
|
189
|
+
const eventName = k.slice(2).toLowerCase();
|
|
190
|
+
const eventDedupeKey = `data-h-${eventName}`;
|
|
191
|
+
ctx.markSideEffect(sdeKey, () => {
|
|
192
|
+
});
|
|
193
|
+
if ($el.hasAttribute(eventDedupeKey))
|
|
194
|
+
return;
|
|
195
|
+
const handler = value;
|
|
196
|
+
$el.setAttribute(eventDedupeKey, "");
|
|
197
|
+
$eventListenerTarget.addEventListener(eventName, handler);
|
|
198
|
+
if (ctx.entry) {
|
|
199
|
+
ctx.entry._sde[sdeKey] = () => {
|
|
200
|
+
$eventListenerTarget.removeEventListener(eventName, handler);
|
|
201
|
+
$el.removeAttribute(eventDedupeKey);
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
if (ctx.tag._delayedSrc) {
|
|
206
|
+
$el.setAttribute("src", ctx.tag._delayedSrc);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const UsesMergeStrategy = ["templateParams", "htmlAttrs", "bodyAttrs"];
|
|
214
|
+
const DedupesTagsPlugin = (options) => {
|
|
215
|
+
options = options || {};
|
|
216
|
+
const dedupeKeys = options.dedupeKeys || ["hid", "vmid", "key"];
|
|
217
|
+
return shared.defineHeadPlugin({
|
|
218
|
+
hooks: {
|
|
219
|
+
"tag:normalise": function({ tag }) {
|
|
220
|
+
dedupeKeys.forEach((key) => {
|
|
221
|
+
if (tag.props[key]) {
|
|
222
|
+
tag.key = tag.props[key];
|
|
223
|
+
delete tag.props[key];
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
const dedupe = tag.key ? `${tag.tag}:${tag.key}` : shared.tagDedupeKey(tag);
|
|
227
|
+
if (dedupe)
|
|
228
|
+
tag._d = dedupe;
|
|
229
|
+
},
|
|
230
|
+
"tags:resolve": function(ctx) {
|
|
231
|
+
const deduping = {};
|
|
232
|
+
ctx.tags.forEach((tag) => {
|
|
233
|
+
const dedupeKey = tag._d || tag._p;
|
|
234
|
+
const dupedTag = deduping[dedupeKey];
|
|
235
|
+
if (dupedTag) {
|
|
236
|
+
let strategy = tag?.tagDuplicateStrategy;
|
|
237
|
+
if (!strategy && UsesMergeStrategy.includes(tag.tag))
|
|
238
|
+
strategy = "merge";
|
|
239
|
+
if (strategy === "merge") {
|
|
240
|
+
const oldProps = dupedTag.props;
|
|
241
|
+
["class", "style"].forEach((key) => {
|
|
242
|
+
if (tag.props[key] && oldProps[key]) {
|
|
243
|
+
if (key === "style" && !oldProps[key].endsWith(";"))
|
|
244
|
+
oldProps[key] += ";";
|
|
245
|
+
tag.props[key] = `${oldProps[key]} ${tag.props[key]}`;
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
deduping[dedupeKey].props = {
|
|
249
|
+
...oldProps,
|
|
250
|
+
...tag.props
|
|
251
|
+
};
|
|
252
|
+
return;
|
|
253
|
+
} else if (tag._e === dupedTag._e) {
|
|
254
|
+
dupedTag._duped = dupedTag._duped || [];
|
|
255
|
+
tag._d = `${dupedTag._d}:${dupedTag._duped.length + 1}`;
|
|
256
|
+
dupedTag._duped.push(tag);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const propCount = Object.keys(tag.props).length;
|
|
260
|
+
if ((propCount === 0 || propCount === 1 && typeof tag.props["data-h-key"] !== "undefined") && !tag.innerHTML && !tag.textContent) {
|
|
261
|
+
delete deduping[dedupeKey];
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
deduping[dedupeKey] = tag;
|
|
266
|
+
});
|
|
267
|
+
const newTags = [];
|
|
268
|
+
Object.values(deduping).forEach((tag) => {
|
|
269
|
+
const dupes = tag._duped;
|
|
270
|
+
delete tag._duped;
|
|
271
|
+
newTags.push(tag);
|
|
272
|
+
if (dupes)
|
|
273
|
+
newTags.push(...dupes);
|
|
274
|
+
});
|
|
275
|
+
ctx.tags = newTags;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
function processTemplateParams(s, config) {
|
|
282
|
+
const replacer = (preserveToken) => (_, token) => {
|
|
283
|
+
if (token === "pageTitle" || token === "s")
|
|
284
|
+
return "%s";
|
|
285
|
+
let val;
|
|
286
|
+
if (token.includes("."))
|
|
287
|
+
val = token.split(".").reduce((acc, key) => acc[key] || {}, config);
|
|
288
|
+
else
|
|
289
|
+
val = config[token];
|
|
290
|
+
return val || (preserveToken ? token : "");
|
|
500
291
|
};
|
|
501
|
-
|
|
292
|
+
let template = s.replace(/%(\w+\.?\w+)%/g, replacer()).replace(/%(\w+\.?\w+)/g, replacer(true)).trim();
|
|
293
|
+
if (config.separator) {
|
|
294
|
+
if (template.endsWith(config.separator))
|
|
295
|
+
template = template.slice(0, -config.separator.length).trim();
|
|
296
|
+
if (template.startsWith(config.separator))
|
|
297
|
+
template = template.slice(config.separator.length).trim();
|
|
298
|
+
template = template.replace(new RegExp(`\\${config.separator}\\s*\\${config.separator}`, "g"), config.separator);
|
|
299
|
+
}
|
|
300
|
+
return template;
|
|
301
|
+
}
|
|
302
|
+
function TemplateParamsPlugin() {
|
|
303
|
+
return shared.defineHeadPlugin({
|
|
502
304
|
hooks: {
|
|
503
|
-
"
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
},
|
|
517
|
-
"dom:renderTag": function(ctx) {
|
|
518
|
-
const $el = ctx.$el;
|
|
519
|
-
if (!ctx.tag._eventHandlers || !$el)
|
|
520
|
-
return;
|
|
521
|
-
const $eventListenerTarget = ctx.tag.tag === "bodyAttrs" && typeof window !== "undefined" ? window : $el;
|
|
522
|
-
Object.entries(ctx.tag._eventHandlers).forEach(([k, value]) => {
|
|
523
|
-
const sdeKey = `${ctx.tag._d || ctx.tag._p}:${k}`;
|
|
524
|
-
const eventName = k.slice(2).toLowerCase();
|
|
525
|
-
const eventDedupeKey = `data-h-${eventName}`;
|
|
526
|
-
delete ctx.staleSideEffects[sdeKey];
|
|
527
|
-
if ($el.hasAttribute(eventDedupeKey))
|
|
528
|
-
return;
|
|
529
|
-
const handler = value;
|
|
530
|
-
$el.setAttribute(eventDedupeKey, "");
|
|
531
|
-
$eventListenerTarget.addEventListener(eventName, handler);
|
|
532
|
-
if (ctx.entry) {
|
|
533
|
-
ctx.entry._sde[sdeKey] = () => {
|
|
534
|
-
$eventListenerTarget.removeEventListener(eventName, handler);
|
|
535
|
-
$el.removeAttribute(eventDedupeKey);
|
|
536
|
-
};
|
|
305
|
+
"tags:resolve": (ctx) => {
|
|
306
|
+
const { tags } = ctx;
|
|
307
|
+
const templateParamsIdx = tags.findIndex((tag) => tag.tag === "templateParams");
|
|
308
|
+
if (templateParamsIdx !== -1) {
|
|
309
|
+
const templateParams = tags[templateParamsIdx].textContent;
|
|
310
|
+
delete tags[templateParamsIdx];
|
|
311
|
+
for (const tag of tags) {
|
|
312
|
+
if (tag) {
|
|
313
|
+
if (["titleTemplate", "title"].includes(tag.tag) && typeof tag.textContent === "string")
|
|
314
|
+
tag.textContent = processTemplateParams(tag.textContent, templateParams);
|
|
315
|
+
if (tag.tag === "meta" && typeof tag.props.content === "string")
|
|
316
|
+
tag.props.content = processTemplateParams(tag.props.content, templateParams);
|
|
317
|
+
}
|
|
537
318
|
}
|
|
538
|
-
});
|
|
539
|
-
if (ctx.tag._delayedSrc) {
|
|
540
|
-
$el.setAttribute("src", ctx.tag._delayedSrc);
|
|
541
319
|
}
|
|
320
|
+
ctx.tags = tags.filter(Boolean);
|
|
542
321
|
}
|
|
543
322
|
}
|
|
544
323
|
});
|
|
545
|
-
};
|
|
546
|
-
|
|
547
|
-
function asArray(value) {
|
|
548
|
-
return Array.isArray(value) ? value : [value];
|
|
549
324
|
}
|
|
550
|
-
const HasElementTags = [
|
|
551
|
-
"base",
|
|
552
|
-
"meta",
|
|
553
|
-
"link",
|
|
554
|
-
"style",
|
|
555
|
-
"script",
|
|
556
|
-
"noscript"
|
|
557
|
-
];
|
|
558
325
|
|
|
559
326
|
exports.activeHead = void 0;
|
|
560
327
|
const setActiveHead = (head) => exports.activeHead = head;
|
|
@@ -569,57 +336,404 @@ function useHead(input, options = {}) {
|
|
|
569
336
|
return head.push(input, options);
|
|
570
337
|
}
|
|
571
338
|
}
|
|
339
|
+
|
|
340
|
+
function useHeadSafe(input, options = {}) {
|
|
341
|
+
return useHead(input, {
|
|
342
|
+
...options || {},
|
|
343
|
+
transform: whitelistSafeInput
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function useServerHead(input, options = {}) {
|
|
348
|
+
return useHead(input, { ...options, mode: "server" });
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function useServerHeadSafe(input, options = {}) {
|
|
352
|
+
return useHeadSafe(input, { ...options, mode: "server" });
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function useSeoMeta(input, options) {
|
|
356
|
+
const { title, titleTemplate, ...meta } = input;
|
|
357
|
+
return useHead({
|
|
358
|
+
title,
|
|
359
|
+
titleTemplate,
|
|
360
|
+
meta: unpackMeta(meta)
|
|
361
|
+
}, options);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function useServerSeoMeta(input, options) {
|
|
365
|
+
return useSeoMeta(input, {
|
|
366
|
+
...options || {},
|
|
367
|
+
mode: "server"
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
|
|
572
371
|
const useTagTitle = (title) => useHead({ title });
|
|
573
372
|
const useTagBase = (base) => useHead({ base });
|
|
574
|
-
const useTagMeta = (meta) => useHead({ meta: asArray(meta) });
|
|
373
|
+
const useTagMeta = (meta) => useHead({ meta: shared.asArray(meta) });
|
|
575
374
|
const useTagMetaFlat = (meta) => useTagMeta(unpackMeta(meta));
|
|
576
|
-
const useTagLink = (link) => useHead({ link: asArray(link) });
|
|
577
|
-
const useTagScript = (script) => useHead({ script: asArray(script) });
|
|
578
|
-
const useTagStyle = (style) => useHead({ style: asArray(style) });
|
|
579
|
-
const useTagNoscript = (noscript) => useHead({ noscript: asArray(noscript) });
|
|
375
|
+
const useTagLink = (link) => useHead({ link: shared.asArray(link) });
|
|
376
|
+
const useTagScript = (script) => useHead({ script: shared.asArray(script) });
|
|
377
|
+
const useTagStyle = (style) => useHead({ style: shared.asArray(style) });
|
|
378
|
+
const useTagNoscript = (noscript) => useHead({ noscript: shared.asArray(noscript) });
|
|
580
379
|
const useHtmlAttrs = (attrs) => useHead({ htmlAttrs: attrs });
|
|
581
380
|
const useBodyAttrs = (attrs) => useHead({ bodyAttrs: attrs });
|
|
582
381
|
const useTitleTemplate = (titleTemplate) => useHead({ titleTemplate });
|
|
583
|
-
|
|
584
|
-
function useServerHead(input, options = {}) {
|
|
585
|
-
return useHead(input, { ...options, mode: "server" });
|
|
586
|
-
}
|
|
587
382
|
const useServerTagTitle = (title) => useServerHead({ title });
|
|
588
383
|
const useServerTagBase = (base) => useServerHead({ base });
|
|
589
|
-
const useServerTagMeta = (meta) => useServerHead({ meta: asArray(meta) });
|
|
384
|
+
const useServerTagMeta = (meta) => useServerHead({ meta: shared.asArray(meta) });
|
|
590
385
|
const useServerTagMetaFlat = (meta) => useServerTagMeta(unpackMeta(meta));
|
|
591
|
-
const useServerTagLink = (link) => useServerHead({ link: asArray(link) });
|
|
592
|
-
const useServerTagScript = (script) => useServerHead({ script: asArray(script) });
|
|
593
|
-
const useServerTagStyle = (style) => useServerHead({ style: asArray(style) });
|
|
594
|
-
const useServerTagNoscript = (noscript) => useServerHead({ noscript: asArray(noscript) });
|
|
386
|
+
const useServerTagLink = (link) => useServerHead({ link: shared.asArray(link) });
|
|
387
|
+
const useServerTagScript = (script) => useServerHead({ script: shared.asArray(script) });
|
|
388
|
+
const useServerTagStyle = (style) => useServerHead({ style: shared.asArray(style) });
|
|
389
|
+
const useServerTagNoscript = (noscript) => useServerHead({ noscript: shared.asArray(noscript) });
|
|
595
390
|
const useServerHtmlAttrs = (attrs) => useServerHead({ htmlAttrs: attrs });
|
|
596
391
|
const useServerBodyAttrs = (attrs) => useServerHead({ bodyAttrs: attrs });
|
|
597
392
|
const useServerTitleTemplate = (titleTemplate) => useServerHead({ titleTemplate });
|
|
598
393
|
|
|
599
|
-
const
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
394
|
+
const MetaPackingSchema = {
|
|
395
|
+
robots: {
|
|
396
|
+
unpack: {
|
|
397
|
+
keyValueSeparator: ":"
|
|
398
|
+
}
|
|
399
|
+
},
|
|
400
|
+
// Pragma directives
|
|
401
|
+
contentSecurityPolicy: {
|
|
402
|
+
unpack: {
|
|
403
|
+
keyValueSeparator: " ",
|
|
404
|
+
entrySeparator: "; "
|
|
405
|
+
},
|
|
406
|
+
metaKey: "http-equiv"
|
|
407
|
+
},
|
|
408
|
+
fbAppId: {
|
|
409
|
+
keyValue: "fb:app_id",
|
|
410
|
+
metaKey: "property"
|
|
411
|
+
},
|
|
412
|
+
msapplicationTileImage: {
|
|
413
|
+
keyValue: "msapplication-TileImage"
|
|
414
|
+
},
|
|
415
|
+
/**
|
|
416
|
+
* Tile colour for windows
|
|
417
|
+
*/
|
|
418
|
+
msapplicationTileColor: {
|
|
419
|
+
keyValue: "msapplication-TileColor"
|
|
420
|
+
},
|
|
421
|
+
/**
|
|
422
|
+
* URL of a config for windows tile.
|
|
423
|
+
*/
|
|
424
|
+
msapplicationConfig: {
|
|
425
|
+
keyValue: "msapplication-Config"
|
|
426
|
+
},
|
|
427
|
+
charset: {
|
|
428
|
+
metaKey: "charset"
|
|
429
|
+
},
|
|
430
|
+
contentType: {
|
|
431
|
+
metaKey: "http-equiv"
|
|
432
|
+
},
|
|
433
|
+
defaultStyle: {
|
|
434
|
+
metaKey: "http-equiv"
|
|
435
|
+
},
|
|
436
|
+
xUaCompatible: {
|
|
437
|
+
metaKey: "http-equiv"
|
|
438
|
+
},
|
|
439
|
+
refresh: {
|
|
440
|
+
metaKey: "http-equiv"
|
|
441
|
+
}
|
|
606
442
|
};
|
|
443
|
+
function resolveMetaKeyType(key) {
|
|
444
|
+
return PropertyPrefixKeys.test(key) ? "property" : MetaPackingSchema[key]?.metaKey || "name";
|
|
445
|
+
}
|
|
607
446
|
|
|
608
|
-
|
|
447
|
+
function packMeta(inputs) {
|
|
448
|
+
const mappedPackingSchema = Object.entries(MetaPackingSchema).map(([key, value]) => [key, value.keyValue]);
|
|
449
|
+
return packrup.packArray(inputs, {
|
|
450
|
+
key: ["name", "property", "httpEquiv", "http-equiv", "charset"],
|
|
451
|
+
value: ["content", "charset"],
|
|
452
|
+
resolveKey(k) {
|
|
453
|
+
let key = mappedPackingSchema.filter((sk) => sk[1] === k)?.[0]?.[0] || k;
|
|
454
|
+
const replacer = (_, letter) => letter?.toUpperCase();
|
|
455
|
+
key = key.replace(/:([a-z])/g, replacer).replace(/-([a-z])/g, replacer);
|
|
456
|
+
return key;
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const ArrayableInputs = ["Image", "Video", "Audio"];
|
|
462
|
+
function unpackMeta(input) {
|
|
463
|
+
const extras = [];
|
|
464
|
+
ArrayableInputs.forEach((key) => {
|
|
465
|
+
const ogKey = `og:${key.toLowerCase()}`;
|
|
466
|
+
const inputKey = `og${key}`;
|
|
467
|
+
const val = input[inputKey];
|
|
468
|
+
if (typeof val === "object") {
|
|
469
|
+
(Array.isArray(val) ? val : [val]).forEach((entry) => {
|
|
470
|
+
if (!entry)
|
|
471
|
+
return;
|
|
472
|
+
const unpackedEntry = packrup.unpackToArray(entry, {
|
|
473
|
+
key: "property",
|
|
474
|
+
value: "content",
|
|
475
|
+
resolveKeyData({ key: key2 }) {
|
|
476
|
+
return fixKeyCase(`${ogKey}${key2 !== "url" ? `:${key2}` : ""}`);
|
|
477
|
+
},
|
|
478
|
+
resolveValueData({ value }) {
|
|
479
|
+
return typeof value === "number" ? value.toString() : value;
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
extras.push(
|
|
483
|
+
...unpackedEntry.sort((a, b) => a.property === ogKey ? -1 : b.property === ogKey ? 1 : 0)
|
|
484
|
+
);
|
|
485
|
+
});
|
|
486
|
+
delete input[inputKey];
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
const meta = packrup.unpackToArray(input, {
|
|
490
|
+
key({ key }) {
|
|
491
|
+
return resolveMetaKeyType(key);
|
|
492
|
+
},
|
|
493
|
+
value({ key }) {
|
|
494
|
+
return key === "charset" ? "charset" : "content";
|
|
495
|
+
},
|
|
496
|
+
resolveKeyData({ key }) {
|
|
497
|
+
return MetaPackingSchema[key]?.keyValue || fixKeyCase(key);
|
|
498
|
+
},
|
|
499
|
+
resolveValueData({ value, key }) {
|
|
500
|
+
if (value === null)
|
|
501
|
+
return "_null";
|
|
502
|
+
if (typeof value === "object")
|
|
503
|
+
return resolvePackedMetaObjectValue(value, key);
|
|
504
|
+
return typeof value === "number" ? value.toString() : value;
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
return [...extras, ...meta].filter((v) => typeof v.content === "undefined" || v.content !== "_null");
|
|
508
|
+
}
|
|
509
|
+
function resolvePackedMetaObjectValue(value, key) {
|
|
510
|
+
const definition = MetaPackingSchema[key];
|
|
511
|
+
if (key === "refresh")
|
|
512
|
+
return `${value.seconds};url=${value.url}`;
|
|
513
|
+
return packrup.unpackToString(
|
|
514
|
+
changeKeyCasingDeep(value),
|
|
515
|
+
{
|
|
516
|
+
entrySeparator: ", ",
|
|
517
|
+
keyValueSeparator: "=",
|
|
518
|
+
resolve({ value: value2, key: key2 }) {
|
|
519
|
+
if (value2 === null)
|
|
520
|
+
return "";
|
|
521
|
+
if (typeof value2 === "boolean")
|
|
522
|
+
return `${key2}`;
|
|
523
|
+
},
|
|
524
|
+
...definition?.unpack
|
|
525
|
+
}
|
|
526
|
+
);
|
|
527
|
+
}
|
|
609
528
|
|
|
529
|
+
async function normaliseTag(tagName, input) {
|
|
530
|
+
const tag = { tag: tagName, props: {} };
|
|
531
|
+
if (["title", "titleTemplate", "templateParams"].includes(tagName)) {
|
|
532
|
+
tag.textContent = input instanceof Promise ? await input : input;
|
|
533
|
+
return tag;
|
|
534
|
+
}
|
|
535
|
+
if (typeof input === "string") {
|
|
536
|
+
if (!["script", "noscript", "style"].includes(tagName))
|
|
537
|
+
return false;
|
|
538
|
+
if (tagName === "script" && (/^(https?:)?\/\//.test(input) || input.startsWith("/"))) {
|
|
539
|
+
tag.props.src = input;
|
|
540
|
+
} else {
|
|
541
|
+
tag.innerHTML = input;
|
|
542
|
+
tag.key = shared.hashCode(input);
|
|
543
|
+
}
|
|
544
|
+
return tag;
|
|
545
|
+
}
|
|
546
|
+
tag.props = await normaliseProps(tagName, { ...input });
|
|
547
|
+
if (tag.props.children) {
|
|
548
|
+
tag.props.innerHTML = tag.props.children;
|
|
549
|
+
}
|
|
550
|
+
delete tag.props.children;
|
|
551
|
+
Object.keys(tag.props).filter((k) => shared.TagConfigKeys.includes(k)).forEach((k) => {
|
|
552
|
+
if (!["innerHTML", "textContent"].includes(k) || shared.TagsWithInnerContent.includes(tag.tag)) {
|
|
553
|
+
tag[k] = tag.props[k];
|
|
554
|
+
}
|
|
555
|
+
delete tag.props[k];
|
|
556
|
+
});
|
|
557
|
+
["innerHTML", "textContent"].forEach((k) => {
|
|
558
|
+
if (tag.tag === "script" && tag[k] && ["application/ld+json", "application/json"].includes(tag.props.type)) {
|
|
559
|
+
try {
|
|
560
|
+
tag[k] = JSON.parse(tag[k]);
|
|
561
|
+
} catch (e) {
|
|
562
|
+
tag[k] = "";
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
if (typeof tag[k] === "object")
|
|
566
|
+
tag[k] = JSON.stringify(tag[k]);
|
|
567
|
+
});
|
|
568
|
+
if (tag.props.class)
|
|
569
|
+
tag.props.class = normaliseClassProp(tag.props.class);
|
|
570
|
+
if (tag.props.content && Array.isArray(tag.props.content))
|
|
571
|
+
return tag.props.content.map((v) => ({ ...tag, props: { ...tag.props, content: v } }));
|
|
572
|
+
return tag;
|
|
573
|
+
}
|
|
574
|
+
function normaliseClassProp(v) {
|
|
575
|
+
if (typeof v === "object" && !Array.isArray(v)) {
|
|
576
|
+
v = Object.keys(v).filter((k) => v[k]);
|
|
577
|
+
}
|
|
578
|
+
return (Array.isArray(v) ? v.join(" ") : v).split(" ").filter((c) => c.trim()).filter(Boolean).join(" ");
|
|
579
|
+
}
|
|
580
|
+
async function normaliseProps(tagName, props) {
|
|
581
|
+
for (const k of Object.keys(props)) {
|
|
582
|
+
const isDataKey = k.startsWith("data-");
|
|
583
|
+
if (props[k] instanceof Promise) {
|
|
584
|
+
props[k] = await props[k];
|
|
585
|
+
}
|
|
586
|
+
if (String(props[k]) === "true") {
|
|
587
|
+
props[k] = isDataKey ? "true" : "";
|
|
588
|
+
} else if (String(props[k]) === "false") {
|
|
589
|
+
if (isDataKey) {
|
|
590
|
+
props[k] = "false";
|
|
591
|
+
} else {
|
|
592
|
+
delete props[k];
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
return props;
|
|
597
|
+
}
|
|
598
|
+
const TagEntityBits = 10;
|
|
610
599
|
async function normaliseEntryTags(e) {
|
|
611
600
|
const tagPromises = [];
|
|
612
|
-
Object.entries(e.resolvedInput
|
|
613
|
-
const v = asArray(value);
|
|
601
|
+
Object.entries(e.resolvedInput).filter(([k, v]) => typeof v !== "undefined" && shared.ValidHeadTags.includes(k)).forEach(([k, value]) => {
|
|
602
|
+
const v = shared.asArray(value);
|
|
614
603
|
tagPromises.push(...v.map((props) => normaliseTag(k, props)).flat());
|
|
615
604
|
});
|
|
616
|
-
return (await Promise.all(tagPromises)).flat().map((t, i) => {
|
|
605
|
+
return (await Promise.all(tagPromises)).flat().filter(Boolean).map((t, i) => {
|
|
617
606
|
t._e = e._i;
|
|
618
607
|
t._p = (e._i << TagEntityBits) + i;
|
|
619
608
|
return t;
|
|
620
609
|
});
|
|
621
610
|
}
|
|
622
611
|
|
|
612
|
+
const PropertyPrefixKeys = /^(og|fb)/;
|
|
613
|
+
const ColonPrefixKeys = /^(og|twitter|fb)/;
|
|
614
|
+
function fixKeyCase(key) {
|
|
615
|
+
key = key.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
616
|
+
if (ColonPrefixKeys.test(key)) {
|
|
617
|
+
key = key.replace("secure-url", "secure_url").replace(/-/g, ":");
|
|
618
|
+
}
|
|
619
|
+
return key;
|
|
620
|
+
}
|
|
621
|
+
function changeKeyCasingDeep(input) {
|
|
622
|
+
if (Array.isArray(input)) {
|
|
623
|
+
return input.map((entry) => changeKeyCasingDeep(entry));
|
|
624
|
+
}
|
|
625
|
+
if (typeof input !== "object" || Array.isArray(input))
|
|
626
|
+
return input;
|
|
627
|
+
const output = {};
|
|
628
|
+
for (const [key, value] of Object.entries(input))
|
|
629
|
+
output[fixKeyCase(key)] = changeKeyCasingDeep(value);
|
|
630
|
+
return output;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const WhitelistAttributes = {
|
|
634
|
+
htmlAttrs: ["id", "class", "lang", "dir"],
|
|
635
|
+
bodyAttrs: ["id", "class"],
|
|
636
|
+
meta: ["id", "name", "property", "charset", "content"],
|
|
637
|
+
noscript: ["id", "textContent"],
|
|
638
|
+
script: ["id", "type", "textContent"],
|
|
639
|
+
link: ["id", "color", "crossorigin", "fetchpriority", "href", "hreflang", "imagesrcset", "imagesizes", "integrity", "media", "referrerpolicy", "rel", "sizes", "type"]
|
|
640
|
+
};
|
|
641
|
+
function whitelistSafeInput(input) {
|
|
642
|
+
const filtered = {};
|
|
643
|
+
Object.keys(input).forEach((key) => {
|
|
644
|
+
const tagValue = input[key];
|
|
645
|
+
if (!tagValue)
|
|
646
|
+
return;
|
|
647
|
+
switch (key) {
|
|
648
|
+
case "title":
|
|
649
|
+
case "titleTemplate":
|
|
650
|
+
case "templateParams":
|
|
651
|
+
filtered[key] = tagValue;
|
|
652
|
+
break;
|
|
653
|
+
case "htmlAttrs":
|
|
654
|
+
case "bodyAttrs":
|
|
655
|
+
filtered[key] = {};
|
|
656
|
+
WhitelistAttributes[key].forEach((a) => {
|
|
657
|
+
if (tagValue[a])
|
|
658
|
+
filtered[key][a] = tagValue[a];
|
|
659
|
+
});
|
|
660
|
+
Object.keys(tagValue || {}).filter((a) => a.startsWith("data-")).forEach((a) => {
|
|
661
|
+
filtered[key][a] = tagValue[a];
|
|
662
|
+
});
|
|
663
|
+
break;
|
|
664
|
+
case "meta":
|
|
665
|
+
if (Array.isArray(tagValue)) {
|
|
666
|
+
filtered[key] = tagValue.map((meta) => {
|
|
667
|
+
const safeMeta = {};
|
|
668
|
+
WhitelistAttributes.meta.forEach((key2) => {
|
|
669
|
+
if (meta[key2] || key2.startsWith("data-"))
|
|
670
|
+
safeMeta[key2] = meta[key2];
|
|
671
|
+
});
|
|
672
|
+
return safeMeta;
|
|
673
|
+
}).filter((meta) => Object.keys(meta).length > 0);
|
|
674
|
+
}
|
|
675
|
+
break;
|
|
676
|
+
case "link":
|
|
677
|
+
if (Array.isArray(tagValue)) {
|
|
678
|
+
filtered[key] = tagValue.map((meta) => {
|
|
679
|
+
const link = {};
|
|
680
|
+
WhitelistAttributes.link.forEach((key2) => {
|
|
681
|
+
if (key2 === "rel" && ["stylesheet", "canonical", "modulepreload", "prerender", "preload", "prefetch"].includes(meta[key2]))
|
|
682
|
+
return;
|
|
683
|
+
if (key2 === "href") {
|
|
684
|
+
try {
|
|
685
|
+
const url = new URL(meta[key2]);
|
|
686
|
+
if (["javascript:", "data:"].includes(url.protocol))
|
|
687
|
+
return;
|
|
688
|
+
link[key2] = meta[key2];
|
|
689
|
+
} catch (e) {
|
|
690
|
+
}
|
|
691
|
+
} else if (meta[key2] || key2.startsWith("data-")) {
|
|
692
|
+
link[key2] = meta[key2];
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
return link;
|
|
696
|
+
}).filter((link) => Object.keys(link).length > 1 && !!link.rel);
|
|
697
|
+
}
|
|
698
|
+
break;
|
|
699
|
+
case "noscript":
|
|
700
|
+
if (Array.isArray(tagValue)) {
|
|
701
|
+
filtered[key] = tagValue.map((meta) => {
|
|
702
|
+
const noscript = {};
|
|
703
|
+
WhitelistAttributes.noscript.forEach((key2) => {
|
|
704
|
+
if (meta[key2] || key2.startsWith("data-"))
|
|
705
|
+
noscript[key2] = meta[key2];
|
|
706
|
+
});
|
|
707
|
+
return noscript;
|
|
708
|
+
}).filter((meta) => Object.keys(meta).length > 0);
|
|
709
|
+
}
|
|
710
|
+
break;
|
|
711
|
+
case "script":
|
|
712
|
+
if (Array.isArray(tagValue)) {
|
|
713
|
+
filtered[key] = tagValue.map((script) => {
|
|
714
|
+
const safeScript = {};
|
|
715
|
+
WhitelistAttributes.script.forEach((s) => {
|
|
716
|
+
if (script[s] || s.startsWith("data-")) {
|
|
717
|
+
if (s === "textContent") {
|
|
718
|
+
try {
|
|
719
|
+
const jsonVal = typeof script[s] === "string" ? JSON.parse(script[s]) : script[s];
|
|
720
|
+
safeScript[s] = JSON.stringify(jsonVal, null, 0);
|
|
721
|
+
} catch (e) {
|
|
722
|
+
}
|
|
723
|
+
} else {
|
|
724
|
+
safeScript[s] = script[s];
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
});
|
|
728
|
+
return safeScript;
|
|
729
|
+
}).filter((meta) => Object.keys(meta).length > 0);
|
|
730
|
+
}
|
|
731
|
+
break;
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
return filtered;
|
|
735
|
+
}
|
|
736
|
+
|
|
623
737
|
const CorePlugins = () => [
|
|
624
738
|
// dedupe needs to come first
|
|
625
739
|
DedupesTagsPlugin(),
|
|
@@ -627,16 +741,19 @@ const CorePlugins = () => [
|
|
|
627
741
|
TitleTemplatePlugin(),
|
|
628
742
|
ProvideTagHashPlugin(),
|
|
629
743
|
EventHandlersPlugin(),
|
|
630
|
-
DeprecatedTagAttrPlugin()
|
|
744
|
+
DeprecatedTagAttrPlugin(),
|
|
745
|
+
TemplateParamsPlugin()
|
|
631
746
|
];
|
|
632
747
|
const DOMPlugins = (options = {}) => [
|
|
633
|
-
PatchDomOnEntryUpdatesPlugin({ document: options?.document, delayFn: options?.domDelayFn })
|
|
748
|
+
dom.PatchDomOnEntryUpdatesPlugin({ document: options?.document, delayFn: options?.domDelayFn })
|
|
634
749
|
];
|
|
635
750
|
function createHead(options = {}) {
|
|
636
751
|
const head = createHeadCore({
|
|
637
752
|
...options,
|
|
638
753
|
plugins: [...DOMPlugins(options), ...options?.plugins || []]
|
|
639
754
|
});
|
|
755
|
+
if (options.experimentalHashHydration && head.resolvedOptions.document)
|
|
756
|
+
head._hash = dom.maybeGetSSRHash(head.resolvedOptions.document);
|
|
640
757
|
setActiveHead(head);
|
|
641
758
|
return head;
|
|
642
759
|
}
|
|
@@ -652,6 +769,7 @@ function createHeadCore(options = {}) {
|
|
|
652
769
|
...options?.plugins || []
|
|
653
770
|
];
|
|
654
771
|
options.plugins.forEach((p) => p.hooks && hooks.addHooks(p.hooks));
|
|
772
|
+
options.document = options.document || (IsBrowser ? document : void 0);
|
|
655
773
|
const updated = () => hooks.callHook("entries:updated", head);
|
|
656
774
|
const head = {
|
|
657
775
|
resolvedOptions: options,
|
|
@@ -673,6 +791,9 @@ function createHeadCore(options = {}) {
|
|
|
673
791
|
};
|
|
674
792
|
if (options2?.mode)
|
|
675
793
|
activeEntry._m = options2?.mode;
|
|
794
|
+
if (options2?.transform) {
|
|
795
|
+
activeEntry._t = options2?.transform;
|
|
796
|
+
}
|
|
676
797
|
entries.push(activeEntry);
|
|
677
798
|
updated();
|
|
678
799
|
return {
|
|
@@ -702,8 +823,10 @@ function createHeadCore(options = {}) {
|
|
|
702
823
|
const resolveCtx = { tags: [], entries: [...entries] };
|
|
703
824
|
await hooks.callHook("entries:resolve", resolveCtx);
|
|
704
825
|
for (const entry of resolveCtx.entries) {
|
|
826
|
+
const transformer = entry._t || ((i) => i);
|
|
827
|
+
entry.resolvedInput = transformer(entry.resolvedInput || entry.input);
|
|
705
828
|
for (const tag of await normaliseEntryTags(entry)) {
|
|
706
|
-
const tagCtx = { tag, entry };
|
|
829
|
+
const tagCtx = { tag, entry, resolvedOptions: head.resolvedOptions };
|
|
707
830
|
await hooks.callHook("tag:normalise", tagCtx);
|
|
708
831
|
resolveCtx.tags.push(tagCtx.tag);
|
|
709
832
|
}
|
|
@@ -711,32 +834,32 @@ function createHeadCore(options = {}) {
|
|
|
711
834
|
await hooks.callHook("tags:resolve", resolveCtx);
|
|
712
835
|
return resolveCtx.tags;
|
|
713
836
|
},
|
|
714
|
-
_elMap: {},
|
|
715
837
|
_popSideEffectQueue() {
|
|
716
838
|
const sde = { ..._sde };
|
|
717
839
|
_sde = {};
|
|
718
840
|
return sde;
|
|
719
|
-
}
|
|
841
|
+
},
|
|
842
|
+
_elMap: {}
|
|
720
843
|
};
|
|
721
844
|
head.hooks.callHook("init", head);
|
|
722
845
|
return head;
|
|
723
846
|
}
|
|
724
847
|
|
|
725
|
-
function defineHeadPlugin(plugin) {
|
|
726
|
-
return plugin;
|
|
727
|
-
}
|
|
728
|
-
|
|
729
848
|
const coreComposableNames = [
|
|
730
849
|
"getActiveHead"
|
|
731
850
|
];
|
|
732
851
|
const composableNames = [
|
|
733
852
|
"useHead",
|
|
853
|
+
"useSeoMeta",
|
|
854
|
+
"useHeadSafe",
|
|
855
|
+
"useServerHead",
|
|
856
|
+
"useServerSeoMeta",
|
|
857
|
+
"useServerHeadSafe",
|
|
858
|
+
// deprecated
|
|
734
859
|
"useTagTitle",
|
|
735
860
|
"useTagBase",
|
|
736
861
|
"useTagMeta",
|
|
737
862
|
"useTagMetaFlat",
|
|
738
|
-
// alias
|
|
739
|
-
"useSeoMeta",
|
|
740
863
|
"useTagLink",
|
|
741
864
|
"useTagScript",
|
|
742
865
|
"useTagStyle",
|
|
@@ -744,8 +867,6 @@ const composableNames = [
|
|
|
744
867
|
"useHtmlAttrs",
|
|
745
868
|
"useBodyAttrs",
|
|
746
869
|
"useTitleTemplate",
|
|
747
|
-
// server only composables
|
|
748
|
-
"useServerHead",
|
|
749
870
|
"useServerTagTitle",
|
|
750
871
|
"useServerTagBase",
|
|
751
872
|
"useServerTagMeta",
|
|
@@ -765,32 +886,49 @@ const unheadComposablesImports = [
|
|
|
765
886
|
}
|
|
766
887
|
];
|
|
767
888
|
|
|
889
|
+
exports.ColonPrefixKeys = ColonPrefixKeys;
|
|
768
890
|
exports.CorePlugins = CorePlugins;
|
|
769
891
|
exports.DOMPlugins = DOMPlugins;
|
|
770
892
|
exports.DedupesTagsPlugin = DedupesTagsPlugin;
|
|
771
893
|
exports.DeprecatedTagAttrPlugin = DeprecatedTagAttrPlugin;
|
|
772
894
|
exports.EventHandlersPlugin = EventHandlersPlugin;
|
|
773
|
-
exports.
|
|
774
|
-
exports.
|
|
895
|
+
exports.MetaPackingSchema = MetaPackingSchema;
|
|
896
|
+
exports.PropertyPrefixKeys = PropertyPrefixKeys;
|
|
775
897
|
exports.ProvideTagHashPlugin = ProvideTagHashPlugin;
|
|
898
|
+
exports.SortModifiers = SortModifiers;
|
|
776
899
|
exports.SortTagsPlugin = SortTagsPlugin;
|
|
900
|
+
exports.TAG_WEIGHTS = TAG_WEIGHTS;
|
|
901
|
+
exports.TagEntityBits = TagEntityBits;
|
|
902
|
+
exports.TemplateParamsPlugin = TemplateParamsPlugin;
|
|
777
903
|
exports.TitleTemplatePlugin = TitleTemplatePlugin;
|
|
778
|
-
exports.
|
|
904
|
+
exports.changeKeyCasingDeep = changeKeyCasingDeep;
|
|
779
905
|
exports.composableNames = composableNames;
|
|
780
906
|
exports.createHead = createHead;
|
|
781
907
|
exports.createHeadCore = createHeadCore;
|
|
782
|
-
exports.
|
|
908
|
+
exports.fixKeyCase = fixKeyCase;
|
|
783
909
|
exports.getActiveHead = getActiveHead;
|
|
910
|
+
exports.normaliseClassProp = normaliseClassProp;
|
|
784
911
|
exports.normaliseEntryTags = normaliseEntryTags;
|
|
912
|
+
exports.normaliseProps = normaliseProps;
|
|
913
|
+
exports.normaliseTag = normaliseTag;
|
|
914
|
+
exports.packMeta = packMeta;
|
|
915
|
+
exports.renderTitleTemplate = renderTitleTemplate;
|
|
916
|
+
exports.resolveMetaKeyType = resolveMetaKeyType;
|
|
917
|
+
exports.resolvePackedMetaObjectValue = resolvePackedMetaObjectValue;
|
|
785
918
|
exports.setActiveHead = setActiveHead;
|
|
919
|
+
exports.tagWeight = tagWeight;
|
|
786
920
|
exports.unheadComposablesImports = unheadComposablesImports;
|
|
921
|
+
exports.unpackMeta = unpackMeta;
|
|
787
922
|
exports.useBodyAttrs = useBodyAttrs;
|
|
788
923
|
exports.useHead = useHead;
|
|
924
|
+
exports.useHeadSafe = useHeadSafe;
|
|
789
925
|
exports.useHtmlAttrs = useHtmlAttrs;
|
|
790
926
|
exports.useSeoMeta = useSeoMeta;
|
|
791
927
|
exports.useServerBodyAttrs = useServerBodyAttrs;
|
|
792
928
|
exports.useServerHead = useServerHead;
|
|
929
|
+
exports.useServerHeadSafe = useServerHeadSafe;
|
|
793
930
|
exports.useServerHtmlAttrs = useServerHtmlAttrs;
|
|
931
|
+
exports.useServerSeoMeta = useServerSeoMeta;
|
|
794
932
|
exports.useServerTagBase = useServerTagBase;
|
|
795
933
|
exports.useServerTagLink = useServerTagLink;
|
|
796
934
|
exports.useServerTagMeta = useServerTagMeta;
|
|
@@ -809,3 +947,7 @@ exports.useTagScript = useTagScript;
|
|
|
809
947
|
exports.useTagStyle = useTagStyle;
|
|
810
948
|
exports.useTagTitle = useTagTitle;
|
|
811
949
|
exports.useTitleTemplate = useTitleTemplate;
|
|
950
|
+
exports.whitelistSafeInput = whitelistSafeInput;
|
|
951
|
+
Object.keys(shared).forEach(function (k) {
|
|
952
|
+
if (k !== 'default' && !exports.hasOwnProperty(k)) exports[k] = shared[k];
|
|
953
|
+
});
|