Almost Vanilla Frontend
Almost — because only two functions from a library are used: Create DOM Element Update DOM Element This library simplifies and streamlines the usage of native DOM functions, such as createElement and replaceChild. The Fusor library is all about making these functions easier and more concise to use. Below are many examples of common problems. Try to recreate them using the tools you are currently using. You might be surprised to find that developing with Fusor could be the most concise, flexible, lightweight, and performant way to build frontend applications. Contents Install Create & Update DOM Parameters & Children Syntax Stateful Component Controlled Input Component Precise DOM Update Escape Update Recursion Component Lifecycle Automatic/Reactive Updates Routing Switching Components Create & Update DOM Dynamically Caching & Memoization Exception Handling Install npm install @fusorjs/dom Or: JSX boilerplate for: JavaScript or TypeScript CDN: https://esm.sh/@fusorjs/dom or https://cdn.skypack.dev/@fusorjs/dom Create & Update DOM import {getElement, update} from '@fusorjs/dom'; import {section, div} from '@fusorjs/dom/html'; let count = 0; const block = section( {class: () => (count % 2 ? 'odd' : 'even')}, div('Seconds ', () => count, ' elapsed'), div('Minutes ', () => Math.floor(count / 60), ' elapsed'), ); document.body.append(getElement(block)); setInterval(() => { count++; update(block); }, 1000); > run this example Only tiny portions of the block DOM tree are updated if they differ from the current values. JSX Support import {getElement, update} from '@fusorjs/dom'; let count = 0; const block = ( (count % 2 ? 'odd' : 'even')}> Seconds {() => count} elapsed Minutes {() => Math.floor(count / 60)} elapsed ); document.body.append(getElement(block)); setInterval(() => { count++; update(block); }, 1000); > run this example Parameters & Children Syntax import {getElement, update} from '@fusorjs/dom'; import {section} from '@fusorjs/dom/html'; let count = 0; const block = section( { id: 'set attribute or property automatically', title_a: 'set attribute', style_p: 'set property', focus_e: () => 'set bubbling event handler', blur_e_capture_once: () => 'set capturing event handler once', // update dynamic values in this DOM node: click_e_update: () => count++, // same as click_e: () => {count++; update(block);}, // same as click_e: (event, self) => {count++; update(self);}, class: count % 2 ? 'odd' : 'even', // static class: () => (count % 2 ? 'odd' : 'even'), // dynamic }, 'Static child ', count, ' never changes.', 'Dynamic child ', () => count, ' is wrapped in a function.', ); document.body.append(getElement(block)); > run this example Stateful Component import {getElement} from '@fusorjs/dom'; import {button, div} from '@fusorjs/dom/html'; const ClickCounter = (count = 0) => button({click_e_update: () => count++}, 'Clicked ', () => count, ' times'); const App = () => div(ClickCounter(), ClickCounter(22), ClickCounter(333)); document.body.append(getElement(App())); > run this example JSX Version import {getElement} from '@fusorjs/dom'; const ClickCounter = ({count = 0}) => ( count++}>Clicked {() => count} times ); const App = () => ( ); document.body.append(getElement()); > run this example Components in both versions are interoperable. Same Component Defined Differently import {button} from '@fusorjs/dom/html'; const ClickCounter = (count = 0) => { const self = button( {click_e: () => {count++; update(self);}}, 'Clicked ', () => count, ' times', ); return self; }; const ClickCounter = (count = 0) => button( {click_e: (event, self) => {count++; update(self);}}, 'Clicked ', () => count, ' times', ); const ClickCounter = (count = 0) => button( {click_e_update: () => count++}, 'Clicked ', () => count, ' times', ); > run this example Controlled Input Component "Controlled Input" in React terms. import {getElement} from '@fusorjs/dom'; import {input, div} from '@fusorjs/dom/html'; const UppercaseInput = (value = '') => input({ value: () => value.toUpperCase(), input_e_update: (event) => (value = event.target.value), }); document.body.append( getElement( div(UppercaseInput(), UppercaseInput('two'), UppercaseInput('three')), ), ); > run this example Precise DOM Update import {getElement, update} from '@fusorjs/dom'; import {section, div} from '@fusorjs/dom/html'; let count = 0; const seconds = div('Seconds ', () => count, ' elapsed'); const block = section( seconds, div('Minutes ', () => Math.floor(count / 60), ' elapsed'), ); document.body.append(getElement(block)); setInterval(() => { count++; update(second

Almost — because only two functions from a library are used:
-
Create
DOM Element -
Update
DOM Element
This library simplifies and streamlines the usage of native DOM functions, such as createElement
and replaceChild
. The Fusor library is all about making these functions easier and more concise to use.
Below are many examples of common problems. Try to recreate them using the tools you are currently using. You might be surprised to find that developing with Fusor could be the most concise, flexible, lightweight, and performant way to build frontend applications.
Contents
- Install
- Create & Update DOM
- Parameters & Children Syntax
- Stateful Component
- Controlled Input Component
- Precise DOM Update
- Escape Update Recursion
- Component Lifecycle
- Automatic/Reactive Updates
- Routing
- Switching Components
- Create & Update DOM Dynamically
- Caching & Memoization
- Exception Handling
Install
npm install @fusorjs/dom
Or:
- JSX boilerplate for: JavaScript or TypeScript
- CDN: https://esm.sh/@fusorjs/dom or https://cdn.skypack.dev/@fusorjs/dom
Create & Update DOM
import {getElement, update} from '@fusorjs/dom';
import {section, div} from '@fusorjs/dom/html';
let count = 0;
const block = section(
{class: () => (count % 2 ? 'odd' : 'even')},
div('Seconds ', () => count, ' elapsed'),
div('Minutes ', () => Math.floor(count / 60), ' elapsed'),
);
document.body.append(getElement(block));
setInterval(() => {
count++;
update(block);
}, 1000);
Only tiny portions of the block
DOM tree are updated if they differ from the current values.
JSX Support
import {getElement, update} from '@fusorjs/dom';
let count = 0;
const block = (
<section class={() => (count % 2 ? 'odd' : 'even')}>
<div>Seconds {() => count} elapseddiv>
<div>Minutes {() => Math.floor(count / 60)} elapseddiv>
section>
);
document.body.append(getElement(block));
setInterval(() => {
count++;
update(block);
}, 1000);
Parameters & Children Syntax
import {getElement, update} from '@fusorjs/dom';
import {section} from '@fusorjs/dom/html';
let count = 0;
const block = section(
{
id: 'set attribute or property automatically',
title_a: 'set attribute',
style_p: 'set property',
focus_e: () => 'set bubbling event handler',
blur_e_capture_once: () => 'set capturing event handler once',
// update dynamic values in this DOM node:
click_e_update: () => count++, // same as
click_e: () => {count++; update(block);}, // same as
click_e: (event, self) => {count++; update(self);},
class: count % 2 ? 'odd' : 'even', // static
class: () => (count % 2 ? 'odd' : 'even'), // dynamic
},
'Static child ', count, ' never changes.',
'Dynamic child ', () => count, ' is wrapped in a function.',
);
document.body.append(getElement(block));
Stateful Component
import {getElement} from '@fusorjs/dom';
import {button, div} from '@fusorjs/dom/html';
const ClickCounter = (count = 0) =>
button({click_e_update: () => count++}, 'Clicked ', () => count, ' times');
const App = () => div(ClickCounter(), ClickCounter(22), ClickCounter(333));
document.body.append(getElement(App()));
JSX Version
import {getElement} from '@fusorjs/dom';
const ClickCounter = ({count = 0}) => (
<button click_e_update={() => count++}>Clicked {() => count} timesbutton>
);
const App = () => (
<div>
<ClickCounter />
<ClickCounter count={22} />
<ClickCounter count={333} />
div>
);
document.body.append(getElement(<App />));
Components in both versions are interoperable.
Same Component Defined Differently
import {button} from '@fusorjs/dom/html';
const ClickCounter = (count = 0) => {
const self = button(
{click_e: () => {count++; update(self);}},
'Clicked ', () => count, ' times',
);
return self;
};
const ClickCounter = (count = 0) =>
button(
{click_e: (event, self) => {count++; update(self);}},
'Clicked ', () => count, ' times',
);
const ClickCounter = (count = 0) =>
button(
{click_e_update: () => count++},
'Clicked ', () => count, ' times',
);
Controlled Input Component
"Controlled Input" in React terms.
import {getElement} from '@fusorjs/dom';
import {input, div} from '@fusorjs/dom/html';
const UppercaseInput = (value = '') =>
input({
value: () => value.toUpperCase(),
input_e_update: (event) => (value = event.target.value),
});
document.body.append(
getElement(
div(UppercaseInput(), UppercaseInput('two'), UppercaseInput('three')),
),
);
Precise DOM Update
import {getElement, update} from '@fusorjs/dom';
import {section, div} from '@fusorjs/dom/html';
let count = 0;
const seconds = div('Seconds ', () => count, ' elapsed');
const block = section(
seconds,
div('Minutes ', () => Math.floor(count / 60), ' elapsed'),
);
document.body.append(getElement(block));
setInterval(() => {
count++;
update(seconds); // not minutes
}, 1000);
This will update only the seconds, not the minutes.
Escape Update Recursion
import {getElement, update} from '@fusorjs/dom';
import {section, div} from '@fusorjs/dom/html';
let count = 0;
const seconds = div('Seconds ', () => count, ' elapsed');
const block = section(
() => seconds, // wrapped in a function to escape
div('Minutes ', () => Math.floor(count / 60), ' elapsed'),
);
document.body.append(getElement(block));
setInterval(() => {
count++;
update(block);
}, 1000);
This will update only the minutes, not the seconds.
Only components (seconds
, block
) are updated recursively. () => seconds
is a function
, not a component.
Every function from @fusorjs/dom/html
returns a component, provided it contains dynamic values. The same applies to JSX definitions.
Component Lifecycle
- Create component
- Connect to DOM
- Update DOM
- Disconnect from DOM
import {getElement, update} from '@fusorjs/dom';
import {div} from '@fusorjs/dom/html';
const IntervalCounter = (count = 0) => {
console.log('1. Create component');
return div(
{
mount: (self) => {
console.log('2. Connect to DOM');
const timerId = setInterval(() => {
count++;
update(self);
console.log('3. Update DOM');
}, 1000);
const unmount = () => {
clearInterval(timerId);
console.log('4. Disconnect from DOM');
};
return unmount;
},
},
'Since mounted ', () => count, ` seconds elapsed`,
);
};
const instance = IntervalCounter(); // 1. Create component
const element = getElement(instance);
document.body.append(element); // 2. Connect to DOM
setTimeout(() => element.remove(), 15000); // 4. Disconnect from DOM
Automatic/Reactive Updates
Automatic/reactive updates in big frameworks are nothing more than an implementation of the Observable pattern. This includes State in React, Signals in Solid, Redux, MobX, and many others. In Fusor, you can use any of those libraries.
Here, we discuss the generic solution:
Router Library
import {update} from '@fusorjs/dom';
import {Observable} from 'Any/Observable/Signal/Redux/Mobx...';
// Modern routing handling
const observable = new Observable();
const read = () => location.hash.substring(1); // omit "#"
let route = read();
window.addEventListener(
'popstate',
() => {
const next = read();
if (route === next) return;
route = next;
observable.notify();
},
false,
);
// Fusor integration
export const getRoute = () => route;
export const mountRoute = (self) => {
const callback = () => update(self);
observable.subscribe(callback);
return () => observable.unsubscribe(callback);
};
Reactive Component
Switching components when current route is selected.
import {span, a} from '@fusorjs/dom/html';
import {getRoute, mountRoute} from './router';
export const RouteLink = (title, route) =>
span({mount: mountRoute}, () =>
getRoute() === route
? title // when selected
: a({href: `#${route}`}, title),
);
Create & Update DOM Dynamically
import {getElement} from '@fusorjs/dom';
import {ul, li} from '@fusorjs/dom/html';
import {RouteLink} from './RouteLink';
const block = ul(
[...Array(10)].map((v, i) =>
li(RouteLink(`${i + 1}. Section`, `url-to-${i + 1}-section`)),
),
);
document.body.append(getElement(block));
Caching & Memoization
The heavy component is created only once.
import {div, br} from '@fusorjs/dom/html';
let isVisible = true; // can change
const block = div(
(
(cache = HeavyComponent()) =>
() =>
isVisible && cache
)(),
br(),
() => RecreatedEveryUpdate(),
);
Exception Handling
import {section, p} from '@fusorjs/dom/html';
const Value = (value) => {
if (value === undefined) throw new Error(`provide a value`);
return p(value);
};
const block = section(
p('Before'),
(() => {
try {
return [
Value(1),
Value(), // will throw
Value(3),
];
} catch (error) {
if (error instanceof Error) return p('Exception: ', error.message);
return p('Exception: unknown');
}
})(),
p('After'),
);
Conclusion
Now you know everything you need to start developing modern front-end applications with Fusor.
As far as I know, developing with Fusor is the most concise, flexible, lightweight, and performant way to build frontend applications.