@unhead/dom 1.2.2 → 1.3.0-beta.1
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 +94 -170
- package/dist/index.d.ts +10 -20
- package/dist/index.mjs +96 -170
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -2,204 +2,128 @@
|
|
|
2
2
|
|
|
3
3
|
const shared = require('@unhead/shared');
|
|
4
4
|
|
|
5
|
-
function
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if (!value)
|
|
14
|
-
return;
|
|
15
|
-
for (const c of value.split(" ")) {
|
|
16
|
-
const classSdeKey = `${attrSdeKey}:${c}`;
|
|
17
|
-
if (markSideEffect)
|
|
18
|
-
markSideEffect(ctx, classSdeKey, () => $el.classList.remove(c));
|
|
19
|
-
if (!$el.classList.contains(c))
|
|
20
|
-
$el.classList.add(c);
|
|
21
|
-
}
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
if (markSideEffect && !k.startsWith("data-h-"))
|
|
25
|
-
markSideEffect(ctx, attrSdeKey, () => $el.removeAttribute(k));
|
|
26
|
-
if (newEntry || $el.getAttribute(k) !== value)
|
|
27
|
-
$el.setAttribute(k, value);
|
|
28
|
-
});
|
|
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
|
-
}
|
|
5
|
+
function elementToTag($el) {
|
|
6
|
+
const tag = {
|
|
7
|
+
tag: $el.tagName.toLowerCase(),
|
|
8
|
+
props: $el.getAttributeNames().reduce((props, name) => ({ ...props, [name]: $el.getAttribute(name) }), {}),
|
|
9
|
+
innerHTML: $el.innerHTML
|
|
10
|
+
};
|
|
11
|
+
tag._d = shared.tagDedupeKey(tag);
|
|
12
|
+
return tag;
|
|
35
13
|
}
|
|
36
|
-
|
|
37
|
-
let prevHash = false;
|
|
38
14
|
async function renderDOMHead(head, options = {}) {
|
|
39
|
-
const
|
|
15
|
+
const dom = options.document || head.resolvedOptions.document;
|
|
16
|
+
if (!dom)
|
|
17
|
+
return;
|
|
18
|
+
const tags = (await head.resolveTags()).map((tag) => ({
|
|
19
|
+
tag,
|
|
20
|
+
id: shared.HasElementTags.includes(tag.tag) ? shared.hashTag(tag) : tag.tag,
|
|
21
|
+
shouldRender: true
|
|
22
|
+
}));
|
|
23
|
+
const beforeRenderCtx = { shouldRender: true, tags };
|
|
40
24
|
await head.hooks.callHook("dom:beforeRender", beforeRenderCtx);
|
|
41
25
|
if (!beforeRenderCtx.shouldRender)
|
|
42
26
|
return;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
27
|
+
let state = head._dom;
|
|
28
|
+
if (!state) {
|
|
29
|
+
state = {
|
|
30
|
+
elMap: { htmlAttrs: dom.documentElement, bodyAttrs: dom.body }
|
|
31
|
+
};
|
|
32
|
+
for (const key of ["body", "head"]) {
|
|
33
|
+
const children = dom?.[key]?.children;
|
|
34
|
+
for (const c of [...children].filter((c2) => shared.HasElementTags.includes(c2.tagName.toLowerCase())))
|
|
35
|
+
state.elMap[c.getAttribute("data-hid") || shared.hashTag(elementToTag(c))] = c;
|
|
52
36
|
}
|
|
53
37
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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) {
|
|
67
|
-
const entry = head.headEntries().find((e) => e._i === tag._e);
|
|
68
|
-
const renderCtx = {
|
|
69
|
-
renderId: tag._d || shared.hashTag(tag),
|
|
70
|
-
$el: null,
|
|
71
|
-
shouldRender: true,
|
|
72
|
-
tag,
|
|
73
|
-
entry,
|
|
74
|
-
markSideEffect: (key, fn) => markSideEffect(renderCtx, key, fn)
|
|
75
|
-
};
|
|
76
|
-
return renderCtx;
|
|
38
|
+
state.pendingSideEffects = { ...state.sideEffects || {} };
|
|
39
|
+
state.sideEffects = {};
|
|
40
|
+
function track(id, scope, fn) {
|
|
41
|
+
const k = `${id}:${scope}`;
|
|
42
|
+
state.sideEffects[k] = fn;
|
|
43
|
+
delete state.pendingSideEffects[k];
|
|
77
44
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
});
|
|
90
|
-
};
|
|
91
|
-
for (const ctx of tagContexts) {
|
|
92
|
-
await head.hooks.callHook("dom:beforeRenderTag", ctx);
|
|
93
|
-
if (!ctx.shouldRender)
|
|
94
|
-
continue;
|
|
95
|
-
const { tag } = ctx;
|
|
96
|
-
if (tag.tag === "title") {
|
|
97
|
-
dom.title = tag.textContent || "";
|
|
98
|
-
renders.push(ctx);
|
|
99
|
-
continue;
|
|
45
|
+
function trackCtx({ id, $el, tag }) {
|
|
46
|
+
const isAttrTag = tag.tag.endsWith("Attrs");
|
|
47
|
+
state.elMap[id] = $el;
|
|
48
|
+
if (!isAttrTag) {
|
|
49
|
+
["textContent", "innerHTML"].forEach((k) => {
|
|
50
|
+
tag[k] && tag[k] !== $el[k] && ($el[k] = tag[k]);
|
|
51
|
+
});
|
|
52
|
+
track(id, "el", () => {
|
|
53
|
+
state.elMap[id].remove();
|
|
54
|
+
delete state.elMap[id];
|
|
55
|
+
});
|
|
100
56
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
continue;
|
|
115
|
-
}
|
|
116
|
-
pendingRenders[tag.tagPosition?.startsWith("body") ? "body" : "head"].push(ctx);
|
|
57
|
+
Object.entries(tag.props).forEach(([k, value]) => {
|
|
58
|
+
value = String(value);
|
|
59
|
+
const ck = `attr:${k}`;
|
|
60
|
+
if (k === "class") {
|
|
61
|
+
for (const c of (value || "").split(" ").filter(Boolean)) {
|
|
62
|
+
isAttrTag && track(id, `${ck}:${c}`, () => $el.classList.remove(c));
|
|
63
|
+
!$el.classList.contains(c) && $el.classList.add(c);
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
$el.getAttribute(k) !== value && $el.setAttribute(k, value);
|
|
67
|
+
isAttrTag && track(id, ck, () => $el.removeAttribute(k));
|
|
68
|
+
}
|
|
69
|
+
});
|
|
117
70
|
}
|
|
118
|
-
const
|
|
71
|
+
const pending = [];
|
|
72
|
+
const frag = {
|
|
119
73
|
bodyClose: void 0,
|
|
120
74
|
bodyOpen: void 0,
|
|
121
75
|
head: void 0
|
|
122
76
|
};
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const elTag = $el.tagName.toLowerCase();
|
|
131
|
-
if (!shared.HasElementTags.includes(elTag))
|
|
132
|
-
continue;
|
|
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
|
-
}
|
|
143
|
-
if (matchIdx !== -1) {
|
|
144
|
-
const ctx = queue[matchIdx];
|
|
145
|
-
ctx.$el = $el;
|
|
146
|
-
setAttrs(ctx);
|
|
147
|
-
markEl(ctx);
|
|
148
|
-
delete queue[matchIdx];
|
|
149
|
-
}
|
|
77
|
+
for (const ctx of tags) {
|
|
78
|
+
const { tag, shouldRender, id } = ctx;
|
|
79
|
+
if (!shouldRender)
|
|
80
|
+
continue;
|
|
81
|
+
if (tag.tag === "title") {
|
|
82
|
+
dom.title = tag.textContent;
|
|
83
|
+
continue;
|
|
150
84
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
85
|
+
ctx.$el = ctx.$el || state.elMap[id];
|
|
86
|
+
if (ctx.$el)
|
|
87
|
+
trackCtx(ctx);
|
|
88
|
+
else
|
|
89
|
+
shared.HasElementTags.includes(tag.tag) && pending.push(ctx);
|
|
90
|
+
}
|
|
91
|
+
for (const ctx of pending) {
|
|
92
|
+
const pos = ctx.tag.tagPosition || "head";
|
|
93
|
+
ctx.$el = dom.createElement(ctx.tag.tag);
|
|
94
|
+
trackCtx(ctx);
|
|
95
|
+
frag[pos] = frag[pos] || dom.createDocumentFragment();
|
|
96
|
+
frag[pos].appendChild(ctx.$el);
|
|
97
|
+
}
|
|
98
|
+
for (const ctx of tags)
|
|
99
|
+
await head.hooks.callHook("dom:renderTag", ctx, dom, track);
|
|
100
|
+
frag.head && dom.head.appendChild(frag.head);
|
|
101
|
+
frag.bodyOpen && dom.body.insertBefore(frag.bodyOpen, dom.body.firstChild);
|
|
102
|
+
frag.bodyClose && dom.body.appendChild(frag.bodyClose);
|
|
103
|
+
Object.values(state.pendingSideEffects).forEach((fn) => fn());
|
|
104
|
+
head._dom = state;
|
|
105
|
+
await head.hooks.callHook("dom:rendered", { renders: tags });
|
|
171
106
|
}
|
|
172
|
-
|
|
107
|
+
|
|
173
108
|
async function debouncedRenderDOMHead(head, options = {}) {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
109
|
+
const fn = options.delayFn || ((fn2) => setTimeout(fn2, 10));
|
|
110
|
+
return head._domUpdatePromise = head._domUpdatePromise || new Promise((resolve) => fn(async () => {
|
|
111
|
+
await renderDOMHead(head, options);
|
|
112
|
+
delete head._domUpdatePromise;
|
|
113
|
+
resolve();
|
|
114
|
+
}));
|
|
180
115
|
}
|
|
181
116
|
|
|
182
117
|
function PatchDomOnEntryUpdatesPlugin(options) {
|
|
183
118
|
return shared.defineHeadPlugin({
|
|
184
119
|
hooks: {
|
|
185
120
|
"entries:updated": function(head) {
|
|
186
|
-
|
|
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 });
|
|
121
|
+
debouncedRenderDOMHead(head, options);
|
|
192
122
|
}
|
|
193
123
|
}
|
|
194
124
|
});
|
|
195
125
|
}
|
|
196
126
|
|
|
197
|
-
function maybeGetSSRHash(document) {
|
|
198
|
-
return document?.head.querySelector('meta[name="unhead:ssr"]')?.getAttribute("content") || false;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
127
|
exports.PatchDomOnEntryUpdatesPlugin = PatchDomOnEntryUpdatesPlugin;
|
|
202
128
|
exports.debouncedRenderDOMHead = debouncedRenderDOMHead;
|
|
203
|
-
exports.maybeGetSSRHash = maybeGetSSRHash;
|
|
204
129
|
exports.renderDOMHead = renderDOMHead;
|
|
205
|
-
exports.setAttrs = setAttrs;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _unhead_schema from '@unhead/schema';
|
|
2
|
-
import { Unhead
|
|
2
|
+
import { Unhead } from '@unhead/schema';
|
|
3
3
|
|
|
4
4
|
interface RenderDomHeadOptions {
|
|
5
5
|
/**
|
|
@@ -7,35 +7,25 @@ interface RenderDomHeadOptions {
|
|
|
7
7
|
*/
|
|
8
8
|
document?: Document;
|
|
9
9
|
}
|
|
10
|
-
interface DebouncedRenderDomHeadOptions extends RenderDomHeadOptions {
|
|
11
|
-
/**
|
|
12
|
-
* Specify a custom delay function for delaying the render.
|
|
13
|
-
*/
|
|
14
|
-
delayFn?: (fn: () => void) => void;
|
|
15
|
-
}
|
|
16
10
|
/**
|
|
17
11
|
* Render the head tags to the DOM.
|
|
18
12
|
*/
|
|
19
13
|
declare function renderDOMHead<T extends Unhead<any>>(head: T, options?: RenderDomHeadOptions): Promise<void>;
|
|
20
|
-
/**
|
|
21
|
-
* Global instance of the dom update promise. Used for debounding head updates.
|
|
22
|
-
*/
|
|
23
|
-
declare let domUpdatePromise: Promise<void> | null;
|
|
24
|
-
/**
|
|
25
|
-
* Queue a debounced update of the DOM head.
|
|
26
|
-
*/
|
|
27
|
-
declare function debouncedRenderDOMHead<T extends Unhead<any>>(head: T, options?: DebouncedRenderDomHeadOptions): Promise<void>;
|
|
28
14
|
|
|
29
15
|
interface TriggerDomPatchingOnUpdatesPluginOptions extends RenderDomHeadOptions {
|
|
30
16
|
delayFn?: (fn: () => void) => void;
|
|
31
17
|
}
|
|
32
18
|
declare function PatchDomOnEntryUpdatesPlugin(options?: TriggerDomPatchingOnUpdatesPluginOptions): _unhead_schema.HeadPlugin;
|
|
33
19
|
|
|
20
|
+
interface DebouncedRenderDomHeadOptions extends RenderDomHeadOptions {
|
|
21
|
+
/**
|
|
22
|
+
* Specify a custom delay function for delaying the render.
|
|
23
|
+
*/
|
|
24
|
+
delayFn?: (fn: () => void) => void;
|
|
25
|
+
}
|
|
34
26
|
/**
|
|
35
|
-
*
|
|
27
|
+
* Queue a debounced update of the DOM head.
|
|
36
28
|
*/
|
|
37
|
-
declare function
|
|
38
|
-
|
|
39
|
-
declare function maybeGetSSRHash(document: Document): string | false;
|
|
29
|
+
declare function debouncedRenderDOMHead<T extends Unhead<any>>(head: T, options?: DebouncedRenderDomHeadOptions): Promise<void>;
|
|
40
30
|
|
|
41
|
-
export { DebouncedRenderDomHeadOptions, PatchDomOnEntryUpdatesPlugin, RenderDomHeadOptions, TriggerDomPatchingOnUpdatesPluginOptions, debouncedRenderDOMHead,
|
|
31
|
+
export { DebouncedRenderDomHeadOptions, PatchDomOnEntryUpdatesPlugin, RenderDomHeadOptions, TriggerDomPatchingOnUpdatesPluginOptions, debouncedRenderDOMHead, renderDOMHead };
|
package/dist/index.mjs
CHANGED
|
@@ -1,199 +1,125 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { HasElementTags, hashTag, tagDedupeKey, defineHeadPlugin } from '@unhead/shared';
|
|
2
2
|
|
|
3
|
-
function
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (!value)
|
|
12
|
-
return;
|
|
13
|
-
for (const c of value.split(" ")) {
|
|
14
|
-
const classSdeKey = `${attrSdeKey}:${c}`;
|
|
15
|
-
if (markSideEffect)
|
|
16
|
-
markSideEffect(ctx, classSdeKey, () => $el.classList.remove(c));
|
|
17
|
-
if (!$el.classList.contains(c))
|
|
18
|
-
$el.classList.add(c);
|
|
19
|
-
}
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
if (markSideEffect && !k.startsWith("data-h-"))
|
|
23
|
-
markSideEffect(ctx, attrSdeKey, () => $el.removeAttribute(k));
|
|
24
|
-
if (newEntry || $el.getAttribute(k) !== value)
|
|
25
|
-
$el.setAttribute(k, value);
|
|
26
|
-
});
|
|
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
|
-
}
|
|
3
|
+
function elementToTag($el) {
|
|
4
|
+
const tag = {
|
|
5
|
+
tag: $el.tagName.toLowerCase(),
|
|
6
|
+
props: $el.getAttributeNames().reduce((props, name) => ({ ...props, [name]: $el.getAttribute(name) }), {}),
|
|
7
|
+
innerHTML: $el.innerHTML
|
|
8
|
+
};
|
|
9
|
+
tag._d = tagDedupeKey(tag);
|
|
10
|
+
return tag;
|
|
33
11
|
}
|
|
34
|
-
|
|
35
|
-
let prevHash = false;
|
|
36
12
|
async function renderDOMHead(head, options = {}) {
|
|
37
|
-
const
|
|
13
|
+
const dom = options.document || head.resolvedOptions.document;
|
|
14
|
+
if (!dom)
|
|
15
|
+
return;
|
|
16
|
+
const tags = (await head.resolveTags()).map((tag) => ({
|
|
17
|
+
tag,
|
|
18
|
+
id: HasElementTags.includes(tag.tag) ? hashTag(tag) : tag.tag,
|
|
19
|
+
shouldRender: true
|
|
20
|
+
}));
|
|
21
|
+
const beforeRenderCtx = { shouldRender: true, tags };
|
|
38
22
|
await head.hooks.callHook("dom:beforeRender", beforeRenderCtx);
|
|
39
23
|
if (!beforeRenderCtx.shouldRender)
|
|
40
24
|
return;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
25
|
+
let state = head._dom;
|
|
26
|
+
if (!state) {
|
|
27
|
+
state = {
|
|
28
|
+
elMap: { htmlAttrs: dom.documentElement, bodyAttrs: dom.body }
|
|
29
|
+
};
|
|
30
|
+
for (const key of ["body", "head"]) {
|
|
31
|
+
const children = dom?.[key]?.children;
|
|
32
|
+
for (const c of [...children].filter((c2) => HasElementTags.includes(c2.tagName.toLowerCase())))
|
|
33
|
+
state.elMap[c.getAttribute("data-hid") || hashTag(elementToTag(c))] = c;
|
|
50
34
|
}
|
|
51
35
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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) {
|
|
65
|
-
const entry = head.headEntries().find((e) => e._i === tag._e);
|
|
66
|
-
const renderCtx = {
|
|
67
|
-
renderId: tag._d || hashTag(tag),
|
|
68
|
-
$el: null,
|
|
69
|
-
shouldRender: true,
|
|
70
|
-
tag,
|
|
71
|
-
entry,
|
|
72
|
-
markSideEffect: (key, fn) => markSideEffect(renderCtx, key, fn)
|
|
73
|
-
};
|
|
74
|
-
return renderCtx;
|
|
36
|
+
state.pendingSideEffects = { ...state.sideEffects || {} };
|
|
37
|
+
state.sideEffects = {};
|
|
38
|
+
function track(id, scope, fn) {
|
|
39
|
+
const k = `${id}:${scope}`;
|
|
40
|
+
state.sideEffects[k] = fn;
|
|
41
|
+
delete state.pendingSideEffects[k];
|
|
75
42
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
});
|
|
88
|
-
};
|
|
89
|
-
for (const ctx of tagContexts) {
|
|
90
|
-
await head.hooks.callHook("dom:beforeRenderTag", ctx);
|
|
91
|
-
if (!ctx.shouldRender)
|
|
92
|
-
continue;
|
|
93
|
-
const { tag } = ctx;
|
|
94
|
-
if (tag.tag === "title") {
|
|
95
|
-
dom.title = tag.textContent || "";
|
|
96
|
-
renders.push(ctx);
|
|
97
|
-
continue;
|
|
43
|
+
function trackCtx({ id, $el, tag }) {
|
|
44
|
+
const isAttrTag = tag.tag.endsWith("Attrs");
|
|
45
|
+
state.elMap[id] = $el;
|
|
46
|
+
if (!isAttrTag) {
|
|
47
|
+
["textContent", "innerHTML"].forEach((k) => {
|
|
48
|
+
tag[k] && tag[k] !== $el[k] && ($el[k] = tag[k]);
|
|
49
|
+
});
|
|
50
|
+
track(id, "el", () => {
|
|
51
|
+
state.elMap[id].remove();
|
|
52
|
+
delete state.elMap[id];
|
|
53
|
+
});
|
|
98
54
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
continue;
|
|
113
|
-
}
|
|
114
|
-
pendingRenders[tag.tagPosition?.startsWith("body") ? "body" : "head"].push(ctx);
|
|
55
|
+
Object.entries(tag.props).forEach(([k, value]) => {
|
|
56
|
+
value = String(value);
|
|
57
|
+
const ck = `attr:${k}`;
|
|
58
|
+
if (k === "class") {
|
|
59
|
+
for (const c of (value || "").split(" ").filter(Boolean)) {
|
|
60
|
+
isAttrTag && track(id, `${ck}:${c}`, () => $el.classList.remove(c));
|
|
61
|
+
!$el.classList.contains(c) && $el.classList.add(c);
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
$el.getAttribute(k) !== value && $el.setAttribute(k, value);
|
|
65
|
+
isAttrTag && track(id, ck, () => $el.removeAttribute(k));
|
|
66
|
+
}
|
|
67
|
+
});
|
|
115
68
|
}
|
|
116
|
-
const
|
|
69
|
+
const pending = [];
|
|
70
|
+
const frag = {
|
|
117
71
|
bodyClose: void 0,
|
|
118
72
|
bodyOpen: void 0,
|
|
119
73
|
head: void 0
|
|
120
74
|
};
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const elTag = $el.tagName.toLowerCase();
|
|
129
|
-
if (!HasElementTags.includes(elTag))
|
|
130
|
-
continue;
|
|
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
|
-
}
|
|
141
|
-
if (matchIdx !== -1) {
|
|
142
|
-
const ctx = queue[matchIdx];
|
|
143
|
-
ctx.$el = $el;
|
|
144
|
-
setAttrs(ctx);
|
|
145
|
-
markEl(ctx);
|
|
146
|
-
delete queue[matchIdx];
|
|
147
|
-
}
|
|
75
|
+
for (const ctx of tags) {
|
|
76
|
+
const { tag, shouldRender, id } = ctx;
|
|
77
|
+
if (!shouldRender)
|
|
78
|
+
continue;
|
|
79
|
+
if (tag.tag === "title") {
|
|
80
|
+
dom.title = tag.textContent;
|
|
81
|
+
continue;
|
|
148
82
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
83
|
+
ctx.$el = ctx.$el || state.elMap[id];
|
|
84
|
+
if (ctx.$el)
|
|
85
|
+
trackCtx(ctx);
|
|
86
|
+
else
|
|
87
|
+
HasElementTags.includes(tag.tag) && pending.push(ctx);
|
|
88
|
+
}
|
|
89
|
+
for (const ctx of pending) {
|
|
90
|
+
const pos = ctx.tag.tagPosition || "head";
|
|
91
|
+
ctx.$el = dom.createElement(ctx.tag.tag);
|
|
92
|
+
trackCtx(ctx);
|
|
93
|
+
frag[pos] = frag[pos] || dom.createDocumentFragment();
|
|
94
|
+
frag[pos].appendChild(ctx.$el);
|
|
95
|
+
}
|
|
96
|
+
for (const ctx of tags)
|
|
97
|
+
await head.hooks.callHook("dom:renderTag", ctx, dom, track);
|
|
98
|
+
frag.head && dom.head.appendChild(frag.head);
|
|
99
|
+
frag.bodyOpen && dom.body.insertBefore(frag.bodyOpen, dom.body.firstChild);
|
|
100
|
+
frag.bodyClose && dom.body.appendChild(frag.bodyClose);
|
|
101
|
+
Object.values(state.pendingSideEffects).forEach((fn) => fn());
|
|
102
|
+
head._dom = state;
|
|
103
|
+
await head.hooks.callHook("dom:rendered", { renders: tags });
|
|
169
104
|
}
|
|
170
|
-
|
|
105
|
+
|
|
171
106
|
async function debouncedRenderDOMHead(head, options = {}) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
107
|
+
const fn = options.delayFn || ((fn2) => setTimeout(fn2, 10));
|
|
108
|
+
return head._domUpdatePromise = head._domUpdatePromise || new Promise((resolve) => fn(async () => {
|
|
109
|
+
await renderDOMHead(head, options);
|
|
110
|
+
delete head._domUpdatePromise;
|
|
111
|
+
resolve();
|
|
112
|
+
}));
|
|
178
113
|
}
|
|
179
114
|
|
|
180
115
|
function PatchDomOnEntryUpdatesPlugin(options) {
|
|
181
116
|
return defineHeadPlugin({
|
|
182
117
|
hooks: {
|
|
183
118
|
"entries:updated": function(head) {
|
|
184
|
-
|
|
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 });
|
|
119
|
+
debouncedRenderDOMHead(head, options);
|
|
190
120
|
}
|
|
191
121
|
}
|
|
192
122
|
});
|
|
193
123
|
}
|
|
194
124
|
|
|
195
|
-
|
|
196
|
-
return document?.head.querySelector('meta[name="unhead:ssr"]')?.getAttribute("content") || false;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
export { PatchDomOnEntryUpdatesPlugin, debouncedRenderDOMHead, domUpdatePromise, maybeGetSSRHash, renderDOMHead, setAttrs };
|
|
125
|
+
export { PatchDomOnEntryUpdatesPlugin, debouncedRenderDOMHead, renderDOMHead };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unhead/dom",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.3.0-beta.1",
|
|
5
5
|
"author": "Harlan Wilton <harlan@harlanzw.com>",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"funding": "https://github.com/sponsors/harlan-zw",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"dist"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@unhead/schema": "1.
|
|
33
|
-
"@unhead/shared": "1.
|
|
32
|
+
"@unhead/schema": "1.3.0-beta.1",
|
|
33
|
+
"@unhead/shared": "1.3.0-beta.1"
|
|
34
34
|
},
|
|
35
35
|
"scripts": {
|
|
36
36
|
"build": "unbuild .",
|