From ed2e84ee0a9fe4fe002ea0333cf67a20cc46c1e0 Mon Sep 17 00:00:00 2001 From: Geoff Doty Date: Sat, 20 Jul 2024 16:22:06 -0400 Subject: [PATCH] dom diffing for more effecent rerendering --- README.md | 9 ++-- index.js | 2 +- src/app.js | 6 ++- src/emerj.js | 122 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 133 insertions(+), 6 deletions(-) create mode 100644 src/emerj.js diff --git a/README.md b/README.md index b11d707..5ed0e2a 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,11 @@ Um, is an experimental composable UI builder that takes ideas from early [hypera Um, because you should think about, um, NOT using it. ## Features -- No Virtual Dom +- Real DOM +- [Non-destructive (re)Rendering](https://github.com/bryhoyt/emerj) - No Build System - No Over Engineering -- ~1kb minified -- Totally INEFFICIENT rendering (at scale) +- ~2kb minified ## Install @@ -81,4 +81,5 @@ The `h()` is an **optional** hypertext build utility that weighs in around **~25 ### TODO -- Improve Update +- Some tag attributes do not work, like rowspan on td +- Rethink State Management, might be ok diff --git a/index.js b/index.js index c164ad9..71f7ae3 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,5 @@ -import h from "./src/tag.js"; import app from "./src/app.js"; +import h from "./src/tag.js"; export {app}; export {h}; \ No newline at end of file diff --git a/src/app.js b/src/app.js index 7e078a4..28d6a2d 100644 --- a/src/app.js +++ b/src/app.js @@ -1,3 +1,7 @@ +import diff from "./emerj.js"; + +/*! Um v0.5.0 | MIT LICENSE | https://github.com/n2geoff/um */ + /** * App Builder * @@ -54,7 +58,7 @@ export default function app(opts) { /** update dom */ const update = () => { - document.querySelector(mount).replaceChildren(view(state, actions)); + diff.merge(document.querySelector(mount), view(state, actions)); } // mount view diff --git a/src/emerj.js b/src/emerj.js new file mode 100644 index 0000000..378aa7e --- /dev/null +++ b/src/emerj.js @@ -0,0 +1,122 @@ +/*! Emerj v1.0.0 | MIT LICENSE | https://github.com/bryhoyt/emerj */ +export default { + attrs(elem) { + const attrs = {}; + for (let i=0; i < elem.attributes.length; i++) { + const attr = elem.attributes[i]; + attrs[attr.name] = attr.value; + } + return attrs; + }, + nodesByKey(parent, makeKey) { + const map = {}; + for (let j=0; j < parent.childNodes.length; j++) { + const key = makeKey(parent.childNodes[j]); + if (key) map[key] = parent.childNodes[j]; + } + return map; + }, + merge(base, modified, opts) { + /* Merge any differences between base and modified back into base. + * + * Operates only the children nodes, and does not change the root node or its + * attributes. + * + * Conceptually similar to React's reconciliation algorithm: + * https://facebook.github.io/react/docs/reconciliation.html + * + * I haven't thoroughly tested performance to compare to naive DOM updates (i.e. + * just updating the entire DOM from a string using .innerHTML), but some quick + * tests on a basic DOMs were twice as fast -- so at least it's not slower in + * a simple scenario -- and it's definitely "fast enough" for responsive UI and + * even smooth animation. + * + * The real advantage for me is not so much performance, but that state & identity + * of existing elements is preserved -- text typed into an , an open + *