@unhead/vue 0.6.5 → 0.6.7
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 +259 -36
- package/dist/index.d.ts +11 -2
- package/dist/index.mjs +258 -37
- package/package.json +3 -3
- package/dist/chunks/index.cjs +0 -164
- package/dist/chunks/index.mjs +0 -161
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,223 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const vue = require('vue');
|
|
4
3
|
const hookable = require('hookable');
|
|
4
|
+
const vue = require('vue');
|
|
5
|
+
|
|
6
|
+
const TagsWithInnerContent = ["script", "style", "noscript"];
|
|
7
|
+
const HasElementTags$1 = [
|
|
8
|
+
"base",
|
|
9
|
+
"meta",
|
|
10
|
+
"link",
|
|
11
|
+
"style",
|
|
12
|
+
"script",
|
|
13
|
+
"noscript"
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
const UniqueTags$1 = ["base", "title", "titleTemplate", "bodyAttrs", "htmlAttrs"];
|
|
17
|
+
const ArrayMetaProperties$1 = [
|
|
18
|
+
"og:image",
|
|
19
|
+
"og:video",
|
|
20
|
+
"og:audio",
|
|
21
|
+
"og:locale:alternate",
|
|
22
|
+
"video:actor",
|
|
23
|
+
"video:director",
|
|
24
|
+
"video:writer",
|
|
25
|
+
"video:tag",
|
|
26
|
+
"article:author",
|
|
27
|
+
"article:tag",
|
|
28
|
+
"book:tag",
|
|
29
|
+
"book:author",
|
|
30
|
+
"music:album",
|
|
31
|
+
"music:musician"
|
|
32
|
+
];
|
|
33
|
+
function tagDedupeKey$1(tag) {
|
|
34
|
+
const { props, tag: tagName } = tag;
|
|
35
|
+
if (UniqueTags$1.includes(tagName))
|
|
36
|
+
return tagName;
|
|
37
|
+
if (tagName === "link" && props.rel === "canonical")
|
|
38
|
+
return "canonical";
|
|
39
|
+
if (props.charset)
|
|
40
|
+
return "charset";
|
|
41
|
+
const name = ["id"];
|
|
42
|
+
if (tagName === "meta")
|
|
43
|
+
name.push(...["name", "property", "http-equiv"]);
|
|
44
|
+
for (const n of name) {
|
|
45
|
+
if (typeof props[n] !== "undefined") {
|
|
46
|
+
const val = String(props[n]);
|
|
47
|
+
if (ArrayMetaProperties$1.findIndex((p) => val.startsWith(p)) !== -1)
|
|
48
|
+
return false;
|
|
49
|
+
return `${tagName}:${n}:${val}`;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const setAttrs = (ctx, markSideEffect) => {
|
|
56
|
+
const { tag, $el } = ctx;
|
|
57
|
+
if (!$el)
|
|
58
|
+
return;
|
|
59
|
+
Object.entries(tag.props).forEach(([k, value]) => {
|
|
60
|
+
value = String(value);
|
|
61
|
+
const attrSdeKey = `attr:${k}`;
|
|
62
|
+
if (k === "class") {
|
|
63
|
+
for (const c of value.split(" ")) {
|
|
64
|
+
const classSdeKey = `${attrSdeKey}:${c}`;
|
|
65
|
+
if (markSideEffect)
|
|
66
|
+
markSideEffect(ctx, classSdeKey, () => $el.classList.remove(c));
|
|
67
|
+
if (!$el.classList.contains(c))
|
|
68
|
+
$el.classList.add(c);
|
|
69
|
+
}
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (markSideEffect && !k.startsWith("data-h-"))
|
|
73
|
+
markSideEffect(ctx, attrSdeKey, () => $el.removeAttribute(k));
|
|
74
|
+
if ($el.getAttribute(k) !== value)
|
|
75
|
+
$el.setAttribute(k, value);
|
|
76
|
+
});
|
|
77
|
+
if (TagsWithInnerContent.includes(tag.tag) && $el.innerHTML !== (tag.children || ""))
|
|
78
|
+
$el.innerHTML = tag.children || "";
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
function hashCode(s) {
|
|
82
|
+
let h = 9;
|
|
83
|
+
for (let i = 0; i < s.length; )
|
|
84
|
+
h = Math.imul(h ^ s.charCodeAt(i++), 9 ** 9);
|
|
85
|
+
return ((h ^ h >>> 9) + 65536).toString(16).substring(1, 8).toLowerCase();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function renderDOMHead(head, options = {}) {
|
|
89
|
+
const ctx = { shouldRender: true };
|
|
90
|
+
await head.hooks.callHook("dom:beforeRender", ctx);
|
|
91
|
+
if (!ctx.shouldRender)
|
|
92
|
+
return;
|
|
93
|
+
const dom = options.document || window.document;
|
|
94
|
+
const staleSideEffects = head._popSideEffectQueue();
|
|
95
|
+
head.headEntries().map((entry) => entry._sde).forEach((sde) => {
|
|
96
|
+
Object.entries(sde).forEach(([key, fn]) => {
|
|
97
|
+
staleSideEffects[key] = fn;
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
const preRenderTag = async (tag) => {
|
|
101
|
+
const entry = head.headEntries().find((e) => e._i === tag._e);
|
|
102
|
+
const renderCtx = {
|
|
103
|
+
renderId: tag._d || hashCode(JSON.stringify({ ...tag, _e: void 0, _p: void 0 })),
|
|
104
|
+
$el: null,
|
|
105
|
+
shouldRender: true,
|
|
106
|
+
tag,
|
|
107
|
+
entry,
|
|
108
|
+
staleSideEffects
|
|
109
|
+
};
|
|
110
|
+
await head.hooks.callHook("dom:beforeRenderTag", renderCtx);
|
|
111
|
+
return renderCtx;
|
|
112
|
+
};
|
|
113
|
+
const renders = [];
|
|
114
|
+
const pendingRenders = {
|
|
115
|
+
body: [],
|
|
116
|
+
head: []
|
|
117
|
+
};
|
|
118
|
+
const markSideEffect = (ctx2, key, fn) => {
|
|
119
|
+
key = `${ctx2.renderId}:${key}`;
|
|
120
|
+
if (ctx2.entry)
|
|
121
|
+
ctx2.entry._sde[key] = fn;
|
|
122
|
+
delete staleSideEffects[key];
|
|
123
|
+
};
|
|
124
|
+
const markEl = (ctx2) => {
|
|
125
|
+
head._elMap[ctx2.renderId] = ctx2.$el;
|
|
126
|
+
renders.push(ctx2);
|
|
127
|
+
markSideEffect(ctx2, "el", () => {
|
|
128
|
+
ctx2.$el?.remove();
|
|
129
|
+
delete head._elMap[ctx2.renderId];
|
|
130
|
+
});
|
|
131
|
+
};
|
|
132
|
+
for (const t of await head.resolveTags()) {
|
|
133
|
+
const ctx2 = await preRenderTag(t);
|
|
134
|
+
if (!ctx2.shouldRender)
|
|
135
|
+
continue;
|
|
136
|
+
const { tag } = ctx2;
|
|
137
|
+
if (tag.tag === "title") {
|
|
138
|
+
dom.title = tag.children || "";
|
|
139
|
+
renders.push(ctx2);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (tag.tag === "htmlAttrs" || tag.tag === "bodyAttrs") {
|
|
143
|
+
ctx2.$el = dom[tag.tag === "htmlAttrs" ? "documentElement" : "body"];
|
|
144
|
+
setAttrs(ctx2, markSideEffect);
|
|
145
|
+
renders.push(ctx2);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
ctx2.$el = head._elMap[ctx2.renderId];
|
|
149
|
+
if (!ctx2.$el && tag._hash) {
|
|
150
|
+
ctx2.$el = dom.querySelector(`${tag.tagPosition?.startsWith("body") ? "body" : "head"} > ${tag.tag}[data-h-${tag._hash}]`);
|
|
151
|
+
}
|
|
152
|
+
if (ctx2.$el) {
|
|
153
|
+
if (ctx2.tag._d)
|
|
154
|
+
setAttrs(ctx2);
|
|
155
|
+
markEl(ctx2);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
ctx2.$el = dom.createElement(tag.tag);
|
|
159
|
+
setAttrs(ctx2);
|
|
160
|
+
pendingRenders[tag.tagPosition?.startsWith("body") ? "body" : "head"].push(ctx2);
|
|
161
|
+
}
|
|
162
|
+
Object.entries(pendingRenders).forEach(([pos, queue]) => {
|
|
163
|
+
if (!queue.length)
|
|
164
|
+
return;
|
|
165
|
+
for (const $el of [...dom[pos].children].reverse()) {
|
|
166
|
+
const elTag = $el.tagName.toLowerCase();
|
|
167
|
+
if (!HasElementTags$1.includes(elTag))
|
|
168
|
+
continue;
|
|
169
|
+
const dedupeKey = tagDedupeKey$1({
|
|
170
|
+
tag: elTag,
|
|
171
|
+
props: $el.getAttributeNames().reduce((props, name) => ({ ...props, [name]: $el.getAttribute(name) }), {})
|
|
172
|
+
});
|
|
173
|
+
const matchIdx = queue.findIndex((ctx2) => ctx2 && (ctx2.tag._d === dedupeKey || $el.isEqualNode(ctx2.$el)));
|
|
174
|
+
if (matchIdx !== -1) {
|
|
175
|
+
const ctx2 = queue[matchIdx];
|
|
176
|
+
ctx2.$el = $el;
|
|
177
|
+
setAttrs(ctx2);
|
|
178
|
+
markEl(ctx2);
|
|
179
|
+
delete queue[matchIdx];
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
queue.forEach((ctx2) => {
|
|
183
|
+
if (!ctx2.$el)
|
|
184
|
+
return;
|
|
185
|
+
switch (ctx2.tag.tagPosition) {
|
|
186
|
+
case "bodyClose":
|
|
187
|
+
dom.body.appendChild(ctx2.$el);
|
|
188
|
+
break;
|
|
189
|
+
case "bodyOpen":
|
|
190
|
+
dom.body.insertBefore(ctx2.$el, dom.body.firstChild);
|
|
191
|
+
break;
|
|
192
|
+
case "head":
|
|
193
|
+
default:
|
|
194
|
+
dom.head.appendChild(ctx2.$el);
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
markEl(ctx2);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
for (const ctx2 of renders)
|
|
201
|
+
await head.hooks.callHook("dom:renderTag", ctx2);
|
|
202
|
+
Object.values(staleSideEffects).forEach((fn) => fn());
|
|
203
|
+
}
|
|
204
|
+
let domUpdatePromise = null;
|
|
205
|
+
async function debouncedRenderDOMHead(head, options = {}) {
|
|
206
|
+
function doDomUpdate() {
|
|
207
|
+
domUpdatePromise = null;
|
|
208
|
+
return renderDOMHead(head, options);
|
|
209
|
+
}
|
|
210
|
+
const delayFn = options.delayFn || ((fn) => setTimeout(fn, 10));
|
|
211
|
+
return domUpdatePromise = domUpdatePromise || new Promise((resolve) => delayFn(() => resolve(doDomUpdate())));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const index = {
|
|
215
|
+
__proto__: null,
|
|
216
|
+
debouncedRenderDOMHead: debouncedRenderDOMHead,
|
|
217
|
+
get domUpdatePromise () { return domUpdatePromise; },
|
|
218
|
+
hashCode: hashCode,
|
|
219
|
+
renderDOMHead: renderDOMHead
|
|
220
|
+
};
|
|
5
221
|
|
|
6
222
|
const ValidHeadTags = [
|
|
7
223
|
"title",
|
|
@@ -295,7 +511,7 @@ const PatchDomOnEntryUpdatesPlugin = (options) => {
|
|
|
295
511
|
let delayFn = options?.delayFn;
|
|
296
512
|
if (!delayFn && typeof requestAnimationFrame !== "undefined")
|
|
297
513
|
delayFn = requestAnimationFrame;
|
|
298
|
-
|
|
514
|
+
Promise.resolve().then(function () { return index; }).then(({ debouncedRenderDOMHead }) => {
|
|
299
515
|
debouncedRenderDOMHead(head, { document: options?.document || window.document, delayFn });
|
|
300
516
|
});
|
|
301
517
|
}
|
|
@@ -345,16 +561,18 @@ const EventHandlersPlugin = () => {
|
|
|
345
561
|
const sdeKey = `${ctx.tag._d || ctx.tag._p}:${k}`;
|
|
346
562
|
const eventName = k.slice(2).toLowerCase();
|
|
347
563
|
const eventDedupeKey = `data-h-${eventName}`;
|
|
348
|
-
delete ctx.
|
|
564
|
+
delete ctx.staleSideEffects[sdeKey];
|
|
349
565
|
if ($el.hasAttribute(eventDedupeKey))
|
|
350
566
|
return;
|
|
351
567
|
const handler = value;
|
|
352
568
|
$el.setAttribute(eventDedupeKey, "");
|
|
353
569
|
$eventListenerTarget.addEventListener(eventName, handler);
|
|
354
|
-
ctx.entry
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
570
|
+
if (ctx.entry) {
|
|
571
|
+
ctx.entry._sde[sdeKey] = () => {
|
|
572
|
+
$eventListenerTarget.removeEventListener(eventName, handler);
|
|
573
|
+
$el.removeAttribute(eventDedupeKey);
|
|
574
|
+
};
|
|
575
|
+
}
|
|
358
576
|
});
|
|
359
577
|
if (ctx.tag._delayedSrc) {
|
|
360
578
|
$el.setAttribute("src", ctx.tag._delayedSrc);
|
|
@@ -367,12 +585,6 @@ const EventHandlersPlugin = () => {
|
|
|
367
585
|
function asArray$1(value) {
|
|
368
586
|
return Array.isArray(value) ? value : [value];
|
|
369
587
|
}
|
|
370
|
-
function hashCode(s) {
|
|
371
|
-
let h = 9;
|
|
372
|
-
for (let i = 0; i < s.length; )
|
|
373
|
-
h = Math.imul(h ^ s.charCodeAt(i++), 9 ** 9);
|
|
374
|
-
return ((h ^ h >>> 9) + 65536).toString(16).substring(1, 8).toLowerCase();
|
|
375
|
-
}
|
|
376
588
|
const HasElementTags = [
|
|
377
589
|
"base",
|
|
378
590
|
"meta",
|
|
@@ -398,7 +610,26 @@ function normaliseEntryTags(e) {
|
|
|
398
610
|
});
|
|
399
611
|
}
|
|
400
612
|
|
|
613
|
+
const CorePlugins = () => [
|
|
614
|
+
DedupesTagsPlugin(),
|
|
615
|
+
SortTagsPlugin(),
|
|
616
|
+
TitleTemplatePlugin(),
|
|
617
|
+
ProvideTagHashPlugin(),
|
|
618
|
+
EventHandlersPlugin(),
|
|
619
|
+
DeprecatedTagAttrPlugin()
|
|
620
|
+
];
|
|
621
|
+
const DOMPlugins = (options = {}) => [
|
|
622
|
+
PatchDomOnEntryUpdatesPlugin({ document: options?.document, delayFn: options?.domDelayFn })
|
|
623
|
+
];
|
|
401
624
|
function createHead$1(options = {}) {
|
|
625
|
+
const head = createHeadCore({
|
|
626
|
+
...options,
|
|
627
|
+
plugins: [...DOMPlugins(options), ...options?.plugins || []]
|
|
628
|
+
});
|
|
629
|
+
setActiveHead(head);
|
|
630
|
+
return head;
|
|
631
|
+
}
|
|
632
|
+
function createHeadCore(options = {}) {
|
|
402
633
|
let entries = [];
|
|
403
634
|
let _sde = {};
|
|
404
635
|
let _eid = 0;
|
|
@@ -406,17 +637,11 @@ function createHead$1(options = {}) {
|
|
|
406
637
|
if (options?.hooks)
|
|
407
638
|
hooks.addHooks(options.hooks);
|
|
408
639
|
options.plugins = [
|
|
409
|
-
|
|
410
|
-
DedupesTagsPlugin(),
|
|
411
|
-
SortTagsPlugin(),
|
|
412
|
-
TitleTemplatePlugin(),
|
|
413
|
-
EventHandlersPlugin(),
|
|
414
|
-
ProvideTagHashPlugin(),
|
|
415
|
-
PatchDomOnEntryUpdatesPlugin({ document: options?.document, delayFn: options?.domDelayFn }),
|
|
640
|
+
...CorePlugins(),
|
|
416
641
|
...options?.plugins || []
|
|
417
642
|
];
|
|
418
643
|
options.plugins.forEach((p) => p.hooks && hooks.addHooks(p.hooks));
|
|
419
|
-
const
|
|
644
|
+
const updated = () => hooks.callHook("entries:updated", head);
|
|
420
645
|
const head = {
|
|
421
646
|
resolvedOptions: options,
|
|
422
647
|
headEntries() {
|
|
@@ -434,27 +659,23 @@ function createHead$1(options = {}) {
|
|
|
434
659
|
if (options2?.mode)
|
|
435
660
|
activeEntry._m = options2?.mode;
|
|
436
661
|
entries.push(activeEntry);
|
|
437
|
-
|
|
438
|
-
const queueSideEffects = (e) => {
|
|
439
|
-
_sde = { ..._sde, ...e._sde || {} };
|
|
440
|
-
e._sde = {};
|
|
441
|
-
triggerUpdateHook();
|
|
442
|
-
};
|
|
662
|
+
updated();
|
|
443
663
|
return {
|
|
444
664
|
dispose() {
|
|
445
665
|
entries = entries.filter((e) => {
|
|
446
666
|
if (e._i !== activeEntry._i)
|
|
447
667
|
return true;
|
|
448
|
-
|
|
668
|
+
_sde = { ..._sde, ...e._sde || {} };
|
|
669
|
+
e._sde = {};
|
|
670
|
+
updated();
|
|
449
671
|
return false;
|
|
450
672
|
});
|
|
451
673
|
},
|
|
452
674
|
patch(input2) {
|
|
453
675
|
entries = entries.map((e) => {
|
|
454
676
|
if (e._i === activeEntry._i) {
|
|
455
|
-
queueSideEffects(e);
|
|
456
677
|
activeEntry.input = e.input = input2;
|
|
457
|
-
|
|
678
|
+
updated();
|
|
458
679
|
}
|
|
459
680
|
return e;
|
|
460
681
|
});
|
|
@@ -482,7 +703,6 @@ function createHead$1(options = {}) {
|
|
|
482
703
|
}
|
|
483
704
|
};
|
|
484
705
|
head.hooks.callHook("init", head);
|
|
485
|
-
setActiveHead(head);
|
|
486
706
|
return head;
|
|
487
707
|
}
|
|
488
708
|
|
|
@@ -495,6 +715,7 @@ const composableNames = [
|
|
|
495
715
|
"useTagBase",
|
|
496
716
|
"useTagMeta",
|
|
497
717
|
"useTagMetaFlat",
|
|
718
|
+
"useSeoMeta",
|
|
498
719
|
"useTagLink",
|
|
499
720
|
"useTagScript",
|
|
500
721
|
"useTagStyle",
|
|
@@ -554,14 +775,13 @@ function injectHead() {
|
|
|
554
775
|
return vue.getCurrentInstance() && vue.inject(headSymbol) || getActiveHead();
|
|
555
776
|
}
|
|
556
777
|
function createHead(options = {}) {
|
|
557
|
-
const plugins = [
|
|
558
|
-
VueReactiveUseHeadPlugin(),
|
|
559
|
-
...options?.plugins || []
|
|
560
|
-
];
|
|
561
778
|
const head = createHead$1({
|
|
562
779
|
...options,
|
|
563
780
|
domDelayFn: (fn) => setTimeout(() => vue.nextTick(() => fn()), 10),
|
|
564
|
-
plugins
|
|
781
|
+
plugins: [
|
|
782
|
+
VueReactiveUseHeadPlugin(),
|
|
783
|
+
...options?.plugins || []
|
|
784
|
+
]
|
|
565
785
|
});
|
|
566
786
|
const vuePlugin = {
|
|
567
787
|
install(app) {
|
|
@@ -819,6 +1039,7 @@ const useTagMetaFlat = (meta) => {
|
|
|
819
1039
|
});
|
|
820
1040
|
return useHead({ meta: input });
|
|
821
1041
|
};
|
|
1042
|
+
const useSeoMeta = useTagMetaFlat;
|
|
822
1043
|
const useTagLink = (link) => useHead({ link: asArray(link) });
|
|
823
1044
|
const useTagScript = (script) => useHead({ script: asArray(script) });
|
|
824
1045
|
const useTagStyle = (style) => useHead({ style: asArray(style) });
|
|
@@ -842,6 +1063,7 @@ exports.VueHeadMixin = VueHeadMixin;
|
|
|
842
1063
|
exports.VueReactiveUseHeadPlugin = VueReactiveUseHeadPlugin;
|
|
843
1064
|
exports.asArray = asArray;
|
|
844
1065
|
exports.createHead = createHead;
|
|
1066
|
+
exports.createHeadCore = createHeadCore;
|
|
845
1067
|
exports.headSymbol = headSymbol;
|
|
846
1068
|
exports.injectHead = injectHead;
|
|
847
1069
|
exports.resolveUnrefHeadInput = resolveUnrefHeadInput;
|
|
@@ -849,6 +1071,7 @@ exports.unheadVueComposablesImports = unheadVueComposablesImports;
|
|
|
849
1071
|
exports.useBodyAttrs = useBodyAttrs;
|
|
850
1072
|
exports.useHead = useHead;
|
|
851
1073
|
exports.useHtmlAttrs = useHtmlAttrs;
|
|
1074
|
+
exports.useSeoMeta = useSeoMeta;
|
|
852
1075
|
exports.useServerBodyAttrs = useServerBodyAttrs;
|
|
853
1076
|
exports.useServerHead = useServerHead;
|
|
854
1077
|
exports.useServerHtmlAttrs = useServerHtmlAttrs;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import * as _unhead_schema from '@unhead/schema';
|
|
2
|
-
import { Title as Title$1, TitleTemplate as TitleTemplate$1, EntryAugmentation, Base as Base$1, Link as Link$1, Meta as Meta$1, Style as Style$1, Script as Script$1, Noscript as Noscript$1, DataKeys, SchemaAugmentations, DefinedValueOrEmptyObject, MergeHead, BaseHtmlAttr, MaybeArray, BaseBodyAttr,
|
|
2
|
+
import { Head, CreateHeadOptions, Unhead, Title as Title$1, TitleTemplate as TitleTemplate$1, EntryAugmentation, Base as Base$1, Link as Link$1, Meta as Meta$1, Style as Style$1, Script as Script$1, Noscript as Noscript$1, DataKeys, SchemaAugmentations, DefinedValueOrEmptyObject, MergeHead, BaseHtmlAttr, MaybeArray, BaseBodyAttr, HeadEntryOptions, MetaFlatInput, ActiveHeadEntry } from '@unhead/schema';
|
|
3
3
|
export { ActiveHeadEntry, Head, HeadEntryOptions, HeadTag, MergeHead, Unhead } from '@unhead/schema';
|
|
4
4
|
import { ComputedRef, Ref, Plugin } from 'vue';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Creates a core instance of unhead. Does not provide a global ctx for composables to work
|
|
8
|
+
* and does not register DOM plugins.
|
|
9
|
+
*
|
|
10
|
+
* @param options
|
|
11
|
+
*/
|
|
12
|
+
declare function createHeadCore<T extends {} = Head>(options?: CreateHeadOptions): Unhead<T>;
|
|
13
|
+
|
|
6
14
|
declare type MaybeReadonlyRef<T> = (() => T) | ComputedRef<T>;
|
|
7
15
|
declare type MaybeComputedRef<T> = T | MaybeReadonlyRef<T> | Ref<T>;
|
|
8
16
|
declare type MaybeComputedRefEntries<T> = MaybeComputedRef<T> | {
|
|
@@ -138,6 +146,7 @@ declare const useTagTitle: (title: ReactiveHead['title']) => void | ActiveHeadEn
|
|
|
138
146
|
declare const useTitleTemplate: (titleTemplate: ReactiveHead['titleTemplate']) => void | ActiveHeadEntry<UseHeadInput<MergeHead>>;
|
|
139
147
|
declare const useTagMeta: (meta: Arrayable<Meta>) => void | ActiveHeadEntry<UseHeadInput<MergeHead>>;
|
|
140
148
|
declare const useTagMetaFlat: (meta: MaybeComputedRefEntries<MetaFlatInput>) => void | ActiveHeadEntry<UseHeadInput<MergeHead>>;
|
|
149
|
+
declare const useSeoMeta: (meta: MaybeComputedRefEntries<MetaFlatInput>) => void | ActiveHeadEntry<UseHeadInput<MergeHead>>;
|
|
141
150
|
declare const useTagLink: (link: Arrayable<Link>) => void | ActiveHeadEntry<UseHeadInput<MergeHead>>;
|
|
142
151
|
declare const useTagScript: (script: Arrayable<Script>) => void | ActiveHeadEntry<UseHeadInput<MergeHead>>;
|
|
143
152
|
declare const useTagStyle: (style: Arrayable<Style>) => void | ActiveHeadEntry<UseHeadInput<MergeHead>>;
|
|
@@ -151,4 +160,4 @@ declare const unheadVueComposablesImports: {
|
|
|
151
160
|
imports: string[];
|
|
152
161
|
}[];
|
|
153
162
|
|
|
154
|
-
export { Arrayable, Base, BodyAttributes, HtmlAttributes, Link, MaybeComputedRef, MaybeComputedRefEntries, MaybeReadonlyRef, Meta, Noscript, ReactiveHead, Script, Style, Title, TitleTemplate, UseHeadInput, Vue2ProvideUnheadPlugin, VueHeadClient, VueHeadMixin, VueReactiveUseHeadPlugin, asArray, createHead, headSymbol, injectHead, resolveUnrefHeadInput, unheadVueComposablesImports, useBodyAttrs, useHead, useHtmlAttrs, useServerBodyAttrs, useServerHead, useServerHtmlAttrs, useServerTagBase, useServerTagLink, useServerTagMeta, useServerTagMetaFlat, useServerTagNoscript, useServerTagScript, useServerTagStyle, useServerTagTitle, useServerTitleTemplate, useTagBase, useTagLink, useTagMeta, useTagMetaFlat, useTagNoscript, useTagScript, useTagStyle, useTagTitle, useTitleTemplate };
|
|
163
|
+
export { Arrayable, Base, BodyAttributes, HtmlAttributes, Link, MaybeComputedRef, MaybeComputedRefEntries, MaybeReadonlyRef, Meta, Noscript, ReactiveHead, Script, Style, Title, TitleTemplate, UseHeadInput, Vue2ProvideUnheadPlugin, VueHeadClient, VueHeadMixin, VueReactiveUseHeadPlugin, asArray, createHead, createHeadCore, headSymbol, injectHead, resolveUnrefHeadInput, unheadVueComposablesImports, useBodyAttrs, useHead, useHtmlAttrs, useSeoMeta, useServerBodyAttrs, useServerHead, useServerHtmlAttrs, useServerTagBase, useServerTagLink, useServerTagMeta, useServerTagMetaFlat, useServerTagNoscript, useServerTagScript, useServerTagStyle, useServerTagTitle, useServerTitleTemplate, useTagBase, useTagLink, useTagMeta, useTagMetaFlat, useTagNoscript, useTagScript, useTagStyle, useTagTitle, useTitleTemplate };
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,221 @@
|
|
|
1
|
-
import { unref, isRef, version, getCurrentInstance, inject, nextTick, ref, watchEffect, watch, onBeforeUnmount } from 'vue';
|
|
2
1
|
import { createHooks } from 'hookable';
|
|
2
|
+
import { unref, isRef, version, getCurrentInstance, inject, nextTick, ref, watchEffect, watch, onBeforeUnmount } from 'vue';
|
|
3
|
+
|
|
4
|
+
const TagsWithInnerContent = ["script", "style", "noscript"];
|
|
5
|
+
const HasElementTags$1 = [
|
|
6
|
+
"base",
|
|
7
|
+
"meta",
|
|
8
|
+
"link",
|
|
9
|
+
"style",
|
|
10
|
+
"script",
|
|
11
|
+
"noscript"
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
const UniqueTags$1 = ["base", "title", "titleTemplate", "bodyAttrs", "htmlAttrs"];
|
|
15
|
+
const ArrayMetaProperties$1 = [
|
|
16
|
+
"og:image",
|
|
17
|
+
"og:video",
|
|
18
|
+
"og:audio",
|
|
19
|
+
"og:locale:alternate",
|
|
20
|
+
"video:actor",
|
|
21
|
+
"video:director",
|
|
22
|
+
"video:writer",
|
|
23
|
+
"video:tag",
|
|
24
|
+
"article:author",
|
|
25
|
+
"article:tag",
|
|
26
|
+
"book:tag",
|
|
27
|
+
"book:author",
|
|
28
|
+
"music:album",
|
|
29
|
+
"music:musician"
|
|
30
|
+
];
|
|
31
|
+
function tagDedupeKey$1(tag) {
|
|
32
|
+
const { props, tag: tagName } = tag;
|
|
33
|
+
if (UniqueTags$1.includes(tagName))
|
|
34
|
+
return tagName;
|
|
35
|
+
if (tagName === "link" && props.rel === "canonical")
|
|
36
|
+
return "canonical";
|
|
37
|
+
if (props.charset)
|
|
38
|
+
return "charset";
|
|
39
|
+
const name = ["id"];
|
|
40
|
+
if (tagName === "meta")
|
|
41
|
+
name.push(...["name", "property", "http-equiv"]);
|
|
42
|
+
for (const n of name) {
|
|
43
|
+
if (typeof props[n] !== "undefined") {
|
|
44
|
+
const val = String(props[n]);
|
|
45
|
+
if (ArrayMetaProperties$1.findIndex((p) => val.startsWith(p)) !== -1)
|
|
46
|
+
return false;
|
|
47
|
+
return `${tagName}:${n}:${val}`;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const setAttrs = (ctx, markSideEffect) => {
|
|
54
|
+
const { tag, $el } = ctx;
|
|
55
|
+
if (!$el)
|
|
56
|
+
return;
|
|
57
|
+
Object.entries(tag.props).forEach(([k, value]) => {
|
|
58
|
+
value = String(value);
|
|
59
|
+
const attrSdeKey = `attr:${k}`;
|
|
60
|
+
if (k === "class") {
|
|
61
|
+
for (const c of value.split(" ")) {
|
|
62
|
+
const classSdeKey = `${attrSdeKey}:${c}`;
|
|
63
|
+
if (markSideEffect)
|
|
64
|
+
markSideEffect(ctx, classSdeKey, () => $el.classList.remove(c));
|
|
65
|
+
if (!$el.classList.contains(c))
|
|
66
|
+
$el.classList.add(c);
|
|
67
|
+
}
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (markSideEffect && !k.startsWith("data-h-"))
|
|
71
|
+
markSideEffect(ctx, attrSdeKey, () => $el.removeAttribute(k));
|
|
72
|
+
if ($el.getAttribute(k) !== value)
|
|
73
|
+
$el.setAttribute(k, value);
|
|
74
|
+
});
|
|
75
|
+
if (TagsWithInnerContent.includes(tag.tag) && $el.innerHTML !== (tag.children || ""))
|
|
76
|
+
$el.innerHTML = tag.children || "";
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
function hashCode(s) {
|
|
80
|
+
let h = 9;
|
|
81
|
+
for (let i = 0; i < s.length; )
|
|
82
|
+
h = Math.imul(h ^ s.charCodeAt(i++), 9 ** 9);
|
|
83
|
+
return ((h ^ h >>> 9) + 65536).toString(16).substring(1, 8).toLowerCase();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function renderDOMHead(head, options = {}) {
|
|
87
|
+
const ctx = { shouldRender: true };
|
|
88
|
+
await head.hooks.callHook("dom:beforeRender", ctx);
|
|
89
|
+
if (!ctx.shouldRender)
|
|
90
|
+
return;
|
|
91
|
+
const dom = options.document || window.document;
|
|
92
|
+
const staleSideEffects = head._popSideEffectQueue();
|
|
93
|
+
head.headEntries().map((entry) => entry._sde).forEach((sde) => {
|
|
94
|
+
Object.entries(sde).forEach(([key, fn]) => {
|
|
95
|
+
staleSideEffects[key] = fn;
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
const preRenderTag = async (tag) => {
|
|
99
|
+
const entry = head.headEntries().find((e) => e._i === tag._e);
|
|
100
|
+
const renderCtx = {
|
|
101
|
+
renderId: tag._d || hashCode(JSON.stringify({ ...tag, _e: void 0, _p: void 0 })),
|
|
102
|
+
$el: null,
|
|
103
|
+
shouldRender: true,
|
|
104
|
+
tag,
|
|
105
|
+
entry,
|
|
106
|
+
staleSideEffects
|
|
107
|
+
};
|
|
108
|
+
await head.hooks.callHook("dom:beforeRenderTag", renderCtx);
|
|
109
|
+
return renderCtx;
|
|
110
|
+
};
|
|
111
|
+
const renders = [];
|
|
112
|
+
const pendingRenders = {
|
|
113
|
+
body: [],
|
|
114
|
+
head: []
|
|
115
|
+
};
|
|
116
|
+
const markSideEffect = (ctx2, key, fn) => {
|
|
117
|
+
key = `${ctx2.renderId}:${key}`;
|
|
118
|
+
if (ctx2.entry)
|
|
119
|
+
ctx2.entry._sde[key] = fn;
|
|
120
|
+
delete staleSideEffects[key];
|
|
121
|
+
};
|
|
122
|
+
const markEl = (ctx2) => {
|
|
123
|
+
head._elMap[ctx2.renderId] = ctx2.$el;
|
|
124
|
+
renders.push(ctx2);
|
|
125
|
+
markSideEffect(ctx2, "el", () => {
|
|
126
|
+
ctx2.$el?.remove();
|
|
127
|
+
delete head._elMap[ctx2.renderId];
|
|
128
|
+
});
|
|
129
|
+
};
|
|
130
|
+
for (const t of await head.resolveTags()) {
|
|
131
|
+
const ctx2 = await preRenderTag(t);
|
|
132
|
+
if (!ctx2.shouldRender)
|
|
133
|
+
continue;
|
|
134
|
+
const { tag } = ctx2;
|
|
135
|
+
if (tag.tag === "title") {
|
|
136
|
+
dom.title = tag.children || "";
|
|
137
|
+
renders.push(ctx2);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (tag.tag === "htmlAttrs" || tag.tag === "bodyAttrs") {
|
|
141
|
+
ctx2.$el = dom[tag.tag === "htmlAttrs" ? "documentElement" : "body"];
|
|
142
|
+
setAttrs(ctx2, markSideEffect);
|
|
143
|
+
renders.push(ctx2);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
ctx2.$el = head._elMap[ctx2.renderId];
|
|
147
|
+
if (!ctx2.$el && tag._hash) {
|
|
148
|
+
ctx2.$el = dom.querySelector(`${tag.tagPosition?.startsWith("body") ? "body" : "head"} > ${tag.tag}[data-h-${tag._hash}]`);
|
|
149
|
+
}
|
|
150
|
+
if (ctx2.$el) {
|
|
151
|
+
if (ctx2.tag._d)
|
|
152
|
+
setAttrs(ctx2);
|
|
153
|
+
markEl(ctx2);
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
ctx2.$el = dom.createElement(tag.tag);
|
|
157
|
+
setAttrs(ctx2);
|
|
158
|
+
pendingRenders[tag.tagPosition?.startsWith("body") ? "body" : "head"].push(ctx2);
|
|
159
|
+
}
|
|
160
|
+
Object.entries(pendingRenders).forEach(([pos, queue]) => {
|
|
161
|
+
if (!queue.length)
|
|
162
|
+
return;
|
|
163
|
+
for (const $el of [...dom[pos].children].reverse()) {
|
|
164
|
+
const elTag = $el.tagName.toLowerCase();
|
|
165
|
+
if (!HasElementTags$1.includes(elTag))
|
|
166
|
+
continue;
|
|
167
|
+
const dedupeKey = tagDedupeKey$1({
|
|
168
|
+
tag: elTag,
|
|
169
|
+
props: $el.getAttributeNames().reduce((props, name) => ({ ...props, [name]: $el.getAttribute(name) }), {})
|
|
170
|
+
});
|
|
171
|
+
const matchIdx = queue.findIndex((ctx2) => ctx2 && (ctx2.tag._d === dedupeKey || $el.isEqualNode(ctx2.$el)));
|
|
172
|
+
if (matchIdx !== -1) {
|
|
173
|
+
const ctx2 = queue[matchIdx];
|
|
174
|
+
ctx2.$el = $el;
|
|
175
|
+
setAttrs(ctx2);
|
|
176
|
+
markEl(ctx2);
|
|
177
|
+
delete queue[matchIdx];
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
queue.forEach((ctx2) => {
|
|
181
|
+
if (!ctx2.$el)
|
|
182
|
+
return;
|
|
183
|
+
switch (ctx2.tag.tagPosition) {
|
|
184
|
+
case "bodyClose":
|
|
185
|
+
dom.body.appendChild(ctx2.$el);
|
|
186
|
+
break;
|
|
187
|
+
case "bodyOpen":
|
|
188
|
+
dom.body.insertBefore(ctx2.$el, dom.body.firstChild);
|
|
189
|
+
break;
|
|
190
|
+
case "head":
|
|
191
|
+
default:
|
|
192
|
+
dom.head.appendChild(ctx2.$el);
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
markEl(ctx2);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
for (const ctx2 of renders)
|
|
199
|
+
await head.hooks.callHook("dom:renderTag", ctx2);
|
|
200
|
+
Object.values(staleSideEffects).forEach((fn) => fn());
|
|
201
|
+
}
|
|
202
|
+
let domUpdatePromise = null;
|
|
203
|
+
async function debouncedRenderDOMHead(head, options = {}) {
|
|
204
|
+
function doDomUpdate() {
|
|
205
|
+
domUpdatePromise = null;
|
|
206
|
+
return renderDOMHead(head, options);
|
|
207
|
+
}
|
|
208
|
+
const delayFn = options.delayFn || ((fn) => setTimeout(fn, 10));
|
|
209
|
+
return domUpdatePromise = domUpdatePromise || new Promise((resolve) => delayFn(() => resolve(doDomUpdate())));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const index = {
|
|
213
|
+
__proto__: null,
|
|
214
|
+
debouncedRenderDOMHead: debouncedRenderDOMHead,
|
|
215
|
+
get domUpdatePromise () { return domUpdatePromise; },
|
|
216
|
+
hashCode: hashCode,
|
|
217
|
+
renderDOMHead: renderDOMHead
|
|
218
|
+
};
|
|
3
219
|
|
|
4
220
|
const ValidHeadTags = [
|
|
5
221
|
"title",
|
|
@@ -293,7 +509,7 @@ const PatchDomOnEntryUpdatesPlugin = (options) => {
|
|
|
293
509
|
let delayFn = options?.delayFn;
|
|
294
510
|
if (!delayFn && typeof requestAnimationFrame !== "undefined")
|
|
295
511
|
delayFn = requestAnimationFrame;
|
|
296
|
-
|
|
512
|
+
Promise.resolve().then(function () { return index; }).then(({ debouncedRenderDOMHead }) => {
|
|
297
513
|
debouncedRenderDOMHead(head, { document: options?.document || window.document, delayFn });
|
|
298
514
|
});
|
|
299
515
|
}
|
|
@@ -343,16 +559,18 @@ const EventHandlersPlugin = () => {
|
|
|
343
559
|
const sdeKey = `${ctx.tag._d || ctx.tag._p}:${k}`;
|
|
344
560
|
const eventName = k.slice(2).toLowerCase();
|
|
345
561
|
const eventDedupeKey = `data-h-${eventName}`;
|
|
346
|
-
delete ctx.
|
|
562
|
+
delete ctx.staleSideEffects[sdeKey];
|
|
347
563
|
if ($el.hasAttribute(eventDedupeKey))
|
|
348
564
|
return;
|
|
349
565
|
const handler = value;
|
|
350
566
|
$el.setAttribute(eventDedupeKey, "");
|
|
351
567
|
$eventListenerTarget.addEventListener(eventName, handler);
|
|
352
|
-
ctx.entry
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
568
|
+
if (ctx.entry) {
|
|
569
|
+
ctx.entry._sde[sdeKey] = () => {
|
|
570
|
+
$eventListenerTarget.removeEventListener(eventName, handler);
|
|
571
|
+
$el.removeAttribute(eventDedupeKey);
|
|
572
|
+
};
|
|
573
|
+
}
|
|
356
574
|
});
|
|
357
575
|
if (ctx.tag._delayedSrc) {
|
|
358
576
|
$el.setAttribute("src", ctx.tag._delayedSrc);
|
|
@@ -365,12 +583,6 @@ const EventHandlersPlugin = () => {
|
|
|
365
583
|
function asArray$1(value) {
|
|
366
584
|
return Array.isArray(value) ? value : [value];
|
|
367
585
|
}
|
|
368
|
-
function hashCode(s) {
|
|
369
|
-
let h = 9;
|
|
370
|
-
for (let i = 0; i < s.length; )
|
|
371
|
-
h = Math.imul(h ^ s.charCodeAt(i++), 9 ** 9);
|
|
372
|
-
return ((h ^ h >>> 9) + 65536).toString(16).substring(1, 8).toLowerCase();
|
|
373
|
-
}
|
|
374
586
|
const HasElementTags = [
|
|
375
587
|
"base",
|
|
376
588
|
"meta",
|
|
@@ -396,7 +608,26 @@ function normaliseEntryTags(e) {
|
|
|
396
608
|
});
|
|
397
609
|
}
|
|
398
610
|
|
|
611
|
+
const CorePlugins = () => [
|
|
612
|
+
DedupesTagsPlugin(),
|
|
613
|
+
SortTagsPlugin(),
|
|
614
|
+
TitleTemplatePlugin(),
|
|
615
|
+
ProvideTagHashPlugin(),
|
|
616
|
+
EventHandlersPlugin(),
|
|
617
|
+
DeprecatedTagAttrPlugin()
|
|
618
|
+
];
|
|
619
|
+
const DOMPlugins = (options = {}) => [
|
|
620
|
+
PatchDomOnEntryUpdatesPlugin({ document: options?.document, delayFn: options?.domDelayFn })
|
|
621
|
+
];
|
|
399
622
|
function createHead$1(options = {}) {
|
|
623
|
+
const head = createHeadCore({
|
|
624
|
+
...options,
|
|
625
|
+
plugins: [...DOMPlugins(options), ...options?.plugins || []]
|
|
626
|
+
});
|
|
627
|
+
setActiveHead(head);
|
|
628
|
+
return head;
|
|
629
|
+
}
|
|
630
|
+
function createHeadCore(options = {}) {
|
|
400
631
|
let entries = [];
|
|
401
632
|
let _sde = {};
|
|
402
633
|
let _eid = 0;
|
|
@@ -404,17 +635,11 @@ function createHead$1(options = {}) {
|
|
|
404
635
|
if (options?.hooks)
|
|
405
636
|
hooks.addHooks(options.hooks);
|
|
406
637
|
options.plugins = [
|
|
407
|
-
|
|
408
|
-
DedupesTagsPlugin(),
|
|
409
|
-
SortTagsPlugin(),
|
|
410
|
-
TitleTemplatePlugin(),
|
|
411
|
-
EventHandlersPlugin(),
|
|
412
|
-
ProvideTagHashPlugin(),
|
|
413
|
-
PatchDomOnEntryUpdatesPlugin({ document: options?.document, delayFn: options?.domDelayFn }),
|
|
638
|
+
...CorePlugins(),
|
|
414
639
|
...options?.plugins || []
|
|
415
640
|
];
|
|
416
641
|
options.plugins.forEach((p) => p.hooks && hooks.addHooks(p.hooks));
|
|
417
|
-
const
|
|
642
|
+
const updated = () => hooks.callHook("entries:updated", head);
|
|
418
643
|
const head = {
|
|
419
644
|
resolvedOptions: options,
|
|
420
645
|
headEntries() {
|
|
@@ -432,27 +657,23 @@ function createHead$1(options = {}) {
|
|
|
432
657
|
if (options2?.mode)
|
|
433
658
|
activeEntry._m = options2?.mode;
|
|
434
659
|
entries.push(activeEntry);
|
|
435
|
-
|
|
436
|
-
const queueSideEffects = (e) => {
|
|
437
|
-
_sde = { ..._sde, ...e._sde || {} };
|
|
438
|
-
e._sde = {};
|
|
439
|
-
triggerUpdateHook();
|
|
440
|
-
};
|
|
660
|
+
updated();
|
|
441
661
|
return {
|
|
442
662
|
dispose() {
|
|
443
663
|
entries = entries.filter((e) => {
|
|
444
664
|
if (e._i !== activeEntry._i)
|
|
445
665
|
return true;
|
|
446
|
-
|
|
666
|
+
_sde = { ..._sde, ...e._sde || {} };
|
|
667
|
+
e._sde = {};
|
|
668
|
+
updated();
|
|
447
669
|
return false;
|
|
448
670
|
});
|
|
449
671
|
},
|
|
450
672
|
patch(input2) {
|
|
451
673
|
entries = entries.map((e) => {
|
|
452
674
|
if (e._i === activeEntry._i) {
|
|
453
|
-
queueSideEffects(e);
|
|
454
675
|
activeEntry.input = e.input = input2;
|
|
455
|
-
|
|
676
|
+
updated();
|
|
456
677
|
}
|
|
457
678
|
return e;
|
|
458
679
|
});
|
|
@@ -480,7 +701,6 @@ function createHead$1(options = {}) {
|
|
|
480
701
|
}
|
|
481
702
|
};
|
|
482
703
|
head.hooks.callHook("init", head);
|
|
483
|
-
setActiveHead(head);
|
|
484
704
|
return head;
|
|
485
705
|
}
|
|
486
706
|
|
|
@@ -493,6 +713,7 @@ const composableNames = [
|
|
|
493
713
|
"useTagBase",
|
|
494
714
|
"useTagMeta",
|
|
495
715
|
"useTagMetaFlat",
|
|
716
|
+
"useSeoMeta",
|
|
496
717
|
"useTagLink",
|
|
497
718
|
"useTagScript",
|
|
498
719
|
"useTagStyle",
|
|
@@ -552,14 +773,13 @@ function injectHead() {
|
|
|
552
773
|
return getCurrentInstance() && inject(headSymbol) || getActiveHead();
|
|
553
774
|
}
|
|
554
775
|
function createHead(options = {}) {
|
|
555
|
-
const plugins = [
|
|
556
|
-
VueReactiveUseHeadPlugin(),
|
|
557
|
-
...options?.plugins || []
|
|
558
|
-
];
|
|
559
776
|
const head = createHead$1({
|
|
560
777
|
...options,
|
|
561
778
|
domDelayFn: (fn) => setTimeout(() => nextTick(() => fn()), 10),
|
|
562
|
-
plugins
|
|
779
|
+
plugins: [
|
|
780
|
+
VueReactiveUseHeadPlugin(),
|
|
781
|
+
...options?.plugins || []
|
|
782
|
+
]
|
|
563
783
|
});
|
|
564
784
|
const vuePlugin = {
|
|
565
785
|
install(app) {
|
|
@@ -817,6 +1037,7 @@ const useTagMetaFlat = (meta) => {
|
|
|
817
1037
|
});
|
|
818
1038
|
return useHead({ meta: input });
|
|
819
1039
|
};
|
|
1040
|
+
const useSeoMeta = useTagMetaFlat;
|
|
820
1041
|
const useTagLink = (link) => useHead({ link: asArray(link) });
|
|
821
1042
|
const useTagScript = (script) => useHead({ script: asArray(script) });
|
|
822
1043
|
const useTagStyle = (style) => useHead({ style: asArray(style) });
|
|
@@ -835,4 +1056,4 @@ const unheadVueComposablesImports = [
|
|
|
835
1056
|
}
|
|
836
1057
|
];
|
|
837
1058
|
|
|
838
|
-
export { Vue2ProvideUnheadPlugin, VueHeadMixin, VueReactiveUseHeadPlugin, asArray, createHead, headSymbol, injectHead, resolveUnrefHeadInput, unheadVueComposablesImports, useBodyAttrs, useHead, useHtmlAttrs, useServerBodyAttrs, useServerHead, useServerHtmlAttrs, useServerTagBase, useServerTagLink, useServerTagMeta, useServerTagMetaFlat, useServerTagNoscript, useServerTagScript, useServerTagStyle, useServerTagTitle, useServerTitleTemplate, useTagBase, useTagLink, useTagMeta, useTagMetaFlat, useTagNoscript, useTagScript, useTagStyle, useTagTitle, useTitleTemplate };
|
|
1059
|
+
export { Vue2ProvideUnheadPlugin, VueHeadMixin, VueReactiveUseHeadPlugin, asArray, createHead, createHeadCore, headSymbol, injectHead, resolveUnrefHeadInput, unheadVueComposablesImports, useBodyAttrs, useHead, useHtmlAttrs, useSeoMeta, useServerBodyAttrs, useServerHead, useServerHtmlAttrs, useServerTagBase, useServerTagLink, useServerTagMeta, useServerTagMetaFlat, useServerTagNoscript, useServerTagScript, useServerTagStyle, useServerTagTitle, useServerTitleTemplate, useTagBase, useTagLink, useTagMeta, useTagMetaFlat, useTagNoscript, useTagScript, useTagStyle, useTagTitle, useTitleTemplate };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unhead/vue",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.6.
|
|
4
|
+
"version": "0.6.7",
|
|
5
5
|
"packageManager": "pnpm@7.14.0",
|
|
6
6
|
"author": "Harlan Wilton <harlan@harlanzw.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -33,11 +33,11 @@
|
|
|
33
33
|
"vue": ">=2.7 || >=3"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@unhead/schema": "0.6.
|
|
36
|
+
"@unhead/schema": "0.6.7",
|
|
37
37
|
"hookable": "^5.4.1"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"unhead": "0.6.
|
|
40
|
+
"unhead": "0.6.7",
|
|
41
41
|
"vue": "^3.2.45"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
package/dist/chunks/index.cjs
DELETED
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const TagsWithInnerContent = ["script", "style", "noscript"];
|
|
4
|
-
|
|
5
|
-
const UniqueTags = ["base", "title", "titleTemplate", "bodyAttrs", "htmlAttrs"];
|
|
6
|
-
const ArrayMetaProperties = [
|
|
7
|
-
"og:image",
|
|
8
|
-
"og:video",
|
|
9
|
-
"og:audio",
|
|
10
|
-
"og:locale:alternate",
|
|
11
|
-
"video:actor",
|
|
12
|
-
"video:director",
|
|
13
|
-
"video:writer",
|
|
14
|
-
"video:tag",
|
|
15
|
-
"article:author",
|
|
16
|
-
"article:tag",
|
|
17
|
-
"book:tag",
|
|
18
|
-
"book:author",
|
|
19
|
-
"music:album",
|
|
20
|
-
"music:musician"
|
|
21
|
-
];
|
|
22
|
-
function tagDedupeKey(tag) {
|
|
23
|
-
const { props, tag: tagName } = tag;
|
|
24
|
-
if (UniqueTags.includes(tagName))
|
|
25
|
-
return tagName;
|
|
26
|
-
if (tagName === "link" && props.rel === "canonical")
|
|
27
|
-
return "canonical";
|
|
28
|
-
if (props.charset)
|
|
29
|
-
return "charset";
|
|
30
|
-
const name = ["id"];
|
|
31
|
-
if (tagName === "meta")
|
|
32
|
-
name.push(...["name", "property", "http-equiv"]);
|
|
33
|
-
for (const n of name) {
|
|
34
|
-
if (typeof props[n] !== "undefined") {
|
|
35
|
-
const val = String(props[n]);
|
|
36
|
-
if (ArrayMetaProperties.findIndex((p) => val.startsWith(p)) !== -1)
|
|
37
|
-
return false;
|
|
38
|
-
return `${tagName}:${n}:${val}`;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async function renderDOMHead(head, options = {}) {
|
|
45
|
-
const ctx = { shouldRender: true };
|
|
46
|
-
await head.hooks.callHook("dom:beforeRender", ctx);
|
|
47
|
-
if (!ctx.shouldRender)
|
|
48
|
-
return;
|
|
49
|
-
const dom = options.document || window.document;
|
|
50
|
-
const staleSideEffects = head._popSideEffectQueue();
|
|
51
|
-
head.headEntries().map((entry) => entry._sde).forEach((sde) => {
|
|
52
|
-
Object.entries(sde).forEach(([key, fn]) => {
|
|
53
|
-
staleSideEffects[key] = fn;
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
const renderTag = (tag, entry) => {
|
|
57
|
-
if (tag.tag === "title" && tag.children) {
|
|
58
|
-
dom.title = tag.children;
|
|
59
|
-
return dom.head.querySelector("title");
|
|
60
|
-
}
|
|
61
|
-
const tagRenderId = tag._d || tag._p;
|
|
62
|
-
const markSideEffect = (key, fn) => {
|
|
63
|
-
key = `${tagRenderId}:${key}`;
|
|
64
|
-
entry._sde[key] = fn;
|
|
65
|
-
delete staleSideEffects[key];
|
|
66
|
-
};
|
|
67
|
-
const setAttrs = ($el, sideEffects = true) => {
|
|
68
|
-
Object.entries(tag.props).forEach(([k, value]) => {
|
|
69
|
-
value = String(value);
|
|
70
|
-
const attrSdeKey = `attr:${k}`;
|
|
71
|
-
if (k === "class") {
|
|
72
|
-
for (const c of value.split(" ")) {
|
|
73
|
-
const classSdeKey = `${attrSdeKey}:${c}`;
|
|
74
|
-
sideEffects && markSideEffect(classSdeKey, () => $el.classList.remove(c));
|
|
75
|
-
if (!$el.classList.contains(c))
|
|
76
|
-
$el.classList.add(c);
|
|
77
|
-
}
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
if (sideEffects && !k.startsWith("data-h-"))
|
|
81
|
-
markSideEffect(attrSdeKey, () => $el.removeAttribute(k));
|
|
82
|
-
if ($el.getAttribute(k) !== value)
|
|
83
|
-
$el.setAttribute(k, value);
|
|
84
|
-
});
|
|
85
|
-
if (TagsWithInnerContent.includes(tag.tag) && $el.innerHTML !== (tag.children || ""))
|
|
86
|
-
$el.innerHTML = tag.children || "";
|
|
87
|
-
};
|
|
88
|
-
if (tag.tag === "htmlAttrs" || tag.tag === "bodyAttrs") {
|
|
89
|
-
const $el = dom[tag.tag === "htmlAttrs" ? "documentElement" : "body"];
|
|
90
|
-
setAttrs($el);
|
|
91
|
-
return $el;
|
|
92
|
-
}
|
|
93
|
-
let $newEl = dom.createElement(tag.tag);
|
|
94
|
-
setAttrs($newEl, false);
|
|
95
|
-
let $previousEl = head._elMap[tagRenderId];
|
|
96
|
-
const $target = dom[tag.tagPosition?.startsWith("body") ? "body" : "head"];
|
|
97
|
-
if (!$previousEl && tag._hash) {
|
|
98
|
-
$previousEl = $target.querySelector(`${tag.tag}[data-h-${tag._hash}]`);
|
|
99
|
-
}
|
|
100
|
-
if (!$previousEl) {
|
|
101
|
-
for (const $el of tag.tagPosition === "bodyClose" ? [...$target.children].reverse() : $target.children) {
|
|
102
|
-
const elTag = $el.tagName.toLowerCase();
|
|
103
|
-
if (elTag !== tag.tag)
|
|
104
|
-
continue;
|
|
105
|
-
const key = tagDedupeKey({
|
|
106
|
-
tag: elTag,
|
|
107
|
-
props: $el.getAttributeNames().reduce((props, name) => ({ ...props, [name]: $el.getAttribute(name) }), {})
|
|
108
|
-
});
|
|
109
|
-
if (key === tag._d || $el.isEqualNode($newEl)) {
|
|
110
|
-
$previousEl = $el;
|
|
111
|
-
break;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
const markEl = ($el) => {
|
|
116
|
-
head._elMap[tagRenderId] = $el;
|
|
117
|
-
markSideEffect("el", () => {
|
|
118
|
-
$el?.remove();
|
|
119
|
-
delete head._elMap[tagRenderId];
|
|
120
|
-
});
|
|
121
|
-
};
|
|
122
|
-
if ($previousEl) {
|
|
123
|
-
markEl($previousEl);
|
|
124
|
-
setAttrs($previousEl, false);
|
|
125
|
-
return $previousEl;
|
|
126
|
-
}
|
|
127
|
-
switch (tag.tagPosition) {
|
|
128
|
-
case "bodyClose":
|
|
129
|
-
$newEl = dom.body.appendChild($newEl);
|
|
130
|
-
break;
|
|
131
|
-
case "bodyOpen":
|
|
132
|
-
$newEl = dom.body.insertBefore($newEl, dom.body.firstChild);
|
|
133
|
-
break;
|
|
134
|
-
case "head":
|
|
135
|
-
default:
|
|
136
|
-
$newEl = dom.head.appendChild($newEl);
|
|
137
|
-
break;
|
|
138
|
-
}
|
|
139
|
-
markEl($newEl);
|
|
140
|
-
return $newEl;
|
|
141
|
-
};
|
|
142
|
-
for (const tag of await head.resolveTags()) {
|
|
143
|
-
const entry = head.headEntries().find((e) => e._i === Number(tag._e));
|
|
144
|
-
const renderCtx = { $el: null, shouldRender: true, tag, entry, queuedSideEffects: staleSideEffects };
|
|
145
|
-
await head.hooks.callHook("dom:beforeRenderTag", renderCtx);
|
|
146
|
-
if (!renderCtx.shouldRender)
|
|
147
|
-
continue;
|
|
148
|
-
renderCtx.$el = renderTag(renderCtx.tag, renderCtx.entry);
|
|
149
|
-
await head.hooks.callHook("dom:renderTag", renderCtx);
|
|
150
|
-
}
|
|
151
|
-
Object.values(staleSideEffects).forEach((fn) => fn());
|
|
152
|
-
}
|
|
153
|
-
exports.domUpdatePromise = null;
|
|
154
|
-
async function debouncedRenderDOMHead(head, options = {}) {
|
|
155
|
-
function doDomUpdate() {
|
|
156
|
-
exports.domUpdatePromise = null;
|
|
157
|
-
return renderDOMHead(head, options);
|
|
158
|
-
}
|
|
159
|
-
const delayFn = options.delayFn || ((fn) => setTimeout(fn, 10));
|
|
160
|
-
return exports.domUpdatePromise = exports.domUpdatePromise || new Promise((resolve) => delayFn(() => resolve(doDomUpdate())));
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
exports.debouncedRenderDOMHead = debouncedRenderDOMHead;
|
|
164
|
-
exports.renderDOMHead = renderDOMHead;
|
package/dist/chunks/index.mjs
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
const TagsWithInnerContent = ["script", "style", "noscript"];
|
|
2
|
-
|
|
3
|
-
const UniqueTags = ["base", "title", "titleTemplate", "bodyAttrs", "htmlAttrs"];
|
|
4
|
-
const ArrayMetaProperties = [
|
|
5
|
-
"og:image",
|
|
6
|
-
"og:video",
|
|
7
|
-
"og:audio",
|
|
8
|
-
"og:locale:alternate",
|
|
9
|
-
"video:actor",
|
|
10
|
-
"video:director",
|
|
11
|
-
"video:writer",
|
|
12
|
-
"video:tag",
|
|
13
|
-
"article:author",
|
|
14
|
-
"article:tag",
|
|
15
|
-
"book:tag",
|
|
16
|
-
"book:author",
|
|
17
|
-
"music:album",
|
|
18
|
-
"music:musician"
|
|
19
|
-
];
|
|
20
|
-
function tagDedupeKey(tag) {
|
|
21
|
-
const { props, tag: tagName } = tag;
|
|
22
|
-
if (UniqueTags.includes(tagName))
|
|
23
|
-
return tagName;
|
|
24
|
-
if (tagName === "link" && props.rel === "canonical")
|
|
25
|
-
return "canonical";
|
|
26
|
-
if (props.charset)
|
|
27
|
-
return "charset";
|
|
28
|
-
const name = ["id"];
|
|
29
|
-
if (tagName === "meta")
|
|
30
|
-
name.push(...["name", "property", "http-equiv"]);
|
|
31
|
-
for (const n of name) {
|
|
32
|
-
if (typeof props[n] !== "undefined") {
|
|
33
|
-
const val = String(props[n]);
|
|
34
|
-
if (ArrayMetaProperties.findIndex((p) => val.startsWith(p)) !== -1)
|
|
35
|
-
return false;
|
|
36
|
-
return `${tagName}:${n}:${val}`;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
return false;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async function renderDOMHead(head, options = {}) {
|
|
43
|
-
const ctx = { shouldRender: true };
|
|
44
|
-
await head.hooks.callHook("dom:beforeRender", ctx);
|
|
45
|
-
if (!ctx.shouldRender)
|
|
46
|
-
return;
|
|
47
|
-
const dom = options.document || window.document;
|
|
48
|
-
const staleSideEffects = head._popSideEffectQueue();
|
|
49
|
-
head.headEntries().map((entry) => entry._sde).forEach((sde) => {
|
|
50
|
-
Object.entries(sde).forEach(([key, fn]) => {
|
|
51
|
-
staleSideEffects[key] = fn;
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
const renderTag = (tag, entry) => {
|
|
55
|
-
if (tag.tag === "title" && tag.children) {
|
|
56
|
-
dom.title = tag.children;
|
|
57
|
-
return dom.head.querySelector("title");
|
|
58
|
-
}
|
|
59
|
-
const tagRenderId = tag._d || tag._p;
|
|
60
|
-
const markSideEffect = (key, fn) => {
|
|
61
|
-
key = `${tagRenderId}:${key}`;
|
|
62
|
-
entry._sde[key] = fn;
|
|
63
|
-
delete staleSideEffects[key];
|
|
64
|
-
};
|
|
65
|
-
const setAttrs = ($el, sideEffects = true) => {
|
|
66
|
-
Object.entries(tag.props).forEach(([k, value]) => {
|
|
67
|
-
value = String(value);
|
|
68
|
-
const attrSdeKey = `attr:${k}`;
|
|
69
|
-
if (k === "class") {
|
|
70
|
-
for (const c of value.split(" ")) {
|
|
71
|
-
const classSdeKey = `${attrSdeKey}:${c}`;
|
|
72
|
-
sideEffects && markSideEffect(classSdeKey, () => $el.classList.remove(c));
|
|
73
|
-
if (!$el.classList.contains(c))
|
|
74
|
-
$el.classList.add(c);
|
|
75
|
-
}
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
if (sideEffects && !k.startsWith("data-h-"))
|
|
79
|
-
markSideEffect(attrSdeKey, () => $el.removeAttribute(k));
|
|
80
|
-
if ($el.getAttribute(k) !== value)
|
|
81
|
-
$el.setAttribute(k, value);
|
|
82
|
-
});
|
|
83
|
-
if (TagsWithInnerContent.includes(tag.tag) && $el.innerHTML !== (tag.children || ""))
|
|
84
|
-
$el.innerHTML = tag.children || "";
|
|
85
|
-
};
|
|
86
|
-
if (tag.tag === "htmlAttrs" || tag.tag === "bodyAttrs") {
|
|
87
|
-
const $el = dom[tag.tag === "htmlAttrs" ? "documentElement" : "body"];
|
|
88
|
-
setAttrs($el);
|
|
89
|
-
return $el;
|
|
90
|
-
}
|
|
91
|
-
let $newEl = dom.createElement(tag.tag);
|
|
92
|
-
setAttrs($newEl, false);
|
|
93
|
-
let $previousEl = head._elMap[tagRenderId];
|
|
94
|
-
const $target = dom[tag.tagPosition?.startsWith("body") ? "body" : "head"];
|
|
95
|
-
if (!$previousEl && tag._hash) {
|
|
96
|
-
$previousEl = $target.querySelector(`${tag.tag}[data-h-${tag._hash}]`);
|
|
97
|
-
}
|
|
98
|
-
if (!$previousEl) {
|
|
99
|
-
for (const $el of tag.tagPosition === "bodyClose" ? [...$target.children].reverse() : $target.children) {
|
|
100
|
-
const elTag = $el.tagName.toLowerCase();
|
|
101
|
-
if (elTag !== tag.tag)
|
|
102
|
-
continue;
|
|
103
|
-
const key = tagDedupeKey({
|
|
104
|
-
tag: elTag,
|
|
105
|
-
props: $el.getAttributeNames().reduce((props, name) => ({ ...props, [name]: $el.getAttribute(name) }), {})
|
|
106
|
-
});
|
|
107
|
-
if (key === tag._d || $el.isEqualNode($newEl)) {
|
|
108
|
-
$previousEl = $el;
|
|
109
|
-
break;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
const markEl = ($el) => {
|
|
114
|
-
head._elMap[tagRenderId] = $el;
|
|
115
|
-
markSideEffect("el", () => {
|
|
116
|
-
$el?.remove();
|
|
117
|
-
delete head._elMap[tagRenderId];
|
|
118
|
-
});
|
|
119
|
-
};
|
|
120
|
-
if ($previousEl) {
|
|
121
|
-
markEl($previousEl);
|
|
122
|
-
setAttrs($previousEl, false);
|
|
123
|
-
return $previousEl;
|
|
124
|
-
}
|
|
125
|
-
switch (tag.tagPosition) {
|
|
126
|
-
case "bodyClose":
|
|
127
|
-
$newEl = dom.body.appendChild($newEl);
|
|
128
|
-
break;
|
|
129
|
-
case "bodyOpen":
|
|
130
|
-
$newEl = dom.body.insertBefore($newEl, dom.body.firstChild);
|
|
131
|
-
break;
|
|
132
|
-
case "head":
|
|
133
|
-
default:
|
|
134
|
-
$newEl = dom.head.appendChild($newEl);
|
|
135
|
-
break;
|
|
136
|
-
}
|
|
137
|
-
markEl($newEl);
|
|
138
|
-
return $newEl;
|
|
139
|
-
};
|
|
140
|
-
for (const tag of await head.resolveTags()) {
|
|
141
|
-
const entry = head.headEntries().find((e) => e._i === Number(tag._e));
|
|
142
|
-
const renderCtx = { $el: null, shouldRender: true, tag, entry, queuedSideEffects: staleSideEffects };
|
|
143
|
-
await head.hooks.callHook("dom:beforeRenderTag", renderCtx);
|
|
144
|
-
if (!renderCtx.shouldRender)
|
|
145
|
-
continue;
|
|
146
|
-
renderCtx.$el = renderTag(renderCtx.tag, renderCtx.entry);
|
|
147
|
-
await head.hooks.callHook("dom:renderTag", renderCtx);
|
|
148
|
-
}
|
|
149
|
-
Object.values(staleSideEffects).forEach((fn) => fn());
|
|
150
|
-
}
|
|
151
|
-
let domUpdatePromise = null;
|
|
152
|
-
async function debouncedRenderDOMHead(head, options = {}) {
|
|
153
|
-
function doDomUpdate() {
|
|
154
|
-
domUpdatePromise = null;
|
|
155
|
-
return renderDOMHead(head, options);
|
|
156
|
-
}
|
|
157
|
-
const delayFn = options.delayFn || ((fn) => setTimeout(fn, 10));
|
|
158
|
-
return domUpdatePromise = domUpdatePromise || new Promise((resolve) => delayFn(() => resolve(doDomUpdate())));
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export { debouncedRenderDOMHead, domUpdatePromise, renderDOMHead };
|