@unhead/dom 0.2.7 → 0.4.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 CHANGED
@@ -2,106 +2,98 @@
2
2
 
3
3
  const TagsWithInnerContent = ["script", "style", "noscript"];
4
4
 
5
- const createElement = (tag, document) => {
6
- const $el = document.createElement(tag.tag);
7
- for (const k in tag.props)
8
- $el.setAttribute(k, String(tag.props[k]));
9
- if (tag.children)
10
- $el.innerHTML = tag.children;
11
- return $el;
12
- };
13
-
14
- function setAttributesWithSideEffects(head, $el, entry, tag) {
15
- const attrs = tag.props || {};
16
- const sdeKey = `${tag._p}:attr`;
17
- const sdeToRun = { ...entry._sde };
18
- Object.entries(attrs).forEach(([k, value]) => {
19
- value = String(value);
20
- const attrSdeKey = `${sdeKey}:${k}`;
21
- head._removeQueuedSideEffect(attrSdeKey);
22
- delete sdeToRun[attrSdeKey];
23
- if (k === "class") {
24
- for (const c of value.split(" ")) {
25
- const classSdeKey = `${sdeKey}:class:${c}`;
26
- if (!$el.classList.contains(c)) {
27
- $el.classList.add(c);
28
- entry._sde[classSdeKey] = () => $el.classList.remove(c);
29
- }
30
- head._removeQueuedSideEffect(classSdeKey);
31
- delete sdeToRun[classSdeKey];
32
- }
33
- return;
34
- }
35
- if ($el.getAttribute(k) !== value) {
36
- $el.setAttribute(k, value);
37
- if (!k.startsWith("data-h-"))
38
- entry._sde[attrSdeKey] = () => $el.removeAttribute(k);
39
- }
40
- });
41
- Object.entries(sdeToRun).filter(([key]) => key.startsWith(sdeKey)).forEach(([key, fn]) => {
42
- delete entry._sde[key] && fn();
43
- });
44
- }
45
-
46
5
  async function renderDOMHead(head, options = {}) {
47
6
  const dom = options.document || window.document;
48
7
  const tags = await head.resolveTags();
49
- const context = { shouldRender: true, tags };
50
- await head.hooks.callHook("dom:beforeRender", context);
51
- if (!context.shouldRender)
8
+ const ctx = { shouldRender: true, tags };
9
+ await head.hooks.callHook("dom:beforeRender", ctx);
10
+ if (!ctx.shouldRender)
52
11
  return;
53
- for (const tag of context.tags) {
54
- const renderCtx = { shouldRender: true, tag };
55
- await head.hooks.callHook("dom:renderTag", renderCtx);
56
- if (!renderCtx.shouldRender)
57
- return;
58
- const entry = head.headEntries().find((e) => e._i === Number(tag._e));
12
+ const queuedSideEffects = head._popSideEffectQueue();
13
+ head.headEntries().map((entry) => entry._sde).forEach((sde) => {
14
+ Object.entries(sde).forEach(([key, fn]) => {
15
+ queuedSideEffects[key] = fn;
16
+ });
17
+ });
18
+ const renderTag = (tag, entry) => {
59
19
  if (tag.tag === "title" && tag.children) {
60
20
  dom.title = tag.children;
61
- continue;
21
+ return dom.head.querySelector("title");
62
22
  }
23
+ const markSideEffect = (key, fn) => {
24
+ key = `${tag._s || tag._p}:${key}`;
25
+ entry._sde[key] = fn;
26
+ delete queuedSideEffects[key];
27
+ };
28
+ const setAttrs = ($el) => {
29
+ Object.entries(tag.props).forEach(([k, value]) => {
30
+ value = String(value);
31
+ const attrSdeKey = `attr:${k}`;
32
+ if (k === "class") {
33
+ for (const c of value.split(" ")) {
34
+ const classSdeKey = `${attrSdeKey}:${c}`;
35
+ markSideEffect(classSdeKey, () => $el.classList.remove(c));
36
+ if (!$el.classList.contains(c))
37
+ $el.classList.add(c);
38
+ }
39
+ return;
40
+ }
41
+ if (!k.startsWith("data-h-"))
42
+ markSideEffect(attrSdeKey, () => $el.removeAttribute(k));
43
+ if ($el.getAttribute(k) !== value)
44
+ $el.setAttribute(k, value);
45
+ });
46
+ if (TagsWithInnerContent.includes(tag.tag))
47
+ $el.innerHTML = tag.children || "";
48
+ };
63
49
  if (tag.tag === "htmlAttrs" || tag.tag === "bodyAttrs") {
64
- setAttributesWithSideEffects(head, dom[tag.tag === "htmlAttrs" ? "documentElement" : "body"], entry, tag);
65
- continue;
50
+ const $el = dom[tag.tag === "htmlAttrs" ? "documentElement" : "body"];
51
+ setAttrs($el);
52
+ return $el;
66
53
  }
67
- const sdeKey = `${tag._s || tag._p}:el`;
68
- const $newEl = createElement(tag, dom);
69
- let $previousEl = null;
54
+ let $newEl = dom.createElement(tag.tag);
55
+ setAttrs($newEl);
56
+ let $previousEl;
70
57
  for (const $el of dom[tag.tagPosition?.startsWith("body") ? "body" : "head"].children) {
71
- if ($el.hasAttribute(`${tag._s}`)) {
58
+ if ($el.hasAttribute(`${tag._s}`) || $el.isEqualNode($newEl)) {
72
59
  $previousEl = $el;
73
60
  break;
74
61
  }
75
62
  }
63
+ if ($newEl.attributes.length === 0 && !$newEl.innerHTML) {
64
+ $previousEl?.remove();
65
+ return;
66
+ }
76
67
  if ($previousEl) {
77
- head._removeQueuedSideEffect(sdeKey);
78
- if ($newEl.isEqualNode($previousEl))
79
- continue;
80
- if (Object.keys(tag.props).length === 0) {
81
- $previousEl.remove();
82
- continue;
83
- }
84
- setAttributesWithSideEffects(head, $previousEl, entry, tag);
85
- if (TagsWithInnerContent.includes(tag.tag))
86
- $previousEl.innerHTML = tag.children || "";
87
- entry._sde[sdeKey] = () => $previousEl?.remove();
88
- continue;
68
+ markSideEffect("el", () => $previousEl?.remove());
69
+ setAttrs($previousEl);
70
+ return $previousEl;
89
71
  }
90
72
  switch (tag.tagPosition) {
91
73
  case "bodyClose":
92
- dom.body.appendChild($newEl);
74
+ $newEl = dom.body.appendChild($newEl);
93
75
  break;
94
76
  case "bodyOpen":
95
- dom.body.insertBefore($newEl, dom.body.firstChild);
77
+ $newEl = dom.body.insertBefore($newEl, dom.body.firstChild);
96
78
  break;
97
79
  case "head":
98
80
  default:
99
- dom.head.appendChild($newEl);
81
+ $newEl = dom.head.appendChild($newEl);
100
82
  break;
101
83
  }
102
- entry._sde[sdeKey] = () => $newEl?.remove();
84
+ markSideEffect("el", () => $newEl?.remove());
85
+ return $newEl;
86
+ };
87
+ for (const tag of ctx.tags) {
88
+ const entry = head.headEntries().find((e) => e._i === Number(tag._e));
89
+ const renderCtx = { $el: null, shouldRender: true, tag, entry, queuedSideEffects };
90
+ await head.hooks.callHook("dom:beforeRenderTag", renderCtx);
91
+ if (!renderCtx.shouldRender)
92
+ continue;
93
+ renderCtx.$el = renderTag(renderCtx.tag, renderCtx.entry);
94
+ await head.hooks.callHook("dom:renderTag", renderCtx);
103
95
  }
104
- head._flushQueuedSideEffects();
96
+ Object.values(queuedSideEffects).forEach((fn) => fn());
105
97
  }
106
98
  exports.domUpdatePromise = null;
107
99
  async function debouncedRenderDOMHead(head, options = {}) {
@@ -109,7 +101,7 @@ async function debouncedRenderDOMHead(head, options = {}) {
109
101
  exports.domUpdatePromise = null;
110
102
  return renderDOMHead(head, options);
111
103
  }
112
- const delayFn = options.delayFn || ((fn) => setTimeout(fn, 25));
104
+ const delayFn = options.delayFn || ((fn) => setTimeout(fn, 10));
113
105
  return exports.domUpdatePromise = exports.domUpdatePromise || new Promise((resolve) => delayFn(() => resolve(doDomUpdate())));
114
106
  }
115
107
 
package/dist/index.mjs CHANGED
@@ -1,105 +1,97 @@
1
1
  const TagsWithInnerContent = ["script", "style", "noscript"];
2
2
 
3
- const createElement = (tag, document) => {
4
- const $el = document.createElement(tag.tag);
5
- for (const k in tag.props)
6
- $el.setAttribute(k, String(tag.props[k]));
7
- if (tag.children)
8
- $el.innerHTML = tag.children;
9
- return $el;
10
- };
11
-
12
- function setAttributesWithSideEffects(head, $el, entry, tag) {
13
- const attrs = tag.props || {};
14
- const sdeKey = `${tag._p}:attr`;
15
- const sdeToRun = { ...entry._sde };
16
- Object.entries(attrs).forEach(([k, value]) => {
17
- value = String(value);
18
- const attrSdeKey = `${sdeKey}:${k}`;
19
- head._removeQueuedSideEffect(attrSdeKey);
20
- delete sdeToRun[attrSdeKey];
21
- if (k === "class") {
22
- for (const c of value.split(" ")) {
23
- const classSdeKey = `${sdeKey}:class:${c}`;
24
- if (!$el.classList.contains(c)) {
25
- $el.classList.add(c);
26
- entry._sde[classSdeKey] = () => $el.classList.remove(c);
27
- }
28
- head._removeQueuedSideEffect(classSdeKey);
29
- delete sdeToRun[classSdeKey];
30
- }
31
- return;
32
- }
33
- if ($el.getAttribute(k) !== value) {
34
- $el.setAttribute(k, value);
35
- if (!k.startsWith("data-h-"))
36
- entry._sde[attrSdeKey] = () => $el.removeAttribute(k);
37
- }
38
- });
39
- Object.entries(sdeToRun).filter(([key]) => key.startsWith(sdeKey)).forEach(([key, fn]) => {
40
- delete entry._sde[key] && fn();
41
- });
42
- }
43
-
44
3
  async function renderDOMHead(head, options = {}) {
45
4
  const dom = options.document || window.document;
46
5
  const tags = await head.resolveTags();
47
- const context = { shouldRender: true, tags };
48
- await head.hooks.callHook("dom:beforeRender", context);
49
- if (!context.shouldRender)
6
+ const ctx = { shouldRender: true, tags };
7
+ await head.hooks.callHook("dom:beforeRender", ctx);
8
+ if (!ctx.shouldRender)
50
9
  return;
51
- for (const tag of context.tags) {
52
- const renderCtx = { shouldRender: true, tag };
53
- await head.hooks.callHook("dom:renderTag", renderCtx);
54
- if (!renderCtx.shouldRender)
55
- return;
56
- const entry = head.headEntries().find((e) => e._i === Number(tag._e));
10
+ const queuedSideEffects = head._popSideEffectQueue();
11
+ head.headEntries().map((entry) => entry._sde).forEach((sde) => {
12
+ Object.entries(sde).forEach(([key, fn]) => {
13
+ queuedSideEffects[key] = fn;
14
+ });
15
+ });
16
+ const renderTag = (tag, entry) => {
57
17
  if (tag.tag === "title" && tag.children) {
58
18
  dom.title = tag.children;
59
- continue;
19
+ return dom.head.querySelector("title");
60
20
  }
21
+ const markSideEffect = (key, fn) => {
22
+ key = `${tag._s || tag._p}:${key}`;
23
+ entry._sde[key] = fn;
24
+ delete queuedSideEffects[key];
25
+ };
26
+ const setAttrs = ($el) => {
27
+ Object.entries(tag.props).forEach(([k, value]) => {
28
+ value = String(value);
29
+ const attrSdeKey = `attr:${k}`;
30
+ if (k === "class") {
31
+ for (const c of value.split(" ")) {
32
+ const classSdeKey = `${attrSdeKey}:${c}`;
33
+ markSideEffect(classSdeKey, () => $el.classList.remove(c));
34
+ if (!$el.classList.contains(c))
35
+ $el.classList.add(c);
36
+ }
37
+ return;
38
+ }
39
+ if (!k.startsWith("data-h-"))
40
+ markSideEffect(attrSdeKey, () => $el.removeAttribute(k));
41
+ if ($el.getAttribute(k) !== value)
42
+ $el.setAttribute(k, value);
43
+ });
44
+ if (TagsWithInnerContent.includes(tag.tag))
45
+ $el.innerHTML = tag.children || "";
46
+ };
61
47
  if (tag.tag === "htmlAttrs" || tag.tag === "bodyAttrs") {
62
- setAttributesWithSideEffects(head, dom[tag.tag === "htmlAttrs" ? "documentElement" : "body"], entry, tag);
63
- continue;
48
+ const $el = dom[tag.tag === "htmlAttrs" ? "documentElement" : "body"];
49
+ setAttrs($el);
50
+ return $el;
64
51
  }
65
- const sdeKey = `${tag._s || tag._p}:el`;
66
- const $newEl = createElement(tag, dom);
67
- let $previousEl = null;
52
+ let $newEl = dom.createElement(tag.tag);
53
+ setAttrs($newEl);
54
+ let $previousEl;
68
55
  for (const $el of dom[tag.tagPosition?.startsWith("body") ? "body" : "head"].children) {
69
- if ($el.hasAttribute(`${tag._s}`)) {
56
+ if ($el.hasAttribute(`${tag._s}`) || $el.isEqualNode($newEl)) {
70
57
  $previousEl = $el;
71
58
  break;
72
59
  }
73
60
  }
61
+ if ($newEl.attributes.length === 0 && !$newEl.innerHTML) {
62
+ $previousEl?.remove();
63
+ return;
64
+ }
74
65
  if ($previousEl) {
75
- head._removeQueuedSideEffect(sdeKey);
76
- if ($newEl.isEqualNode($previousEl))
77
- continue;
78
- if (Object.keys(tag.props).length === 0) {
79
- $previousEl.remove();
80
- continue;
81
- }
82
- setAttributesWithSideEffects(head, $previousEl, entry, tag);
83
- if (TagsWithInnerContent.includes(tag.tag))
84
- $previousEl.innerHTML = tag.children || "";
85
- entry._sde[sdeKey] = () => $previousEl?.remove();
86
- continue;
66
+ markSideEffect("el", () => $previousEl?.remove());
67
+ setAttrs($previousEl);
68
+ return $previousEl;
87
69
  }
88
70
  switch (tag.tagPosition) {
89
71
  case "bodyClose":
90
- dom.body.appendChild($newEl);
72
+ $newEl = dom.body.appendChild($newEl);
91
73
  break;
92
74
  case "bodyOpen":
93
- dom.body.insertBefore($newEl, dom.body.firstChild);
75
+ $newEl = dom.body.insertBefore($newEl, dom.body.firstChild);
94
76
  break;
95
77
  case "head":
96
78
  default:
97
- dom.head.appendChild($newEl);
79
+ $newEl = dom.head.appendChild($newEl);
98
80
  break;
99
81
  }
100
- entry._sde[sdeKey] = () => $newEl?.remove();
82
+ markSideEffect("el", () => $newEl?.remove());
83
+ return $newEl;
84
+ };
85
+ for (const tag of ctx.tags) {
86
+ const entry = head.headEntries().find((e) => e._i === Number(tag._e));
87
+ const renderCtx = { $el: null, shouldRender: true, tag, entry, queuedSideEffects };
88
+ await head.hooks.callHook("dom:beforeRenderTag", renderCtx);
89
+ if (!renderCtx.shouldRender)
90
+ continue;
91
+ renderCtx.$el = renderTag(renderCtx.tag, renderCtx.entry);
92
+ await head.hooks.callHook("dom:renderTag", renderCtx);
101
93
  }
102
- head._flushQueuedSideEffects();
94
+ Object.values(queuedSideEffects).forEach((fn) => fn());
103
95
  }
104
96
  let domUpdatePromise = null;
105
97
  async function debouncedRenderDOMHead(head, options = {}) {
@@ -107,7 +99,7 @@ async function debouncedRenderDOMHead(head, options = {}) {
107
99
  domUpdatePromise = null;
108
100
  return renderDOMHead(head, options);
109
101
  }
110
- const delayFn = options.delayFn || ((fn) => setTimeout(fn, 25));
102
+ const delayFn = options.delayFn || ((fn) => setTimeout(fn, 10));
111
103
  return domUpdatePromise = domUpdatePromise || new Promise((resolve) => delayFn(() => resolve(doDomUpdate())));
112
104
  }
113
105
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@unhead/dom",
3
3
  "type": "module",
4
- "version": "0.2.7",
4
+ "version": "0.4.1",
5
5
  "packageManager": "pnpm@7.14.0",
6
6
  "author": "Harlan Wilton <harlan@harlanzw.com>",
7
7
  "license": "MIT",
@@ -30,10 +30,10 @@
30
30
  "dist"
31
31
  ],
32
32
  "dependencies": {
33
- "@unhead/schema": "0.2.7"
33
+ "@unhead/schema": "0.4.1"
34
34
  },
35
35
  "devDependencies": {
36
- "zhead": "1.0.0-beta.10"
36
+ "zhead": "1.0.0-beta.11"
37
37
  },
38
38
  "scripts": {
39
39
  "build": "unbuild .",