Skip to content

Commit

Permalink
feat: implement DOM reconciliation for templates (#82)
Browse files Browse the repository at this point in the history
Adds a more efficient way re-render templates via DOM reconciliation and patching.

Fixes issues with CSS animations (e.g. old implementation could not have "out"-animations for stuff like hover or classname toggling).
  • Loading branch information
ehellman authored Aug 13, 2024
1 parent f16a173 commit 480613f
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 4 deletions.
1 change: 1 addition & 0 deletions packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"wait:client-api": "wait-on ../client-api/dist/index.d.ts"
},
"dependencies": {
"morphdom": "2.7.4",
"solid-js": "1.8.14",
"zebar": "workspace:*"
},
Expand Down
42 changes: 38 additions & 4 deletions packages/client/src/app/template-element.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
toCssSelector,
getScriptManager,
} from 'zebar';
import morphdom from 'morphdom';

export interface TemplateElementProps {
context: ElementContext;
Expand All @@ -27,14 +28,47 @@ export function TemplateElement(props: TemplateElementProps) {
// Currently active event listeners.
let listeners: ElementEventListener[] = [];

// Update the HTML element when the template changes.
createEffect(() => {
// @ts-ignore - TODO
element.innerHTML = config.template;
// Subsequent template updates after the initial render

// Since templates do not include the root template element,
// copy the existing one without its children.
const templateRoot = element.cloneNode(false) as Element;

// Insert the template into the cloned root element
templateRoot.innerHTML = (config as any).template;

try {
// Reconcile the DOM with the updated template
// @ts-ignore - TODO: fix config.template type
morphdom(element, templateRoot, {
// Don't morph fromNode or toNode, only their children
childrenOnly: true,
});
} catch (error) {
// TODO - add error handling for reconciliation here
logger.error(
`Failed to reconciliate ${props.context.id} template:`,
error,
);
}

updateEventListeners();
});

onMount(() => logger.debug('Mounted'));
onMount(() => {
logger.debug('Mounted');
try {
// Initial render, set innerHTML to the template
// @ts-ignore - TODO: fix config.template type
element.innerHTML = config.template;
} catch (error) {
logger.error(
`Initial render of ${[props.context.id]} failed:`,
error,
);
}
});
onCleanup(() => logger.debug('Cleanup'));

function createRootElement() {
Expand Down
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 480613f

Please sign in to comment.