unhead 1.0.21 → 1.0.22

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 CHANGED
@@ -1,68 +1,313 @@
1
1
  'use strict';
2
2
 
3
3
  const hookable = require('hookable');
4
+ const shared = require('@unhead/shared');
4
5
  const dom = require('@unhead/dom');
5
6
 
6
- const ValidHeadTags = [
7
- "title",
8
- "titleTemplate",
9
- "base",
10
- "htmlAttrs",
11
- "bodyAttrs",
12
- "meta",
13
- "link",
14
- "style",
15
- "script",
16
- "noscript"
17
- ];
18
- const TagConfigKeys = ["tagPosition", "tagPriority", "tagDuplicateStrategy"];
19
-
20
- async function normaliseTag(tagName, input) {
21
- const tag = { tag: tagName, props: {} };
22
- if (tagName === "title" || tagName === "titleTemplate") {
23
- tag.children = input instanceof Promise ? await input : input;
24
- return tag;
7
+ const TAG_WEIGHTS = {
8
+ // aliases
9
+ critical: 2,
10
+ high: 9,
11
+ low: 12,
12
+ // tags
13
+ base: -1,
14
+ title: 1,
15
+ meta: 10
16
+ };
17
+ function tagWeight(tag) {
18
+ if (typeof tag.tagPriority === "number")
19
+ return tag.tagPriority;
20
+ if (tag.tag === "meta") {
21
+ if (tag.props.charset)
22
+ return -2;
23
+ if (tag.props["http-equiv"] === "content-security-policy")
24
+ return 0;
25
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];
26
+ const key = tag.tagPriority || tag.tag;
27
+ if (key in TAG_WEIGHTS) {
28
+ return TAG_WEIGHTS[key];
29
+ }
30
+ return 10;
31
+ }
32
+ const SortModifiers = [{ prefix: "before:", offset: -1 }, { prefix: "after:", offset: 1 }];
33
+ function SortTagsPlugin() {
34
+ return shared.defineHeadPlugin({
35
+ hooks: {
36
+ "tags:resolve": (ctx) => {
37
+ const tagPositionForKey = (key) => ctx.tags.find((tag) => tag._d === key)?._p;
38
+ for (const { prefix, offset } of SortModifiers) {
39
+ for (const tag of ctx.tags.filter((tag2) => typeof tag2.tagPriority === "string" && tag2.tagPriority.startsWith(prefix))) {
40
+ const position = tagPositionForKey(
41
+ tag.tagPriority.replace(prefix, "")
42
+ );
43
+ if (typeof position !== "undefined")
44
+ tag._p = position + offset;
45
+ }
46
+ }
47
+ ctx.tags.sort((a, b) => a._p - b._p).sort((a, b) => tagWeight(a) - tagWeight(b));
48
+ }
33
49
  }
34
50
  });
35
- Object.keys(tag.props).filter((k) => TagConfigKeys.includes(k)).forEach((k) => {
36
- tag[k] = tag.props[k];
37
- delete tag.props[k];
51
+ }
52
+
53
+ const renderTitleTemplate = (template, title) => {
54
+ if (template == null)
55
+ return title || null;
56
+ if (typeof template === "function")
57
+ return template(title);
58
+ return template.replace("%s", title ?? "");
59
+ };
60
+ const TitleTemplatePlugin = () => {
61
+ return shared.defineHeadPlugin({
62
+ hooks: {
63
+ "tags:resolve": (ctx) => {
64
+ const { tags } = ctx;
65
+ let titleTemplateIdx = tags.findIndex((i) => i.tag === "titleTemplate");
66
+ const titleIdx = tags.findIndex((i) => i.tag === "title");
67
+ if (titleIdx !== -1 && titleTemplateIdx !== -1) {
68
+ const newTitle = renderTitleTemplate(
69
+ tags[titleTemplateIdx].children,
70
+ tags[titleIdx].children
71
+ );
72
+ if (newTitle !== null) {
73
+ tags[titleIdx].children = newTitle || tags[titleIdx].children;
74
+ } else {
75
+ delete tags[titleIdx];
76
+ }
77
+ } else if (titleTemplateIdx !== -1) {
78
+ const newTitle = renderTitleTemplate(
79
+ tags[titleTemplateIdx].children
80
+ );
81
+ if (newTitle !== null) {
82
+ tags[titleTemplateIdx].children = newTitle;
83
+ tags[titleTemplateIdx].tag = "title";
84
+ titleTemplateIdx = -1;
85
+ }
86
+ }
87
+ if (titleTemplateIdx !== -1) {
88
+ delete tags[titleTemplateIdx];
89
+ }
90
+ ctx.tags = tags.filter(Boolean);
91
+ }
92
+ }
38
93
  });
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;
94
+ };
95
+
96
+ const DeprecatedTagAttrPlugin = () => {
97
+ return shared.defineHeadPlugin({
98
+ hooks: {
99
+ "tag:normalise": function({ tag }) {
100
+ if (typeof tag.props.body !== "undefined") {
101
+ tag.tagPosition = "bodyClose";
102
+ delete tag.props.body;
103
+ }
104
+ }
105
+ }
106
+ });
107
+ };
108
+
109
+ const IsBrowser = typeof window !== "undefined";
110
+
111
+ const ProvideTagHashPlugin = () => {
112
+ return shared.defineHeadPlugin({
113
+ hooks: {
114
+ "tag:normalise": (ctx) => {
115
+ const { tag, entry } = ctx;
116
+ const isDynamic = typeof tag.props._dynamic !== "undefined";
117
+ if (!shared.HasElementTags.includes(tag.tag) || !tag.key)
118
+ return;
119
+ tag._hash = shared.hashCode(JSON.stringify({ tag: tag.tag, key: tag.key }));
120
+ if (IsBrowser || getActiveHead()?.resolvedOptions?.document)
121
+ return;
122
+ if (entry._m === "server" || isDynamic) {
123
+ tag.props[`data-h-${tag._hash}`] = "";
124
+ }
125
+ },
126
+ "tags:resolve": (ctx) => {
127
+ ctx.tags = ctx.tags.map((t) => {
128
+ delete t.props._dynamic;
129
+ return t;
130
+ });
131
+ }
132
+ }
133
+ });
134
+ };
135
+
136
+ const ValidEventTags = ["script", "link", "bodyAttrs"];
137
+ const EventHandlersPlugin = () => {
138
+ const stripEventHandlers = (mode, tag) => {
139
+ const props = {};
140
+ const eventHandlers = {};
141
+ Object.entries(tag.props).forEach(([key, value]) => {
142
+ if (key.startsWith("on") && typeof value === "function")
143
+ eventHandlers[key] = value;
144
+ else
145
+ props[key] = value;
50
146
  });
147
+ let delayedSrc;
148
+ if (mode === "dom" && tag.tag === "script" && typeof props.src === "string" && typeof eventHandlers.onload !== "undefined") {
149
+ delayedSrc = props.src;
150
+ delete props.src;
151
+ }
152
+ return { props, eventHandlers, delayedSrc };
153
+ };
154
+ return shared.defineHeadPlugin({
155
+ hooks: {
156
+ "ssr:render": function(ctx) {
157
+ ctx.tags = ctx.tags.map((tag) => {
158
+ if (!ValidEventTags.includes(tag.tag))
159
+ return tag;
160
+ if (!Object.entries(tag.props).find(([key, value]) => key.startsWith("on") && typeof value === "function"))
161
+ return tag;
162
+ tag.props = stripEventHandlers("ssr", tag).props;
163
+ return tag;
164
+ });
165
+ },
166
+ "dom:beforeRenderTag": function(ctx) {
167
+ if (!ValidEventTags.includes(ctx.tag.tag))
168
+ return;
169
+ if (!Object.entries(ctx.tag.props).find(([key, value]) => key.startsWith("on") && typeof value === "function"))
170
+ return;
171
+ const { props, eventHandlers, delayedSrc } = stripEventHandlers("dom", ctx.tag);
172
+ if (!Object.keys(eventHandlers).length)
173
+ return;
174
+ ctx.tag.props = props;
175
+ ctx.tag._eventHandlers = eventHandlers;
176
+ ctx.tag._delayedSrc = delayedSrc;
177
+ },
178
+ "dom:renderTag": function(ctx) {
179
+ const $el = ctx.$el;
180
+ if (!ctx.tag._eventHandlers || !$el)
181
+ return;
182
+ const $eventListenerTarget = ctx.tag.tag === "bodyAttrs" && typeof window !== "undefined" ? window : $el;
183
+ Object.entries(ctx.tag._eventHandlers).forEach(([k, value]) => {
184
+ const sdeKey = `${ctx.tag._d || ctx.tag._p}:${k}`;
185
+ const eventName = k.slice(2).toLowerCase();
186
+ const eventDedupeKey = `data-h-${eventName}`;
187
+ delete ctx.staleSideEffects[sdeKey];
188
+ if ($el.hasAttribute(eventDedupeKey))
189
+ return;
190
+ const handler = value;
191
+ $el.setAttribute(eventDedupeKey, "");
192
+ $eventListenerTarget.addEventListener(eventName, handler);
193
+ if (ctx.entry) {
194
+ ctx.entry._sde[sdeKey] = () => {
195
+ $eventListenerTarget.removeEventListener(eventName, handler);
196
+ $el.removeAttribute(eventDedupeKey);
197
+ };
198
+ }
199
+ });
200
+ if (ctx.tag._delayedSrc) {
201
+ $el.setAttribute("src", ctx.tag._delayedSrc);
202
+ }
203
+ }
204
+ }
205
+ });
206
+ };
207
+
208
+ exports.activeHead = void 0;
209
+ const setActiveHead = (head) => exports.activeHead = head;
210
+ const getActiveHead = () => exports.activeHead;
211
+
212
+ function useHead(input, options = {}) {
213
+ const head = getActiveHead();
214
+ if (head) {
215
+ const isBrowser = IsBrowser || head.resolvedOptions?.document;
216
+ if (options.mode === "server" && isBrowser || options.mode === "client" && !isBrowser)
217
+ return;
218
+ return head.push(input, options);
51
219
  }
52
- return tag;
53
220
  }
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];
221
+ const useTagTitle = (title) => useHead({ title });
222
+ const useTagBase = (base) => useHead({ base });
223
+ const useTagMeta = (meta) => useHead({ meta: shared.asArray(meta) });
224
+ const useTagMetaFlat = (meta) => useTagMeta(unpackMeta(meta));
225
+ const useTagLink = (link) => useHead({ link: shared.asArray(link) });
226
+ const useTagScript = (script) => useHead({ script: shared.asArray(script) });
227
+ const useTagStyle = (style) => useHead({ style: shared.asArray(style) });
228
+ const useTagNoscript = (noscript) => useHead({ noscript: shared.asArray(noscript) });
229
+ const useHtmlAttrs = (attrs) => useHead({ htmlAttrs: attrs });
230
+ const useBodyAttrs = (attrs) => useHead({ bodyAttrs: attrs });
231
+ const useTitleTemplate = (titleTemplate) => useHead({ titleTemplate });
232
+
233
+ function useServerHead(input, options = {}) {
234
+ return useHead(input, { ...options, mode: "server" });
235
+ }
236
+ const useServerTagTitle = (title) => useServerHead({ title });
237
+ const useServerTagBase = (base) => useServerHead({ base });
238
+ const useServerTagMeta = (meta) => useServerHead({ meta: shared.asArray(meta) });
239
+ const useServerTagMetaFlat = (meta) => useServerTagMeta(unpackMeta(meta));
240
+ const useServerTagLink = (link) => useServerHead({ link: shared.asArray(link) });
241
+ const useServerTagScript = (script) => useServerHead({ script: shared.asArray(script) });
242
+ const useServerTagStyle = (style) => useServerHead({ style: shared.asArray(style) });
243
+ const useServerTagNoscript = (noscript) => useServerHead({ noscript: shared.asArray(noscript) });
244
+ const useServerHtmlAttrs = (attrs) => useServerHead({ htmlAttrs: attrs });
245
+ const useServerBodyAttrs = (attrs) => useServerHead({ bodyAttrs: attrs });
246
+ const useServerTitleTemplate = (titleTemplate) => useServerHead({ titleTemplate });
247
+
248
+ const useSeoMeta = (input) => {
249
+ const { title, titleTemplate, ...meta } = input;
250
+ return useHead({
251
+ title,
252
+ titleTemplate,
253
+ meta: unpackMeta(meta)
254
+ });
255
+ };
256
+
257
+ function asArray(input) {
258
+ return Array.isArray(input) ? input : [input];
259
+ }
260
+ const InternalKeySymbol = "_$key";
261
+ function packObject(input, options) {
262
+ const keys = Object.keys(input);
263
+ let [k, v] = keys;
264
+ options = options || {};
265
+ options.key = options.key || k;
266
+ options.value = options.value || v;
267
+ options.resolveKey = options.resolveKey || ((k2) => k2);
268
+ const resolveKey = (index) => {
269
+ const arr = asArray(options?.[index]);
270
+ return arr.find((k2) => {
271
+ if (typeof k2 === "string" && k2.includes(".")) {
272
+ return k2;
273
+ }
274
+ return k2 && keys.includes(k2);
275
+ });
276
+ };
277
+ const resolveValue = (k2, input2) => {
278
+ if (k2.includes(".")) {
279
+ const paths = k2.split(".");
280
+ let val = input2;
281
+ for (const path of paths)
282
+ val = val[path];
283
+ return val;
58
284
  }
59
- if (String(props[k]) === "true") {
60
- props[k] = "";
61
- } else if (String(props[k]) === "false") {
62
- delete props[k];
285
+ return input2[k2];
286
+ };
287
+ k = resolveKey("key") || k;
288
+ v = resolveKey("value") || v;
289
+ const dedupeKeyPrefix = input.key ? `${InternalKeySymbol}${input.key}-` : "";
290
+ let keyValue = resolveValue(k, input);
291
+ keyValue = options.resolveKey(keyValue);
292
+ return {
293
+ [`${dedupeKeyPrefix}${keyValue}`]: resolveValue(v, input)
294
+ };
295
+ }
296
+
297
+ function packArray(input, options) {
298
+ const packed = {};
299
+ for (const i of input) {
300
+ const packedObj = packObject(i, options);
301
+ const pKey = Object.keys(packedObj)[0];
302
+ const isDedupeKey = pKey.startsWith(InternalKeySymbol);
303
+ if (!isDedupeKey && packed[pKey]) {
304
+ packed[pKey] = Array.isArray(packed[pKey]) ? packed[pKey] : [packed[pKey]];
305
+ packed[pKey].push(Object.values(packedObj)[0]);
306
+ } else {
307
+ packed[isDedupeKey ? pKey.split("-").slice(1).join("-") || pKey : pKey] = packedObj[pKey];
63
308
  }
64
309
  }
65
- return props;
310
+ return packed;
66
311
  }
67
312
 
68
313
  function unpackToArray(input, options) {
@@ -158,6 +403,20 @@ function resolveMetaKeyType(key) {
158
403
  return PropertyPrefixKeys.test(key) ? "property" : MetaPackingSchema[key]?.metaKey || "name";
159
404
  }
160
405
 
406
+ function packMeta(inputs) {
407
+ const mappedPackingSchema = Object.entries(MetaPackingSchema).map(([key, value]) => [key, value.keyValue]);
408
+ return packArray(inputs, {
409
+ key: ["name", "property", "httpEquiv", "http-equiv", "charset"],
410
+ value: ["content", "charset"],
411
+ resolveKey(k) {
412
+ let key = mappedPackingSchema.filter((sk) => sk[1] === k)?.[0]?.[0] || k;
413
+ const replacer = (_, letter) => letter?.toUpperCase();
414
+ key = key.replace(/:([a-z])/g, replacer).replace(/-([a-z])/g, replacer);
415
+ return key;
416
+ }
417
+ });
418
+ }
419
+
161
420
  const ArrayableInputs = ["Image", "Video", "Audio"];
162
421
  function unpackMeta(input) {
163
422
  const extras = [];
@@ -224,393 +483,58 @@ function unpackMeta(input) {
224
483
  return [...extras, ...meta].filter((v) => typeof v.content === "undefined" || v.content !== "_null");
225
484
  }
226
485
 
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
- }
300
-
301
- const renderTitleTemplate = (template, title) => {
302
- if (template == null)
303
- return title || null;
304
- if (typeof template === "function")
305
- return template(title);
306
- return template.replace("%s", title ?? "");
307
- };
308
- function resolveTitleTemplateFromTags(tags) {
309
- let titleTemplateIdx = tags.findIndex((i) => i.tag === "titleTemplate");
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];
486
+ async function normaliseTag(tagName, input) {
487
+ const tag = { tag: tagName, props: {} };
488
+ if (tagName === "title" || tagName === "titleTemplate") {
489
+ tag.children = input instanceof Promise ? await input : input;
490
+ return tag;
333
491
  }
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({
395
- hooks: {
396
- "tags:resolve": (ctx) => {
397
- const tagIndexForKey = (key) => ctx.tags.find((tag) => tag._d === key)?._p;
398
- for (const tag of ctx.tags) {
399
- if (!tag.tagPriority || typeof tag.tagPriority === "number")
400
- continue;
401
- const modifiers = [{ prefix: "before:", offset: -1 }, { prefix: "after:", offset: 1 }];
402
- for (const { prefix, offset } of modifiers) {
403
- if (tag.tagPriority.startsWith(prefix)) {
404
- const key = tag.tagPriority.replace(prefix, "");
405
- const index = tagIndexForKey(key);
406
- if (typeof index !== "undefined")
407
- tag._p = index + offset;
408
- }
409
- }
410
- }
411
- ctx.tags.sort((a, b) => a._p - b._p).sort(sortTags);
412
- }
413
- }
414
- });
415
- };
416
-
417
- const TitleTemplatePlugin = () => {
418
- return defineHeadPlugin({
419
- hooks: {
420
- "tags:resolve": (ctx) => {
421
- ctx.tags = resolveTitleTemplateFromTags(ctx.tags);
422
- }
423
- }
424
- });
425
- };
426
-
427
- const DeprecatedTagAttrPlugin = () => {
428
- return defineHeadPlugin({
429
- hooks: {
430
- "tag:normalise": function({ tag }) {
431
- if (typeof tag.props.body !== "undefined") {
432
- tag.tagPosition = "bodyClose";
433
- delete tag.props.body;
434
- }
435
- }
436
- }
437
- });
438
- };
439
-
440
- const IsBrowser = typeof window !== "undefined";
441
-
442
- const ProvideTagHashPlugin = () => {
443
- return defineHeadPlugin({
444
- hooks: {
445
- "tag:normalise": (ctx) => {
446
- const { tag, entry } = ctx;
447
- const isDynamic = typeof tag.props._dynamic !== "undefined";
448
- if (!HasElementTags.includes(tag.tag) || !tag.key)
449
- return;
450
- tag._hash = dom.hashCode(JSON.stringify({ tag: tag.tag, key: tag.key }));
451
- if (IsBrowser || getActiveHead()?.resolvedOptions?.document)
452
- return;
453
- if (entry._m === "server" || isDynamic) {
454
- tag.props[`data-h-${tag._hash}`] = "";
455
- }
456
- },
457
- "tags:resolve": (ctx) => {
458
- ctx.tags = ctx.tags.map((t) => {
459
- delete t.props._dynamic;
460
- return t;
461
- });
462
- }
492
+ tag.props = await normaliseProps({ ...input });
493
+ ["children", "innerHtml", "innerHTML"].forEach((key) => {
494
+ if (typeof tag.props[key] !== "undefined") {
495
+ tag.children = tag.props[key];
496
+ if (typeof tag.children === "object")
497
+ tag.children = JSON.stringify(tag.children);
498
+ delete tag.props[key];
463
499
  }
464
500
  });
465
- };
466
-
467
- const PatchDomOnEntryUpdatesPlugin = (options) => {
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
- }
501
+ Object.keys(tag.props).filter((k) => shared.TagConfigKeys.includes(k)).forEach((k) => {
502
+ tag[k] = tag.props[k];
503
+ delete tag.props[k];
481
504
  });
482
- };
483
-
484
- const EventHandlersPlugin = () => {
485
- const stripEventHandlers = (mode, tag) => {
486
- const props = {};
487
- const eventHandlers = {};
488
- Object.entries(tag.props).forEach(([key, value]) => {
489
- if (key.startsWith("on") && typeof value === "function")
490
- eventHandlers[key] = value;
491
- else
492
- props[key] = value;
505
+ if (typeof tag.props.class === "object" && !Array.isArray(tag.props.class)) {
506
+ tag.props.class = Object.keys(tag.props.class).filter((k) => tag.props.class[k]);
507
+ }
508
+ if (Array.isArray(tag.props.class))
509
+ tag.props.class = tag.props.class.join(" ");
510
+ if (tag.props.content && Array.isArray(tag.props.content)) {
511
+ return tag.props.content.map((v, i) => {
512
+ const newTag = { ...tag, props: { ...tag.props } };
513
+ newTag.props.content = v;
514
+ newTag.key = `${tag.props.name || tag.props.property}:${i}`;
515
+ return newTag;
493
516
  });
494
- let delayedSrc;
495
- if (mode === "dom" && tag.tag === "script" && typeof props.src === "string" && typeof eventHandlers.onload !== "undefined") {
496
- delayedSrc = props.src;
497
- delete props.src;
517
+ }
518
+ return tag;
519
+ }
520
+ async function normaliseProps(props) {
521
+ for (const k of Object.keys(props)) {
522
+ if (props[k] instanceof Promise) {
523
+ props[k] = await props[k];
498
524
  }
499
- return { props, eventHandlers, delayedSrc };
500
- };
501
- return defineHeadPlugin({
502
- hooks: {
503
- "ssr:render": function(ctx) {
504
- ctx.tags = ctx.tags.map((tag) => {
505
- tag.props = stripEventHandlers("ssr", tag).props;
506
- return tag;
507
- });
508
- },
509
- "dom:beforeRenderTag": function(ctx) {
510
- const { props, eventHandlers, delayedSrc } = stripEventHandlers("dom", ctx.tag);
511
- if (!Object.keys(eventHandlers).length)
512
- return;
513
- ctx.tag.props = props;
514
- ctx.tag._eventHandlers = eventHandlers;
515
- ctx.tag._delayedSrc = delayedSrc;
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
- };
537
- }
538
- });
539
- if (ctx.tag._delayedSrc) {
540
- $el.setAttribute("src", ctx.tag._delayedSrc);
541
- }
542
- }
525
+ if (String(props[k]) === "true") {
526
+ props[k] = "";
527
+ } else if (String(props[k]) === "false") {
528
+ delete props[k];
543
529
  }
544
- });
545
- };
546
-
547
- function asArray(value) {
548
- return Array.isArray(value) ? value : [value];
549
- }
550
- const HasElementTags = [
551
- "base",
552
- "meta",
553
- "link",
554
- "style",
555
- "script",
556
- "noscript"
557
- ];
558
-
559
- exports.activeHead = void 0;
560
- const setActiveHead = (head) => exports.activeHead = head;
561
- const getActiveHead = () => exports.activeHead;
562
-
563
- function useHead(input, options = {}) {
564
- const head = getActiveHead();
565
- if (head) {
566
- const isBrowser = IsBrowser || head.resolvedOptions?.document;
567
- if (options.mode === "server" && isBrowser || options.mode === "client" && !isBrowser)
568
- return;
569
- return head.push(input, options);
570
530
  }
531
+ return props;
571
532
  }
572
- const useTagTitle = (title) => useHead({ title });
573
- const useTagBase = (base) => useHead({ base });
574
- const useTagMeta = (meta) => useHead({ meta: asArray(meta) });
575
- 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) });
580
- const useHtmlAttrs = (attrs) => useHead({ htmlAttrs: attrs });
581
- const useBodyAttrs = (attrs) => useHead({ bodyAttrs: attrs });
582
- const useTitleTemplate = (titleTemplate) => useHead({ titleTemplate });
583
-
584
- function useServerHead(input, options = {}) {
585
- return useHead(input, { ...options, mode: "server" });
586
- }
587
- const useServerTagTitle = (title) => useServerHead({ title });
588
- const useServerTagBase = (base) => useServerHead({ base });
589
- const useServerTagMeta = (meta) => useServerHead({ meta: asArray(meta) });
590
- 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) });
595
- const useServerHtmlAttrs = (attrs) => useServerHead({ htmlAttrs: attrs });
596
- const useServerBodyAttrs = (attrs) => useServerHead({ bodyAttrs: attrs });
597
- const useServerTitleTemplate = (titleTemplate) => useServerHead({ titleTemplate });
598
-
599
- const useSeoMeta = (input) => {
600
- const { title, titleTemplate, ...meta } = input;
601
- return useHead({
602
- title,
603
- titleTemplate,
604
- meta: unpackMeta(meta)
605
- });
606
- };
607
-
608
533
  const TagEntityBits = 10;
609
-
610
534
  async function normaliseEntryTags(e) {
611
535
  const tagPromises = [];
612
- Object.entries(e.resolvedInput || e.input).filter(([k, v]) => typeof v !== "undefined" && ValidHeadTags.includes(k)).forEach(([k, value]) => {
613
- const v = asArray(value);
536
+ Object.entries(e.resolvedInput || e.input).filter(([k, v]) => typeof v !== "undefined" && shared.ValidHeadTags.includes(k)).forEach(([k, value]) => {
537
+ const v = shared.asArray(value);
614
538
  tagPromises.push(...v.map((props) => normaliseTag(k, props)).flat());
615
539
  });
616
540
  return (await Promise.all(tagPromises)).flat().map((t, i) => {
@@ -620,9 +544,30 @@ async function normaliseEntryTags(e) {
620
544
  });
621
545
  }
622
546
 
547
+ const PropertyPrefixKeys = /^(og|fb)/;
548
+ const ColonPrefixKeys = /^(og|twitter|fb)/;
549
+ function fixKeyCase(key) {
550
+ key = key.replace(/([A-Z])/g, "-$1").toLowerCase();
551
+ if (ColonPrefixKeys.test(key)) {
552
+ key = key.replace("secure-url", "secure_url").replace(/-/g, ":");
553
+ }
554
+ return key;
555
+ }
556
+ function changeKeyCasingDeep(input) {
557
+ if (Array.isArray(input)) {
558
+ return input.map((entry) => changeKeyCasingDeep(entry));
559
+ }
560
+ if (typeof input !== "object" || Array.isArray(input))
561
+ return input;
562
+ const output = {};
563
+ for (const [key, value] of Object.entries(input))
564
+ output[fixKeyCase(key)] = changeKeyCasingDeep(value);
565
+ return output;
566
+ }
567
+
623
568
  const CorePlugins = () => [
624
569
  // dedupe needs to come first
625
- DedupesTagsPlugin(),
570
+ shared.DedupesTagsPlugin(),
626
571
  SortTagsPlugin(),
627
572
  TitleTemplatePlugin(),
628
573
  ProvideTagHashPlugin(),
@@ -630,7 +575,7 @@ const CorePlugins = () => [
630
575
  DeprecatedTagAttrPlugin()
631
576
  ];
632
577
  const DOMPlugins = (options = {}) => [
633
- PatchDomOnEntryUpdatesPlugin({ document: options?.document, delayFn: options?.domDelayFn })
578
+ dom.PatchDomOnEntryUpdatesPlugin({ document: options?.document, delayFn: options?.domDelayFn })
634
579
  ];
635
580
  function createHead(options = {}) {
636
581
  const head = createHeadCore({
@@ -722,10 +667,6 @@ function createHeadCore(options = {}) {
722
667
  return head;
723
668
  }
724
669
 
725
- function defineHeadPlugin(plugin) {
726
- return plugin;
727
- }
728
-
729
670
  const coreComposableNames = [
730
671
  "getActiveHead"
731
672
  ];
@@ -765,25 +706,33 @@ const unheadComposablesImports = [
765
706
  }
766
707
  ];
767
708
 
709
+ exports.ColonPrefixKeys = ColonPrefixKeys;
768
710
  exports.CorePlugins = CorePlugins;
769
711
  exports.DOMPlugins = DOMPlugins;
770
- exports.DedupesTagsPlugin = DedupesTagsPlugin;
771
712
  exports.DeprecatedTagAttrPlugin = DeprecatedTagAttrPlugin;
772
713
  exports.EventHandlersPlugin = EventHandlersPlugin;
773
- exports.HasElementTags = HasElementTags;
774
- exports.PatchDomOnEntryUpdatesPlugin = PatchDomOnEntryUpdatesPlugin;
714
+ exports.PropertyPrefixKeys = PropertyPrefixKeys;
775
715
  exports.ProvideTagHashPlugin = ProvideTagHashPlugin;
716
+ exports.SortModifiers = SortModifiers;
776
717
  exports.SortTagsPlugin = SortTagsPlugin;
718
+ exports.TAG_WEIGHTS = TAG_WEIGHTS;
719
+ exports.TagEntityBits = TagEntityBits;
777
720
  exports.TitleTemplatePlugin = TitleTemplatePlugin;
778
- exports.asArray = asArray;
721
+ exports.changeKeyCasingDeep = changeKeyCasingDeep;
779
722
  exports.composableNames = composableNames;
780
723
  exports.createHead = createHead;
781
724
  exports.createHeadCore = createHeadCore;
782
- exports.defineHeadPlugin = defineHeadPlugin;
725
+ exports.fixKeyCase = fixKeyCase;
783
726
  exports.getActiveHead = getActiveHead;
784
727
  exports.normaliseEntryTags = normaliseEntryTags;
728
+ exports.normaliseProps = normaliseProps;
729
+ exports.normaliseTag = normaliseTag;
730
+ exports.packMeta = packMeta;
731
+ exports.renderTitleTemplate = renderTitleTemplate;
785
732
  exports.setActiveHead = setActiveHead;
733
+ exports.tagWeight = tagWeight;
786
734
  exports.unheadComposablesImports = unheadComposablesImports;
735
+ exports.unpackMeta = unpackMeta;
787
736
  exports.useBodyAttrs = useBodyAttrs;
788
737
  exports.useHead = useHead;
789
738
  exports.useHtmlAttrs = useHtmlAttrs;
@@ -809,3 +758,6 @@ exports.useTagScript = useTagScript;
809
758
  exports.useTagStyle = useTagStyle;
810
759
  exports.useTagTitle = useTagTitle;
811
760
  exports.useTitleTemplate = useTitleTemplate;
761
+ Object.keys(shared).forEach(function (k) {
762
+ if (k !== 'default' && !exports.hasOwnProperty(k)) exports[k] = shared[k];
763
+ });