From c58c97b9341232e9872049ff71b16c34bb561633 Mon Sep 17 00:00:00 2001 From: Joel Drapper Date: Fri, 8 Mar 2024 12:26:56 +0000 Subject: [PATCH 1/2] Simplify handling value nodes --- dist/morphlex.js | 16 ++++------------ package-lock.json | 26 +++++++++++++------------- package.json | 2 +- src/morphlex.ts | 22 ++++------------------ test/morphlex.test.js | 2 +- 5 files changed, 23 insertions(+), 45 deletions(-) diff --git a/dist/morphlex.js b/dist/morphlex.js index 3d8c8f0..c8e28fc 100644 --- a/dist/morphlex.js +++ b/dist/morphlex.js @@ -87,9 +87,7 @@ class Morph { for (const refChild of refChildNodes.values()) this.#appendChild(node, refChild.cloneNode(true)); } else if (node.hasChildNodes() || ref.hasChildNodes()) this.#morphChildNodes(node, ref); } else { - if (isText(node) && isText(ref)) { - this.#updateProperty(node, "textContent", ref.textContent); - } else if (isComment(node) && isComment(ref)) { + if (node.nodeType === ref.nodeType && node.nodeValue !== null && ref.nodeValue !== null) { this.#updateProperty(node, "nodeValue", ref.nodeValue); } else this.#replaceNode(node, ref.cloneNode(true)); } @@ -132,8 +130,8 @@ class Morph { } else if (isOption(element) && isOption(ref)) this.#updateProperty(element, "selected", ref.selected); else if (isTextArea(element) && isTextArea(ref)) { this.#updateProperty(element, "value", ref.value); - const text = element.firstChild; - if (text && isText(text)) this.#updateProperty(text, "textContent", ref.value); + const text = element.firstElementChild; + if (text) this.#updateProperty(text, "textContent", ref.value); } } // Iterates over the child nodes of the reference element, morphing the main element’s child nodes to match. @@ -145,7 +143,7 @@ class Morph { const refChild = refChildNodes[i]; if (child && refChild) { if (isElement(child) && isElement(refChild)) this.#morphChildElement(child, refChild, element); - else this.#morphNode(child, refChild); + else this.#morphNode(child, refChild); // TODO: performance optimization here } else if (refChild) { this.#appendChild(element, refChild.cloneNode(true)); } else if (child) { @@ -247,12 +245,6 @@ class Morph { } } } -function isText(node) { - return node.nodeType === 3; -} -function isComment(node) { - return node.nodeType === 8; -} function isElement(node) { return node.nodeType === 1; } diff --git a/package-lock.json b/package-lock.json index 68c2b25..cb958be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -807,9 +807,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.11.24", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.24.tgz", - "integrity": "sha512-Kza43ewS3xoLgCEpQrsT+xRo/EJej1y0kVYGiLFE1NEODXGzTfwiC6tXTLMQskn1X4/Rjlh0MQUvx9W+L9long==", + "version": "20.11.25", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.25.tgz", + "integrity": "sha512-TBHyJxk2b7HceLVGFcpAUjsa5zIdsPWlR6XHfyGzd0SFu+/NFgQgMAl96MSDZgQDvJAvV6BKsFOrt6zIL09JDw==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -4955,9 +4955,9 @@ } }, "node_modules/puppeteer-core": { - "version": "22.4.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.4.0.tgz", - "integrity": "sha512-MZttAbttrxi6O/B//rY6zQihjFe/vXeCLb5YvKH2xG6yrcVESo0Hc5/Cv49omwZyZzAJ1BK8BnDeatDsj+3hMw==", + "version": "22.4.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.4.1.tgz", + "integrity": "sha512-l9nf8NcirYOHdID12CIMWyy7dqcJCVtgVS+YAiJuUJHg8+9yjgPiG2PcNhojIEEpCkvw3FxvnyITVfKVmkWpjA==", "dev": true, "dependencies": { "@puppeteer/browsers": "2.1.0", @@ -4993,12 +4993,12 @@ } }, "node_modules/qs": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", - "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.0.tgz", + "integrity": "sha512-trVZiI6RMOkO476zLGaBIzszOdFPnCCXHPG9kn0yuS1uz6xdVxPfZdB3vUig9pxPFDM9BRAgz/YUIVQ1/vuiUg==", "dev": true, "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -5953,9 +5953,9 @@ } }, "node_modules/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", + "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==", "dev": true, "bin": { "tsc": "bin/tsc", diff --git a/package.json b/package.json index 329fa09..a77c930 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "test:watch": "npm run test -- --watch", "lint": "prettier --check ./src ./dist ./test", "minify": "terser dist/morphlex.js -o dist/morphlex.min.js --config-file terser-config.json", - "prepare": "npm update && npm run build && npm run minify", + "prepare": "npm run build && npm run minify", "ship": "npm run prepare && npm run test && npm run lint && npm publish", "format": "prettier --write ./src ./dist ./test", "size": "npm run prepare && gzip-size ./dist/morphlex.min.js --raw --include-original" diff --git a/src/morphlex.ts b/src/morphlex.ts index 9795ee9..e27b7ae 100644 --- a/src/morphlex.ts +++ b/src/morphlex.ts @@ -189,9 +189,7 @@ class Morph { for (const refChild of refChildNodes.values()) this.#appendChild(node, refChild.cloneNode(true)); } else if (node.hasChildNodes() || ref.hasChildNodes()) this.#morphChildNodes(node, ref); } else { - if (isText(node) && isText(ref)) { - this.#updateProperty(node, "textContent", ref.textContent); - } else if (isComment(node) && isComment(ref)) { + if (node.nodeType === ref.nodeType && node.nodeValue !== null && ref.nodeValue !== null) { this.#updateProperty(node, "nodeValue", ref.nodeValue); } else this.#replaceNode(node, ref.cloneNode(true)); } @@ -239,8 +237,8 @@ class Morph { else if (isTextArea(element) && isTextArea(ref)) { this.#updateProperty(element, "value", ref.value); - const text = element.firstChild; - if (text && isText(text)) this.#updateProperty(text, "textContent", ref.value); + const text = element.firstElementChild; + if (text) this.#updateProperty(text, "textContent", ref.value); } } @@ -255,7 +253,7 @@ class Morph { if (child && refChild) { if (isElement(child) && isElement(refChild)) this.#morphChildElement(child, refChild, element); - else this.#morphNode(child, refChild); + else this.#morphNode(child, refChild); // TODO: performance optimization here } else if (refChild) { this.#appendChild(element, refChild.cloneNode(true)); } else if (child) { @@ -383,18 +381,6 @@ class Morph { // so we use type guards instead. This keeps TypeScript happy, while doing // the necessary checks at runtime. -function isText(node: Node): node is Text; -function isText(node: ReadonlyNode): node is ReadonlyNode; -function isText(node: Node | ReadonlyNode): boolean { - return node.nodeType === 3; -} - -function isComment(node: Node): node is Comment; -function isComment(node: ReadonlyNode): node is ReadonlyNode; -function isComment(node: Node | ReadonlyNode): boolean { - return node.nodeType === 8; -} - function isElement(node: Node): node is Element; function isElement(node: ReadonlyNode): node is ReadonlyNode; function isElement(node: Node | ReadonlyNode): boolean { diff --git a/test/morphlex.test.js b/test/morphlex.test.js index 575a086..9623e70 100644 --- a/test/morphlex.test.js +++ b/test/morphlex.test.js @@ -2,7 +2,7 @@ import { fixture, html, expect } from "@open-wc/testing"; import { morph } from "../"; describe("morph", () => { - it.only("doesn't cause iframes to reload", async () => { + it("doesn't cause iframes to reload", async () => { const original = await fixture( `

From 84251446e67a930fbe544dbbf71396c2c14552b5 Mon Sep 17 00:00:00 2001 From: Joel Drapper Date: Fri, 8 Mar 2024 12:51:05 +0000 Subject: [PATCH 2/2] Improve naming --- dist/morphlex.js | 11 ++++++----- src/morphlex.ts | 11 ++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/dist/morphlex.js b/dist/morphlex.js index c8e28fc..5e8dc7e 100644 --- a/dist/morphlex.js +++ b/dist/morphlex.js @@ -77,17 +77,18 @@ class Morph { if (isElement(node) && isElement(ref) && node.localName === ref.localName) { if (node.hasAttributes() || ref.hasAttributes()) this.#morphAttributes(node, ref); if (isHead(node) && isHead(ref)) { - const refChildNodes = new Map(); - for (const child of ref.children) refChildNodes.set(child.outerHTML, child); + const refChildNodesMap = new Map(); + for (const child of ref.children) refChildNodesMap.set(child.outerHTML, child); for (const child of node.children) { const key = child.outerHTML; - const refChild = refChildNodes.get(key); - refChild ? refChildNodes.delete(key) : this.#removeNode(child); + const refChild = refChildNodesMap.get(key); + refChild ? refChildNodesMap.delete(key) : this.#removeNode(child); } - for (const refChild of refChildNodes.values()) this.#appendChild(node, refChild.cloneNode(true)); + for (const refChild of refChildNodesMap.values()) this.#appendChild(node, refChild.cloneNode(true)); } else if (node.hasChildNodes() || ref.hasChildNodes()) this.#morphChildNodes(node, ref); } else { if (node.nodeType === ref.nodeType && node.nodeValue !== null && ref.nodeValue !== null) { + // Handle text nodes, comments, and CDATA sections. this.#updateProperty(node, "nodeValue", ref.nodeValue); } else this.#replaceNode(node, ref.cloneNode(true)); } diff --git a/src/morphlex.ts b/src/morphlex.ts index e27b7ae..708be6b 100644 --- a/src/morphlex.ts +++ b/src/morphlex.ts @@ -179,17 +179,18 @@ class Morph { if (isElement(node) && isElement(ref) && node.localName === ref.localName) { if (node.hasAttributes() || ref.hasAttributes()) this.#morphAttributes(node, ref); if (isHead(node) && isHead(ref)) { - const refChildNodes: Map> = new Map(); - for (const child of ref.children) refChildNodes.set(child.outerHTML, child); + const refChildNodesMap: Map> = new Map(); + for (const child of ref.children) refChildNodesMap.set(child.outerHTML, child); for (const child of node.children) { const key = child.outerHTML; - const refChild = refChildNodes.get(key); - refChild ? refChildNodes.delete(key) : this.#removeNode(child); + const refChild = refChildNodesMap.get(key); + refChild ? refChildNodesMap.delete(key) : this.#removeNode(child); } - for (const refChild of refChildNodes.values()) this.#appendChild(node, refChild.cloneNode(true)); + for (const refChild of refChildNodesMap.values()) this.#appendChild(node, refChild.cloneNode(true)); } else if (node.hasChildNodes() || ref.hasChildNodes()) this.#morphChildNodes(node, ref); } else { if (node.nodeType === ref.nodeType && node.nodeValue !== null && ref.nodeValue !== null) { + // Handle text nodes, comments, and CDATA sections. this.#updateProperty(node, "nodeValue", ref.nodeValue); } else this.#replaceNode(node, ref.cloneNode(true)); }