DEV Community

Igor Sukharev
Igor Sukharev

Posted on • Edited on

Almost Vanilla Frontend

Almost — because only two functions from a library are used:

  1. Create DOM Element
  2. 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

npm install @fusorjs/dom 
Enter fullscreen mode Exit fullscreen mode

Or:

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); 
Enter fullscreen mode Exit fullscreen mode

> 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 = ( <section class={() => (count % 2 ? 'odd' : 'even')}> <div>Seconds {() => count} elapsed</div> <div>Minutes {() => Math.floor(count / 60)} elapsed</div> </section> ); document.body.append(getElement(block)); setInterval(() => { count++; update(block); }, 1000); 
Enter fullscreen mode Exit fullscreen mode

> 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)); 
Enter fullscreen mode Exit fullscreen mode

> 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())); 
Enter fullscreen mode Exit fullscreen mode

> run this example

JSX Version

import {getElement} from '@fusorjs/dom'; const ClickCounter = ({count = 0}) => ( <button click_e_update={() => count++}>Clicked {() => count} times</button> ); const App = () => ( <div> <ClickCounter /> <ClickCounter count={22} /> <ClickCounter count={333} /> </div> ); document.body.append(getElement(<App />)); 
Enter fullscreen mode Exit fullscreen mode

> 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', ); 
Enter fullscreen mode Exit fullscreen mode

> 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')), ), ); 
Enter fullscreen mode Exit fullscreen mode

> 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(seconds); // not minutes }, 1000); 
Enter fullscreen mode Exit fullscreen mode

> run this example

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); 
Enter fullscreen mode Exit fullscreen mode

> run this example

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

  1. Create component
  2. Connect to DOM
  3. Update DOM
  4. 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 
Enter fullscreen mode Exit fullscreen mode

> run this example

> run SVG Analog Clock

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, ); export const getRoute = () => route; // Fusor integration export const mountRoute = (self) => observable.subscribe(() => update(self)); 
Enter fullscreen mode Exit fullscreen mode

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), ); 
Enter fullscreen mode Exit fullscreen mode

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)); 
Enter fullscreen mode Exit fullscreen mode

> run this example

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(), ); 
Enter fullscreen mode Exit fullscreen mode

> run this example

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'), ); 
Enter fullscreen mode Exit fullscreen mode

> run this example

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.

Top comments (0)