@unhead/shared 1.11.14 → 2.0.0-alpha.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 CHANGED
@@ -1,10 +1,9 @@
1
1
  'use strict';
2
2
 
3
- function asArray$1(value) {
4
- return Array.isArray(value) ? value : [value];
5
- }
3
+ const packrup = require('packrup');
6
4
 
7
5
  const SelfClosingTags = /* @__PURE__ */ new Set(["meta", "link", "base"]);
6
+ const DupeableTags = /* @__PURE__ */ new Set(["link", "style", "script", "noscript"]);
8
7
  const TagsWithInnerContent = /* @__PURE__ */ new Set(["title", "titleTemplate", "script", "style", "noscript"]);
9
8
  const HasElementTags = /* @__PURE__ */ new Set([
10
9
  "base",
@@ -28,10 +27,9 @@ const ValidHeadTags = /* @__PURE__ */ new Set([
28
27
  "noscript"
29
28
  ]);
30
29
  const UniqueTags = /* @__PURE__ */ new Set(["base", "title", "titleTemplate", "bodyAttrs", "htmlAttrs", "templateParams"]);
31
- const TagConfigKeys = /* @__PURE__ */ new Set(["tagPosition", "tagPriority", "tagDuplicateStrategy", "children", "innerHTML", "textContent", "processTemplateParams"]);
30
+ const TagConfigKeys = /* @__PURE__ */ new Set(["key", "tagPosition", "tagPriority", "tagDuplicateStrategy", "innerHTML", "textContent", "processTemplateParams"]);
32
31
  const IsBrowser = typeof window !== "undefined";
33
32
  const composableNames = [
34
- "getActiveHead",
35
33
  "useHead",
36
34
  "useSeoMeta",
37
35
  "useHeadSafe",
@@ -39,6 +37,8 @@ const composableNames = [
39
37
  "useServerSeoMeta",
40
38
  "useServerHeadSafe"
41
39
  ];
40
+ const NetworkEvents = /* @__PURE__ */ new Set(["onload", "onerror", "onabort", "onprogress", "onloadstart"]);
41
+ const ScriptNetworkEvents = /* @__PURE__ */ new Set(["onload", "onerror"]);
42
42
 
43
43
  function defineHeadPlugin(plugin) {
44
44
  return plugin;
@@ -64,102 +64,6 @@ function hashTag(tag) {
64
64
  return hashCode(content);
65
65
  }
66
66
 
67
- function asArray(input) {
68
- return Array.isArray(input) ? input : [input];
69
- }
70
- const InternalKeySymbol = "_$key";
71
- function packObject(input, options) {
72
- const keys = Object.keys(input);
73
- let [k, v] = keys;
74
- options = options || {};
75
- options.key = options.key || k;
76
- options.value = options.value || v;
77
- options.resolveKey = options.resolveKey || ((k2) => k2);
78
- const resolveKey = (index) => {
79
- const arr = asArray(options[index]);
80
- return arr.find((k2) => {
81
- if (typeof k2 === "string" && k2.includes(".")) {
82
- return k2;
83
- }
84
- return k2 && keys.includes(k2);
85
- });
86
- };
87
- const resolveValue = (k2, input2) => {
88
- if (k2.includes(".")) {
89
- const paths = k2.split(".");
90
- let val = input2;
91
- for (const path of paths)
92
- val = val[path];
93
- return val;
94
- }
95
- return input2[k2];
96
- };
97
- k = resolveKey("key") || k;
98
- v = resolveKey("value") || v;
99
- const dedupeKeyPrefix = input.key ? `${InternalKeySymbol}${input.key}-` : "";
100
- let keyValue = resolveValue(k, input);
101
- keyValue = options.resolveKey(keyValue);
102
- return {
103
- [`${dedupeKeyPrefix}${keyValue}`]: resolveValue(v, input)
104
- };
105
- }
106
-
107
- function packArray(input, options) {
108
- const packed = {};
109
- for (const i of input) {
110
- const packedObj = packObject(i, options);
111
- const pKey = Object.keys(packedObj)[0];
112
- const isDedupeKey = pKey.startsWith(InternalKeySymbol);
113
- if (!isDedupeKey && packed[pKey]) {
114
- packed[pKey] = Array.isArray(packed[pKey]) ? packed[pKey] : [packed[pKey]];
115
- packed[pKey].push(Object.values(packedObj)[0]);
116
- } else {
117
- packed[isDedupeKey ? pKey.split("-").slice(1).join("-") || pKey : pKey] = packedObj[pKey];
118
- }
119
- }
120
- return packed;
121
- }
122
-
123
- function unpackToArray(input, options) {
124
- const unpacked = [];
125
- const kFn = options.resolveKeyData || ((ctx) => ctx.key);
126
- const vFn = options.resolveValueData || ((ctx) => ctx.value);
127
- for (const [k, v] of Object.entries(input)) {
128
- unpacked.push(...(Array.isArray(v) ? v : [v]).map((i) => {
129
- const ctx = { key: k, value: i };
130
- const val = vFn(ctx);
131
- if (typeof val === "object")
132
- return unpackToArray(val, options);
133
- if (Array.isArray(val))
134
- return val;
135
- return {
136
- [typeof options.key === "function" ? options.key(ctx) : options.key]: kFn(ctx),
137
- [typeof options.value === "function" ? options.value(ctx) : options.value]: val
138
- };
139
- }).flat());
140
- }
141
- return unpacked;
142
- }
143
-
144
- function unpackToString(value, options) {
145
- return Object.entries(value).map(([key, value2]) => {
146
- if (typeof value2 === "object")
147
- value2 = unpackToString(value2, options);
148
- if (options.resolve) {
149
- const resolved = options.resolve({ key, value: value2 });
150
- if (typeof resolved !== "undefined")
151
- return resolved;
152
- }
153
- if (typeof value2 === "number")
154
- value2 = value2.toString();
155
- if (typeof value2 === "string" && options.wrapValue) {
156
- value2 = value2.replace(new RegExp(options.wrapValue, "g"), `\\${options.wrapValue}`);
157
- value2 = `${options.wrapValue}${value2}${options.wrapValue}`;
158
- }
159
- return `${key}${options.keyValueSeparator || ""}${value2}`;
160
- }).join(options.entrySeparator || "");
161
- }
162
-
163
67
  const p = (p2) => ({ keyValue: p2, metaKey: "property" });
164
68
  const k = (p2) => ({ keyValue: p2 });
165
69
  const MetaPackingSchema = {
@@ -275,7 +179,7 @@ function resolvePackedMetaObjectValue(value, key) {
275
179
  const definition = MetaPackingSchema[key];
276
180
  if (key === "refresh")
277
181
  return `${value.seconds};url=${value.url}`;
278
- return unpackToString(
182
+ return packrup.unpackToString(
279
183
  changeKeyCasingDeep(value),
280
184
  {
281
185
  keyValueSeparator: "=",
@@ -343,7 +247,7 @@ function unpackMeta(input) {
343
247
  extras.push(...typeof v === "string" ? unpackMeta({ [key]: v }) : handleObjectEntry(key, v));
344
248
  }
345
249
  }
346
- const meta = unpackToArray(primitives, {
250
+ const meta = packrup.unpackToArray(primitives, {
347
251
  key({ key }) {
348
252
  return resolveMetaKeyType(key);
349
253
  },
@@ -369,7 +273,7 @@ function unpackMeta(input) {
369
273
  }
370
274
  function packMeta(inputs) {
371
275
  const mappedPackingSchema = Object.entries(MetaPackingSchema).map(([key, value]) => [key, value.keyValue]);
372
- return packArray(inputs, {
276
+ return packrup.packArray(inputs, {
373
277
  key: ["name", "property", "httpEquiv", "http-equiv", "charset"],
374
278
  value: ["content", "charset"],
375
279
  resolveKey(k2) {
@@ -381,40 +285,55 @@ function packMeta(inputs) {
381
285
  });
382
286
  }
383
287
 
384
- function thenable(val, thenFn) {
385
- if (val instanceof Promise) {
386
- return val.then(thenFn);
288
+ const allowedMetaProperties = ["name", "property", "http-equiv"];
289
+ function tagDedupeKey(tag) {
290
+ const { props, tag: tagName } = tag;
291
+ if (UniqueTags.has(tagName))
292
+ return tagName;
293
+ if (tagName === "link" && props.rel === "canonical")
294
+ return "canonical";
295
+ if (props.charset)
296
+ return "charset";
297
+ if (props.id) {
298
+ return `${tagName}:id:${props.id}`;
299
+ }
300
+ for (const n of allowedMetaProperties) {
301
+ if (props[n] !== undefined) {
302
+ return `${tagName}:${n}:${props[n]}`;
303
+ }
387
304
  }
388
- return thenFn(val);
305
+ return false;
389
306
  }
390
307
 
391
308
  function normaliseTag(tagName, input, e, normalizedProps) {
392
309
  const props = normalizedProps || normaliseProps(
393
310
  // explicitly check for an object
394
- // @ts-expect-error untyped
395
- typeof input === "object" && typeof input !== "function" && !(input instanceof Promise) ? { ...input } : { [tagName === "script" || tagName === "noscript" || tagName === "style" ? "innerHTML" : "textContent"]: input },
311
+ typeof input === "object" && typeof input !== "function" ? { ...input } : { [tagName === "script" || tagName === "noscript" || tagName === "style" ? "innerHTML" : "textContent"]: input },
396
312
  tagName === "templateParams" || tagName === "titleTemplate"
397
313
  );
398
- if (props instanceof Promise) {
399
- return props.then((val) => normaliseTag(tagName, input, e, val));
400
- }
401
314
  const tag = {
402
315
  tag: tagName,
403
316
  props
404
317
  };
405
318
  for (const k of TagConfigKeys) {
406
- const val = tag.props[k] !== void 0 ? tag.props[k] : e[k];
407
- if (val !== void 0) {
408
- if (!(k === "innerHTML" || k === "textContent" || k === "children") || TagsWithInnerContent.has(tag.tag)) {
409
- tag[k === "children" ? "innerHTML" : k] = val;
319
+ const val = tag.props[k] !== undefined ? tag.props[k] : e[k];
320
+ if (val !== undefined) {
321
+ if (!(k === "innerHTML" || k === "textContent") || TagsWithInnerContent.has(tag.tag)) {
322
+ tag[k] = val;
410
323
  }
411
324
  delete tag.props[k];
412
325
  }
413
326
  }
414
- if (tag.props.body) {
415
- tag.tagPosition = "bodyClose";
416
- delete tag.props.body;
327
+ if (tag.key && DupeableTags.has(tag.tag)) {
328
+ tag.props["data-hid"] = tag._h = hashCode(tag.key);
329
+ }
330
+ const generatedKey = tagDedupeKey(tag);
331
+ if (generatedKey && !generatedKey.startsWith("meta:og:") && !generatedKey.startsWith("meta:twitter:")) {
332
+ delete tag.key;
417
333
  }
334
+ const dedupe = generatedKey || (tag.key ? `${tag.tag}:${tag.key}` : false);
335
+ if (dedupe)
336
+ tag._d = dedupe;
418
337
  if (tag.tag === "script") {
419
338
  if (typeof tag.innerHTML === "object") {
420
339
  tag.innerHTML = JSON.stringify(tag.innerHTML);
@@ -430,20 +349,16 @@ function normaliseStyleClassProps(key, v) {
430
349
  }
431
350
  return String(Array.isArray(v) ? v.join(sep) : v)?.split(sep).filter((c) => Boolean(c.trim())).join(sep);
432
351
  }
433
- function nestedNormaliseProps(props, virtual, keys, startIndex) {
434
- for (let i = startIndex; i < keys.length; i += 1) {
435
- const k = keys[i];
352
+ function normaliseProps(props, virtual = false) {
353
+ for (const k in props) {
436
354
  if (k === "class" || k === "style") {
437
355
  props[k] = normaliseStyleClassProps(k, props[k]);
438
356
  continue;
439
357
  }
440
- if (props[k] instanceof Promise) {
441
- return props[k].then((val) => {
442
- props[k] = val;
443
- return nestedNormaliseProps(props, virtual, keys, i);
444
- });
445
- }
446
358
  if (!virtual && !TagConfigKeys.has(k)) {
359
+ if (typeof props[k] === "function" && !String(k).startsWith("on")) {
360
+ props[k] = props[k]();
361
+ }
447
362
  const v = String(props[k]);
448
363
  const isDataKey = k.startsWith("data-");
449
364
  if (v === "true" || v === "") {
@@ -456,60 +371,37 @@ function nestedNormaliseProps(props, virtual, keys, startIndex) {
456
371
  }
457
372
  }
458
373
  }
459
- }
460
- function normaliseProps(props, virtual = false) {
461
- const resolvedProps = nestedNormaliseProps(props, virtual, Object.keys(props), 0);
462
- if (resolvedProps instanceof Promise) {
463
- return resolvedProps.then(() => props);
464
- }
465
374
  return props;
466
375
  }
467
376
  const TagEntityBits = 10;
468
- function nestedNormaliseEntryTags(headTags, tagPromises, startIndex) {
469
- for (let i = startIndex; i < tagPromises.length; i += 1) {
470
- const tags = tagPromises[i];
471
- if (tags instanceof Promise) {
472
- return tags.then((val) => {
473
- tagPromises[i] = val;
474
- return nestedNormaliseEntryTags(headTags, tagPromises, i);
475
- });
476
- }
477
- if (Array.isArray(tags)) {
478
- headTags.push(...tags);
479
- } else {
480
- headTags.push(tags);
481
- }
482
- }
483
- }
484
377
  function normaliseEntryTags(e) {
485
- const tagPromises = [];
378
+ const tags = [];
486
379
  const input = e.resolvedInput;
487
380
  for (const k in input) {
488
381
  if (!Object.prototype.hasOwnProperty.call(input, k)) {
489
382
  continue;
490
383
  }
491
384
  const v = input[k];
492
- if (v === void 0 || !ValidHeadTags.has(k)) {
385
+ if (v === undefined || !ValidHeadTags.has(k)) {
493
386
  continue;
494
387
  }
495
388
  if (Array.isArray(v)) {
496
389
  for (const props of v) {
497
- tagPromises.push(normaliseTag(k, props, e));
390
+ tags.push(normaliseTag(k, props, e));
498
391
  }
499
392
  continue;
393
+ } else if (typeof v === "function" && k !== "titleTemplate") {
394
+ input[k] = v();
395
+ continue;
500
396
  }
501
- tagPromises.push(normaliseTag(k, v, e));
397
+ tags.push(normaliseTag(k, v, e));
502
398
  }
503
- if (tagPromises.length === 0) {
504
- return [];
505
- }
506
- const headTags = [];
507
- return thenable(nestedNormaliseEntryTags(headTags, tagPromises, 0), () => headTags.map((t, i) => {
399
+ return tags.flat().map((t, i) => {
508
400
  t._e = e._i;
509
401
  e.mode && (t._m = e.mode);
510
402
  t._p = (e._i << TagEntityBits) + i;
511
403
  return t;
512
- }));
404
+ });
513
405
  }
514
406
 
515
407
  const WhitelistAttributes = {
@@ -534,6 +426,7 @@ function whitelistSafeInput(input) {
534
426
  if (!tagValue)
535
427
  return;
536
428
  switch (key) {
429
+ // always safe
537
430
  case "title":
538
431
  case "titleTemplate":
539
432
  case "templateParams":
@@ -547,6 +440,7 @@ function whitelistSafeInput(input) {
547
440
  filtered[key][a] = tagValue[a];
548
441
  });
549
442
  break;
443
+ // meta is safe, except for http-equiv
550
444
  case "meta":
551
445
  if (Array.isArray(tagValue)) {
552
446
  filtered[key] = tagValue.map((meta) => {
@@ -559,6 +453,7 @@ function whitelistSafeInput(input) {
559
453
  }).filter((meta) => Object.keys(meta).length > 0);
560
454
  }
561
455
  break;
456
+ // link tags we don't allow stylesheets, scripts, preloading, prerendering, prefetching, etc
562
457
  case "link":
563
458
  if (Array.isArray(tagValue)) {
564
459
  filtered[key] = tagValue.map((meta) => {
@@ -591,6 +486,7 @@ function whitelistSafeInput(input) {
591
486
  }).filter((meta) => Object.keys(meta).length > 0);
592
487
  }
593
488
  break;
489
+ // we only allow JSON in scripts
594
490
  case "script":
595
491
  if (Array.isArray(tagValue)) {
596
492
  filtered[key] = tagValue.map((script) => {
@@ -601,7 +497,7 @@ function whitelistSafeInput(input) {
601
497
  try {
602
498
  const jsonVal = typeof script[s] === "string" ? JSON.parse(script[s]) : script[s];
603
499
  safeScript[s] = JSON.stringify(jsonVal, null, 0);
604
- } catch (e) {
500
+ } catch {
605
501
  }
606
502
  } else {
607
503
  safeScript[s] = script[s];
@@ -617,9 +513,6 @@ function whitelistSafeInput(input) {
617
513
  return filtered;
618
514
  }
619
515
 
620
- const NetworkEvents = /* @__PURE__ */ new Set(["onload", "onerror", "onabort", "onprogress", "onloadstart"]);
621
- const ScriptNetworkEvents = /* @__PURE__ */ new Set(["onload", "onerror"]);
622
-
623
516
  const TAG_WEIGHTS = {
624
517
  // tags
625
518
  base: -10,
@@ -631,10 +524,16 @@ const TAG_ALIASES = {
631
524
  high: -10,
632
525
  low: 20
633
526
  };
634
- function tagWeight(tag) {
527
+ const SortModifiers = [{ prefix: "before:", offset: -1 }, { prefix: "after:", offset: 1 }];
528
+ const importRe = /@import/;
529
+ const isTruthy = (val) => val === "" || val === true;
530
+ function tagWeight(head, tag) {
635
531
  const priority = tag.tagPriority;
636
532
  if (typeof priority === "number")
637
533
  return priority;
534
+ const isScript = tag.tag === "script";
535
+ const isLink = tag.tag === "link";
536
+ const isStyle = tag.tag === "style";
638
537
  let weight = 100;
639
538
  if (tag.tag === "meta") {
640
539
  if (tag.props["http-equiv"] === "content-security-policy")
@@ -643,7 +542,7 @@ function tagWeight(tag) {
643
542
  weight = -20;
644
543
  else if (tag.props.name === "viewport")
645
544
  weight = -15;
646
- } else if (tag.tag === "link" && tag.props.rel === "preconnect") {
545
+ } else if (isLink && tag.props.rel === "preconnect") {
647
546
  weight = 20;
648
547
  } else if (tag.tag in TAG_WEIGHTS) {
649
548
  weight = TAG_WEIGHTS[tag.tag];
@@ -651,28 +550,28 @@ function tagWeight(tag) {
651
550
  if (priority && priority in TAG_ALIASES) {
652
551
  return weight + TAG_ALIASES[priority];
653
552
  }
654
- return weight;
655
- }
656
- const SortModifiers = [{ prefix: "before:", offset: -1 }, { prefix: "after:", offset: 1 }];
657
-
658
- const allowedMetaProperties = ["name", "property", "http-equiv"];
659
- function tagDedupeKey(tag) {
660
- const { props, tag: tagName } = tag;
661
- if (UniqueTags.has(tagName))
662
- return tagName;
663
- if (tagName === "link" && props.rel === "canonical")
664
- return "canonical";
665
- if (props.charset)
666
- return "charset";
667
- if (props.id) {
668
- return `${tagName}:id:${props.id}`;
553
+ if (tag.tagPosition && tag.tagPosition !== "head") {
554
+ return weight;
669
555
  }
670
- for (const n of allowedMetaProperties) {
671
- if (props[n] !== void 0) {
672
- return `${tagName}:${n}:${props[n]}`;
673
- }
556
+ if (!head.ssr || head.resolvedOptions.disableCapoSorting) {
557
+ return weight;
674
558
  }
675
- return false;
559
+ if (isScript && isTruthy(tag.props.async)) {
560
+ weight = 30;
561
+ } else if (isStyle && tag.innerHTML && importRe.test(tag.innerHTML)) {
562
+ weight = 40;
563
+ } else if (isScript && tag.props.src && !isTruthy(tag.props.defer) && !isTruthy(tag.props.async) && tag.props.type !== "module" && !tag.props.type?.endsWith("json")) {
564
+ weight = 50;
565
+ } else if (isLink && tag.props.rel === "stylesheet" || tag.tag === "style") {
566
+ weight = 60;
567
+ } else if (isLink && (tag.props.rel === "preload" || tag.props.rel === "modulepreload")) {
568
+ weight = 70;
569
+ } else if (isScript && isTruthy(tag.props.defer) && tag.props.src && !isTruthy(tag.props.async)) {
570
+ weight = 80;
571
+ } else if (isLink && (tag.props.rel === "prefetch" || tag.props.rel === "dns-prefetch" || tag.props.rel === "prerender")) {
572
+ weight = 90;
573
+ }
574
+ return weight;
676
575
  }
677
576
 
678
577
  const sepSub = "%separator";
@@ -686,10 +585,10 @@ function sub(p, token, isJson = false) {
686
585
  } else {
687
586
  val = p[token];
688
587
  }
689
- if (val !== void 0) {
588
+ if (val !== undefined) {
690
589
  return isJson ? (val || "").replace(/"/g, '\\"') : val || "";
691
590
  }
692
- return void 0;
591
+ return undefined;
693
592
  }
694
593
  const sepSubRe = new RegExp(`${sepSub}(?:\\s*${sepSub})*`, "g");
695
594
  function processTemplateParams(s, p, sep, isJson = false) {
@@ -710,7 +609,7 @@ function processTemplateParams(s, p, sep, isJson = false) {
710
609
  return token;
711
610
  }
712
611
  const re = sub(p, token.slice(1), isJson);
713
- return re !== void 0 ? re : token;
612
+ return re !== undefined ? re : token;
714
613
  }).trim();
715
614
  if (hasSepSub) {
716
615
  if (s.endsWith(sepSub))
@@ -730,6 +629,11 @@ function resolveTitleTemplate(template, title) {
730
629
  return template;
731
630
  }
732
631
 
632
+ function asArray(value) {
633
+ return Array.isArray(value) ? value : [value];
634
+ }
635
+
636
+ exports.DupeableTags = DupeableTags;
733
637
  exports.HasElementTags = HasElementTags;
734
638
  exports.IsBrowser = IsBrowser;
735
639
  exports.NetworkEvents = NetworkEvents;
@@ -743,7 +647,7 @@ exports.TagEntityBits = TagEntityBits;
743
647
  exports.TagsWithInnerContent = TagsWithInnerContent;
744
648
  exports.UniqueTags = UniqueTags;
745
649
  exports.ValidHeadTags = ValidHeadTags;
746
- exports.asArray = asArray$1;
650
+ exports.asArray = asArray;
747
651
  exports.composableNames = composableNames;
748
652
  exports.defineHeadPlugin = defineHeadPlugin;
749
653
  exports.hashCode = hashCode;
@@ -760,6 +664,5 @@ exports.resolvePackedMetaObjectValue = resolvePackedMetaObjectValue;
760
664
  exports.resolveTitleTemplate = resolveTitleTemplate;
761
665
  exports.tagDedupeKey = tagDedupeKey;
762
666
  exports.tagWeight = tagWeight;
763
- exports.thenable = thenable;
764
667
  exports.unpackMeta = unpackMeta;
765
668
  exports.whitelistSafeInput = whitelistSafeInput;
package/dist/index.d.cts CHANGED
@@ -1,9 +1,7 @@
1
- import { HeadPluginInput, HeadTag, BaseMeta, MetaFlatInput, Head, HeadEntry, MaybeArray, HeadSafe, TemplateParams } from '@unhead/schema';
2
-
3
- type Arrayable<T> = T | Array<T>;
4
- declare function asArray<T>(value: Arrayable<T>): T[];
1
+ import { HeadPluginInput, HeadTag, BaseMeta, MetaFlatInput, Head, HeadEntry, MaybeArray, HeadSafe, Unhead, TemplateParams } from '@unhead/schema';
5
2
 
6
3
  declare const SelfClosingTags: Set<string>;
4
+ declare const DupeableTags: Set<string>;
7
5
  declare const TagsWithInnerContent: Set<string>;
8
6
  declare const HasElementTags: Set<string>;
9
7
  declare const ValidHeadTags: Set<string>;
@@ -11,6 +9,8 @@ declare const UniqueTags: Set<string>;
11
9
  declare const TagConfigKeys: Set<string>;
12
10
  declare const IsBrowser: boolean;
13
11
  declare const composableNames: string[];
12
+ declare const NetworkEvents: Set<string>;
13
+ declare const ScriptNetworkEvents: Set<string>;
14
14
 
15
15
  declare function defineHeadPlugin(plugin: HeadPluginInput): HeadPluginInput;
16
16
 
@@ -31,20 +31,14 @@ declare function unpackMeta<T extends MetaFlatInput>(input: T): Required<Head>['
31
31
  */
32
32
  declare function packMeta<T extends Required<Head>['meta']>(inputs: T): MetaFlatInput;
33
33
 
34
- type Thenable<T> = Promise<T> | T;
35
- declare function thenable<T, R>(val: T, thenFn: (val: Awaited<T>) => R): Promise<R> | R;
36
-
37
- declare function normaliseTag<T extends HeadTag>(tagName: T['tag'], input: HeadTag['props'] | string, e: HeadEntry<T>, normalizedProps?: HeadTag['props']): Thenable<T | T[]>;
34
+ declare function normaliseTag<T extends HeadTag>(tagName: T['tag'], input: HeadTag['props'] | string, e: HeadEntry<T>, normalizedProps?: HeadTag['props']): T | T[];
38
35
  declare function normaliseStyleClassProps<T extends 'class' | 'style'>(key: T, v: Required<Required<Head>['htmlAttrs']['class']> | Required<Required<Head>['htmlAttrs']['style']>): string;
39
- declare function normaliseProps<T extends HeadTag>(props: T['props'], virtual?: boolean): Thenable<T['props']>;
36
+ declare function normaliseProps<T extends HeadTag>(props: T['props'], virtual?: boolean): T["props"];
40
37
  declare const TagEntityBits = 10;
41
- declare function normaliseEntryTags<T extends object = Head>(e: HeadEntry<T>): Thenable<HeadTag[]>;
38
+ declare function normaliseEntryTags<T extends object = Head>(e: HeadEntry<T>): HeadTag[];
42
39
 
43
40
  declare function whitelistSafeInput(input: Record<string, MaybeArray<Record<string, string>>>): HeadSafe;
44
41
 
45
- declare const NetworkEvents: Set<string>;
46
- declare const ScriptNetworkEvents: Set<string>;
47
-
48
42
  declare const TAG_WEIGHTS: {
49
43
  readonly base: -10;
50
44
  readonly title: 10;
@@ -54,11 +48,11 @@ declare const TAG_ALIASES: {
54
48
  readonly high: -10;
55
49
  readonly low: 20;
56
50
  };
57
- declare function tagWeight<T extends HeadTag>(tag: T): number;
58
51
  declare const SortModifiers: {
59
52
  prefix: string;
60
53
  offset: number;
61
54
  }[];
55
+ declare function tagWeight<T extends HeadTag>(head: Unhead<any>, tag: T): number;
62
56
 
63
57
  declare function tagDedupeKey<T extends HeadTag>(tag: T): string | false;
64
58
 
@@ -66,4 +60,7 @@ declare function processTemplateParams(s: string, p: TemplateParams, sep: string
66
60
 
67
61
  declare function resolveTitleTemplate(template: string | ((title?: string) => string | null) | null, title?: string): string | null;
68
62
 
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 };
63
+ type Arrayable<T> = T | Array<T>;
64
+ declare function asArray<T>(value: Arrayable<T>): T[];
65
+
66
+ export { type Arrayable, DupeableTags, 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 };
package/dist/index.d.mts CHANGED
@@ -1,9 +1,7 @@
1
- import { HeadPluginInput, HeadTag, BaseMeta, MetaFlatInput, Head, HeadEntry, MaybeArray, HeadSafe, TemplateParams } from '@unhead/schema';
2
-
3
- type Arrayable<T> = T | Array<T>;
4
- declare function asArray<T>(value: Arrayable<T>): T[];
1
+ import { HeadPluginInput, HeadTag, BaseMeta, MetaFlatInput, Head, HeadEntry, MaybeArray, HeadSafe, Unhead, TemplateParams } from '@unhead/schema';
5
2
 
6
3
  declare const SelfClosingTags: Set<string>;
4
+ declare const DupeableTags: Set<string>;
7
5
  declare const TagsWithInnerContent: Set<string>;
8
6
  declare const HasElementTags: Set<string>;
9
7
  declare const ValidHeadTags: Set<string>;
@@ -11,6 +9,8 @@ declare const UniqueTags: Set<string>;
11
9
  declare const TagConfigKeys: Set<string>;
12
10
  declare const IsBrowser: boolean;
13
11
  declare const composableNames: string[];
12
+ declare const NetworkEvents: Set<string>;
13
+ declare const ScriptNetworkEvents: Set<string>;
14
14
 
15
15
  declare function defineHeadPlugin(plugin: HeadPluginInput): HeadPluginInput;
16
16
 
@@ -31,20 +31,14 @@ declare function unpackMeta<T extends MetaFlatInput>(input: T): Required<Head>['
31
31
  */
32
32
  declare function packMeta<T extends Required<Head>['meta']>(inputs: T): MetaFlatInput;
33
33
 
34
- type Thenable<T> = Promise<T> | T;
35
- declare function thenable<T, R>(val: T, thenFn: (val: Awaited<T>) => R): Promise<R> | R;
36
-
37
- declare function normaliseTag<T extends HeadTag>(tagName: T['tag'], input: HeadTag['props'] | string, e: HeadEntry<T>, normalizedProps?: HeadTag['props']): Thenable<T | T[]>;
34
+ declare function normaliseTag<T extends HeadTag>(tagName: T['tag'], input: HeadTag['props'] | string, e: HeadEntry<T>, normalizedProps?: HeadTag['props']): T | T[];
38
35
  declare function normaliseStyleClassProps<T extends 'class' | 'style'>(key: T, v: Required<Required<Head>['htmlAttrs']['class']> | Required<Required<Head>['htmlAttrs']['style']>): string;
39
- declare function normaliseProps<T extends HeadTag>(props: T['props'], virtual?: boolean): Thenable<T['props']>;
36
+ declare function normaliseProps<T extends HeadTag>(props: T['props'], virtual?: boolean): T["props"];
40
37
  declare const TagEntityBits = 10;
41
- declare function normaliseEntryTags<T extends object = Head>(e: HeadEntry<T>): Thenable<HeadTag[]>;
38
+ declare function normaliseEntryTags<T extends object = Head>(e: HeadEntry<T>): HeadTag[];
42
39
 
43
40
  declare function whitelistSafeInput(input: Record<string, MaybeArray<Record<string, string>>>): HeadSafe;
44
41
 
45
- declare const NetworkEvents: Set<string>;
46
- declare const ScriptNetworkEvents: Set<string>;
47
-
48
42
  declare const TAG_WEIGHTS: {
49
43
  readonly base: -10;
50
44
  readonly title: 10;
@@ -54,11 +48,11 @@ declare const TAG_ALIASES: {
54
48
  readonly high: -10;
55
49
  readonly low: 20;
56
50
  };
57
- declare function tagWeight<T extends HeadTag>(tag: T): number;
58
51
  declare const SortModifiers: {
59
52
  prefix: string;
60
53
  offset: number;
61
54
  }[];
55
+ declare function tagWeight<T extends HeadTag>(head: Unhead<any>, tag: T): number;
62
56
 
63
57
  declare function tagDedupeKey<T extends HeadTag>(tag: T): string | false;
64
58
 
@@ -66,4 +60,7 @@ declare function processTemplateParams(s: string, p: TemplateParams, sep: string
66
60
 
67
61
  declare function resolveTitleTemplate(template: string | ((title?: string) => string | null) | null, title?: string): string | null;
68
62
 
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 };
63
+ type Arrayable<T> = T | Array<T>;
64
+ declare function asArray<T>(value: Arrayable<T>): T[];
65
+
66
+ export { type Arrayable, DupeableTags, 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 };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,7 @@
1
- import { HeadPluginInput, HeadTag, BaseMeta, MetaFlatInput, Head, HeadEntry, MaybeArray, HeadSafe, TemplateParams } from '@unhead/schema';
2
-
3
- type Arrayable<T> = T | Array<T>;
4
- declare function asArray<T>(value: Arrayable<T>): T[];
1
+ import { HeadPluginInput, HeadTag, BaseMeta, MetaFlatInput, Head, HeadEntry, MaybeArray, HeadSafe, Unhead, TemplateParams } from '@unhead/schema';
5
2
 
6
3
  declare const SelfClosingTags: Set<string>;
4
+ declare const DupeableTags: Set<string>;
7
5
  declare const TagsWithInnerContent: Set<string>;
8
6
  declare const HasElementTags: Set<string>;
9
7
  declare const ValidHeadTags: Set<string>;
@@ -11,6 +9,8 @@ declare const UniqueTags: Set<string>;
11
9
  declare const TagConfigKeys: Set<string>;
12
10
  declare const IsBrowser: boolean;
13
11
  declare const composableNames: string[];
12
+ declare const NetworkEvents: Set<string>;
13
+ declare const ScriptNetworkEvents: Set<string>;
14
14
 
15
15
  declare function defineHeadPlugin(plugin: HeadPluginInput): HeadPluginInput;
16
16
 
@@ -31,20 +31,14 @@ declare function unpackMeta<T extends MetaFlatInput>(input: T): Required<Head>['
31
31
  */
32
32
  declare function packMeta<T extends Required<Head>['meta']>(inputs: T): MetaFlatInput;
33
33
 
34
- type Thenable<T> = Promise<T> | T;
35
- declare function thenable<T, R>(val: T, thenFn: (val: Awaited<T>) => R): Promise<R> | R;
36
-
37
- declare function normaliseTag<T extends HeadTag>(tagName: T['tag'], input: HeadTag['props'] | string, e: HeadEntry<T>, normalizedProps?: HeadTag['props']): Thenable<T | T[]>;
34
+ declare function normaliseTag<T extends HeadTag>(tagName: T['tag'], input: HeadTag['props'] | string, e: HeadEntry<T>, normalizedProps?: HeadTag['props']): T | T[];
38
35
  declare function normaliseStyleClassProps<T extends 'class' | 'style'>(key: T, v: Required<Required<Head>['htmlAttrs']['class']> | Required<Required<Head>['htmlAttrs']['style']>): string;
39
- declare function normaliseProps<T extends HeadTag>(props: T['props'], virtual?: boolean): Thenable<T['props']>;
36
+ declare function normaliseProps<T extends HeadTag>(props: T['props'], virtual?: boolean): T["props"];
40
37
  declare const TagEntityBits = 10;
41
- declare function normaliseEntryTags<T extends object = Head>(e: HeadEntry<T>): Thenable<HeadTag[]>;
38
+ declare function normaliseEntryTags<T extends object = Head>(e: HeadEntry<T>): HeadTag[];
42
39
 
43
40
  declare function whitelistSafeInput(input: Record<string, MaybeArray<Record<string, string>>>): HeadSafe;
44
41
 
45
- declare const NetworkEvents: Set<string>;
46
- declare const ScriptNetworkEvents: Set<string>;
47
-
48
42
  declare const TAG_WEIGHTS: {
49
43
  readonly base: -10;
50
44
  readonly title: 10;
@@ -54,11 +48,11 @@ declare const TAG_ALIASES: {
54
48
  readonly high: -10;
55
49
  readonly low: 20;
56
50
  };
57
- declare function tagWeight<T extends HeadTag>(tag: T): number;
58
51
  declare const SortModifiers: {
59
52
  prefix: string;
60
53
  offset: number;
61
54
  }[];
55
+ declare function tagWeight<T extends HeadTag>(head: Unhead<any>, tag: T): number;
62
56
 
63
57
  declare function tagDedupeKey<T extends HeadTag>(tag: T): string | false;
64
58
 
@@ -66,4 +60,7 @@ declare function processTemplateParams(s: string, p: TemplateParams, sep: string
66
60
 
67
61
  declare function resolveTitleTemplate(template: string | ((title?: string) => string | null) | null, title?: string): string | null;
68
62
 
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 };
63
+ type Arrayable<T> = T | Array<T>;
64
+ declare function asArray<T>(value: Arrayable<T>): T[];
65
+
66
+ export { type Arrayable, DupeableTags, 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 };
package/dist/index.mjs CHANGED
@@ -1,8 +1,7 @@
1
- function asArray$1(value) {
2
- return Array.isArray(value) ? value : [value];
3
- }
1
+ import { unpackToString, unpackToArray, packArray } from 'packrup';
4
2
 
5
3
  const SelfClosingTags = /* @__PURE__ */ new Set(["meta", "link", "base"]);
4
+ const DupeableTags = /* @__PURE__ */ new Set(["link", "style", "script", "noscript"]);
6
5
  const TagsWithInnerContent = /* @__PURE__ */ new Set(["title", "titleTemplate", "script", "style", "noscript"]);
7
6
  const HasElementTags = /* @__PURE__ */ new Set([
8
7
  "base",
@@ -26,10 +25,9 @@ const ValidHeadTags = /* @__PURE__ */ new Set([
26
25
  "noscript"
27
26
  ]);
28
27
  const UniqueTags = /* @__PURE__ */ new Set(["base", "title", "titleTemplate", "bodyAttrs", "htmlAttrs", "templateParams"]);
29
- const TagConfigKeys = /* @__PURE__ */ new Set(["tagPosition", "tagPriority", "tagDuplicateStrategy", "children", "innerHTML", "textContent", "processTemplateParams"]);
28
+ const TagConfigKeys = /* @__PURE__ */ new Set(["key", "tagPosition", "tagPriority", "tagDuplicateStrategy", "innerHTML", "textContent", "processTemplateParams"]);
30
29
  const IsBrowser = typeof window !== "undefined";
31
30
  const composableNames = [
32
- "getActiveHead",
33
31
  "useHead",
34
32
  "useSeoMeta",
35
33
  "useHeadSafe",
@@ -37,6 +35,8 @@ const composableNames = [
37
35
  "useServerSeoMeta",
38
36
  "useServerHeadSafe"
39
37
  ];
38
+ const NetworkEvents = /* @__PURE__ */ new Set(["onload", "onerror", "onabort", "onprogress", "onloadstart"]);
39
+ const ScriptNetworkEvents = /* @__PURE__ */ new Set(["onload", "onerror"]);
40
40
 
41
41
  function defineHeadPlugin(plugin) {
42
42
  return plugin;
@@ -62,102 +62,6 @@ function hashTag(tag) {
62
62
  return hashCode(content);
63
63
  }
64
64
 
65
- function asArray(input) {
66
- return Array.isArray(input) ? input : [input];
67
- }
68
- const InternalKeySymbol = "_$key";
69
- function packObject(input, options) {
70
- const keys = Object.keys(input);
71
- let [k, v] = keys;
72
- options = options || {};
73
- options.key = options.key || k;
74
- options.value = options.value || v;
75
- options.resolveKey = options.resolveKey || ((k2) => k2);
76
- const resolveKey = (index) => {
77
- const arr = asArray(options[index]);
78
- return arr.find((k2) => {
79
- if (typeof k2 === "string" && k2.includes(".")) {
80
- return k2;
81
- }
82
- return k2 && keys.includes(k2);
83
- });
84
- };
85
- const resolveValue = (k2, input2) => {
86
- if (k2.includes(".")) {
87
- const paths = k2.split(".");
88
- let val = input2;
89
- for (const path of paths)
90
- val = val[path];
91
- return val;
92
- }
93
- return input2[k2];
94
- };
95
- k = resolveKey("key") || k;
96
- v = resolveKey("value") || v;
97
- const dedupeKeyPrefix = input.key ? `${InternalKeySymbol}${input.key}-` : "";
98
- let keyValue = resolveValue(k, input);
99
- keyValue = options.resolveKey(keyValue);
100
- return {
101
- [`${dedupeKeyPrefix}${keyValue}`]: resolveValue(v, input)
102
- };
103
- }
104
-
105
- function packArray(input, options) {
106
- const packed = {};
107
- for (const i of input) {
108
- const packedObj = packObject(i, options);
109
- const pKey = Object.keys(packedObj)[0];
110
- const isDedupeKey = pKey.startsWith(InternalKeySymbol);
111
- if (!isDedupeKey && packed[pKey]) {
112
- packed[pKey] = Array.isArray(packed[pKey]) ? packed[pKey] : [packed[pKey]];
113
- packed[pKey].push(Object.values(packedObj)[0]);
114
- } else {
115
- packed[isDedupeKey ? pKey.split("-").slice(1).join("-") || pKey : pKey] = packedObj[pKey];
116
- }
117
- }
118
- return packed;
119
- }
120
-
121
- function unpackToArray(input, options) {
122
- const unpacked = [];
123
- const kFn = options.resolveKeyData || ((ctx) => ctx.key);
124
- const vFn = options.resolveValueData || ((ctx) => ctx.value);
125
- for (const [k, v] of Object.entries(input)) {
126
- unpacked.push(...(Array.isArray(v) ? v : [v]).map((i) => {
127
- const ctx = { key: k, value: i };
128
- const val = vFn(ctx);
129
- if (typeof val === "object")
130
- return unpackToArray(val, options);
131
- if (Array.isArray(val))
132
- return val;
133
- return {
134
- [typeof options.key === "function" ? options.key(ctx) : options.key]: kFn(ctx),
135
- [typeof options.value === "function" ? options.value(ctx) : options.value]: val
136
- };
137
- }).flat());
138
- }
139
- return unpacked;
140
- }
141
-
142
- function unpackToString(value, options) {
143
- return Object.entries(value).map(([key, value2]) => {
144
- if (typeof value2 === "object")
145
- value2 = unpackToString(value2, options);
146
- if (options.resolve) {
147
- const resolved = options.resolve({ key, value: value2 });
148
- if (typeof resolved !== "undefined")
149
- return resolved;
150
- }
151
- if (typeof value2 === "number")
152
- value2 = value2.toString();
153
- if (typeof value2 === "string" && options.wrapValue) {
154
- value2 = value2.replace(new RegExp(options.wrapValue, "g"), `\\${options.wrapValue}`);
155
- value2 = `${options.wrapValue}${value2}${options.wrapValue}`;
156
- }
157
- return `${key}${options.keyValueSeparator || ""}${value2}`;
158
- }).join(options.entrySeparator || "");
159
- }
160
-
161
65
  const p = (p2) => ({ keyValue: p2, metaKey: "property" });
162
66
  const k = (p2) => ({ keyValue: p2 });
163
67
  const MetaPackingSchema = {
@@ -379,40 +283,55 @@ function packMeta(inputs) {
379
283
  });
380
284
  }
381
285
 
382
- function thenable(val, thenFn) {
383
- if (val instanceof Promise) {
384
- return val.then(thenFn);
286
+ const allowedMetaProperties = ["name", "property", "http-equiv"];
287
+ function tagDedupeKey(tag) {
288
+ const { props, tag: tagName } = tag;
289
+ if (UniqueTags.has(tagName))
290
+ return tagName;
291
+ if (tagName === "link" && props.rel === "canonical")
292
+ return "canonical";
293
+ if (props.charset)
294
+ return "charset";
295
+ if (props.id) {
296
+ return `${tagName}:id:${props.id}`;
297
+ }
298
+ for (const n of allowedMetaProperties) {
299
+ if (props[n] !== undefined) {
300
+ return `${tagName}:${n}:${props[n]}`;
301
+ }
385
302
  }
386
- return thenFn(val);
303
+ return false;
387
304
  }
388
305
 
389
306
  function normaliseTag(tagName, input, e, normalizedProps) {
390
307
  const props = normalizedProps || normaliseProps(
391
308
  // explicitly check for an object
392
- // @ts-expect-error untyped
393
- typeof input === "object" && typeof input !== "function" && !(input instanceof Promise) ? { ...input } : { [tagName === "script" || tagName === "noscript" || tagName === "style" ? "innerHTML" : "textContent"]: input },
309
+ typeof input === "object" && typeof input !== "function" ? { ...input } : { [tagName === "script" || tagName === "noscript" || tagName === "style" ? "innerHTML" : "textContent"]: input },
394
310
  tagName === "templateParams" || tagName === "titleTemplate"
395
311
  );
396
- if (props instanceof Promise) {
397
- return props.then((val) => normaliseTag(tagName, input, e, val));
398
- }
399
312
  const tag = {
400
313
  tag: tagName,
401
314
  props
402
315
  };
403
316
  for (const k of TagConfigKeys) {
404
- const val = tag.props[k] !== void 0 ? tag.props[k] : e[k];
405
- if (val !== void 0) {
406
- if (!(k === "innerHTML" || k === "textContent" || k === "children") || TagsWithInnerContent.has(tag.tag)) {
407
- tag[k === "children" ? "innerHTML" : k] = val;
317
+ const val = tag.props[k] !== undefined ? tag.props[k] : e[k];
318
+ if (val !== undefined) {
319
+ if (!(k === "innerHTML" || k === "textContent") || TagsWithInnerContent.has(tag.tag)) {
320
+ tag[k] = val;
408
321
  }
409
322
  delete tag.props[k];
410
323
  }
411
324
  }
412
- if (tag.props.body) {
413
- tag.tagPosition = "bodyClose";
414
- delete tag.props.body;
325
+ if (tag.key && DupeableTags.has(tag.tag)) {
326
+ tag.props["data-hid"] = tag._h = hashCode(tag.key);
327
+ }
328
+ const generatedKey = tagDedupeKey(tag);
329
+ if (generatedKey && !generatedKey.startsWith("meta:og:") && !generatedKey.startsWith("meta:twitter:")) {
330
+ delete tag.key;
415
331
  }
332
+ const dedupe = generatedKey || (tag.key ? `${tag.tag}:${tag.key}` : false);
333
+ if (dedupe)
334
+ tag._d = dedupe;
416
335
  if (tag.tag === "script") {
417
336
  if (typeof tag.innerHTML === "object") {
418
337
  tag.innerHTML = JSON.stringify(tag.innerHTML);
@@ -428,20 +347,16 @@ function normaliseStyleClassProps(key, v) {
428
347
  }
429
348
  return String(Array.isArray(v) ? v.join(sep) : v)?.split(sep).filter((c) => Boolean(c.trim())).join(sep);
430
349
  }
431
- function nestedNormaliseProps(props, virtual, keys, startIndex) {
432
- for (let i = startIndex; i < keys.length; i += 1) {
433
- const k = keys[i];
350
+ function normaliseProps(props, virtual = false) {
351
+ for (const k in props) {
434
352
  if (k === "class" || k === "style") {
435
353
  props[k] = normaliseStyleClassProps(k, props[k]);
436
354
  continue;
437
355
  }
438
- if (props[k] instanceof Promise) {
439
- return props[k].then((val) => {
440
- props[k] = val;
441
- return nestedNormaliseProps(props, virtual, keys, i);
442
- });
443
- }
444
356
  if (!virtual && !TagConfigKeys.has(k)) {
357
+ if (typeof props[k] === "function" && !String(k).startsWith("on")) {
358
+ props[k] = props[k]();
359
+ }
445
360
  const v = String(props[k]);
446
361
  const isDataKey = k.startsWith("data-");
447
362
  if (v === "true" || v === "") {
@@ -454,60 +369,37 @@ function nestedNormaliseProps(props, virtual, keys, startIndex) {
454
369
  }
455
370
  }
456
371
  }
457
- }
458
- function normaliseProps(props, virtual = false) {
459
- const resolvedProps = nestedNormaliseProps(props, virtual, Object.keys(props), 0);
460
- if (resolvedProps instanceof Promise) {
461
- return resolvedProps.then(() => props);
462
- }
463
372
  return props;
464
373
  }
465
374
  const TagEntityBits = 10;
466
- function nestedNormaliseEntryTags(headTags, tagPromises, startIndex) {
467
- for (let i = startIndex; i < tagPromises.length; i += 1) {
468
- const tags = tagPromises[i];
469
- if (tags instanceof Promise) {
470
- return tags.then((val) => {
471
- tagPromises[i] = val;
472
- return nestedNormaliseEntryTags(headTags, tagPromises, i);
473
- });
474
- }
475
- if (Array.isArray(tags)) {
476
- headTags.push(...tags);
477
- } else {
478
- headTags.push(tags);
479
- }
480
- }
481
- }
482
375
  function normaliseEntryTags(e) {
483
- const tagPromises = [];
376
+ const tags = [];
484
377
  const input = e.resolvedInput;
485
378
  for (const k in input) {
486
379
  if (!Object.prototype.hasOwnProperty.call(input, k)) {
487
380
  continue;
488
381
  }
489
382
  const v = input[k];
490
- if (v === void 0 || !ValidHeadTags.has(k)) {
383
+ if (v === undefined || !ValidHeadTags.has(k)) {
491
384
  continue;
492
385
  }
493
386
  if (Array.isArray(v)) {
494
387
  for (const props of v) {
495
- tagPromises.push(normaliseTag(k, props, e));
388
+ tags.push(normaliseTag(k, props, e));
496
389
  }
497
390
  continue;
391
+ } else if (typeof v === "function" && k !== "titleTemplate") {
392
+ input[k] = v();
393
+ continue;
498
394
  }
499
- tagPromises.push(normaliseTag(k, v, e));
395
+ tags.push(normaliseTag(k, v, e));
500
396
  }
501
- if (tagPromises.length === 0) {
502
- return [];
503
- }
504
- const headTags = [];
505
- return thenable(nestedNormaliseEntryTags(headTags, tagPromises, 0), () => headTags.map((t, i) => {
397
+ return tags.flat().map((t, i) => {
506
398
  t._e = e._i;
507
399
  e.mode && (t._m = e.mode);
508
400
  t._p = (e._i << TagEntityBits) + i;
509
401
  return t;
510
- }));
402
+ });
511
403
  }
512
404
 
513
405
  const WhitelistAttributes = {
@@ -532,6 +424,7 @@ function whitelistSafeInput(input) {
532
424
  if (!tagValue)
533
425
  return;
534
426
  switch (key) {
427
+ // always safe
535
428
  case "title":
536
429
  case "titleTemplate":
537
430
  case "templateParams":
@@ -545,6 +438,7 @@ function whitelistSafeInput(input) {
545
438
  filtered[key][a] = tagValue[a];
546
439
  });
547
440
  break;
441
+ // meta is safe, except for http-equiv
548
442
  case "meta":
549
443
  if (Array.isArray(tagValue)) {
550
444
  filtered[key] = tagValue.map((meta) => {
@@ -557,6 +451,7 @@ function whitelistSafeInput(input) {
557
451
  }).filter((meta) => Object.keys(meta).length > 0);
558
452
  }
559
453
  break;
454
+ // link tags we don't allow stylesheets, scripts, preloading, prerendering, prefetching, etc
560
455
  case "link":
561
456
  if (Array.isArray(tagValue)) {
562
457
  filtered[key] = tagValue.map((meta) => {
@@ -589,6 +484,7 @@ function whitelistSafeInput(input) {
589
484
  }).filter((meta) => Object.keys(meta).length > 0);
590
485
  }
591
486
  break;
487
+ // we only allow JSON in scripts
592
488
  case "script":
593
489
  if (Array.isArray(tagValue)) {
594
490
  filtered[key] = tagValue.map((script) => {
@@ -599,7 +495,7 @@ function whitelistSafeInput(input) {
599
495
  try {
600
496
  const jsonVal = typeof script[s] === "string" ? JSON.parse(script[s]) : script[s];
601
497
  safeScript[s] = JSON.stringify(jsonVal, null, 0);
602
- } catch (e) {
498
+ } catch {
603
499
  }
604
500
  } else {
605
501
  safeScript[s] = script[s];
@@ -615,9 +511,6 @@ function whitelistSafeInput(input) {
615
511
  return filtered;
616
512
  }
617
513
 
618
- const NetworkEvents = /* @__PURE__ */ new Set(["onload", "onerror", "onabort", "onprogress", "onloadstart"]);
619
- const ScriptNetworkEvents = /* @__PURE__ */ new Set(["onload", "onerror"]);
620
-
621
514
  const TAG_WEIGHTS = {
622
515
  // tags
623
516
  base: -10,
@@ -629,10 +522,16 @@ const TAG_ALIASES = {
629
522
  high: -10,
630
523
  low: 20
631
524
  };
632
- function tagWeight(tag) {
525
+ const SortModifiers = [{ prefix: "before:", offset: -1 }, { prefix: "after:", offset: 1 }];
526
+ const importRe = /@import/;
527
+ const isTruthy = (val) => val === "" || val === true;
528
+ function tagWeight(head, tag) {
633
529
  const priority = tag.tagPriority;
634
530
  if (typeof priority === "number")
635
531
  return priority;
532
+ const isScript = tag.tag === "script";
533
+ const isLink = tag.tag === "link";
534
+ const isStyle = tag.tag === "style";
636
535
  let weight = 100;
637
536
  if (tag.tag === "meta") {
638
537
  if (tag.props["http-equiv"] === "content-security-policy")
@@ -641,7 +540,7 @@ function tagWeight(tag) {
641
540
  weight = -20;
642
541
  else if (tag.props.name === "viewport")
643
542
  weight = -15;
644
- } else if (tag.tag === "link" && tag.props.rel === "preconnect") {
543
+ } else if (isLink && tag.props.rel === "preconnect") {
645
544
  weight = 20;
646
545
  } else if (tag.tag in TAG_WEIGHTS) {
647
546
  weight = TAG_WEIGHTS[tag.tag];
@@ -649,28 +548,28 @@ function tagWeight(tag) {
649
548
  if (priority && priority in TAG_ALIASES) {
650
549
  return weight + TAG_ALIASES[priority];
651
550
  }
652
- return weight;
653
- }
654
- const SortModifiers = [{ prefix: "before:", offset: -1 }, { prefix: "after:", offset: 1 }];
655
-
656
- const allowedMetaProperties = ["name", "property", "http-equiv"];
657
- function tagDedupeKey(tag) {
658
- const { props, tag: tagName } = tag;
659
- if (UniqueTags.has(tagName))
660
- return tagName;
661
- if (tagName === "link" && props.rel === "canonical")
662
- return "canonical";
663
- if (props.charset)
664
- return "charset";
665
- if (props.id) {
666
- return `${tagName}:id:${props.id}`;
551
+ if (tag.tagPosition && tag.tagPosition !== "head") {
552
+ return weight;
667
553
  }
668
- for (const n of allowedMetaProperties) {
669
- if (props[n] !== void 0) {
670
- return `${tagName}:${n}:${props[n]}`;
671
- }
554
+ if (!head.ssr || head.resolvedOptions.disableCapoSorting) {
555
+ return weight;
672
556
  }
673
- return false;
557
+ if (isScript && isTruthy(tag.props.async)) {
558
+ weight = 30;
559
+ } else if (isStyle && tag.innerHTML && importRe.test(tag.innerHTML)) {
560
+ weight = 40;
561
+ } else if (isScript && tag.props.src && !isTruthy(tag.props.defer) && !isTruthy(tag.props.async) && tag.props.type !== "module" && !tag.props.type?.endsWith("json")) {
562
+ weight = 50;
563
+ } else if (isLink && tag.props.rel === "stylesheet" || tag.tag === "style") {
564
+ weight = 60;
565
+ } else if (isLink && (tag.props.rel === "preload" || tag.props.rel === "modulepreload")) {
566
+ weight = 70;
567
+ } else if (isScript && isTruthy(tag.props.defer) && tag.props.src && !isTruthy(tag.props.async)) {
568
+ weight = 80;
569
+ } else if (isLink && (tag.props.rel === "prefetch" || tag.props.rel === "dns-prefetch" || tag.props.rel === "prerender")) {
570
+ weight = 90;
571
+ }
572
+ return weight;
674
573
  }
675
574
 
676
575
  const sepSub = "%separator";
@@ -684,10 +583,10 @@ function sub(p, token, isJson = false) {
684
583
  } else {
685
584
  val = p[token];
686
585
  }
687
- if (val !== void 0) {
586
+ if (val !== undefined) {
688
587
  return isJson ? (val || "").replace(/"/g, '\\"') : val || "";
689
588
  }
690
- return void 0;
589
+ return undefined;
691
590
  }
692
591
  const sepSubRe = new RegExp(`${sepSub}(?:\\s*${sepSub})*`, "g");
693
592
  function processTemplateParams(s, p, sep, isJson = false) {
@@ -708,7 +607,7 @@ function processTemplateParams(s, p, sep, isJson = false) {
708
607
  return token;
709
608
  }
710
609
  const re = sub(p, token.slice(1), isJson);
711
- return re !== void 0 ? re : token;
610
+ return re !== undefined ? re : token;
712
611
  }).trim();
713
612
  if (hasSepSub) {
714
613
  if (s.endsWith(sepSub))
@@ -728,4 +627,8 @@ function resolveTitleTemplate(template, title) {
728
627
  return template;
729
628
  }
730
629
 
731
- 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 };
630
+ function asArray(value) {
631
+ return Array.isArray(value) ? value : [value];
632
+ }
633
+
634
+ export { DupeableTags, 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 };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@unhead/shared",
3
3
  "type": "module",
4
- "version": "1.11.14",
4
+ "version": "2.0.0-alpha.0",
5
5
  "author": "Harlan Wilton <harlan@harlanzw.com>",
6
6
  "license": "MIT",
7
7
  "funding": "https://github.com/sponsors/harlan-zw",
@@ -34,14 +34,11 @@
34
34
  "dist"
35
35
  ],
36
36
  "dependencies": {
37
- "@unhead/schema": "1.11.14"
38
- },
39
- "devDependencies": {
40
- "packrup": "^0.1.2"
37
+ "packrup": "^0.1.2",
38
+ "@unhead/schema": "2.0.0-alpha.0"
41
39
  },
42
40
  "scripts": {
43
41
  "build": "unbuild .",
44
- "stub": "unbuild . --stub",
45
- "export:sizes": "npx export-size . -r"
42
+ "stub": "unbuild . --stub"
46
43
  }
47
44
  }