From 42067167e278fef921a07addf974f2b278944222 Mon Sep 17 00:00:00 2001 From: Hayleigh Thompson Date: Wed, 19 Jul 2023 10:48:10 +0100 Subject: :alembic: Maybe we can just roll our own DOM patching? --- src/lustre.ffi.mjs | 8 +- src/runtime.ffi.mjs | 988 ++++------------------------------------------------ 2 files changed, 79 insertions(+), 917 deletions(-) diff --git a/src/lustre.ffi.mjs b/src/lustre.ffi.mjs index 3a6556e..b57026f 100644 --- a/src/lustre.ffi.mjs +++ b/src/lustre.ffi.mjs @@ -1,4 +1,4 @@ -import { morphdom } from "./runtime.ffi.mjs"; +import { morph } from "./runtime.ffi.mjs"; import { Ok, Error } from "./gleam.mjs"; import { map } from "./lustre/element.mjs"; @@ -14,6 +14,10 @@ export class App { #willUpdate = false; #didUpdate = false; + #init = null; + #update = null; + #view = null; + constructor(init, update, render) { this.#init = init; this.#update = update; @@ -51,7 +55,7 @@ export class App { const node = this.#view(this.#state); const vdom = map(node, (msg) => this.dispatch(msg)); - morphdom(this.#root.firstChild, vdom); + morph(this.#root, vdom); } #tick() { diff --git a/src/runtime.ffi.mjs b/src/runtime.ffi.mjs index c349a70..355c923 100644 --- a/src/runtime.ffi.mjs +++ b/src/runtime.ffi.mjs @@ -1,948 +1,106 @@ import { h, t } from "./lustre/element.mjs"; +import { fold, each } from "../gleam_stdlib/gleam/list.mjs"; -// This file is vendored from https://github.com/patrick-steele-idem/morphdom/ -// and is licensed under the MIT license. For a copy of the original license -// head on over to: -// -// https://github.com/patrick-steele-idem/morphdom/blob/master/LICENSE -// +const Element = h("").constructor; +const Text = t("").constructor; -var DOCUMENT_FRAGMENT_NODE = 11; +export function morph(prev, curr) { + if (curr instanceof Element) { + if (prev?.nodeType === 1 && prev.nodeName !== curr[0].toUpperCase()) { + return morphElement(prev, curr); + } else { + const el = document.createElement(curr[0]); -function morphAttrs(fromNode, toNode) { - var toNodeAttrs = toNode.attributes; - var attr; - var attrName; - var attrNamespaceURI; - var attrValue; - var fromValue; + each(curr[1], (attr) => { + const name = attr[0]; + const value = attr[1]; - // document-fragments dont have attributes so lets not do anything - if ( - toNode.nodeType === DOCUMENT_FRAGMENT_NODE || - fromNode.nodeType === DOCUMENT_FRAGMENT_NODE - ) { - return; - } + morphAttr(el, name, value); + }); - // update attributes on original DOM element - for (var i = toNodeAttrs.length - 1; i >= 0; i--) { - attr = toNodeAttrs[i]; - attrName = attr.name; - attrNamespaceURI = attr.namespaceURI; - attrValue = attr.value; + each(curr[2], (child) => { + el.appendChild(morph(null, child)); + }); - if (attrNamespaceURI) { - attrName = attr.localName || attrName; - fromValue = fromNode.getAttributeNS(attrNamespaceURI, attrName); + if (prev) prev.replaceWith(el); - if (fromValue !== attrValue) { - if (attr.prefix === "xmlns") { - attrName = attr.name; // It's not allowed to set an attribute with the XMLNS namespace without specifying the `xmlns` prefix - } - fromNode.setAttributeNS(attrNamespaceURI, attrName, attrValue); - } - } else { - fromValue = fromNode.getAttribute(attrName); - - if (fromValue !== attrValue) { - fromNode.setAttribute(attrName, attrValue); - } + return el; } } - // Remove any extra attributes found on the original DOM element that - // weren't found on the target element. - var fromNodeAttrs = fromNode.attributes; - - for (var d = fromNodeAttrs.length - 1; d >= 0; d--) { - attr = fromNodeAttrs[d]; - attrName = attr.name; - attrNamespaceURI = attr.namespaceURI; - - if (attrNamespaceURI) { - attrName = attr.localName || attrName; - - if (!toNode.hasAttributeNS(attrNamespaceURI, attrName)) { - fromNode.removeAttributeNS(attrNamespaceURI, attrName); - } + if (curr instanceof Text) { + if (prev?.nodeType === 3) { + return morphText(prev, curr); } else { - if (!toNode.hasAttribute(attrName)) { - fromNode.removeAttribute(attrName); - } + const el = document.createTextNode(curr[0]); + if (prev) prev.replaceWith(el); + return el; } } -} - -var range; // Create a range object for efficently rendering strings to elements. -var NS_XHTML = "http://www.w3.org/1999/xhtml"; - -var doc = typeof document === "undefined" ? undefined : document; -var HAS_TEMPLATE_SUPPORT = !!doc && "content" in doc.createElement("template"); -var HAS_RANGE_SUPPORT = - !!doc && doc.createRange && "createContextualFragment" in doc.createRange(); - -function createFragmentFromTemplate(str) { - var template = doc.createElement("template"); - template.innerHTML = str; - return template.content.childNodes[0]; -} - -function createFragmentFromRange(str) { - if (!range) { - range = doc.createRange(); - range.selectNode(doc.body); - } - - var fragment = range.createContextualFragment(str); - return fragment.childNodes[0]; -} - -function createFragmentFromWrap(str) { - var fragment = doc.createElement("body"); - fragment.innerHTML = str; - return fragment.childNodes[0]; -} - -/** - * This is about the same - * var html = new DOMParser().parseFromString(str, 'text/html'); - * return html.body.firstChild; - * - * @method toElement - * @param {String} str - */ -function toElement(str) { - str = str.trim(); - if (HAS_TEMPLATE_SUPPORT) { - // avoid restrictions on content for things like `Hi` which - // createContextualFragment doesn't support - //