@unhead/dom 1.0.21 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +115 -113
- package/dist/index.d.ts +20 -5
- package/dist/index.mjs +112 -112
- package/package.json +7 -9
package/dist/index.cjs
CHANGED
|
@@ -1,39 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const HasElementTags = [
|
|
5
|
-
"base",
|
|
6
|
-
"meta",
|
|
7
|
-
"link",
|
|
8
|
-
"style",
|
|
9
|
-
"script",
|
|
10
|
-
"noscript"
|
|
11
|
-
];
|
|
3
|
+
const shared = require('@unhead/shared');
|
|
12
4
|
|
|
13
|
-
const
|
|
14
|
-
function tagDedupeKey(tag, fn) {
|
|
15
|
-
const { props, tag: tagName } = tag;
|
|
16
|
-
if (UniqueTags.includes(tagName))
|
|
17
|
-
return tagName;
|
|
18
|
-
if (tagName === "link" && props.rel === "canonical")
|
|
19
|
-
return "canonical";
|
|
20
|
-
if (props.charset)
|
|
21
|
-
return "charset";
|
|
22
|
-
const name = ["id"];
|
|
23
|
-
if (tagName === "meta")
|
|
24
|
-
name.push(...["name", "property", "http-equiv"]);
|
|
25
|
-
for (const n of name) {
|
|
26
|
-
if (typeof props[n] !== "undefined") {
|
|
27
|
-
const val = String(props[n]);
|
|
28
|
-
if (fn && !fn(val))
|
|
29
|
-
return false;
|
|
30
|
-
return `${tagName}:${n}:${val}`;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const setAttrs = (ctx, markSideEffect) => {
|
|
5
|
+
const setAttrs = (ctx, newEntry = false, markSideEffect) => {
|
|
37
6
|
const { tag, $el } = ctx;
|
|
38
7
|
if (!$el)
|
|
39
8
|
return;
|
|
@@ -54,94 +23,103 @@ const setAttrs = (ctx, markSideEffect) => {
|
|
|
54
23
|
}
|
|
55
24
|
if (markSideEffect && !k.startsWith("data-h-"))
|
|
56
25
|
markSideEffect(ctx, attrSdeKey, () => $el.removeAttribute(k));
|
|
57
|
-
if ($el.getAttribute(k) !== value)
|
|
26
|
+
if (newEntry || $el.getAttribute(k) !== value)
|
|
58
27
|
$el.setAttribute(k, value);
|
|
59
28
|
});
|
|
60
|
-
if (TagsWithInnerContent.includes(tag.tag)
|
|
61
|
-
|
|
29
|
+
if (shared.TagsWithInnerContent.includes(tag.tag)) {
|
|
30
|
+
if (tag.textContent && tag.textContent !== $el.textContent)
|
|
31
|
+
$el.textContent = tag.textContent;
|
|
32
|
+
else if (tag.innerHTML && tag.innerHTML !== $el.innerHTML)
|
|
33
|
+
$el.innerHTML = tag.innerHTML;
|
|
34
|
+
}
|
|
62
35
|
};
|
|
63
36
|
|
|
64
|
-
|
|
65
|
-
let h = 9;
|
|
66
|
-
for (let i = 0; i < s.length; )
|
|
67
|
-
h = Math.imul(h ^ s.charCodeAt(i++), 9 ** 9);
|
|
68
|
-
return ((h ^ h >>> 9) + 65536).toString(16).substring(1, 8).toLowerCase();
|
|
69
|
-
}
|
|
70
|
-
|
|
37
|
+
let prevHash = false;
|
|
71
38
|
async function renderDOMHead(head, options = {}) {
|
|
72
|
-
const
|
|
73
|
-
await head.hooks.callHook("dom:beforeRender",
|
|
74
|
-
if (!
|
|
39
|
+
const beforeRenderCtx = { shouldRender: true };
|
|
40
|
+
await head.hooks.callHook("dom:beforeRender", beforeRenderCtx);
|
|
41
|
+
if (!beforeRenderCtx.shouldRender)
|
|
75
42
|
return;
|
|
76
|
-
const dom = options.document || window.document;
|
|
43
|
+
const dom = options.document || head.resolvedOptions.document || window.document;
|
|
44
|
+
const tagContexts = (await head.resolveTags()).map(setupTagRenderCtx);
|
|
45
|
+
if (head.resolvedOptions.experimentalHashHydration) {
|
|
46
|
+
prevHash = prevHash || head._hash || false;
|
|
47
|
+
if (prevHash) {
|
|
48
|
+
const hash = shared.computeHashes(tagContexts.map((ctx) => ctx.tag._h));
|
|
49
|
+
if (prevHash === hash)
|
|
50
|
+
return;
|
|
51
|
+
prevHash = hash;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
77
54
|
const staleSideEffects = head._popSideEffectQueue();
|
|
78
55
|
head.headEntries().map((entry) => entry._sde).forEach((sde) => {
|
|
79
56
|
Object.entries(sde).forEach(([key, fn]) => {
|
|
80
57
|
staleSideEffects[key] = fn;
|
|
81
58
|
});
|
|
82
59
|
});
|
|
83
|
-
const
|
|
60
|
+
const markSideEffect = (ctx, key, fn) => {
|
|
61
|
+
key = `${ctx.renderId}:${key}`;
|
|
62
|
+
if (ctx.entry)
|
|
63
|
+
ctx.entry._sde[key] = fn;
|
|
64
|
+
delete staleSideEffects[key];
|
|
65
|
+
};
|
|
66
|
+
function setupTagRenderCtx(tag) {
|
|
84
67
|
const entry = head.headEntries().find((e) => e._i === tag._e);
|
|
85
68
|
const renderCtx = {
|
|
86
|
-
renderId: tag._d ||
|
|
69
|
+
renderId: tag._d || shared.hashTag(tag),
|
|
87
70
|
$el: null,
|
|
88
71
|
shouldRender: true,
|
|
89
72
|
tag,
|
|
90
73
|
entry,
|
|
91
|
-
|
|
74
|
+
markSideEffect: (key, fn) => markSideEffect(renderCtx, key, fn)
|
|
92
75
|
};
|
|
93
|
-
await head.hooks.callHook("dom:beforeRenderTag", renderCtx);
|
|
94
76
|
return renderCtx;
|
|
95
|
-
}
|
|
77
|
+
}
|
|
96
78
|
const renders = [];
|
|
97
79
|
const pendingRenders = {
|
|
98
80
|
body: [],
|
|
99
81
|
head: []
|
|
100
82
|
};
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const markEl = (ctx2) => {
|
|
108
|
-
head._elMap[ctx2.renderId] = ctx2.$el;
|
|
109
|
-
renders.push(ctx2);
|
|
110
|
-
markSideEffect(ctx2, "el", () => {
|
|
111
|
-
ctx2.$el?.remove();
|
|
112
|
-
delete head._elMap[ctx2.renderId];
|
|
83
|
+
const markEl = (ctx) => {
|
|
84
|
+
head._elMap[ctx.renderId] = ctx.$el;
|
|
85
|
+
renders.push(ctx);
|
|
86
|
+
markSideEffect(ctx, "el", () => {
|
|
87
|
+
ctx.$el?.remove();
|
|
88
|
+
delete head._elMap[ctx.renderId];
|
|
113
89
|
});
|
|
114
90
|
};
|
|
115
|
-
for (const
|
|
116
|
-
|
|
117
|
-
if (!
|
|
91
|
+
for (const ctx of tagContexts) {
|
|
92
|
+
await head.hooks.callHook("dom:beforeRenderTag", ctx);
|
|
93
|
+
if (!ctx.shouldRender)
|
|
118
94
|
continue;
|
|
119
|
-
const { tag } =
|
|
95
|
+
const { tag } = ctx;
|
|
120
96
|
if (tag.tag === "title") {
|
|
121
|
-
dom.title = tag.
|
|
122
|
-
renders.push(
|
|
97
|
+
dom.title = tag.textContent || "";
|
|
98
|
+
renders.push(ctx);
|
|
123
99
|
continue;
|
|
124
100
|
}
|
|
125
101
|
if (tag.tag === "htmlAttrs" || tag.tag === "bodyAttrs") {
|
|
126
|
-
|
|
127
|
-
setAttrs(
|
|
128
|
-
renders.push(
|
|
102
|
+
ctx.$el = dom[tag.tag === "htmlAttrs" ? "documentElement" : "body"];
|
|
103
|
+
setAttrs(ctx, false, markSideEffect);
|
|
104
|
+
renders.push(ctx);
|
|
129
105
|
continue;
|
|
130
106
|
}
|
|
131
|
-
|
|
132
|
-
if (!
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
markEl(ctx2);
|
|
107
|
+
ctx.$el = head._elMap[ctx.renderId];
|
|
108
|
+
if (!ctx.$el && tag.key)
|
|
109
|
+
ctx.$el = dom.querySelector(`${tag.tagPosition?.startsWith("body") ? "body" : "head"} > ${tag.tag}[data-h-${tag._h}]`);
|
|
110
|
+
if (ctx.$el) {
|
|
111
|
+
if (ctx.tag._d)
|
|
112
|
+
setAttrs(ctx);
|
|
113
|
+
markEl(ctx);
|
|
139
114
|
continue;
|
|
140
115
|
}
|
|
141
|
-
|
|
142
|
-
setAttrs(ctx2);
|
|
143
|
-
pendingRenders[tag.tagPosition?.startsWith("body") ? "body" : "head"].push(ctx2);
|
|
116
|
+
pendingRenders[tag.tagPosition?.startsWith("body") ? "body" : "head"].push(ctx);
|
|
144
117
|
}
|
|
118
|
+
const fragments = {
|
|
119
|
+
bodyClose: void 0,
|
|
120
|
+
bodyOpen: void 0,
|
|
121
|
+
head: void 0
|
|
122
|
+
};
|
|
145
123
|
Object.entries(pendingRenders).forEach(([pos, queue]) => {
|
|
146
124
|
if (!queue.length)
|
|
147
125
|
return;
|
|
@@ -150,42 +128,45 @@ async function renderDOMHead(head, options = {}) {
|
|
|
150
128
|
return;
|
|
151
129
|
for (const $el of [...children].reverse()) {
|
|
152
130
|
const elTag = $el.tagName.toLowerCase();
|
|
153
|
-
if (!HasElementTags.includes(elTag))
|
|
131
|
+
if (!shared.HasElementTags.includes(elTag))
|
|
154
132
|
continue;
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
133
|
+
const props = $el.getAttributeNames().reduce((props2, name) => ({ ...props2, [name]: $el.getAttribute(name) }), {});
|
|
134
|
+
const tmpTag = { tag: elTag, props };
|
|
135
|
+
if ($el.innerHTML)
|
|
136
|
+
tmpTag.innerHTML = $el.innerHTML;
|
|
137
|
+
const tmpRenderId = shared.hashTag(tmpTag);
|
|
138
|
+
let matchIdx = queue.findIndex((ctx) => ctx?.renderId === tmpRenderId);
|
|
139
|
+
if (matchIdx === -1) {
|
|
140
|
+
const tmpDedupeKey = shared.tagDedupeKey(tmpTag);
|
|
141
|
+
matchIdx = queue.findIndex((ctx) => ctx?.tag._d && ctx.tag._d === tmpDedupeKey);
|
|
142
|
+
}
|
|
161
143
|
if (matchIdx !== -1) {
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
setAttrs(
|
|
165
|
-
markEl(
|
|
144
|
+
const ctx = queue[matchIdx];
|
|
145
|
+
ctx.$el = $el;
|
|
146
|
+
setAttrs(ctx);
|
|
147
|
+
markEl(ctx);
|
|
166
148
|
delete queue[matchIdx];
|
|
167
149
|
}
|
|
168
150
|
}
|
|
169
|
-
queue.forEach((
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
break;
|
|
176
|
-
case "bodyOpen":
|
|
177
|
-
dom.body.insertBefore(ctx2.$el, dom.body.firstChild);
|
|
178
|
-
break;
|
|
179
|
-
case "head":
|
|
180
|
-
default:
|
|
181
|
-
dom.head.appendChild(ctx2.$el);
|
|
182
|
-
break;
|
|
151
|
+
queue.forEach((ctx) => {
|
|
152
|
+
const pos2 = ctx.tag.tagPosition || "head";
|
|
153
|
+
fragments[pos2] = fragments[pos2] || dom.createDocumentFragment();
|
|
154
|
+
if (!ctx.$el) {
|
|
155
|
+
ctx.$el = dom.createElement(ctx.tag.tag);
|
|
156
|
+
setAttrs(ctx, true);
|
|
183
157
|
}
|
|
184
|
-
|
|
158
|
+
fragments[pos2].appendChild(ctx.$el);
|
|
159
|
+
markEl(ctx);
|
|
185
160
|
});
|
|
186
161
|
});
|
|
187
|
-
|
|
188
|
-
|
|
162
|
+
if (fragments.head)
|
|
163
|
+
dom.head.appendChild(fragments.head);
|
|
164
|
+
if (fragments.bodyOpen)
|
|
165
|
+
dom.body.insertBefore(fragments.bodyOpen, dom.body.firstChild);
|
|
166
|
+
if (fragments.bodyClose)
|
|
167
|
+
dom.body.appendChild(fragments.bodyClose);
|
|
168
|
+
for (const ctx of renders)
|
|
169
|
+
await head.hooks.callHook("dom:renderTag", ctx);
|
|
189
170
|
Object.values(staleSideEffects).forEach((fn) => fn());
|
|
190
171
|
}
|
|
191
172
|
exports.domUpdatePromise = null;
|
|
@@ -198,6 +179,27 @@ async function debouncedRenderDOMHead(head, options = {}) {
|
|
|
198
179
|
return exports.domUpdatePromise = exports.domUpdatePromise || new Promise((resolve) => delayFn(() => resolve(doDomUpdate())));
|
|
199
180
|
}
|
|
200
181
|
|
|
182
|
+
const PatchDomOnEntryUpdatesPlugin = (options) => {
|
|
183
|
+
return shared.defineHeadPlugin({
|
|
184
|
+
hooks: {
|
|
185
|
+
"entries:updated": function(head) {
|
|
186
|
+
if (typeof options?.document === "undefined" && typeof window === "undefined")
|
|
187
|
+
return;
|
|
188
|
+
let delayFn = options?.delayFn;
|
|
189
|
+
if (!delayFn && typeof requestAnimationFrame !== "undefined")
|
|
190
|
+
delayFn = requestAnimationFrame;
|
|
191
|
+
debouncedRenderDOMHead(head, { document: options?.document || window.document, delayFn });
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
function maybeGetSSRHash(document) {
|
|
198
|
+
return document?.head.querySelector('meta[name="unhead:ssr"]')?.getAttribute("content") || false;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
exports.PatchDomOnEntryUpdatesPlugin = PatchDomOnEntryUpdatesPlugin;
|
|
201
202
|
exports.debouncedRenderDOMHead = debouncedRenderDOMHead;
|
|
202
|
-
exports.
|
|
203
|
+
exports.maybeGetSSRHash = maybeGetSSRHash;
|
|
203
204
|
exports.renderDOMHead = renderDOMHead;
|
|
205
|
+
exports.setAttrs = setAttrs;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as _unhead_schema from '@unhead/schema';
|
|
2
|
+
import { Unhead, DomRenderTagContext } from '@unhead/schema';
|
|
2
3
|
|
|
3
4
|
interface RenderDomHeadOptions {
|
|
4
5
|
/**
|
|
@@ -6,6 +7,12 @@ interface RenderDomHeadOptions {
|
|
|
6
7
|
*/
|
|
7
8
|
document?: Document;
|
|
8
9
|
}
|
|
10
|
+
interface DebouncedRenderDomHeadOptions extends RenderDomHeadOptions {
|
|
11
|
+
/**
|
|
12
|
+
* Specify a custom delay function for delaying the render.
|
|
13
|
+
*/
|
|
14
|
+
delayFn?: (fn: () => void) => void;
|
|
15
|
+
}
|
|
9
16
|
/**
|
|
10
17
|
* Render the head tags to the DOM.
|
|
11
18
|
*/
|
|
@@ -17,10 +24,18 @@ declare let domUpdatePromise: Promise<void> | null;
|
|
|
17
24
|
/**
|
|
18
25
|
* Queue a debounced update of the DOM head.
|
|
19
26
|
*/
|
|
20
|
-
declare function debouncedRenderDOMHead<T extends Unhead<any>>(head: T, options?:
|
|
27
|
+
declare function debouncedRenderDOMHead<T extends Unhead<any>>(head: T, options?: DebouncedRenderDomHeadOptions): Promise<void>;
|
|
28
|
+
|
|
29
|
+
interface TriggerDomPatchingOnUpdatesPluginOptions extends RenderDomHeadOptions {
|
|
21
30
|
delayFn?: (fn: () => void) => void;
|
|
22
|
-
}
|
|
31
|
+
}
|
|
32
|
+
declare const PatchDomOnEntryUpdatesPlugin: (options?: TriggerDomPatchingOnUpdatesPluginOptions) => _unhead_schema.HeadPlugin;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Set attributes on a DOM element, while adding entry side effects.
|
|
36
|
+
*/
|
|
37
|
+
declare const setAttrs: (ctx: DomRenderTagContext, newEntry?: boolean, markSideEffect?: ((ctx: DomRenderTagContext, k: string, fn: () => void) => void) | undefined) => void;
|
|
23
38
|
|
|
24
|
-
declare function
|
|
39
|
+
declare function maybeGetSSRHash(document: Document): string | false;
|
|
25
40
|
|
|
26
|
-
export { RenderDomHeadOptions, debouncedRenderDOMHead, domUpdatePromise,
|
|
41
|
+
export { DebouncedRenderDomHeadOptions, PatchDomOnEntryUpdatesPlugin, RenderDomHeadOptions, TriggerDomPatchingOnUpdatesPluginOptions, debouncedRenderDOMHead, domUpdatePromise, maybeGetSSRHash, renderDOMHead, setAttrs };
|
package/dist/index.mjs
CHANGED
|
@@ -1,37 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
const HasElementTags = [
|
|
3
|
-
"base",
|
|
4
|
-
"meta",
|
|
5
|
-
"link",
|
|
6
|
-
"style",
|
|
7
|
-
"script",
|
|
8
|
-
"noscript"
|
|
9
|
-
];
|
|
1
|
+
import { TagsWithInnerContent, computeHashes, hashTag, HasElementTags, tagDedupeKey, defineHeadPlugin } from '@unhead/shared';
|
|
10
2
|
|
|
11
|
-
const
|
|
12
|
-
function tagDedupeKey(tag, fn) {
|
|
13
|
-
const { props, tag: tagName } = tag;
|
|
14
|
-
if (UniqueTags.includes(tagName))
|
|
15
|
-
return tagName;
|
|
16
|
-
if (tagName === "link" && props.rel === "canonical")
|
|
17
|
-
return "canonical";
|
|
18
|
-
if (props.charset)
|
|
19
|
-
return "charset";
|
|
20
|
-
const name = ["id"];
|
|
21
|
-
if (tagName === "meta")
|
|
22
|
-
name.push(...["name", "property", "http-equiv"]);
|
|
23
|
-
for (const n of name) {
|
|
24
|
-
if (typeof props[n] !== "undefined") {
|
|
25
|
-
const val = String(props[n]);
|
|
26
|
-
if (fn && !fn(val))
|
|
27
|
-
return false;
|
|
28
|
-
return `${tagName}:${n}:${val}`;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const setAttrs = (ctx, markSideEffect) => {
|
|
3
|
+
const setAttrs = (ctx, newEntry = false, markSideEffect) => {
|
|
35
4
|
const { tag, $el } = ctx;
|
|
36
5
|
if (!$el)
|
|
37
6
|
return;
|
|
@@ -52,94 +21,103 @@ const setAttrs = (ctx, markSideEffect) => {
|
|
|
52
21
|
}
|
|
53
22
|
if (markSideEffect && !k.startsWith("data-h-"))
|
|
54
23
|
markSideEffect(ctx, attrSdeKey, () => $el.removeAttribute(k));
|
|
55
|
-
if ($el.getAttribute(k) !== value)
|
|
24
|
+
if (newEntry || $el.getAttribute(k) !== value)
|
|
56
25
|
$el.setAttribute(k, value);
|
|
57
26
|
});
|
|
58
|
-
if (TagsWithInnerContent.includes(tag.tag)
|
|
59
|
-
|
|
27
|
+
if (TagsWithInnerContent.includes(tag.tag)) {
|
|
28
|
+
if (tag.textContent && tag.textContent !== $el.textContent)
|
|
29
|
+
$el.textContent = tag.textContent;
|
|
30
|
+
else if (tag.innerHTML && tag.innerHTML !== $el.innerHTML)
|
|
31
|
+
$el.innerHTML = tag.innerHTML;
|
|
32
|
+
}
|
|
60
33
|
};
|
|
61
34
|
|
|
62
|
-
|
|
63
|
-
let h = 9;
|
|
64
|
-
for (let i = 0; i < s.length; )
|
|
65
|
-
h = Math.imul(h ^ s.charCodeAt(i++), 9 ** 9);
|
|
66
|
-
return ((h ^ h >>> 9) + 65536).toString(16).substring(1, 8).toLowerCase();
|
|
67
|
-
}
|
|
68
|
-
|
|
35
|
+
let prevHash = false;
|
|
69
36
|
async function renderDOMHead(head, options = {}) {
|
|
70
|
-
const
|
|
71
|
-
await head.hooks.callHook("dom:beforeRender",
|
|
72
|
-
if (!
|
|
37
|
+
const beforeRenderCtx = { shouldRender: true };
|
|
38
|
+
await head.hooks.callHook("dom:beforeRender", beforeRenderCtx);
|
|
39
|
+
if (!beforeRenderCtx.shouldRender)
|
|
73
40
|
return;
|
|
74
|
-
const dom = options.document || window.document;
|
|
41
|
+
const dom = options.document || head.resolvedOptions.document || window.document;
|
|
42
|
+
const tagContexts = (await head.resolveTags()).map(setupTagRenderCtx);
|
|
43
|
+
if (head.resolvedOptions.experimentalHashHydration) {
|
|
44
|
+
prevHash = prevHash || head._hash || false;
|
|
45
|
+
if (prevHash) {
|
|
46
|
+
const hash = computeHashes(tagContexts.map((ctx) => ctx.tag._h));
|
|
47
|
+
if (prevHash === hash)
|
|
48
|
+
return;
|
|
49
|
+
prevHash = hash;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
75
52
|
const staleSideEffects = head._popSideEffectQueue();
|
|
76
53
|
head.headEntries().map((entry) => entry._sde).forEach((sde) => {
|
|
77
54
|
Object.entries(sde).forEach(([key, fn]) => {
|
|
78
55
|
staleSideEffects[key] = fn;
|
|
79
56
|
});
|
|
80
57
|
});
|
|
81
|
-
const
|
|
58
|
+
const markSideEffect = (ctx, key, fn) => {
|
|
59
|
+
key = `${ctx.renderId}:${key}`;
|
|
60
|
+
if (ctx.entry)
|
|
61
|
+
ctx.entry._sde[key] = fn;
|
|
62
|
+
delete staleSideEffects[key];
|
|
63
|
+
};
|
|
64
|
+
function setupTagRenderCtx(tag) {
|
|
82
65
|
const entry = head.headEntries().find((e) => e._i === tag._e);
|
|
83
66
|
const renderCtx = {
|
|
84
|
-
renderId: tag._d ||
|
|
67
|
+
renderId: tag._d || hashTag(tag),
|
|
85
68
|
$el: null,
|
|
86
69
|
shouldRender: true,
|
|
87
70
|
tag,
|
|
88
71
|
entry,
|
|
89
|
-
|
|
72
|
+
markSideEffect: (key, fn) => markSideEffect(renderCtx, key, fn)
|
|
90
73
|
};
|
|
91
|
-
await head.hooks.callHook("dom:beforeRenderTag", renderCtx);
|
|
92
74
|
return renderCtx;
|
|
93
|
-
}
|
|
75
|
+
}
|
|
94
76
|
const renders = [];
|
|
95
77
|
const pendingRenders = {
|
|
96
78
|
body: [],
|
|
97
79
|
head: []
|
|
98
80
|
};
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const markEl = (ctx2) => {
|
|
106
|
-
head._elMap[ctx2.renderId] = ctx2.$el;
|
|
107
|
-
renders.push(ctx2);
|
|
108
|
-
markSideEffect(ctx2, "el", () => {
|
|
109
|
-
ctx2.$el?.remove();
|
|
110
|
-
delete head._elMap[ctx2.renderId];
|
|
81
|
+
const markEl = (ctx) => {
|
|
82
|
+
head._elMap[ctx.renderId] = ctx.$el;
|
|
83
|
+
renders.push(ctx);
|
|
84
|
+
markSideEffect(ctx, "el", () => {
|
|
85
|
+
ctx.$el?.remove();
|
|
86
|
+
delete head._elMap[ctx.renderId];
|
|
111
87
|
});
|
|
112
88
|
};
|
|
113
|
-
for (const
|
|
114
|
-
|
|
115
|
-
if (!
|
|
89
|
+
for (const ctx of tagContexts) {
|
|
90
|
+
await head.hooks.callHook("dom:beforeRenderTag", ctx);
|
|
91
|
+
if (!ctx.shouldRender)
|
|
116
92
|
continue;
|
|
117
|
-
const { tag } =
|
|
93
|
+
const { tag } = ctx;
|
|
118
94
|
if (tag.tag === "title") {
|
|
119
|
-
dom.title = tag.
|
|
120
|
-
renders.push(
|
|
95
|
+
dom.title = tag.textContent || "";
|
|
96
|
+
renders.push(ctx);
|
|
121
97
|
continue;
|
|
122
98
|
}
|
|
123
99
|
if (tag.tag === "htmlAttrs" || tag.tag === "bodyAttrs") {
|
|
124
|
-
|
|
125
|
-
setAttrs(
|
|
126
|
-
renders.push(
|
|
100
|
+
ctx.$el = dom[tag.tag === "htmlAttrs" ? "documentElement" : "body"];
|
|
101
|
+
setAttrs(ctx, false, markSideEffect);
|
|
102
|
+
renders.push(ctx);
|
|
127
103
|
continue;
|
|
128
104
|
}
|
|
129
|
-
|
|
130
|
-
if (!
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
markEl(ctx2);
|
|
105
|
+
ctx.$el = head._elMap[ctx.renderId];
|
|
106
|
+
if (!ctx.$el && tag.key)
|
|
107
|
+
ctx.$el = dom.querySelector(`${tag.tagPosition?.startsWith("body") ? "body" : "head"} > ${tag.tag}[data-h-${tag._h}]`);
|
|
108
|
+
if (ctx.$el) {
|
|
109
|
+
if (ctx.tag._d)
|
|
110
|
+
setAttrs(ctx);
|
|
111
|
+
markEl(ctx);
|
|
137
112
|
continue;
|
|
138
113
|
}
|
|
139
|
-
|
|
140
|
-
setAttrs(ctx2);
|
|
141
|
-
pendingRenders[tag.tagPosition?.startsWith("body") ? "body" : "head"].push(ctx2);
|
|
114
|
+
pendingRenders[tag.tagPosition?.startsWith("body") ? "body" : "head"].push(ctx);
|
|
142
115
|
}
|
|
116
|
+
const fragments = {
|
|
117
|
+
bodyClose: void 0,
|
|
118
|
+
bodyOpen: void 0,
|
|
119
|
+
head: void 0
|
|
120
|
+
};
|
|
143
121
|
Object.entries(pendingRenders).forEach(([pos, queue]) => {
|
|
144
122
|
if (!queue.length)
|
|
145
123
|
return;
|
|
@@ -150,40 +128,43 @@ async function renderDOMHead(head, options = {}) {
|
|
|
150
128
|
const elTag = $el.tagName.toLowerCase();
|
|
151
129
|
if (!HasElementTags.includes(elTag))
|
|
152
130
|
continue;
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
131
|
+
const props = $el.getAttributeNames().reduce((props2, name) => ({ ...props2, [name]: $el.getAttribute(name) }), {});
|
|
132
|
+
const tmpTag = { tag: elTag, props };
|
|
133
|
+
if ($el.innerHTML)
|
|
134
|
+
tmpTag.innerHTML = $el.innerHTML;
|
|
135
|
+
const tmpRenderId = hashTag(tmpTag);
|
|
136
|
+
let matchIdx = queue.findIndex((ctx) => ctx?.renderId === tmpRenderId);
|
|
137
|
+
if (matchIdx === -1) {
|
|
138
|
+
const tmpDedupeKey = tagDedupeKey(tmpTag);
|
|
139
|
+
matchIdx = queue.findIndex((ctx) => ctx?.tag._d && ctx.tag._d === tmpDedupeKey);
|
|
140
|
+
}
|
|
159
141
|
if (matchIdx !== -1) {
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
setAttrs(
|
|
163
|
-
markEl(
|
|
142
|
+
const ctx = queue[matchIdx];
|
|
143
|
+
ctx.$el = $el;
|
|
144
|
+
setAttrs(ctx);
|
|
145
|
+
markEl(ctx);
|
|
164
146
|
delete queue[matchIdx];
|
|
165
147
|
}
|
|
166
148
|
}
|
|
167
|
-
queue.forEach((
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
break;
|
|
174
|
-
case "bodyOpen":
|
|
175
|
-
dom.body.insertBefore(ctx2.$el, dom.body.firstChild);
|
|
176
|
-
break;
|
|
177
|
-
case "head":
|
|
178
|
-
default:
|
|
179
|
-
dom.head.appendChild(ctx2.$el);
|
|
180
|
-
break;
|
|
149
|
+
queue.forEach((ctx) => {
|
|
150
|
+
const pos2 = ctx.tag.tagPosition || "head";
|
|
151
|
+
fragments[pos2] = fragments[pos2] || dom.createDocumentFragment();
|
|
152
|
+
if (!ctx.$el) {
|
|
153
|
+
ctx.$el = dom.createElement(ctx.tag.tag);
|
|
154
|
+
setAttrs(ctx, true);
|
|
181
155
|
}
|
|
182
|
-
|
|
156
|
+
fragments[pos2].appendChild(ctx.$el);
|
|
157
|
+
markEl(ctx);
|
|
183
158
|
});
|
|
184
159
|
});
|
|
185
|
-
|
|
186
|
-
|
|
160
|
+
if (fragments.head)
|
|
161
|
+
dom.head.appendChild(fragments.head);
|
|
162
|
+
if (fragments.bodyOpen)
|
|
163
|
+
dom.body.insertBefore(fragments.bodyOpen, dom.body.firstChild);
|
|
164
|
+
if (fragments.bodyClose)
|
|
165
|
+
dom.body.appendChild(fragments.bodyClose);
|
|
166
|
+
for (const ctx of renders)
|
|
167
|
+
await head.hooks.callHook("dom:renderTag", ctx);
|
|
187
168
|
Object.values(staleSideEffects).forEach((fn) => fn());
|
|
188
169
|
}
|
|
189
170
|
let domUpdatePromise = null;
|
|
@@ -196,4 +177,23 @@ async function debouncedRenderDOMHead(head, options = {}) {
|
|
|
196
177
|
return domUpdatePromise = domUpdatePromise || new Promise((resolve) => delayFn(() => resolve(doDomUpdate())));
|
|
197
178
|
}
|
|
198
179
|
|
|
199
|
-
|
|
180
|
+
const PatchDomOnEntryUpdatesPlugin = (options) => {
|
|
181
|
+
return defineHeadPlugin({
|
|
182
|
+
hooks: {
|
|
183
|
+
"entries:updated": function(head) {
|
|
184
|
+
if (typeof options?.document === "undefined" && typeof window === "undefined")
|
|
185
|
+
return;
|
|
186
|
+
let delayFn = options?.delayFn;
|
|
187
|
+
if (!delayFn && typeof requestAnimationFrame !== "undefined")
|
|
188
|
+
delayFn = requestAnimationFrame;
|
|
189
|
+
debouncedRenderDOMHead(head, { document: options?.document || window.document, delayFn });
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
function maybeGetSSRHash(document) {
|
|
196
|
+
return document?.head.querySelector('meta[name="unhead:ssr"]')?.getAttribute("content") || false;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export { PatchDomOnEntryUpdatesPlugin, debouncedRenderDOMHead, domUpdatePromise, maybeGetSSRHash, renderDOMHead, setAttrs };
|
package/package.json
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unhead/dom",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0
|
|
5
|
-
"packageManager": "pnpm@7.
|
|
4
|
+
"version": "1.1.0",
|
|
5
|
+
"packageManager": "pnpm@7.27.1",
|
|
6
6
|
"author": "Harlan Wilton <harlan@harlanzw.com>",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"funding": "https://github.com/sponsors/harlan-zw",
|
|
9
|
-
"homepage": "https://
|
|
9
|
+
"homepage": "https://unhead.harlanzw.com",
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
12
|
-
"url": "git+https://github.com/
|
|
12
|
+
"url": "git+https://github.com/unjs/unhead.git",
|
|
13
13
|
"directory": "packages/dom"
|
|
14
14
|
},
|
|
15
15
|
"bugs": {
|
|
16
|
-
"url": "https://github.com/
|
|
16
|
+
"url": "https://github.com/unjs/unhead/issues"
|
|
17
17
|
},
|
|
18
18
|
"sideEffects": false,
|
|
19
19
|
"exports": {
|
|
@@ -30,10 +30,8 @@
|
|
|
30
30
|
"dist"
|
|
31
31
|
],
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@unhead/schema": "1.0
|
|
34
|
-
|
|
35
|
-
"devDependencies": {
|
|
36
|
-
"zhead": "^1.1.0"
|
|
33
|
+
"@unhead/schema": "1.1.0",
|
|
34
|
+
"@unhead/shared": "1.1.0"
|
|
37
35
|
},
|
|
38
36
|
"scripts": {
|
|
39
37
|
"build": "unbuild .",
|