@unhead/vue 1.0.20 → 1.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,740 +1,8 @@
1
1
  'use strict';
2
2
 
3
- const hookable = require('hookable');
3
+ const unhead = require('unhead');
4
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
- function tagDedupeKey$1(tag, fn) {
18
- const { props, tag: tagName } = tag;
19
- if (UniqueTags$1.includes(tagName))
20
- return tagName;
21
- if (tagName === "link" && props.rel === "canonical")
22
- return "canonical";
23
- if (props.charset)
24
- return "charset";
25
- const name = ["id"];
26
- if (tagName === "meta")
27
- name.push(...["name", "property", "http-equiv"]);
28
- for (const n of name) {
29
- if (typeof props[n] !== "undefined") {
30
- const val = String(props[n]);
31
- if (fn && !fn(val))
32
- return false;
33
- return `${tagName}:${n}:${val}`;
34
- }
35
- }
36
- return false;
37
- }
38
-
39
- const setAttrs = (ctx, markSideEffect) => {
40
- const { tag, $el } = ctx;
41
- if (!$el)
42
- return;
43
- Object.entries(tag.props).forEach(([k, value]) => {
44
- value = String(value);
45
- const attrSdeKey = `attr:${k}`;
46
- if (k === "class") {
47
- if (!value)
48
- return;
49
- for (const c of value.split(" ")) {
50
- const classSdeKey = `${attrSdeKey}:${c}`;
51
- if (markSideEffect)
52
- markSideEffect(ctx, classSdeKey, () => $el.classList.remove(c));
53
- if (!$el.classList.contains(c))
54
- $el.classList.add(c);
55
- }
56
- return;
57
- }
58
- if (markSideEffect && !k.startsWith("data-h-"))
59
- markSideEffect(ctx, attrSdeKey, () => $el.removeAttribute(k));
60
- if ($el.getAttribute(k) !== value)
61
- $el.setAttribute(k, value);
62
- });
63
- if (TagsWithInnerContent.includes(tag.tag) && $el.innerHTML !== (tag.children || ""))
64
- $el.innerHTML = tag.children || "";
65
- };
66
-
67
- function hashCode(s) {
68
- let h = 9;
69
- for (let i = 0; i < s.length; )
70
- h = Math.imul(h ^ s.charCodeAt(i++), 9 ** 9);
71
- return ((h ^ h >>> 9) + 65536).toString(16).substring(1, 8).toLowerCase();
72
- }
73
-
74
- async function renderDOMHead(head, options = {}) {
75
- const ctx = { shouldRender: true };
76
- await head.hooks.callHook("dom:beforeRender", ctx);
77
- if (!ctx.shouldRender)
78
- return;
79
- const dom = options.document || window.document;
80
- const staleSideEffects = head._popSideEffectQueue();
81
- head.headEntries().map((entry) => entry._sde).forEach((sde) => {
82
- Object.entries(sde).forEach(([key, fn]) => {
83
- staleSideEffects[key] = fn;
84
- });
85
- });
86
- const preRenderTag = async (tag) => {
87
- const entry = head.headEntries().find((e) => e._i === tag._e);
88
- const renderCtx = {
89
- renderId: tag._d || hashCode(JSON.stringify({ ...tag, _e: void 0, _p: void 0 })),
90
- $el: null,
91
- shouldRender: true,
92
- tag,
93
- entry,
94
- staleSideEffects
95
- };
96
- await head.hooks.callHook("dom:beforeRenderTag", renderCtx);
97
- return renderCtx;
98
- };
99
- const renders = [];
100
- const pendingRenders = {
101
- body: [],
102
- head: []
103
- };
104
- const markSideEffect = (ctx2, key, fn) => {
105
- key = `${ctx2.renderId}:${key}`;
106
- if (ctx2.entry)
107
- ctx2.entry._sde[key] = fn;
108
- delete staleSideEffects[key];
109
- };
110
- const markEl = (ctx2) => {
111
- head._elMap[ctx2.renderId] = ctx2.$el;
112
- renders.push(ctx2);
113
- markSideEffect(ctx2, "el", () => {
114
- ctx2.$el?.remove();
115
- delete head._elMap[ctx2.renderId];
116
- });
117
- };
118
- for (const t of await head.resolveTags()) {
119
- const ctx2 = await preRenderTag(t);
120
- if (!ctx2.shouldRender)
121
- continue;
122
- const { tag } = ctx2;
123
- if (tag.tag === "title") {
124
- dom.title = tag.children || "";
125
- renders.push(ctx2);
126
- continue;
127
- }
128
- if (tag.tag === "htmlAttrs" || tag.tag === "bodyAttrs") {
129
- ctx2.$el = dom[tag.tag === "htmlAttrs" ? "documentElement" : "body"];
130
- setAttrs(ctx2, markSideEffect);
131
- renders.push(ctx2);
132
- continue;
133
- }
134
- ctx2.$el = head._elMap[ctx2.renderId];
135
- if (!ctx2.$el && tag._hash) {
136
- ctx2.$el = dom.querySelector(`${tag.tagPosition?.startsWith("body") ? "body" : "head"} > ${tag.tag}[data-h-${tag._hash}]`);
137
- }
138
- if (ctx2.$el) {
139
- if (ctx2.tag._d)
140
- setAttrs(ctx2);
141
- markEl(ctx2);
142
- continue;
143
- }
144
- ctx2.$el = dom.createElement(tag.tag);
145
- setAttrs(ctx2);
146
- pendingRenders[tag.tagPosition?.startsWith("body") ? "body" : "head"].push(ctx2);
147
- }
148
- Object.entries(pendingRenders).forEach(([pos, queue]) => {
149
- if (!queue.length)
150
- return;
151
- const children = dom?.[pos]?.children;
152
- if (!children)
153
- return;
154
- for (const $el of [...children].reverse()) {
155
- const elTag = $el.tagName.toLowerCase();
156
- if (!HasElementTags$1.includes(elTag))
157
- continue;
158
- const dedupeKey = tagDedupeKey$1({
159
- tag: elTag,
160
- // convert attributes to object
161
- props: $el.getAttributeNames().reduce((props, name) => ({ ...props, [name]: $el.getAttribute(name) }), {})
162
- });
163
- const matchIdx = queue.findIndex((ctx2) => ctx2 && (ctx2.tag._d === dedupeKey || $el.isEqualNode?.(ctx2.$el)));
164
- if (matchIdx !== -1) {
165
- const ctx2 = queue[matchIdx];
166
- ctx2.$el = $el;
167
- setAttrs(ctx2);
168
- markEl(ctx2);
169
- delete queue[matchIdx];
170
- }
171
- }
172
- queue.forEach((ctx2) => {
173
- if (!ctx2.$el)
174
- return;
175
- switch (ctx2.tag.tagPosition) {
176
- case "bodyClose":
177
- dom.body.appendChild(ctx2.$el);
178
- break;
179
- case "bodyOpen":
180
- dom.body.insertBefore(ctx2.$el, dom.body.firstChild);
181
- break;
182
- case "head":
183
- default:
184
- dom.head.appendChild(ctx2.$el);
185
- break;
186
- }
187
- markEl(ctx2);
188
- });
189
- });
190
- for (const ctx2 of renders)
191
- await head.hooks.callHook("dom:renderTag", ctx2);
192
- Object.values(staleSideEffects).forEach((fn) => fn());
193
- }
194
- let domUpdatePromise = null;
195
- async function debouncedRenderDOMHead(head, options = {}) {
196
- function doDomUpdate() {
197
- domUpdatePromise = null;
198
- return renderDOMHead(head, options);
199
- }
200
- const delayFn = options.delayFn || ((fn) => setTimeout(fn, 10));
201
- return domUpdatePromise = domUpdatePromise || new Promise((resolve) => delayFn(() => resolve(doDomUpdate())));
202
- }
203
-
204
- const index = {
205
- __proto__: null,
206
- debouncedRenderDOMHead: debouncedRenderDOMHead,
207
- get domUpdatePromise () { return domUpdatePromise; },
208
- hashCode: hashCode,
209
- renderDOMHead: renderDOMHead
210
- };
211
-
212
- const ValidHeadTags = [
213
- "title",
214
- "titleTemplate",
215
- "base",
216
- "htmlAttrs",
217
- "bodyAttrs",
218
- "meta",
219
- "link",
220
- "style",
221
- "script",
222
- "noscript"
223
- ];
224
- const TagConfigKeys = ["tagPosition", "tagPriority", "tagDuplicateStrategy"];
225
-
226
- async function normaliseTag(tagName, input) {
227
- const tag = { tag: tagName, props: {} };
228
- if (tagName === "title" || tagName === "titleTemplate") {
229
- tag.children = input instanceof Promise ? await input : input;
230
- return tag;
231
- }
232
- tag.props = await normaliseProps({ ...input });
233
- ["children", "innerHtml", "innerHTML"].forEach((key) => {
234
- if (typeof tag.props[key] !== "undefined") {
235
- tag.children = tag.props[key];
236
- if (typeof tag.children === "object")
237
- tag.children = JSON.stringify(tag.children);
238
- delete tag.props[key];
239
- }
240
- });
241
- Object.keys(tag.props).filter((k) => TagConfigKeys.includes(k)).forEach((k) => {
242
- tag[k] = tag.props[k];
243
- delete tag.props[k];
244
- });
245
- if (typeof tag.props.class === "object" && !Array.isArray(tag.props.class)) {
246
- tag.props.class = Object.keys(tag.props.class).filter((k) => tag.props.class[k]);
247
- }
248
- if (Array.isArray(tag.props.class))
249
- tag.props.class = tag.props.class.join(" ");
250
- if (tag.props.content && Array.isArray(tag.props.content)) {
251
- return tag.props.content.map((v, i) => {
252
- const newTag = { ...tag, props: { ...tag.props } };
253
- newTag.props.content = v;
254
- newTag.key = `${tag.props.name || tag.props.property}:${i}`;
255
- return newTag;
256
- });
257
- }
258
- return tag;
259
- }
260
- async function normaliseProps(props) {
261
- for (const k of Object.keys(props)) {
262
- if (props[k] instanceof Promise) {
263
- props[k] = await props[k];
264
- }
265
- if (String(props[k]) === "true") {
266
- props[k] = "";
267
- } else if (String(props[k]) === "false") {
268
- delete props[k];
269
- }
270
- }
271
- return props;
272
- }
273
-
274
- const tagWeight = (tag) => {
275
- if (typeof tag.tagPriority === "number")
276
- return tag.tagPriority;
277
- switch (tag.tagPriority) {
278
- case "critical":
279
- return 2;
280
- case "high":
281
- return 9;
282
- case "low":
283
- return 12;
284
- }
285
- switch (tag.tag) {
286
- case "base":
287
- return -1;
288
- case "title":
289
- return 1;
290
- case "meta":
291
- if (tag.props.charset)
292
- return -2;
293
- if (tag.props["http-equiv"] === "content-security-policy")
294
- return 0;
295
- return 10;
296
- default:
297
- return 10;
298
- }
299
- };
300
- const sortTags = (aTag, bTag) => {
301
- return tagWeight(aTag) - tagWeight(bTag);
302
- };
303
-
304
- const UniqueTags = ["base", "title", "titleTemplate", "bodyAttrs", "htmlAttrs"];
305
- function tagDedupeKey(tag, fn) {
306
- const { props, tag: tagName } = tag;
307
- if (UniqueTags.includes(tagName))
308
- return tagName;
309
- if (tagName === "link" && props.rel === "canonical")
310
- return "canonical";
311
- if (props.charset)
312
- return "charset";
313
- const name = ["id"];
314
- if (tagName === "meta")
315
- name.push(...["name", "property", "http-equiv"]);
316
- for (const n of name) {
317
- if (typeof props[n] !== "undefined") {
318
- const val = String(props[n]);
319
- if (fn && !fn(val))
320
- return false;
321
- return `${tagName}:${n}:${val}`;
322
- }
323
- }
324
- return false;
325
- }
326
-
327
- const renderTitleTemplate = (template, title) => {
328
- if (template == null)
329
- return title || null;
330
- if (typeof template === "function")
331
- return template(title);
332
- return template.replace("%s", title ?? "");
333
- };
334
- function resolveTitleTemplateFromTags(tags) {
335
- let titleTemplateIdx = tags.findIndex((i) => i.tag === "titleTemplate");
336
- const titleIdx = tags.findIndex((i) => i.tag === "title");
337
- if (titleIdx !== -1 && titleTemplateIdx !== -1) {
338
- const newTitle = renderTitleTemplate(
339
- tags[titleTemplateIdx].children,
340
- tags[titleIdx].children
341
- );
342
- if (newTitle !== null) {
343
- tags[titleIdx].children = newTitle || tags[titleIdx].children;
344
- } else {
345
- delete tags[titleIdx];
346
- }
347
- } else if (titleTemplateIdx !== -1) {
348
- const newTitle = renderTitleTemplate(
349
- tags[titleTemplateIdx].children
350
- );
351
- if (newTitle !== null) {
352
- tags[titleTemplateIdx].children = newTitle;
353
- tags[titleTemplateIdx].tag = "title";
354
- titleTemplateIdx = -1;
355
- }
356
- }
357
- if (titleTemplateIdx !== -1) {
358
- delete tags[titleTemplateIdx];
359
- }
360
- return tags.filter(Boolean);
361
- }
362
-
363
- const DedupesTagsPlugin = (options) => {
364
- options = options || {};
365
- const dedupeKeys = options.dedupeKeys || ["hid", "vmid", "key"];
366
- return defineHeadPlugin({
367
- hooks: {
368
- "tag:normalise": function({ tag }) {
369
- dedupeKeys.forEach((key) => {
370
- if (tag.props[key]) {
371
- tag.key = tag.props[key];
372
- delete tag.props[key];
373
- }
374
- });
375
- const dedupe = tag.key ? `${tag.tag}:${tag.key}` : tagDedupeKey(tag);
376
- if (dedupe)
377
- tag._d = dedupe;
378
- },
379
- "tags:resolve": function(ctx) {
380
- const deduping = {};
381
- ctx.tags.forEach((tag) => {
382
- let dedupeKey = tag._d || tag._p;
383
- const dupedTag = deduping[dedupeKey];
384
- if (dupedTag) {
385
- let strategy = tag?.tagDuplicateStrategy;
386
- if (!strategy && (tag.tag === "htmlAttrs" || tag.tag === "bodyAttrs"))
387
- strategy = "merge";
388
- if (strategy === "merge") {
389
- const oldProps = dupedTag.props;
390
- ["class", "style"].forEach((key) => {
391
- if (tag.props[key] && oldProps[key]) {
392
- if (key === "style" && !oldProps[key].endsWith(";"))
393
- oldProps[key] += ";";
394
- tag.props[key] = `${oldProps[key]} ${tag.props[key]}`;
395
- }
396
- });
397
- deduping[dedupeKey].props = {
398
- ...oldProps,
399
- ...tag.props
400
- };
401
- return;
402
- } else if (tag._e === dupedTag._e) {
403
- dedupeKey = tag._d = `${dedupeKey}:${tag._p}`;
404
- }
405
- const propCount = Object.keys(tag.props).length;
406
- if ((propCount === 0 || propCount === 1 && typeof tag.props["data-h-key"] !== "undefined") && !tag.children) {
407
- delete deduping[dedupeKey];
408
- return;
409
- }
410
- }
411
- deduping[dedupeKey] = tag;
412
- });
413
- ctx.tags = Object.values(deduping);
414
- }
415
- }
416
- });
417
- };
418
-
419
- const SortTagsPlugin = () => {
420
- return defineHeadPlugin({
421
- hooks: {
422
- "tags:resolve": (ctx) => {
423
- const tagIndexForKey = (key) => ctx.tags.find((tag) => tag._d === key)?._p;
424
- for (const tag of ctx.tags) {
425
- if (!tag.tagPriority || typeof tag.tagPriority === "number")
426
- continue;
427
- const modifiers = [{ prefix: "before:", offset: -1 }, { prefix: "after:", offset: 1 }];
428
- for (const { prefix, offset } of modifiers) {
429
- if (tag.tagPriority.startsWith(prefix)) {
430
- const key = tag.tagPriority.replace(prefix, "");
431
- const index = tagIndexForKey(key);
432
- if (typeof index !== "undefined")
433
- tag._p = index + offset;
434
- }
435
- }
436
- }
437
- ctx.tags.sort((a, b) => a._p - b._p).sort(sortTags);
438
- }
439
- }
440
- });
441
- };
442
-
443
- const TitleTemplatePlugin = () => {
444
- return defineHeadPlugin({
445
- hooks: {
446
- "tags:resolve": (ctx) => {
447
- ctx.tags = resolveTitleTemplateFromTags(ctx.tags);
448
- }
449
- }
450
- });
451
- };
452
-
453
- const DeprecatedTagAttrPlugin = () => {
454
- return defineHeadPlugin({
455
- hooks: {
456
- "tag:normalise": function({ tag }) {
457
- if (typeof tag.props.body !== "undefined") {
458
- tag.tagPosition = "bodyClose";
459
- delete tag.props.body;
460
- }
461
- }
462
- }
463
- });
464
- };
465
-
466
- const IsBrowser$1 = typeof window !== "undefined";
467
-
468
- const ProvideTagHashPlugin = () => {
469
- return defineHeadPlugin({
470
- hooks: {
471
- "tag:normalise": (ctx) => {
472
- const { tag, entry } = ctx;
473
- const isDynamic = typeof tag.props._dynamic !== "undefined";
474
- if (!HasElementTags.includes(tag.tag) || !tag.key)
475
- return;
476
- tag._hash = hashCode(JSON.stringify({ tag: tag.tag, key: tag.key }));
477
- if (IsBrowser$1 || getActiveHead()?.resolvedOptions?.document)
478
- return;
479
- if (entry._m === "server" || isDynamic) {
480
- tag.props[`data-h-${tag._hash}`] = "";
481
- }
482
- },
483
- "tags:resolve": (ctx) => {
484
- ctx.tags = ctx.tags.map((t) => {
485
- delete t.props._dynamic;
486
- return t;
487
- });
488
- }
489
- }
490
- });
491
- };
492
-
493
- const PatchDomOnEntryUpdatesPlugin = (options) => {
494
- return defineHeadPlugin({
495
- hooks: {
496
- "entries:updated": function(head) {
497
- if (typeof options?.document === "undefined" && typeof window === "undefined")
498
- return;
499
- let delayFn = options?.delayFn;
500
- if (!delayFn && typeof requestAnimationFrame !== "undefined")
501
- delayFn = requestAnimationFrame;
502
- Promise.resolve().then(function () { return index; }).then(({ debouncedRenderDOMHead }) => {
503
- debouncedRenderDOMHead(head, { document: options?.document || window.document, delayFn });
504
- });
505
- }
506
- }
507
- });
508
- };
509
-
510
- const EventHandlersPlugin = () => {
511
- const stripEventHandlers = (mode, tag) => {
512
- const props = {};
513
- const eventHandlers = {};
514
- Object.entries(tag.props).forEach(([key, value]) => {
515
- if (key.startsWith("on") && typeof value === "function")
516
- eventHandlers[key] = value;
517
- else
518
- props[key] = value;
519
- });
520
- let delayedSrc;
521
- if (mode === "dom" && tag.tag === "script" && typeof props.src === "string" && typeof eventHandlers.onload !== "undefined") {
522
- delayedSrc = props.src;
523
- delete props.src;
524
- }
525
- return { props, eventHandlers, delayedSrc };
526
- };
527
- return defineHeadPlugin({
528
- hooks: {
529
- "ssr:render": function(ctx) {
530
- ctx.tags = ctx.tags.map((tag) => {
531
- tag.props = stripEventHandlers("ssr", tag).props;
532
- return tag;
533
- });
534
- },
535
- "dom:beforeRenderTag": function(ctx) {
536
- const { props, eventHandlers, delayedSrc } = stripEventHandlers("dom", ctx.tag);
537
- if (!Object.keys(eventHandlers).length)
538
- return;
539
- ctx.tag.props = props;
540
- ctx.tag._eventHandlers = eventHandlers;
541
- ctx.tag._delayedSrc = delayedSrc;
542
- },
543
- "dom:renderTag": function(ctx) {
544
- const $el = ctx.$el;
545
- if (!ctx.tag._eventHandlers || !$el)
546
- return;
547
- const $eventListenerTarget = ctx.tag.tag === "bodyAttrs" && typeof window !== "undefined" ? window : $el;
548
- Object.entries(ctx.tag._eventHandlers).forEach(([k, value]) => {
549
- const sdeKey = `${ctx.tag._d || ctx.tag._p}:${k}`;
550
- const eventName = k.slice(2).toLowerCase();
551
- const eventDedupeKey = `data-h-${eventName}`;
552
- delete ctx.staleSideEffects[sdeKey];
553
- if ($el.hasAttribute(eventDedupeKey))
554
- return;
555
- const handler = value;
556
- $el.setAttribute(eventDedupeKey, "");
557
- $eventListenerTarget.addEventListener(eventName, handler);
558
- if (ctx.entry) {
559
- ctx.entry._sde[sdeKey] = () => {
560
- $eventListenerTarget.removeEventListener(eventName, handler);
561
- $el.removeAttribute(eventDedupeKey);
562
- };
563
- }
564
- });
565
- if (ctx.tag._delayedSrc) {
566
- $el.setAttribute("src", ctx.tag._delayedSrc);
567
- }
568
- }
569
- }
570
- });
571
- };
572
-
573
- function asArray$1(value) {
574
- return Array.isArray(value) ? value : [value];
575
- }
576
- const HasElementTags = [
577
- "base",
578
- "meta",
579
- "link",
580
- "style",
581
- "script",
582
- "noscript"
583
- ];
584
-
585
- let activeHead;
586
- const setActiveHead = (head) => activeHead = head;
587
- const getActiveHead = () => activeHead;
588
-
589
- const TagEntityBits = 10;
590
-
591
- async function normaliseEntryTags(e) {
592
- const tagPromises = [];
593
- Object.entries(e.resolvedInput || e.input).filter(([k, v]) => typeof v !== "undefined" && ValidHeadTags.includes(k)).forEach(([k, value]) => {
594
- const v = asArray$1(value);
595
- tagPromises.push(...v.map((props) => normaliseTag(k, props)).flat());
596
- });
597
- return (await Promise.all(tagPromises)).flat().map((t, i) => {
598
- t._e = e._i;
599
- t._p = (e._i << TagEntityBits) + i;
600
- return t;
601
- });
602
- }
603
-
604
- const CorePlugins = () => [
605
- // dedupe needs to come first
606
- DedupesTagsPlugin(),
607
- SortTagsPlugin(),
608
- TitleTemplatePlugin(),
609
- ProvideTagHashPlugin(),
610
- EventHandlersPlugin(),
611
- DeprecatedTagAttrPlugin()
612
- ];
613
- const DOMPlugins = (options = {}) => [
614
- PatchDomOnEntryUpdatesPlugin({ document: options?.document, delayFn: options?.domDelayFn })
615
- ];
616
- function createHead$1(options = {}) {
617
- const head = createHeadCore({
618
- ...options,
619
- plugins: [...DOMPlugins(options), ...options?.plugins || []]
620
- });
621
- setActiveHead(head);
622
- return head;
623
- }
624
- function createHeadCore(options = {}) {
625
- let entries = [];
626
- let _sde = {};
627
- let _eid = 0;
628
- const hooks = hookable.createHooks();
629
- if (options?.hooks)
630
- hooks.addHooks(options.hooks);
631
- options.plugins = [
632
- ...CorePlugins(),
633
- ...options?.plugins || []
634
- ];
635
- options.plugins.forEach((p) => p.hooks && hooks.addHooks(p.hooks));
636
- const updated = () => hooks.callHook("entries:updated", head);
637
- const head = {
638
- resolvedOptions: options,
639
- headEntries() {
640
- return entries;
641
- },
642
- get hooks() {
643
- return hooks;
644
- },
645
- use(plugin) {
646
- if (plugin.hooks)
647
- hooks.addHooks(plugin.hooks);
648
- },
649
- push(input, options2) {
650
- const activeEntry = {
651
- _i: _eid++,
652
- input,
653
- _sde: {}
654
- };
655
- if (options2?.mode)
656
- activeEntry._m = options2?.mode;
657
- entries.push(activeEntry);
658
- updated();
659
- return {
660
- dispose() {
661
- entries = entries.filter((e) => {
662
- if (e._i !== activeEntry._i)
663
- return true;
664
- _sde = { ..._sde, ...e._sde || {} };
665
- e._sde = {};
666
- updated();
667
- return false;
668
- });
669
- },
670
- // a patch is the same as creating a new entry, just a nice DX
671
- patch(input2) {
672
- entries = entries.map((e) => {
673
- if (e._i === activeEntry._i) {
674
- activeEntry.input = e.input = input2;
675
- updated();
676
- }
677
- return e;
678
- });
679
- }
680
- };
681
- },
682
- async resolveTags() {
683
- const resolveCtx = { tags: [], entries: [...entries] };
684
- await hooks.callHook("entries:resolve", resolveCtx);
685
- for (const entry of resolveCtx.entries) {
686
- for (const tag of await normaliseEntryTags(entry)) {
687
- const tagCtx = { tag, entry };
688
- await hooks.callHook("tag:normalise", tagCtx);
689
- resolveCtx.tags.push(tagCtx.tag);
690
- }
691
- }
692
- await hooks.callHook("tags:resolve", resolveCtx);
693
- return resolveCtx.tags;
694
- },
695
- _elMap: {},
696
- _popSideEffectQueue() {
697
- const sde = { ..._sde };
698
- _sde = {};
699
- return sde;
700
- }
701
- };
702
- head.hooks.callHook("init", head);
703
- return head;
704
- }
705
-
706
- function defineHeadPlugin(plugin) {
707
- return plugin;
708
- }
709
- const composableNames = [
710
- "useHead",
711
- "useTagTitle",
712
- "useTagBase",
713
- "useTagMeta",
714
- "useTagMetaFlat",
715
- // alias
716
- "useSeoMeta",
717
- "useTagLink",
718
- "useTagScript",
719
- "useTagStyle",
720
- "useTagNoscript",
721
- "useHtmlAttrs",
722
- "useBodyAttrs",
723
- "useTitleTemplate",
724
- // server only composables
725
- "useServerHead",
726
- "useServerTagTitle",
727
- "useServerTagBase",
728
- "useServerTagMeta",
729
- "useServerTagMetaFlat",
730
- "useServerTagLink",
731
- "useServerTagScript",
732
- "useServerTagStyle",
733
- "useServerTagNoscript",
734
- "useServerHtmlAttrs",
735
- "useServerBodyAttrs",
736
- "useServerTitleTemplate"
737
- ];
5
+ const shared = require('@unhead/shared');
738
6
 
739
7
  function resolveUnref(r) {
740
8
  return typeof r === "function" ? r() : vue.unref(r);
@@ -758,25 +26,22 @@ function resolveUnrefHeadInput(ref, lastKey = "") {
758
26
  return [k, resolveUnrefHeadInput(v, k)];
759
27
  })
760
28
  );
761
- if (dynamic && HasElementTags.includes(String(lastKey)))
29
+ if (dynamic && shared.HasElementTags.includes(String(lastKey)))
762
30
  unrefdObj._dynamic = true;
763
31
  return unrefdObj;
764
32
  }
765
33
  return root;
766
34
  }
767
- function asArray(value) {
768
- return Array.isArray(value) ? value : [value];
769
- }
770
35
 
771
36
  const Vue3 = vue.version.startsWith("3");
772
37
  const IsBrowser = typeof window !== "undefined";
773
38
 
774
39
  const headSymbol = "usehead";
775
40
  function injectHead() {
776
- return vue.getCurrentInstance() && vue.inject(headSymbol) || getActiveHead();
41
+ return vue.getCurrentInstance() && vue.inject(headSymbol) || unhead.getActiveHead();
777
42
  }
778
43
  function createHead(options = {}) {
779
- const head = createHead$1({
44
+ const head = unhead.createHead({
780
45
  ...options,
781
46
  // arbitrary delay the dom update for batch updates
782
47
  domDelayFn: (fn) => setTimeout(() => vue.nextTick(() => fn()), 10),
@@ -811,7 +76,7 @@ const VueHeadMixin = {
811
76
  };
812
77
 
813
78
  const VueReactiveUseHeadPlugin = () => {
814
- return defineHeadPlugin({
79
+ return shared.defineHeadPlugin({
815
80
  hooks: {
816
81
  "entries:resolve": function(ctx) {
817
82
  for (const entry of ctx.entries)
@@ -841,199 +106,28 @@ const Vue2ProvideUnheadPlugin = function(_Vue, head) {
841
106
  });
842
107
  };
843
108
 
844
- function unpackToArray(input, options) {
845
- const unpacked = [];
846
- const kFn = options.resolveKeyData || ((ctx) => ctx.key);
847
- const vFn = options.resolveValueData || ((ctx) => ctx.value);
848
- for (const [k, v] of Object.entries(input)) {
849
- unpacked.push(...(Array.isArray(v) ? v : [v]).map((i) => {
850
- const ctx = { key: k, value: i };
851
- const val = vFn(ctx);
852
- if (typeof val === "object")
853
- return unpackToArray(val, options);
854
- if (Array.isArray(val))
855
- return val;
856
- return {
857
- [typeof options.key === "function" ? options.key(ctx) : options.key]: kFn(ctx),
858
- [typeof options.value === "function" ? options.value(ctx) : options.value]: val
859
- };
860
- }).flat());
861
- }
862
- return unpacked;
863
- }
864
-
865
- function unpackToString(value, options) {
866
- return Object.entries(value).map(([key, value2]) => {
867
- if (typeof value2 === "object")
868
- value2 = unpackToString(value2, options);
869
- if (options.resolve) {
870
- const resolved = options.resolve({ key, value: value2 });
871
- if (resolved)
872
- return resolved;
873
- }
874
- if (typeof value2 === "number")
875
- value2 = value2.toString();
876
- if (typeof value2 === "string" && options.wrapValue) {
877
- value2 = value2.replace(new RegExp(options.wrapValue, "g"), `\\${options.wrapValue}`);
878
- value2 = `${options.wrapValue}${value2}${options.wrapValue}`;
879
- }
880
- return `${key}${options.keyValueSeparator || ""}${value2}`;
881
- }).join(options.entrySeparator || "");
882
- }
883
-
884
- const MetaPackingSchema = {
885
- robots: {
886
- unpack: {
887
- keyValueSeparator: ":"
888
- }
889
- },
890
- // Pragma directives
891
- contentSecurityPolicy: {
892
- unpack: {
893
- keyValueSeparator: " ",
894
- entrySeparator: "; "
895
- },
896
- metaKey: "http-equiv"
897
- },
898
- fbAppId: {
899
- keyValue: "fb:app_id",
900
- metaKey: "property"
901
- },
902
- msapplicationTileImage: {
903
- keyValue: "msapplication-TileImage"
904
- },
905
- /**
906
- * Tile colour for windows
907
- */
908
- msapplicationTileColor: {
909
- keyValue: "msapplication-TileColor"
910
- },
911
- /**
912
- * URL of a config for windows tile.
913
- */
914
- msapplicationConfig: {
915
- keyValue: "msapplication-Config"
916
- },
917
- charset: {
918
- metaKey: "charset"
919
- },
920
- contentType: {
921
- metaKey: "http-equiv"
922
- },
923
- defaultStyle: {
924
- metaKey: "http-equiv"
925
- },
926
- xUaCompatible: {
927
- metaKey: "http-equiv"
928
- },
929
- refresh: {
930
- metaKey: "http-equiv"
931
- }
932
- };
933
- function resolveMetaKeyType(key) {
934
- return PropertyPrefixKeys.test(key) ? "property" : MetaPackingSchema[key]?.metaKey || "name";
935
- }
936
-
937
- const ArrayableInputs = ["Image", "Video", "Audio"];
938
- function unpackMeta(input) {
939
- const extras = [];
940
- ArrayableInputs.forEach((key) => {
941
- const ogKey = `og:${key.toLowerCase()}`;
942
- const inputKey = `og${key}`;
943
- const val = input[inputKey];
944
- if (typeof val === "object") {
945
- (Array.isArray(val) ? val : [val]).forEach((entry) => {
946
- if (!entry)
947
- return;
948
- const unpackedEntry = unpackToArray(entry, {
949
- key: "property",
950
- value: "content",
951
- resolveKeyData({ key: key2 }) {
952
- return fixKeyCase(`${ogKey}${key2 !== "url" ? `:${key2}` : ""}`);
953
- },
954
- resolveValueData({ value }) {
955
- return typeof value === "number" ? value.toString() : value;
956
- }
957
- });
958
- extras.push(
959
- ...unpackedEntry.sort((a, b) => a.property === ogKey ? -1 : b.property === ogKey ? 1 : 0)
960
- );
961
- });
962
- delete input[inputKey];
963
- }
964
- });
965
- const meta = unpackToArray(input, {
966
- key({ key }) {
967
- return resolveMetaKeyType(key);
968
- },
969
- value({ key }) {
970
- return key === "charset" ? "charset" : "content";
971
- },
972
- resolveKeyData({ key }) {
973
- return MetaPackingSchema[key]?.keyValue || fixKeyCase(key);
974
- },
975
- resolveValueData({ value, key }) {
976
- if (value === null)
977
- return "_null";
978
- if (typeof value === "object") {
979
- const definition = MetaPackingSchema[key];
980
- if (key === "refresh")
981
- return `${value.seconds};url=${value.url}`;
982
- return unpackToString(
983
- changeKeyCasingDeep(value),
984
- {
985
- entrySeparator: ", ",
986
- keyValueSeparator: "=",
987
- resolve({ value: value2, key: key2 }) {
988
- if (value2 === null)
989
- return "";
990
- if (typeof value2 === "boolean")
991
- return `${key2}`;
992
- },
993
- ...definition?.unpack
994
- }
995
- );
996
- }
997
- return typeof value === "number" ? value.toString() : value;
998
- }
999
- });
1000
- return [...extras, ...meta].filter((v) => typeof v.content === "undefined" || v.content !== "_null");
1001
- }
1002
-
1003
- const PropertyPrefixKeys = /^(og|fb)/;
1004
- const ColonPrefixKeys = /^(og|twitter|fb)/;
1005
- function fixKeyCase(key) {
1006
- key = key.replace(/([A-Z])/g, "-$1").toLowerCase();
1007
- if (ColonPrefixKeys.test(key)) {
1008
- key = key.replace("secure-url", "secure_url").replace(/-/g, ":");
1009
- }
1010
- return key;
1011
- }
1012
- function changeKeyCasingDeep(input) {
1013
- if (Array.isArray(input)) {
1014
- return input.map((entry) => changeKeyCasingDeep(entry));
1015
- }
1016
- if (typeof input !== "object" || Array.isArray(input))
1017
- return input;
1018
- const output = {};
1019
- for (const [key, value] of Object.entries(input))
1020
- output[fixKeyCase(key)] = changeKeyCasingDeep(value);
1021
- return output;
1022
- }
1023
-
1024
109
  function clientUseHead(input, options = {}) {
1025
110
  const head = injectHead();
111
+ const deactivated = vue.ref(false);
1026
112
  const resolvedInput = vue.ref({});
1027
113
  vue.watchEffect(() => {
1028
- resolvedInput.value = resolveUnrefHeadInput(input);
114
+ resolvedInput.value = deactivated.value ? {} : resolveUnrefHeadInput(input);
1029
115
  });
1030
116
  const entry = head.push(resolvedInput.value, options);
1031
- vue.watch(resolvedInput, (e) => entry.patch(e));
117
+ vue.watch(resolvedInput, (e) => {
118
+ entry.patch(e);
119
+ });
1032
120
  const vm = vue.getCurrentInstance();
1033
121
  if (vm) {
1034
122
  vue.onBeforeUnmount(() => {
1035
123
  entry.dispose();
1036
124
  });
125
+ vue.onDeactivated(() => {
126
+ deactivated.value = true;
127
+ });
128
+ vue.onActivated(() => {
129
+ deactivated.value = false;
130
+ });
1037
131
  }
1038
132
  return entry;
1039
133
  }
@@ -1048,18 +142,18 @@ function useServerHead(input, options = {}) {
1048
142
  }
1049
143
  const useServerTagTitle = (title) => useServerHead({ title });
1050
144
  const useServerTitleTemplate = (titleTemplate) => useServerHead({ titleTemplate });
1051
- const useServerTagMeta = (meta) => useServerHead({ meta: asArray(meta) });
145
+ const useServerTagMeta = (meta) => useServerHead({ meta: shared.asArray(meta) });
1052
146
  const useServerTagMetaFlat = (meta) => {
1053
147
  const input = vue.ref({});
1054
148
  vue.watchEffect(() => {
1055
- input.value = unpackMeta(resolveUnrefHeadInput(meta));
149
+ input.value = unhead.unpackMeta(resolveUnrefHeadInput(meta));
1056
150
  });
1057
151
  return useServerHead({ meta: input });
1058
152
  };
1059
- const useServerTagLink = (link) => useServerHead({ link: asArray(link) });
1060
- const useServerTagScript = (script) => useServerHead({ script: asArray(script) });
1061
- const useServerTagStyle = (style) => useServerHead({ style: asArray(style) });
1062
- const useServerTagNoscript = (noscript) => useServerHead({ noscript: asArray(noscript) });
153
+ const useServerTagLink = (link) => useServerHead({ link: shared.asArray(link) });
154
+ const useServerTagScript = (script) => useServerHead({ script: shared.asArray(script) });
155
+ const useServerTagStyle = (style) => useServerHead({ style: shared.asArray(style) });
156
+ const useServerTagNoscript = (noscript) => useServerHead({ noscript: shared.asArray(noscript) });
1063
157
  const useServerTagBase = (base) => useServerHead({ base });
1064
158
  const useServerHtmlAttrs = (attrs) => useServerHead({ htmlAttrs: attrs });
1065
159
  const useServerBodyAttrs = (attrs) => useHead({ bodyAttrs: attrs });
@@ -1072,7 +166,7 @@ const useSeoMeta = (input) => {
1072
166
  headInput.value = {
1073
167
  title,
1074
168
  titleTemplate,
1075
- meta: unpackMeta(meta)
169
+ meta: unhead.unpackMeta(meta)
1076
170
  };
1077
171
  });
1078
172
  return useHead(headInput);
@@ -1089,18 +183,18 @@ function useHead(input, options = {}) {
1089
183
  }
1090
184
  const useTagTitle = (title) => useHead({ title });
1091
185
  const useTitleTemplate = (titleTemplate) => useHead({ titleTemplate });
1092
- const useTagMeta = (meta) => useHead({ meta: asArray(meta) });
186
+ const useTagMeta = (meta) => useHead({ meta: shared.asArray(meta) });
1093
187
  const useTagMetaFlat = (meta) => {
1094
188
  const input = vue.ref({});
1095
189
  vue.watchEffect(() => {
1096
- input.value = unpackMeta(resolveUnrefHeadInput(meta));
190
+ input.value = unhead.unpackMeta(resolveUnrefHeadInput(meta));
1097
191
  });
1098
192
  return useHead({ meta: input });
1099
193
  };
1100
- const useTagLink = (link) => useHead({ link: asArray(link) });
1101
- const useTagScript = (script) => useHead({ script: asArray(script) });
1102
- const useTagStyle = (style) => useHead({ style: asArray(style) });
1103
- const useTagNoscript = (noscript) => useHead({ noscript: asArray(noscript) });
194
+ const useTagLink = (link) => useHead({ link: shared.asArray(link) });
195
+ const useTagScript = (script) => useHead({ script: shared.asArray(script) });
196
+ const useTagStyle = (style) => useHead({ style: shared.asArray(style) });
197
+ const useTagNoscript = (noscript) => useHead({ noscript: shared.asArray(noscript) });
1104
198
  const useTagBase = (base) => useHead({ base });
1105
199
  const useHtmlAttrs = (attrs) => useHead({ htmlAttrs: attrs });
1106
200
  const useBodyAttrs = (attrs) => useHead({ bodyAttrs: attrs });
@@ -1109,15 +203,14 @@ const coreComposableNames = [
1109
203
  "injectHead"
1110
204
  ];
1111
205
  const unheadVueComposablesImports = {
1112
- "@unhead/vue": [...coreComposableNames, ...composableNames]
206
+ "@unhead/vue": [...coreComposableNames, ...unhead.composableNames]
1113
207
  };
1114
208
 
209
+ exports.createHeadCore = unhead.createHeadCore;
1115
210
  exports.Vue2ProvideUnheadPlugin = Vue2ProvideUnheadPlugin;
1116
211
  exports.VueHeadMixin = VueHeadMixin;
1117
212
  exports.VueReactiveUseHeadPlugin = VueReactiveUseHeadPlugin;
1118
- exports.asArray = asArray;
1119
213
  exports.createHead = createHead;
1120
- exports.createHeadCore = createHeadCore;
1121
214
  exports.headSymbol = headSymbol;
1122
215
  exports.injectHead = injectHead;
1123
216
  exports.resolveUnrefHeadInput = resolveUnrefHeadInput;