@unhead/shared 1.9.16 → 1.10.0-beta.1

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
@@ -4,17 +4,17 @@ function asArray$1(value) {
4
4
  return Array.isArray(value) ? value : [value];
5
5
  }
6
6
 
7
- const SelfClosingTags = ["meta", "link", "base"];
8
- const TagsWithInnerContent = ["title", "titleTemplate", "script", "style", "noscript"];
9
- const HasElementTags = [
7
+ const SelfClosingTags = /* @__PURE__ */ new Set(["meta", "link", "base"]);
8
+ const TagsWithInnerContent = /* @__PURE__ */ new Set(["title", "titleTemplate", "script", "style", "noscript"]);
9
+ const HasElementTags = /* @__PURE__ */ new Set([
10
10
  "base",
11
11
  "meta",
12
12
  "link",
13
13
  "style",
14
14
  "script",
15
15
  "noscript"
16
- ];
17
- const ValidHeadTags = [
16
+ ]);
17
+ const ValidHeadTags = /* @__PURE__ */ new Set([
18
18
  "title",
19
19
  "titleTemplate",
20
20
  "templateParams",
@@ -26,9 +26,9 @@ const ValidHeadTags = [
26
26
  "style",
27
27
  "script",
28
28
  "noscript"
29
- ];
30
- const UniqueTags = ["base", "title", "titleTemplate", "bodyAttrs", "htmlAttrs", "templateParams"];
31
- const TagConfigKeys = ["tagPosition", "tagPriority", "tagDuplicateStrategy", "children", "innerHTML", "textContent", "processTemplateParams"];
29
+ ]);
30
+ const UniqueTags = /* @__PURE__ */ new Set(["base", "title", "titleTemplate", "bodyAttrs", "htmlAttrs", "templateParams"]);
31
+ const TagConfigKeys = /* @__PURE__ */ new Set(["tagPosition", "tagPriority", "tagDuplicateStrategy", "children", "innerHTML", "textContent", "processTemplateParams"]);
32
32
  const IsBrowser = typeof window !== "undefined";
33
33
  const composableNames = [
34
34
  "getActiveHead",
@@ -51,26 +51,34 @@ function hashCode(s) {
51
51
  return ((h ^ h >>> 9) + 65536).toString(16).substring(1, 8).toLowerCase();
52
52
  }
53
53
  function hashTag(tag) {
54
- return tag._h || hashCode(tag._d ? tag._d : `${tag.tag}:${tag.textContent || tag.innerHTML || ""}:${Object.entries(tag.props).map(([key, value]) => `${key}:${String(value)}`).join(",")}`);
54
+ if (tag._h) {
55
+ return tag._h;
56
+ }
57
+ if (tag._d) {
58
+ return hashCode(tag._d);
59
+ }
60
+ let content = `${tag.tag}:${tag.textContent || tag.innerHTML || ""}:`;
61
+ for (const key in tag.props) {
62
+ content += `${key}:${tag.props[key]},`;
63
+ }
64
+ return hashCode(content);
55
65
  }
56
66
 
57
- function tagDedupeKey(tag, fn) {
67
+ const allowedMetaProperties = ["name", "property", "http-equiv"];
68
+ function tagDedupeKey(tag) {
58
69
  const { props, tag: tagName } = tag;
59
- if (UniqueTags.includes(tagName))
70
+ if (UniqueTags.has(tagName))
60
71
  return tagName;
61
72
  if (tagName === "link" && props.rel === "canonical")
62
73
  return "canonical";
63
74
  if (props.charset)
64
75
  return "charset";
65
- const name = ["id"];
66
- if (tagName === "meta")
67
- name.push(...["name", "property", "http-equiv"]);
68
- for (const n of name) {
69
- if (typeof props[n] !== "undefined") {
70
- const val = String(props[n]);
71
- if (fn && !fn(val))
72
- return false;
73
- return `${tagName}:${n}:${val}`;
76
+ if (props.id) {
77
+ return `${tagName}:id:${props.id}`;
78
+ }
79
+ for (const n of allowedMetaProperties) {
80
+ if (props[n] !== void 0) {
81
+ return `${tagName}:${n}:${props[n]}`;
74
82
  }
75
83
  }
76
84
  return false;
@@ -252,15 +260,16 @@ const MetaPackingSchema = {
252
260
  metaKey: "http-equiv"
253
261
  }
254
262
  };
255
- const openGraphNamespaces = [
263
+ const openGraphNamespaces = /* @__PURE__ */ new Set([
256
264
  "og",
257
265
  "book",
258
266
  "article",
259
267
  "profile"
260
- ];
268
+ ]);
261
269
  function resolveMetaKeyType(key) {
262
- const fKey = fixKeyCase(key).split(":")[0];
263
- if (openGraphNamespaces.includes(fKey))
270
+ const fKey = fixKeyCase(key);
271
+ const prefixIndex = fKey.indexOf(":");
272
+ if (openGraphNamespaces.has(fKey.substring(0, prefixIndex)))
264
273
  return "property";
265
274
  return MetaPackingSchema[key]?.metaKey || "name";
266
275
  }
@@ -269,8 +278,9 @@ function resolveMetaKeyValue(key) {
269
278
  }
270
279
  function fixKeyCase(key) {
271
280
  const updated = key.replace(/([A-Z])/g, "-$1").toLowerCase();
272
- const fKey = updated.split("-")[0];
273
- if (openGraphNamespaces.includes(fKey) || fKey === "twitter")
281
+ const prefixIndex = updated.indexOf("-");
282
+ const fKey = updated.substring(0, prefixIndex);
283
+ if (fKey === "twitter" || openGraphNamespaces.has(fKey))
274
284
  return key.replace(/([A-Z])/g, ":$1").toLowerCase();
275
285
  return updated;
276
286
  }
@@ -281,8 +291,12 @@ function changeKeyCasingDeep(input) {
281
291
  if (typeof input !== "object" || Array.isArray(input))
282
292
  return input;
283
293
  const output = {};
284
- for (const [key, value] of Object.entries(input))
285
- output[fixKeyCase(key)] = changeKeyCasingDeep(value);
294
+ for (const key in input) {
295
+ if (!Object.prototype.hasOwnProperty.call(input, key)) {
296
+ continue;
297
+ }
298
+ output[fixKeyCase(key)] = changeKeyCasingDeep(input[key]);
299
+ }
286
300
  return output;
287
301
  }
288
302
  function resolvePackedMetaObjectValue(value, key) {
@@ -304,24 +318,31 @@ function resolvePackedMetaObjectValue(value, key) {
304
318
  }
305
319
  );
306
320
  }
307
- const ObjectArrayEntries = ["og:image", "og:video", "og:audio", "twitter:image"];
321
+ const ObjectArrayEntries = /* @__PURE__ */ new Set(["og:image", "og:video", "og:audio", "twitter:image"]);
308
322
  function sanitize(input) {
309
323
  const out = {};
310
- Object.entries(input).forEach(([k2, v]) => {
324
+ for (const k2 in input) {
325
+ if (!Object.prototype.hasOwnProperty.call(input, k2)) {
326
+ continue;
327
+ }
328
+ const v = input[k2];
311
329
  if (String(v) !== "false" && k2)
312
330
  out[k2] = v;
313
- });
331
+ }
314
332
  return out;
315
333
  }
316
334
  function handleObjectEntry(key, v) {
317
335
  const value = sanitize(v);
318
336
  const fKey = fixKeyCase(key);
319
337
  const attr = resolveMetaKeyType(fKey);
320
- if (ObjectArrayEntries.includes(fKey)) {
338
+ if (ObjectArrayEntries.has(fKey)) {
321
339
  const input = {};
322
- Object.entries(value).forEach(([k2, v2]) => {
323
- input[`${key}${k2 === "url" ? "" : `${k2.charAt(0).toUpperCase()}${k2.slice(1)}`}`] = v2;
324
- });
340
+ for (const k2 in value) {
341
+ if (!Object.prototype.hasOwnProperty.call(value, k2)) {
342
+ continue;
343
+ }
344
+ input[`${key}${k2 === "url" ? "" : `${k2[0].toUpperCase()}${k2.slice(1)}`}`] = value[k2];
345
+ }
325
346
  return unpackMeta(input).sort((a, b) => (a[attr]?.length || 0) - (b[attr]?.length || 0));
326
347
  }
327
348
  return [{ [attr]: fKey, ...value }];
@@ -329,23 +350,27 @@ function handleObjectEntry(key, v) {
329
350
  function unpackMeta(input) {
330
351
  const extras = [];
331
352
  const primitives = {};
332
- Object.entries(input).forEach(([key, value]) => {
353
+ for (const key in input) {
354
+ if (!Object.prototype.hasOwnProperty.call(input, key)) {
355
+ continue;
356
+ }
357
+ const value = input[key];
333
358
  if (!Array.isArray(value)) {
334
359
  if (typeof value === "object" && value) {
335
- if (ObjectArrayEntries.includes(fixKeyCase(key))) {
360
+ if (ObjectArrayEntries.has(fixKeyCase(key))) {
336
361
  extras.push(...handleObjectEntry(key, value));
337
- return;
362
+ continue;
338
363
  }
339
364
  primitives[key] = sanitize(value);
340
365
  } else {
341
366
  primitives[key] = value;
342
367
  }
343
- return;
368
+ continue;
344
369
  }
345
- value.forEach((v) => {
370
+ for (const v of value) {
346
371
  extras.push(...typeof v === "string" ? unpackMeta({ [key]: v }) : handleObjectEntry(key, v));
347
- });
348
- });
372
+ }
373
+ }
349
374
  const meta = unpackToArray(primitives, {
350
375
  key({ key }) {
351
376
  return resolveMetaKeyType(key);
@@ -437,7 +462,7 @@ function whitelistSafeInput(input) {
437
462
  const link = acceptDataAttrs(meta);
438
463
  WhitelistAttributes.link.forEach((key2) => {
439
464
  const val = meta[key2];
440
- if (key2 === "rel" && ["stylesheet", "canonical", "modulepreload", "prerender", "preload", "prefetch"].includes(val))
465
+ if (key2 === "rel" && (val === "stylesheet" || val === "canonical" || val === "modulepreload" || val === "prerender" || val === "preload" || val === "prefetch"))
441
466
  return;
442
467
  if (key2 === "href") {
443
468
  if (val.includes("javascript:") || val.includes("data:"))
@@ -489,25 +514,36 @@ function whitelistSafeInput(input) {
489
514
  return filtered;
490
515
  }
491
516
 
492
- async function normaliseTag(tagName, input, e) {
517
+ function thenable(val, thenFn) {
518
+ if (val instanceof Promise) {
519
+ return val.then(thenFn);
520
+ }
521
+ return thenFn(val);
522
+ }
523
+
524
+ function normaliseTag(tagName, input, e, normalizedProps) {
525
+ const props = normalizedProps || normaliseProps(
526
+ // explicitly check for an object
527
+ // @ts-expect-error untyped
528
+ typeof input === "object" && typeof input !== "function" && !(input instanceof Promise) ? { ...input } : { [tagName === "script" || tagName === "noscript" || tagName === "style" ? "innerHTML" : "textContent"]: input },
529
+ tagName === "templateParams" || tagName === "titleTemplate"
530
+ );
531
+ if (props instanceof Promise) {
532
+ return props.then((val) => normaliseTag(tagName, input, e, val));
533
+ }
493
534
  const tag = {
494
535
  tag: tagName,
495
- props: await normaliseProps(
496
- // explicitly check for an object
497
- // @ts-expect-error untyped
498
- typeof input === "object" && typeof input !== "function" && !(input instanceof Promise) ? { ...input } : { [["script", "noscript", "style"].includes(tagName) ? "innerHTML" : "textContent"]: input },
499
- ["templateParams", "titleTemplate"].includes(tagName)
500
- )
536
+ props
501
537
  };
502
- TagConfigKeys.forEach((k) => {
503
- const val = typeof tag.props[k] !== "undefined" ? tag.props[k] : e[k];
504
- if (typeof val !== "undefined") {
505
- if (!["innerHTML", "textContent", "children"].includes(k) || TagsWithInnerContent.includes(tag.tag)) {
538
+ for (const k of TagConfigKeys) {
539
+ const val = tag.props[k] !== void 0 ? tag.props[k] : e[k];
540
+ if (val !== void 0) {
541
+ if (!(k === "innerHTML" || k === "textContent" || k === "children") || TagsWithInnerContent.has(tag.tag)) {
506
542
  tag[k === "children" ? "innerHTML" : k] = val;
507
543
  }
508
544
  delete tag.props[k];
509
545
  }
510
- });
546
+ }
511
547
  if (tag.props.body) {
512
548
  tag.tagPosition = "bodyClose";
513
549
  delete tag.props.body;
@@ -525,17 +561,22 @@ function normaliseStyleClassProps(key, v) {
525
561
  if (typeof v === "object" && !Array.isArray(v)) {
526
562
  v = Object.entries(v).filter(([, v2]) => v2).map(([k, v2]) => key === "style" ? `${k}:${v2}` : k);
527
563
  }
528
- return String(Array.isArray(v) ? v.join(sep) : v)?.split(sep).filter((c) => c.trim()).filter(Boolean).join(sep);
564
+ return String(Array.isArray(v) ? v.join(sep) : v)?.split(sep).filter((c) => Boolean(c.trim())).join(sep);
529
565
  }
530
- async function normaliseProps(props, virtual) {
531
- for (const k of Object.keys(props)) {
532
- if (["class", "style"].includes(k)) {
566
+ function nestedNormaliseProps(props, virtual, keys, startIndex) {
567
+ for (let i = startIndex; i < keys.length; i += 1) {
568
+ const k = keys[i];
569
+ if (k === "class" || k === "style") {
533
570
  props[k] = normaliseStyleClassProps(k, props[k]);
534
571
  continue;
535
572
  }
536
- if (props[k] instanceof Promise)
537
- props[k] = await props[k];
538
- if (!virtual && !TagConfigKeys.includes(k)) {
573
+ if (props[k] instanceof Promise) {
574
+ return props[k].then((val) => {
575
+ props[k] = val;
576
+ return nestedNormaliseProps(props, virtual, keys, i);
577
+ });
578
+ }
579
+ if (!virtual && !TagConfigKeys.has(k)) {
539
580
  const v = String(props[k]);
540
581
  const isDataKey = k.startsWith("data-");
541
582
  if (v === "true" || v === "") {
@@ -548,21 +589,60 @@ async function normaliseProps(props, virtual) {
548
589
  }
549
590
  }
550
591
  }
592
+ }
593
+ function normaliseProps(props, virtual = false) {
594
+ const resolvedProps = nestedNormaliseProps(props, virtual, Object.keys(props), 0);
595
+ if (resolvedProps instanceof Promise) {
596
+ return resolvedProps.then(() => props);
597
+ }
551
598
  return props;
552
599
  }
553
600
  const TagEntityBits = 10;
554
- async function normaliseEntryTags(e) {
601
+ function nestedNormaliseEntryTags(headTags, tagPromises, startIndex) {
602
+ for (let i = startIndex; i < tagPromises.length; i += 1) {
603
+ const tags = tagPromises[i];
604
+ if (tags instanceof Promise) {
605
+ return tags.then((val) => {
606
+ tagPromises[i] = val;
607
+ return nestedNormaliseEntryTags(headTags, tagPromises, i);
608
+ });
609
+ }
610
+ if (Array.isArray(tags)) {
611
+ headTags.push(...tags);
612
+ } else {
613
+ headTags.push(tags);
614
+ }
615
+ }
616
+ }
617
+ function normaliseEntryTags(e) {
555
618
  const tagPromises = [];
556
- Object.entries(e.resolvedInput).filter(([k, v]) => typeof v !== "undefined" && ValidHeadTags.includes(k)).forEach(([k, value]) => {
557
- const v = asArray$1(value);
558
- tagPromises.push(...v.map((props) => normaliseTag(k, props, e)).flat());
559
- });
560
- return (await Promise.all(tagPromises)).flat().filter(Boolean).map((t, i) => {
619
+ const input = e.resolvedInput;
620
+ for (const k in input) {
621
+ if (!Object.prototype.hasOwnProperty.call(input, k)) {
622
+ continue;
623
+ }
624
+ const v = input[k];
625
+ if (v === void 0 || !ValidHeadTags.has(k)) {
626
+ continue;
627
+ }
628
+ if (Array.isArray(v)) {
629
+ for (const props of v) {
630
+ tagPromises.push(normaliseTag(k, props, e));
631
+ }
632
+ continue;
633
+ }
634
+ tagPromises.push(normaliseTag(k, v, e));
635
+ }
636
+ if (tagPromises.length === 0) {
637
+ return [];
638
+ }
639
+ const headTags = [];
640
+ return thenable(nestedNormaliseEntryTags(headTags, tagPromises, 0), () => headTags.map((t, i) => {
561
641
  t._e = e._i;
562
642
  e.mode && (t._m = e.mode);
563
643
  t._p = (e._i << TagEntityBits) + i;
564
644
  return t;
565
- });
645
+ }));
566
646
  }
567
647
 
568
648
  const TAG_WEIGHTS = {
@@ -577,66 +657,72 @@ const TAG_ALIASES = {
577
657
  low: 20
578
658
  };
579
659
  function tagWeight(tag) {
580
- let weight = 100;
581
660
  const priority = tag.tagPriority;
582
661
  if (typeof priority === "number")
583
662
  return priority;
663
+ let weight = 100;
584
664
  if (tag.tag === "meta") {
585
665
  if (tag.props["http-equiv"] === "content-security-policy")
586
666
  weight = -30;
587
- if (tag.props.charset)
667
+ else if (tag.props.charset)
588
668
  weight = -20;
589
- if (tag.props.name === "viewport")
669
+ else if (tag.props.name === "viewport")
590
670
  weight = -15;
591
671
  } else if (tag.tag === "link" && tag.props.rel === "preconnect") {
592
672
  weight = 20;
593
673
  } else if (tag.tag in TAG_WEIGHTS) {
594
674
  weight = TAG_WEIGHTS[tag.tag];
595
675
  }
596
- if (typeof priority === "string" && priority in TAG_ALIASES) {
676
+ if (priority && priority in TAG_ALIASES) {
597
677
  return weight + TAG_ALIASES[priority];
598
678
  }
599
679
  return weight;
600
680
  }
601
681
  const SortModifiers = [{ prefix: "before:", offset: -1 }, { prefix: "after:", offset: 1 }];
602
682
 
603
- const NetworkEvents = ["onload", "onerror", "onabort", "onprogress", "onloadstart"];
604
- const ScriptNetworkEvents = ["onload", "onerror"];
683
+ const NetworkEvents = /* @__PURE__ */ new Set(["onload", "onerror", "onabort", "onprogress", "onloadstart"]);
684
+ const ScriptNetworkEvents = /* @__PURE__ */ new Set(["onload", "onerror"]);
605
685
 
606
686
  const sepSub = "%separator";
687
+ function sub(p, token) {
688
+ let val;
689
+ if (token === "s" || token === "pageTitle") {
690
+ val = p.pageTitle;
691
+ } else if (token.includes(".")) {
692
+ const dotIndex = token.indexOf(".");
693
+ val = p[token.substring(0, dotIndex)]?.[token.substring(dotIndex + 1)];
694
+ } else {
695
+ val = p[token];
696
+ }
697
+ return val !== void 0 ? (val || "").replace(/"/g, '\\"') : void 0;
698
+ }
699
+ const sepSubRe = new RegExp(`${sepSub}(?:\\s*${sepSub})*`, "g");
607
700
  function processTemplateParams(s, p, sep) {
608
701
  if (typeof s !== "string" || !s.includes("%"))
609
702
  return s;
610
- function sub(token) {
611
- let val;
612
- if (["s", "pageTitle"].includes(token)) {
613
- val = p.pageTitle;
614
- } else if (token.includes(".")) {
615
- val = token.split(".").reduce((acc, key) => acc ? acc[key] || void 0 : void 0, p);
616
- } else {
617
- val = p[token];
618
- }
619
- return typeof val !== "undefined" ? (val || "").replace(/"/g, '\\"') : false;
620
- }
621
703
  let decoded = s;
622
704
  try {
623
705
  decoded = decodeURI(s);
624
706
  } catch {
625
707
  }
626
- const tokens = (decoded.match(/%(\w+\.+\w+)|%(\w+)/g) || []).sort().reverse();
627
- tokens.forEach((token) => {
628
- const re = sub(token.slice(1));
629
- if (typeof re === "string") {
630
- s = s.replace(new RegExp(`\\${token}(\\W|$)`, "g"), (_, args) => `${re}${args}`).trim();
708
+ const tokens = decoded.match(/%\w+(?:\.\w+)?/g);
709
+ if (!tokens) {
710
+ return s;
711
+ }
712
+ const hasSepSub = s.includes(sepSub);
713
+ s = s.replace(/%\w+(?:\.\w+)?/g, (token) => {
714
+ if (token === sepSub || !tokens.includes(token)) {
715
+ return token;
631
716
  }
632
- });
633
- if (s.includes(sepSub)) {
717
+ const re = sub(p, token.slice(1));
718
+ return re !== void 0 ? re : token;
719
+ }).trim();
720
+ if (hasSepSub) {
634
721
  if (s.endsWith(sepSub))
635
- s = s.slice(0, -sepSub.length).trim();
722
+ s = s.slice(0, -sepSub.length);
636
723
  if (s.startsWith(sepSub))
637
- s = s.slice(sepSub.length).trim();
638
- s = s.replace(new RegExp(`\\${sepSub}\\s*\\${sepSub}`, "g"), sepSub);
639
- s = processTemplateParams(s, { separator: sep }, sep);
724
+ s = s.slice(sepSub.length);
725
+ s = s.replace(sepSubRe, sep).trim();
640
726
  }
641
727
  return s;
642
728
  }
@@ -671,5 +757,6 @@ exports.resolvePackedMetaObjectValue = resolvePackedMetaObjectValue;
671
757
  exports.resolveTitleTemplate = resolveTitleTemplate;
672
758
  exports.tagDedupeKey = tagDedupeKey;
673
759
  exports.tagWeight = tagWeight;
760
+ exports.thenable = thenable;
674
761
  exports.unpackMeta = unpackMeta;
675
762
  exports.whitelistSafeInput = whitelistSafeInput;
package/dist/index.d.cts CHANGED
@@ -3,12 +3,12 @@ import { HeadPluginInput, HeadTag, BaseMeta, MetaFlatInput, Head, MaybeArray, He
3
3
  type Arrayable<T> = T | Array<T>;
4
4
  declare function asArray<T>(value: Arrayable<T>): T[];
5
5
 
6
- declare const SelfClosingTags: string[];
7
- declare const TagsWithInnerContent: string[];
8
- declare const HasElementTags: string[];
9
- declare const ValidHeadTags: string[];
10
- declare const UniqueTags: string[];
11
- declare const TagConfigKeys: string[];
6
+ declare const SelfClosingTags: Set<string>;
7
+ declare const TagsWithInnerContent: Set<string>;
8
+ declare const HasElementTags: Set<string>;
9
+ declare const ValidHeadTags: Set<string>;
10
+ declare const UniqueTags: Set<string>;
11
+ declare const TagConfigKeys: Set<string>;
12
12
  declare const IsBrowser: boolean;
13
13
  declare const composableNames: string[];
14
14
 
@@ -17,7 +17,7 @@ declare function defineHeadPlugin(plugin: HeadPluginInput): HeadPluginInput;
17
17
  declare function hashCode(s: string): string;
18
18
  declare function hashTag(tag: HeadTag): string;
19
19
 
20
- declare function tagDedupeKey<T extends HeadTag>(tag: T, fn?: (key: string) => boolean): string | false;
20
+ declare function tagDedupeKey<T extends HeadTag>(tag: T): string | false;
21
21
 
22
22
  declare function resolveTitleTemplate(template: string | ((title?: string) => string | null) | null, title?: string): string | null;
23
23
 
@@ -37,11 +37,14 @@ declare function packMeta<T extends Required<Head>['meta']>(inputs: T): MetaFlat
37
37
 
38
38
  declare function whitelistSafeInput(input: Record<string, MaybeArray<Record<string, string>>>): HeadSafe;
39
39
 
40
- declare function normaliseTag<T extends HeadTag>(tagName: T['tag'], input: HeadTag['props'] | string, e: HeadEntry<T>): Promise<T | T[] | false>;
40
+ type Thenable<T> = Promise<T> | T;
41
+ declare function thenable<T, R>(val: T, thenFn: (val: Awaited<T>) => R): Promise<R> | R;
42
+
43
+ declare function normaliseTag<T extends HeadTag>(tagName: T['tag'], input: HeadTag['props'] | string, e: HeadEntry<T>, normalizedProps?: HeadTag['props']): Thenable<T | T[]>;
41
44
  declare function normaliseStyleClassProps<T extends 'class' | 'style'>(key: T, v: Required<Required<Head>['htmlAttrs']['class']> | Required<Required<Head>['htmlAttrs']['style']>): string;
42
- declare function normaliseProps<T extends HeadTag>(props: T['props'], virtual?: boolean): Promise<T['props']>;
45
+ declare function normaliseProps<T extends HeadTag>(props: T['props'], virtual?: boolean): Thenable<T['props']>;
43
46
  declare const TagEntityBits = 10;
44
- declare function normaliseEntryTags<T extends {} = Head>(e: HeadEntry<T>): Promise<HeadTag[]>;
47
+ declare function normaliseEntryTags<T extends object = Head>(e: HeadEntry<T>): Thenable<HeadTag[]>;
45
48
 
46
49
  declare const TAG_WEIGHTS: {
47
50
  readonly base: -10;
@@ -52,15 +55,15 @@ declare const TAG_ALIASES: {
52
55
  readonly high: -10;
53
56
  readonly low: 20;
54
57
  };
55
- declare function tagWeight<T extends HeadTag>(tag: T): any;
58
+ declare function tagWeight<T extends HeadTag>(tag: T): number;
56
59
  declare const SortModifiers: {
57
60
  prefix: string;
58
61
  offset: number;
59
62
  }[];
60
63
 
61
- declare const NetworkEvents: string[];
62
- declare const ScriptNetworkEvents: string[];
64
+ declare const NetworkEvents: Set<string>;
65
+ declare const ScriptNetworkEvents: Set<string>;
63
66
 
64
67
  declare function processTemplateParams(s: string, p: TemplateParams, sep: string): string;
65
68
 
66
- export { type Arrayable, 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, unpackMeta, whitelistSafeInput };
69
+ 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 };
package/dist/index.d.mts CHANGED
@@ -3,12 +3,12 @@ import { HeadPluginInput, HeadTag, BaseMeta, MetaFlatInput, Head, MaybeArray, He
3
3
  type Arrayable<T> = T | Array<T>;
4
4
  declare function asArray<T>(value: Arrayable<T>): T[];
5
5
 
6
- declare const SelfClosingTags: string[];
7
- declare const TagsWithInnerContent: string[];
8
- declare const HasElementTags: string[];
9
- declare const ValidHeadTags: string[];
10
- declare const UniqueTags: string[];
11
- declare const TagConfigKeys: string[];
6
+ declare const SelfClosingTags: Set<string>;
7
+ declare const TagsWithInnerContent: Set<string>;
8
+ declare const HasElementTags: Set<string>;
9
+ declare const ValidHeadTags: Set<string>;
10
+ declare const UniqueTags: Set<string>;
11
+ declare const TagConfigKeys: Set<string>;
12
12
  declare const IsBrowser: boolean;
13
13
  declare const composableNames: string[];
14
14
 
@@ -17,7 +17,7 @@ declare function defineHeadPlugin(plugin: HeadPluginInput): HeadPluginInput;
17
17
  declare function hashCode(s: string): string;
18
18
  declare function hashTag(tag: HeadTag): string;
19
19
 
20
- declare function tagDedupeKey<T extends HeadTag>(tag: T, fn?: (key: string) => boolean): string | false;
20
+ declare function tagDedupeKey<T extends HeadTag>(tag: T): string | false;
21
21
 
22
22
  declare function resolveTitleTemplate(template: string | ((title?: string) => string | null) | null, title?: string): string | null;
23
23
 
@@ -37,11 +37,14 @@ declare function packMeta<T extends Required<Head>['meta']>(inputs: T): MetaFlat
37
37
 
38
38
  declare function whitelistSafeInput(input: Record<string, MaybeArray<Record<string, string>>>): HeadSafe;
39
39
 
40
- declare function normaliseTag<T extends HeadTag>(tagName: T['tag'], input: HeadTag['props'] | string, e: HeadEntry<T>): Promise<T | T[] | false>;
40
+ type Thenable<T> = Promise<T> | T;
41
+ declare function thenable<T, R>(val: T, thenFn: (val: Awaited<T>) => R): Promise<R> | R;
42
+
43
+ declare function normaliseTag<T extends HeadTag>(tagName: T['tag'], input: HeadTag['props'] | string, e: HeadEntry<T>, normalizedProps?: HeadTag['props']): Thenable<T | T[]>;
41
44
  declare function normaliseStyleClassProps<T extends 'class' | 'style'>(key: T, v: Required<Required<Head>['htmlAttrs']['class']> | Required<Required<Head>['htmlAttrs']['style']>): string;
42
- declare function normaliseProps<T extends HeadTag>(props: T['props'], virtual?: boolean): Promise<T['props']>;
45
+ declare function normaliseProps<T extends HeadTag>(props: T['props'], virtual?: boolean): Thenable<T['props']>;
43
46
  declare const TagEntityBits = 10;
44
- declare function normaliseEntryTags<T extends {} = Head>(e: HeadEntry<T>): Promise<HeadTag[]>;
47
+ declare function normaliseEntryTags<T extends object = Head>(e: HeadEntry<T>): Thenable<HeadTag[]>;
45
48
 
46
49
  declare const TAG_WEIGHTS: {
47
50
  readonly base: -10;
@@ -52,15 +55,15 @@ declare const TAG_ALIASES: {
52
55
  readonly high: -10;
53
56
  readonly low: 20;
54
57
  };
55
- declare function tagWeight<T extends HeadTag>(tag: T): any;
58
+ declare function tagWeight<T extends HeadTag>(tag: T): number;
56
59
  declare const SortModifiers: {
57
60
  prefix: string;
58
61
  offset: number;
59
62
  }[];
60
63
 
61
- declare const NetworkEvents: string[];
62
- declare const ScriptNetworkEvents: string[];
64
+ declare const NetworkEvents: Set<string>;
65
+ declare const ScriptNetworkEvents: Set<string>;
63
66
 
64
67
  declare function processTemplateParams(s: string, p: TemplateParams, sep: string): string;
65
68
 
66
- export { type Arrayable, 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, unpackMeta, whitelistSafeInput };
69
+ 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 };
package/dist/index.d.ts CHANGED
@@ -3,12 +3,12 @@ import { HeadPluginInput, HeadTag, BaseMeta, MetaFlatInput, Head, MaybeArray, He
3
3
  type Arrayable<T> = T | Array<T>;
4
4
  declare function asArray<T>(value: Arrayable<T>): T[];
5
5
 
6
- declare const SelfClosingTags: string[];
7
- declare const TagsWithInnerContent: string[];
8
- declare const HasElementTags: string[];
9
- declare const ValidHeadTags: string[];
10
- declare const UniqueTags: string[];
11
- declare const TagConfigKeys: string[];
6
+ declare const SelfClosingTags: Set<string>;
7
+ declare const TagsWithInnerContent: Set<string>;
8
+ declare const HasElementTags: Set<string>;
9
+ declare const ValidHeadTags: Set<string>;
10
+ declare const UniqueTags: Set<string>;
11
+ declare const TagConfigKeys: Set<string>;
12
12
  declare const IsBrowser: boolean;
13
13
  declare const composableNames: string[];
14
14
 
@@ -17,7 +17,7 @@ declare function defineHeadPlugin(plugin: HeadPluginInput): HeadPluginInput;
17
17
  declare function hashCode(s: string): string;
18
18
  declare function hashTag(tag: HeadTag): string;
19
19
 
20
- declare function tagDedupeKey<T extends HeadTag>(tag: T, fn?: (key: string) => boolean): string | false;
20
+ declare function tagDedupeKey<T extends HeadTag>(tag: T): string | false;
21
21
 
22
22
  declare function resolveTitleTemplate(template: string | ((title?: string) => string | null) | null, title?: string): string | null;
23
23
 
@@ -37,11 +37,14 @@ declare function packMeta<T extends Required<Head>['meta']>(inputs: T): MetaFlat
37
37
 
38
38
  declare function whitelistSafeInput(input: Record<string, MaybeArray<Record<string, string>>>): HeadSafe;
39
39
 
40
- declare function normaliseTag<T extends HeadTag>(tagName: T['tag'], input: HeadTag['props'] | string, e: HeadEntry<T>): Promise<T | T[] | false>;
40
+ type Thenable<T> = Promise<T> | T;
41
+ declare function thenable<T, R>(val: T, thenFn: (val: Awaited<T>) => R): Promise<R> | R;
42
+
43
+ declare function normaliseTag<T extends HeadTag>(tagName: T['tag'], input: HeadTag['props'] | string, e: HeadEntry<T>, normalizedProps?: HeadTag['props']): Thenable<T | T[]>;
41
44
  declare function normaliseStyleClassProps<T extends 'class' | 'style'>(key: T, v: Required<Required<Head>['htmlAttrs']['class']> | Required<Required<Head>['htmlAttrs']['style']>): string;
42
- declare function normaliseProps<T extends HeadTag>(props: T['props'], virtual?: boolean): Promise<T['props']>;
45
+ declare function normaliseProps<T extends HeadTag>(props: T['props'], virtual?: boolean): Thenable<T['props']>;
43
46
  declare const TagEntityBits = 10;
44
- declare function normaliseEntryTags<T extends {} = Head>(e: HeadEntry<T>): Promise<HeadTag[]>;
47
+ declare function normaliseEntryTags<T extends object = Head>(e: HeadEntry<T>): Thenable<HeadTag[]>;
45
48
 
46
49
  declare const TAG_WEIGHTS: {
47
50
  readonly base: -10;
@@ -52,15 +55,15 @@ declare const TAG_ALIASES: {
52
55
  readonly high: -10;
53
56
  readonly low: 20;
54
57
  };
55
- declare function tagWeight<T extends HeadTag>(tag: T): any;
58
+ declare function tagWeight<T extends HeadTag>(tag: T): number;
56
59
  declare const SortModifiers: {
57
60
  prefix: string;
58
61
  offset: number;
59
62
  }[];
60
63
 
61
- declare const NetworkEvents: string[];
62
- declare const ScriptNetworkEvents: string[];
64
+ declare const NetworkEvents: Set<string>;
65
+ declare const ScriptNetworkEvents: Set<string>;
63
66
 
64
67
  declare function processTemplateParams(s: string, p: TemplateParams, sep: string): string;
65
68
 
66
- export { type Arrayable, 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, unpackMeta, whitelistSafeInput };
69
+ 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 };
package/dist/index.mjs CHANGED
@@ -2,17 +2,17 @@ function asArray$1(value) {
2
2
  return Array.isArray(value) ? value : [value];
3
3
  }
4
4
 
5
- const SelfClosingTags = ["meta", "link", "base"];
6
- const TagsWithInnerContent = ["title", "titleTemplate", "script", "style", "noscript"];
7
- const HasElementTags = [
5
+ const SelfClosingTags = /* @__PURE__ */ new Set(["meta", "link", "base"]);
6
+ const TagsWithInnerContent = /* @__PURE__ */ new Set(["title", "titleTemplate", "script", "style", "noscript"]);
7
+ const HasElementTags = /* @__PURE__ */ new Set([
8
8
  "base",
9
9
  "meta",
10
10
  "link",
11
11
  "style",
12
12
  "script",
13
13
  "noscript"
14
- ];
15
- const ValidHeadTags = [
14
+ ]);
15
+ const ValidHeadTags = /* @__PURE__ */ new Set([
16
16
  "title",
17
17
  "titleTemplate",
18
18
  "templateParams",
@@ -24,9 +24,9 @@ const ValidHeadTags = [
24
24
  "style",
25
25
  "script",
26
26
  "noscript"
27
- ];
28
- const UniqueTags = ["base", "title", "titleTemplate", "bodyAttrs", "htmlAttrs", "templateParams"];
29
- const TagConfigKeys = ["tagPosition", "tagPriority", "tagDuplicateStrategy", "children", "innerHTML", "textContent", "processTemplateParams"];
27
+ ]);
28
+ const UniqueTags = /* @__PURE__ */ new Set(["base", "title", "titleTemplate", "bodyAttrs", "htmlAttrs", "templateParams"]);
29
+ const TagConfigKeys = /* @__PURE__ */ new Set(["tagPosition", "tagPriority", "tagDuplicateStrategy", "children", "innerHTML", "textContent", "processTemplateParams"]);
30
30
  const IsBrowser = typeof window !== "undefined";
31
31
  const composableNames = [
32
32
  "getActiveHead",
@@ -49,26 +49,34 @@ function hashCode(s) {
49
49
  return ((h ^ h >>> 9) + 65536).toString(16).substring(1, 8).toLowerCase();
50
50
  }
51
51
  function hashTag(tag) {
52
- return tag._h || hashCode(tag._d ? tag._d : `${tag.tag}:${tag.textContent || tag.innerHTML || ""}:${Object.entries(tag.props).map(([key, value]) => `${key}:${String(value)}`).join(",")}`);
52
+ if (tag._h) {
53
+ return tag._h;
54
+ }
55
+ if (tag._d) {
56
+ return hashCode(tag._d);
57
+ }
58
+ let content = `${tag.tag}:${tag.textContent || tag.innerHTML || ""}:`;
59
+ for (const key in tag.props) {
60
+ content += `${key}:${tag.props[key]},`;
61
+ }
62
+ return hashCode(content);
53
63
  }
54
64
 
55
- function tagDedupeKey(tag, fn) {
65
+ const allowedMetaProperties = ["name", "property", "http-equiv"];
66
+ function tagDedupeKey(tag) {
56
67
  const { props, tag: tagName } = tag;
57
- if (UniqueTags.includes(tagName))
68
+ if (UniqueTags.has(tagName))
58
69
  return tagName;
59
70
  if (tagName === "link" && props.rel === "canonical")
60
71
  return "canonical";
61
72
  if (props.charset)
62
73
  return "charset";
63
- const name = ["id"];
64
- if (tagName === "meta")
65
- name.push(...["name", "property", "http-equiv"]);
66
- for (const n of name) {
67
- if (typeof props[n] !== "undefined") {
68
- const val = String(props[n]);
69
- if (fn && !fn(val))
70
- return false;
71
- return `${tagName}:${n}:${val}`;
74
+ if (props.id) {
75
+ return `${tagName}:id:${props.id}`;
76
+ }
77
+ for (const n of allowedMetaProperties) {
78
+ if (props[n] !== void 0) {
79
+ return `${tagName}:${n}:${props[n]}`;
72
80
  }
73
81
  }
74
82
  return false;
@@ -250,15 +258,16 @@ const MetaPackingSchema = {
250
258
  metaKey: "http-equiv"
251
259
  }
252
260
  };
253
- const openGraphNamespaces = [
261
+ const openGraphNamespaces = /* @__PURE__ */ new Set([
254
262
  "og",
255
263
  "book",
256
264
  "article",
257
265
  "profile"
258
- ];
266
+ ]);
259
267
  function resolveMetaKeyType(key) {
260
- const fKey = fixKeyCase(key).split(":")[0];
261
- if (openGraphNamespaces.includes(fKey))
268
+ const fKey = fixKeyCase(key);
269
+ const prefixIndex = fKey.indexOf(":");
270
+ if (openGraphNamespaces.has(fKey.substring(0, prefixIndex)))
262
271
  return "property";
263
272
  return MetaPackingSchema[key]?.metaKey || "name";
264
273
  }
@@ -267,8 +276,9 @@ function resolveMetaKeyValue(key) {
267
276
  }
268
277
  function fixKeyCase(key) {
269
278
  const updated = key.replace(/([A-Z])/g, "-$1").toLowerCase();
270
- const fKey = updated.split("-")[0];
271
- if (openGraphNamespaces.includes(fKey) || fKey === "twitter")
279
+ const prefixIndex = updated.indexOf("-");
280
+ const fKey = updated.substring(0, prefixIndex);
281
+ if (fKey === "twitter" || openGraphNamespaces.has(fKey))
272
282
  return key.replace(/([A-Z])/g, ":$1").toLowerCase();
273
283
  return updated;
274
284
  }
@@ -279,8 +289,12 @@ function changeKeyCasingDeep(input) {
279
289
  if (typeof input !== "object" || Array.isArray(input))
280
290
  return input;
281
291
  const output = {};
282
- for (const [key, value] of Object.entries(input))
283
- output[fixKeyCase(key)] = changeKeyCasingDeep(value);
292
+ for (const key in input) {
293
+ if (!Object.prototype.hasOwnProperty.call(input, key)) {
294
+ continue;
295
+ }
296
+ output[fixKeyCase(key)] = changeKeyCasingDeep(input[key]);
297
+ }
284
298
  return output;
285
299
  }
286
300
  function resolvePackedMetaObjectValue(value, key) {
@@ -302,24 +316,31 @@ function resolvePackedMetaObjectValue(value, key) {
302
316
  }
303
317
  );
304
318
  }
305
- const ObjectArrayEntries = ["og:image", "og:video", "og:audio", "twitter:image"];
319
+ const ObjectArrayEntries = /* @__PURE__ */ new Set(["og:image", "og:video", "og:audio", "twitter:image"]);
306
320
  function sanitize(input) {
307
321
  const out = {};
308
- Object.entries(input).forEach(([k2, v]) => {
322
+ for (const k2 in input) {
323
+ if (!Object.prototype.hasOwnProperty.call(input, k2)) {
324
+ continue;
325
+ }
326
+ const v = input[k2];
309
327
  if (String(v) !== "false" && k2)
310
328
  out[k2] = v;
311
- });
329
+ }
312
330
  return out;
313
331
  }
314
332
  function handleObjectEntry(key, v) {
315
333
  const value = sanitize(v);
316
334
  const fKey = fixKeyCase(key);
317
335
  const attr = resolveMetaKeyType(fKey);
318
- if (ObjectArrayEntries.includes(fKey)) {
336
+ if (ObjectArrayEntries.has(fKey)) {
319
337
  const input = {};
320
- Object.entries(value).forEach(([k2, v2]) => {
321
- input[`${key}${k2 === "url" ? "" : `${k2.charAt(0).toUpperCase()}${k2.slice(1)}`}`] = v2;
322
- });
338
+ for (const k2 in value) {
339
+ if (!Object.prototype.hasOwnProperty.call(value, k2)) {
340
+ continue;
341
+ }
342
+ input[`${key}${k2 === "url" ? "" : `${k2[0].toUpperCase()}${k2.slice(1)}`}`] = value[k2];
343
+ }
323
344
  return unpackMeta(input).sort((a, b) => (a[attr]?.length || 0) - (b[attr]?.length || 0));
324
345
  }
325
346
  return [{ [attr]: fKey, ...value }];
@@ -327,23 +348,27 @@ function handleObjectEntry(key, v) {
327
348
  function unpackMeta(input) {
328
349
  const extras = [];
329
350
  const primitives = {};
330
- Object.entries(input).forEach(([key, value]) => {
351
+ for (const key in input) {
352
+ if (!Object.prototype.hasOwnProperty.call(input, key)) {
353
+ continue;
354
+ }
355
+ const value = input[key];
331
356
  if (!Array.isArray(value)) {
332
357
  if (typeof value === "object" && value) {
333
- if (ObjectArrayEntries.includes(fixKeyCase(key))) {
358
+ if (ObjectArrayEntries.has(fixKeyCase(key))) {
334
359
  extras.push(...handleObjectEntry(key, value));
335
- return;
360
+ continue;
336
361
  }
337
362
  primitives[key] = sanitize(value);
338
363
  } else {
339
364
  primitives[key] = value;
340
365
  }
341
- return;
366
+ continue;
342
367
  }
343
- value.forEach((v) => {
368
+ for (const v of value) {
344
369
  extras.push(...typeof v === "string" ? unpackMeta({ [key]: v }) : handleObjectEntry(key, v));
345
- });
346
- });
370
+ }
371
+ }
347
372
  const meta = unpackToArray(primitives, {
348
373
  key({ key }) {
349
374
  return resolveMetaKeyType(key);
@@ -435,7 +460,7 @@ function whitelistSafeInput(input) {
435
460
  const link = acceptDataAttrs(meta);
436
461
  WhitelistAttributes.link.forEach((key2) => {
437
462
  const val = meta[key2];
438
- if (key2 === "rel" && ["stylesheet", "canonical", "modulepreload", "prerender", "preload", "prefetch"].includes(val))
463
+ if (key2 === "rel" && (val === "stylesheet" || val === "canonical" || val === "modulepreload" || val === "prerender" || val === "preload" || val === "prefetch"))
439
464
  return;
440
465
  if (key2 === "href") {
441
466
  if (val.includes("javascript:") || val.includes("data:"))
@@ -487,25 +512,36 @@ function whitelistSafeInput(input) {
487
512
  return filtered;
488
513
  }
489
514
 
490
- async function normaliseTag(tagName, input, e) {
515
+ function thenable(val, thenFn) {
516
+ if (val instanceof Promise) {
517
+ return val.then(thenFn);
518
+ }
519
+ return thenFn(val);
520
+ }
521
+
522
+ function normaliseTag(tagName, input, e, normalizedProps) {
523
+ const props = normalizedProps || normaliseProps(
524
+ // explicitly check for an object
525
+ // @ts-expect-error untyped
526
+ typeof input === "object" && typeof input !== "function" && !(input instanceof Promise) ? { ...input } : { [tagName === "script" || tagName === "noscript" || tagName === "style" ? "innerHTML" : "textContent"]: input },
527
+ tagName === "templateParams" || tagName === "titleTemplate"
528
+ );
529
+ if (props instanceof Promise) {
530
+ return props.then((val) => normaliseTag(tagName, input, e, val));
531
+ }
491
532
  const tag = {
492
533
  tag: tagName,
493
- props: await normaliseProps(
494
- // explicitly check for an object
495
- // @ts-expect-error untyped
496
- typeof input === "object" && typeof input !== "function" && !(input instanceof Promise) ? { ...input } : { [["script", "noscript", "style"].includes(tagName) ? "innerHTML" : "textContent"]: input },
497
- ["templateParams", "titleTemplate"].includes(tagName)
498
- )
534
+ props
499
535
  };
500
- TagConfigKeys.forEach((k) => {
501
- const val = typeof tag.props[k] !== "undefined" ? tag.props[k] : e[k];
502
- if (typeof val !== "undefined") {
503
- if (!["innerHTML", "textContent", "children"].includes(k) || TagsWithInnerContent.includes(tag.tag)) {
536
+ for (const k of TagConfigKeys) {
537
+ const val = tag.props[k] !== void 0 ? tag.props[k] : e[k];
538
+ if (val !== void 0) {
539
+ if (!(k === "innerHTML" || k === "textContent" || k === "children") || TagsWithInnerContent.has(tag.tag)) {
504
540
  tag[k === "children" ? "innerHTML" : k] = val;
505
541
  }
506
542
  delete tag.props[k];
507
543
  }
508
- });
544
+ }
509
545
  if (tag.props.body) {
510
546
  tag.tagPosition = "bodyClose";
511
547
  delete tag.props.body;
@@ -523,17 +559,22 @@ function normaliseStyleClassProps(key, v) {
523
559
  if (typeof v === "object" && !Array.isArray(v)) {
524
560
  v = Object.entries(v).filter(([, v2]) => v2).map(([k, v2]) => key === "style" ? `${k}:${v2}` : k);
525
561
  }
526
- return String(Array.isArray(v) ? v.join(sep) : v)?.split(sep).filter((c) => c.trim()).filter(Boolean).join(sep);
562
+ return String(Array.isArray(v) ? v.join(sep) : v)?.split(sep).filter((c) => Boolean(c.trim())).join(sep);
527
563
  }
528
- async function normaliseProps(props, virtual) {
529
- for (const k of Object.keys(props)) {
530
- if (["class", "style"].includes(k)) {
564
+ function nestedNormaliseProps(props, virtual, keys, startIndex) {
565
+ for (let i = startIndex; i < keys.length; i += 1) {
566
+ const k = keys[i];
567
+ if (k === "class" || k === "style") {
531
568
  props[k] = normaliseStyleClassProps(k, props[k]);
532
569
  continue;
533
570
  }
534
- if (props[k] instanceof Promise)
535
- props[k] = await props[k];
536
- if (!virtual && !TagConfigKeys.includes(k)) {
571
+ if (props[k] instanceof Promise) {
572
+ return props[k].then((val) => {
573
+ props[k] = val;
574
+ return nestedNormaliseProps(props, virtual, keys, i);
575
+ });
576
+ }
577
+ if (!virtual && !TagConfigKeys.has(k)) {
537
578
  const v = String(props[k]);
538
579
  const isDataKey = k.startsWith("data-");
539
580
  if (v === "true" || v === "") {
@@ -546,21 +587,60 @@ async function normaliseProps(props, virtual) {
546
587
  }
547
588
  }
548
589
  }
590
+ }
591
+ function normaliseProps(props, virtual = false) {
592
+ const resolvedProps = nestedNormaliseProps(props, virtual, Object.keys(props), 0);
593
+ if (resolvedProps instanceof Promise) {
594
+ return resolvedProps.then(() => props);
595
+ }
549
596
  return props;
550
597
  }
551
598
  const TagEntityBits = 10;
552
- async function normaliseEntryTags(e) {
599
+ function nestedNormaliseEntryTags(headTags, tagPromises, startIndex) {
600
+ for (let i = startIndex; i < tagPromises.length; i += 1) {
601
+ const tags = tagPromises[i];
602
+ if (tags instanceof Promise) {
603
+ return tags.then((val) => {
604
+ tagPromises[i] = val;
605
+ return nestedNormaliseEntryTags(headTags, tagPromises, i);
606
+ });
607
+ }
608
+ if (Array.isArray(tags)) {
609
+ headTags.push(...tags);
610
+ } else {
611
+ headTags.push(tags);
612
+ }
613
+ }
614
+ }
615
+ function normaliseEntryTags(e) {
553
616
  const tagPromises = [];
554
- Object.entries(e.resolvedInput).filter(([k, v]) => typeof v !== "undefined" && ValidHeadTags.includes(k)).forEach(([k, value]) => {
555
- const v = asArray$1(value);
556
- tagPromises.push(...v.map((props) => normaliseTag(k, props, e)).flat());
557
- });
558
- return (await Promise.all(tagPromises)).flat().filter(Boolean).map((t, i) => {
617
+ const input = e.resolvedInput;
618
+ for (const k in input) {
619
+ if (!Object.prototype.hasOwnProperty.call(input, k)) {
620
+ continue;
621
+ }
622
+ const v = input[k];
623
+ if (v === void 0 || !ValidHeadTags.has(k)) {
624
+ continue;
625
+ }
626
+ if (Array.isArray(v)) {
627
+ for (const props of v) {
628
+ tagPromises.push(normaliseTag(k, props, e));
629
+ }
630
+ continue;
631
+ }
632
+ tagPromises.push(normaliseTag(k, v, e));
633
+ }
634
+ if (tagPromises.length === 0) {
635
+ return [];
636
+ }
637
+ const headTags = [];
638
+ return thenable(nestedNormaliseEntryTags(headTags, tagPromises, 0), () => headTags.map((t, i) => {
559
639
  t._e = e._i;
560
640
  e.mode && (t._m = e.mode);
561
641
  t._p = (e._i << TagEntityBits) + i;
562
642
  return t;
563
- });
643
+ }));
564
644
  }
565
645
 
566
646
  const TAG_WEIGHTS = {
@@ -575,68 +655,74 @@ const TAG_ALIASES = {
575
655
  low: 20
576
656
  };
577
657
  function tagWeight(tag) {
578
- let weight = 100;
579
658
  const priority = tag.tagPriority;
580
659
  if (typeof priority === "number")
581
660
  return priority;
661
+ let weight = 100;
582
662
  if (tag.tag === "meta") {
583
663
  if (tag.props["http-equiv"] === "content-security-policy")
584
664
  weight = -30;
585
- if (tag.props.charset)
665
+ else if (tag.props.charset)
586
666
  weight = -20;
587
- if (tag.props.name === "viewport")
667
+ else if (tag.props.name === "viewport")
588
668
  weight = -15;
589
669
  } else if (tag.tag === "link" && tag.props.rel === "preconnect") {
590
670
  weight = 20;
591
671
  } else if (tag.tag in TAG_WEIGHTS) {
592
672
  weight = TAG_WEIGHTS[tag.tag];
593
673
  }
594
- if (typeof priority === "string" && priority in TAG_ALIASES) {
674
+ if (priority && priority in TAG_ALIASES) {
595
675
  return weight + TAG_ALIASES[priority];
596
676
  }
597
677
  return weight;
598
678
  }
599
679
  const SortModifiers = [{ prefix: "before:", offset: -1 }, { prefix: "after:", offset: 1 }];
600
680
 
601
- const NetworkEvents = ["onload", "onerror", "onabort", "onprogress", "onloadstart"];
602
- const ScriptNetworkEvents = ["onload", "onerror"];
681
+ const NetworkEvents = /* @__PURE__ */ new Set(["onload", "onerror", "onabort", "onprogress", "onloadstart"]);
682
+ const ScriptNetworkEvents = /* @__PURE__ */ new Set(["onload", "onerror"]);
603
683
 
604
684
  const sepSub = "%separator";
685
+ function sub(p, token) {
686
+ let val;
687
+ if (token === "s" || token === "pageTitle") {
688
+ val = p.pageTitle;
689
+ } else if (token.includes(".")) {
690
+ const dotIndex = token.indexOf(".");
691
+ val = p[token.substring(0, dotIndex)]?.[token.substring(dotIndex + 1)];
692
+ } else {
693
+ val = p[token];
694
+ }
695
+ return val !== void 0 ? (val || "").replace(/"/g, '\\"') : void 0;
696
+ }
697
+ const sepSubRe = new RegExp(`${sepSub}(?:\\s*${sepSub})*`, "g");
605
698
  function processTemplateParams(s, p, sep) {
606
699
  if (typeof s !== "string" || !s.includes("%"))
607
700
  return s;
608
- function sub(token) {
609
- let val;
610
- if (["s", "pageTitle"].includes(token)) {
611
- val = p.pageTitle;
612
- } else if (token.includes(".")) {
613
- val = token.split(".").reduce((acc, key) => acc ? acc[key] || void 0 : void 0, p);
614
- } else {
615
- val = p[token];
616
- }
617
- return typeof val !== "undefined" ? (val || "").replace(/"/g, '\\"') : false;
618
- }
619
701
  let decoded = s;
620
702
  try {
621
703
  decoded = decodeURI(s);
622
704
  } catch {
623
705
  }
624
- const tokens = (decoded.match(/%(\w+\.+\w+)|%(\w+)/g) || []).sort().reverse();
625
- tokens.forEach((token) => {
626
- const re = sub(token.slice(1));
627
- if (typeof re === "string") {
628
- s = s.replace(new RegExp(`\\${token}(\\W|$)`, "g"), (_, args) => `${re}${args}`).trim();
706
+ const tokens = decoded.match(/%\w+(?:\.\w+)?/g);
707
+ if (!tokens) {
708
+ return s;
709
+ }
710
+ const hasSepSub = s.includes(sepSub);
711
+ s = s.replace(/%\w+(?:\.\w+)?/g, (token) => {
712
+ if (token === sepSub || !tokens.includes(token)) {
713
+ return token;
629
714
  }
630
- });
631
- if (s.includes(sepSub)) {
715
+ const re = sub(p, token.slice(1));
716
+ return re !== void 0 ? re : token;
717
+ }).trim();
718
+ if (hasSepSub) {
632
719
  if (s.endsWith(sepSub))
633
- s = s.slice(0, -sepSub.length).trim();
720
+ s = s.slice(0, -sepSub.length);
634
721
  if (s.startsWith(sepSub))
635
- s = s.slice(sepSub.length).trim();
636
- s = s.replace(new RegExp(`\\${sepSub}\\s*\\${sepSub}`, "g"), sepSub);
637
- s = processTemplateParams(s, { separator: sep }, sep);
722
+ s = s.slice(sepSub.length);
723
+ s = s.replace(sepSubRe, sep).trim();
638
724
  }
639
725
  return s;
640
726
  }
641
727
 
642
- export { HasElementTags, IsBrowser, NetworkEvents, ScriptNetworkEvents, SelfClosingTags, SortModifiers, TAG_ALIASES, TAG_WEIGHTS, TagConfigKeys, TagEntityBits, TagsWithInnerContent, UniqueTags, ValidHeadTags, asArray$1 as asArray, composableNames, defineHeadPlugin, hashCode, hashTag, normaliseEntryTags, normaliseProps, normaliseStyleClassProps, normaliseTag, packMeta, processTemplateParams, resolveMetaKeyType, resolveMetaKeyValue, resolvePackedMetaObjectValue, resolveTitleTemplate, tagDedupeKey, tagWeight, unpackMeta, whitelistSafeInput };
728
+ export { HasElementTags, IsBrowser, NetworkEvents, ScriptNetworkEvents, SelfClosingTags, SortModifiers, TAG_ALIASES, TAG_WEIGHTS, TagConfigKeys, TagEntityBits, TagsWithInnerContent, UniqueTags, ValidHeadTags, asArray$1 as asArray, composableNames, defineHeadPlugin, hashCode, hashTag, normaliseEntryTags, normaliseProps, normaliseStyleClassProps, normaliseTag, packMeta, processTemplateParams, resolveMetaKeyType, resolveMetaKeyValue, resolvePackedMetaObjectValue, resolveTitleTemplate, tagDedupeKey, tagWeight, thenable, unpackMeta, whitelistSafeInput };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@unhead/shared",
3
3
  "type": "module",
4
- "version": "1.9.16",
4
+ "version": "1.10.0-beta.1",
5
5
  "author": "Harlan Wilton <harlan@harlanzw.com>",
6
6
  "license": "MIT",
7
7
  "funding": "https://github.com/sponsors/harlan-zw",
@@ -34,7 +34,7 @@
34
34
  "dist"
35
35
  ],
36
36
  "dependencies": {
37
- "@unhead/schema": "1.9.16"
37
+ "@unhead/schema": "1.10.0-beta.1"
38
38
  },
39
39
  "devDependencies": {
40
40
  "packrup": "^0.1.2"