@unhead/vue 0.6.6 → 0.6.8

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
@@ -3,6 +3,222 @@
3
3
  const hookable = require('hookable');
4
4
  const vue = require('vue');
5
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
+ };
221
+
6
222
  const ValidHeadTags = [
7
223
  "title",
8
224
  "titleTemplate",
@@ -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
- import('./chunks/index.cjs').then(({ debouncedRenderDOMHead }) => {
514
+ Promise.resolve().then(function () { return index; }).then(({ debouncedRenderDOMHead }) => {
299
515
  debouncedRenderDOMHead(head, { document: options?.document || window.document, delayFn });
300
516
  });
301
517
  }
@@ -345,7 +561,7 @@ 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.queuedSideEffects[sdeKey];
564
+ delete ctx.staleSideEffects[sdeKey];
349
565
  if ($el.hasAttribute(eventDedupeKey))
350
566
  return;
351
567
  const handler = value;
@@ -369,12 +585,6 @@ const EventHandlersPlugin = () => {
369
585
  function asArray$1(value) {
370
586
  return Array.isArray(value) ? value : [value];
371
587
  }
372
- function hashCode(s) {
373
- let h = 9;
374
- for (let i = 0; i < s.length; )
375
- h = Math.imul(h ^ s.charCodeAt(i++), 9 ** 9);
376
- return ((h ^ h >>> 9) + 65536).toString(16).substring(1, 8).toLowerCase();
377
- }
378
588
  const HasElementTags = [
379
589
  "base",
380
590
  "meta",
@@ -431,7 +641,7 @@ function createHeadCore(options = {}) {
431
641
  ...options?.plugins || []
432
642
  ];
433
643
  options.plugins.forEach((p) => p.hooks && hooks.addHooks(p.hooks));
434
- const triggerUpdateHook = () => hooks.callHook("entries:updated", head);
644
+ const updated = () => hooks.callHook("entries:updated", head);
435
645
  const head = {
436
646
  resolvedOptions: options,
437
647
  headEntries() {
@@ -449,27 +659,23 @@ function createHeadCore(options = {}) {
449
659
  if (options2?.mode)
450
660
  activeEntry._m = options2?.mode;
451
661
  entries.push(activeEntry);
452
- triggerUpdateHook();
453
- const queueSideEffects = (e) => {
454
- _sde = { ..._sde, ...e._sde || {} };
455
- e._sde = {};
456
- triggerUpdateHook();
457
- };
662
+ updated();
458
663
  return {
459
664
  dispose() {
460
665
  entries = entries.filter((e) => {
461
666
  if (e._i !== activeEntry._i)
462
667
  return true;
463
- queueSideEffects(e);
668
+ _sde = { ..._sde, ...e._sde || {} };
669
+ e._sde = {};
670
+ updated();
464
671
  return false;
465
672
  });
466
673
  },
467
674
  patch(input2) {
468
675
  entries = entries.map((e) => {
469
676
  if (e._i === activeEntry._i) {
470
- queueSideEffects(e);
471
677
  activeEntry.input = e.input = input2;
472
- activeEntry._i = e._i = _eid++;
678
+ updated();
473
679
  }
474
680
  return e;
475
681
  });
package/dist/index.mjs CHANGED
@@ -1,6 +1,222 @@
1
1
  import { createHooks } from 'hookable';
2
2
  import { unref, isRef, version, getCurrentInstance, inject, nextTick, ref, watchEffect, watch, onBeforeUnmount } from 'vue';
3
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
+ };
219
+
4
220
  const ValidHeadTags = [
5
221
  "title",
6
222
  "titleTemplate",
@@ -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
- import('./chunks/index.mjs').then(({ debouncedRenderDOMHead }) => {
512
+ Promise.resolve().then(function () { return index; }).then(({ debouncedRenderDOMHead }) => {
297
513
  debouncedRenderDOMHead(head, { document: options?.document || window.document, delayFn });
298
514
  });
299
515
  }
@@ -343,7 +559,7 @@ 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.queuedSideEffects[sdeKey];
562
+ delete ctx.staleSideEffects[sdeKey];
347
563
  if ($el.hasAttribute(eventDedupeKey))
348
564
  return;
349
565
  const handler = value;
@@ -367,12 +583,6 @@ const EventHandlersPlugin = () => {
367
583
  function asArray$1(value) {
368
584
  return Array.isArray(value) ? value : [value];
369
585
  }
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
586
  const HasElementTags = [
377
587
  "base",
378
588
  "meta",
@@ -429,7 +639,7 @@ function createHeadCore(options = {}) {
429
639
  ...options?.plugins || []
430
640
  ];
431
641
  options.plugins.forEach((p) => p.hooks && hooks.addHooks(p.hooks));
432
- const triggerUpdateHook = () => hooks.callHook("entries:updated", head);
642
+ const updated = () => hooks.callHook("entries:updated", head);
433
643
  const head = {
434
644
  resolvedOptions: options,
435
645
  headEntries() {
@@ -447,27 +657,23 @@ function createHeadCore(options = {}) {
447
657
  if (options2?.mode)
448
658
  activeEntry._m = options2?.mode;
449
659
  entries.push(activeEntry);
450
- triggerUpdateHook();
451
- const queueSideEffects = (e) => {
452
- _sde = { ..._sde, ...e._sde || {} };
453
- e._sde = {};
454
- triggerUpdateHook();
455
- };
660
+ updated();
456
661
  return {
457
662
  dispose() {
458
663
  entries = entries.filter((e) => {
459
664
  if (e._i !== activeEntry._i)
460
665
  return true;
461
- queueSideEffects(e);
666
+ _sde = { ..._sde, ...e._sde || {} };
667
+ e._sde = {};
668
+ updated();
462
669
  return false;
463
670
  });
464
671
  },
465
672
  patch(input2) {
466
673
  entries = entries.map((e) => {
467
674
  if (e._i === activeEntry._i) {
468
- queueSideEffects(e);
469
675
  activeEntry.input = e.input = input2;
470
- activeEntry._i = e._i = _eid++;
676
+ updated();
471
677
  }
472
678
  return e;
473
679
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@unhead/vue",
3
3
  "type": "module",
4
- "version": "0.6.6",
4
+ "version": "0.6.8",
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.6",
36
+ "@unhead/schema": "0.6.8",
37
37
  "hookable": "^5.4.1"
38
38
  },
39
39
  "devDependencies": {
40
- "unhead": "0.6.6",
40
+ "unhead": "0.6.8",
41
41
  "vue": "^3.2.45"
42
42
  },
43
43
  "scripts": {
@@ -1,165 +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
- if (entry)
65
- entry._sde[key] = fn;
66
- delete staleSideEffects[key];
67
- };
68
- const setAttrs = ($el, sideEffects = true) => {
69
- Object.entries(tag.props).forEach(([k, value]) => {
70
- value = String(value);
71
- const attrSdeKey = `attr:${k}`;
72
- if (k === "class") {
73
- for (const c of value.split(" ")) {
74
- const classSdeKey = `${attrSdeKey}:${c}`;
75
- sideEffects && markSideEffect(classSdeKey, () => $el.classList.remove(c));
76
- if (!$el.classList.contains(c))
77
- $el.classList.add(c);
78
- }
79
- return;
80
- }
81
- if (sideEffects && !k.startsWith("data-h-"))
82
- markSideEffect(attrSdeKey, () => $el.removeAttribute(k));
83
- if ($el.getAttribute(k) !== value)
84
- $el.setAttribute(k, value);
85
- });
86
- if (TagsWithInnerContent.includes(tag.tag) && $el.innerHTML !== (tag.children || ""))
87
- $el.innerHTML = tag.children || "";
88
- };
89
- if (tag.tag === "htmlAttrs" || tag.tag === "bodyAttrs") {
90
- const $el = dom[tag.tag === "htmlAttrs" ? "documentElement" : "body"];
91
- setAttrs($el);
92
- return $el;
93
- }
94
- let $newEl = dom.createElement(tag.tag);
95
- setAttrs($newEl, false);
96
- let $previousEl = head._elMap[tagRenderId];
97
- const $target = dom[tag.tagPosition?.startsWith("body") ? "body" : "head"];
98
- if (!$previousEl && tag._hash) {
99
- $previousEl = $target.querySelector(`${tag.tag}[data-h-${tag._hash}]`);
100
- }
101
- if (!$previousEl) {
102
- for (const $el of tag.tagPosition === "bodyClose" ? [...$target.children].reverse() : $target.children) {
103
- const elTag = $el.tagName.toLowerCase();
104
- if (elTag !== tag.tag)
105
- continue;
106
- const key = tagDedupeKey({
107
- tag: elTag,
108
- props: $el.getAttributeNames().reduce((props, name) => ({ ...props, [name]: $el.getAttribute(name) }), {})
109
- });
110
- if (key === tag._d || $el.isEqualNode($newEl)) {
111
- $previousEl = $el;
112
- break;
113
- }
114
- }
115
- }
116
- const markEl = ($el) => {
117
- head._elMap[tagRenderId] = $el;
118
- markSideEffect("el", () => {
119
- $el?.remove();
120
- delete head._elMap[tagRenderId];
121
- });
122
- };
123
- if ($previousEl) {
124
- markEl($previousEl);
125
- setAttrs($previousEl, false);
126
- return $previousEl;
127
- }
128
- switch (tag.tagPosition) {
129
- case "bodyClose":
130
- $newEl = dom.body.appendChild($newEl);
131
- break;
132
- case "bodyOpen":
133
- $newEl = dom.body.insertBefore($newEl, dom.body.firstChild);
134
- break;
135
- case "head":
136
- default:
137
- $newEl = dom.head.appendChild($newEl);
138
- break;
139
- }
140
- markEl($newEl);
141
- return $newEl;
142
- };
143
- for (const tag of await head.resolveTags()) {
144
- const entry = head.headEntries().find((e) => e._i === tag._e);
145
- const renderCtx = { $el: null, shouldRender: true, tag, entry, queuedSideEffects: staleSideEffects };
146
- await head.hooks.callHook("dom:beforeRenderTag", renderCtx);
147
- if (!renderCtx.shouldRender)
148
- continue;
149
- renderCtx.$el = renderTag(renderCtx.tag, renderCtx.entry);
150
- await head.hooks.callHook("dom:renderTag", renderCtx);
151
- }
152
- Object.values(staleSideEffects).forEach((fn) => fn());
153
- }
154
- exports.domUpdatePromise = null;
155
- async function debouncedRenderDOMHead(head, options = {}) {
156
- function doDomUpdate() {
157
- exports.domUpdatePromise = null;
158
- return renderDOMHead(head, options);
159
- }
160
- const delayFn = options.delayFn || ((fn) => setTimeout(fn, 10));
161
- return exports.domUpdatePromise = exports.domUpdatePromise || new Promise((resolve) => delayFn(() => resolve(doDomUpdate())));
162
- }
163
-
164
- exports.debouncedRenderDOMHead = debouncedRenderDOMHead;
165
- exports.renderDOMHead = renderDOMHead;
@@ -1,162 +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
- if (entry)
63
- entry._sde[key] = fn;
64
- delete staleSideEffects[key];
65
- };
66
- const setAttrs = ($el, sideEffects = true) => {
67
- Object.entries(tag.props).forEach(([k, value]) => {
68
- value = String(value);
69
- const attrSdeKey = `attr:${k}`;
70
- if (k === "class") {
71
- for (const c of value.split(" ")) {
72
- const classSdeKey = `${attrSdeKey}:${c}`;
73
- sideEffects && markSideEffect(classSdeKey, () => $el.classList.remove(c));
74
- if (!$el.classList.contains(c))
75
- $el.classList.add(c);
76
- }
77
- return;
78
- }
79
- if (sideEffects && !k.startsWith("data-h-"))
80
- markSideEffect(attrSdeKey, () => $el.removeAttribute(k));
81
- if ($el.getAttribute(k) !== value)
82
- $el.setAttribute(k, value);
83
- });
84
- if (TagsWithInnerContent.includes(tag.tag) && $el.innerHTML !== (tag.children || ""))
85
- $el.innerHTML = tag.children || "";
86
- };
87
- if (tag.tag === "htmlAttrs" || tag.tag === "bodyAttrs") {
88
- const $el = dom[tag.tag === "htmlAttrs" ? "documentElement" : "body"];
89
- setAttrs($el);
90
- return $el;
91
- }
92
- let $newEl = dom.createElement(tag.tag);
93
- setAttrs($newEl, false);
94
- let $previousEl = head._elMap[tagRenderId];
95
- const $target = dom[tag.tagPosition?.startsWith("body") ? "body" : "head"];
96
- if (!$previousEl && tag._hash) {
97
- $previousEl = $target.querySelector(`${tag.tag}[data-h-${tag._hash}]`);
98
- }
99
- if (!$previousEl) {
100
- for (const $el of tag.tagPosition === "bodyClose" ? [...$target.children].reverse() : $target.children) {
101
- const elTag = $el.tagName.toLowerCase();
102
- if (elTag !== tag.tag)
103
- continue;
104
- const key = tagDedupeKey({
105
- tag: elTag,
106
- props: $el.getAttributeNames().reduce((props, name) => ({ ...props, [name]: $el.getAttribute(name) }), {})
107
- });
108
- if (key === tag._d || $el.isEqualNode($newEl)) {
109
- $previousEl = $el;
110
- break;
111
- }
112
- }
113
- }
114
- const markEl = ($el) => {
115
- head._elMap[tagRenderId] = $el;
116
- markSideEffect("el", () => {
117
- $el?.remove();
118
- delete head._elMap[tagRenderId];
119
- });
120
- };
121
- if ($previousEl) {
122
- markEl($previousEl);
123
- setAttrs($previousEl, false);
124
- return $previousEl;
125
- }
126
- switch (tag.tagPosition) {
127
- case "bodyClose":
128
- $newEl = dom.body.appendChild($newEl);
129
- break;
130
- case "bodyOpen":
131
- $newEl = dom.body.insertBefore($newEl, dom.body.firstChild);
132
- break;
133
- case "head":
134
- default:
135
- $newEl = dom.head.appendChild($newEl);
136
- break;
137
- }
138
- markEl($newEl);
139
- return $newEl;
140
- };
141
- for (const tag of await head.resolveTags()) {
142
- const entry = head.headEntries().find((e) => e._i === tag._e);
143
- const renderCtx = { $el: null, shouldRender: true, tag, entry, queuedSideEffects: staleSideEffects };
144
- await head.hooks.callHook("dom:beforeRenderTag", renderCtx);
145
- if (!renderCtx.shouldRender)
146
- continue;
147
- renderCtx.$el = renderTag(renderCtx.tag, renderCtx.entry);
148
- await head.hooks.callHook("dom:renderTag", renderCtx);
149
- }
150
- Object.values(staleSideEffects).forEach((fn) => fn());
151
- }
152
- let domUpdatePromise = null;
153
- async function debouncedRenderDOMHead(head, options = {}) {
154
- function doDomUpdate() {
155
- domUpdatePromise = null;
156
- return renderDOMHead(head, options);
157
- }
158
- const delayFn = options.delayFn || ((fn) => setTimeout(fn, 10));
159
- return domUpdatePromise = domUpdatePromise || new Promise((resolve) => delayFn(() => resolve(doDomUpdate())));
160
- }
161
-
162
- export { debouncedRenderDOMHead, domUpdatePromise, renderDOMHead };