There are three dominated front-end frameworks: Angular, Vue, React. All of those are good!

Angular is good just I don't like the class component, it's too heavy; React is good just I don't like the VDOM; I never used Vue.

So I try to create my own reactivity front-end framework, I think it's similar to Solid as like use Signal.

I implemented a simple, signal based reactivily library: dvorakjs

First, which component?

Which Component I want to use?

Not class component as like Angular, because it's to heavy as I said above. What about the file component like Vue, I would need a file with extension .vue? No, I like the function component as like React.

For example a file named app.tsx or app.jsx

function App() {
  return <div>This is a Function Component</div>
}

Yeap it's a smalllest component, I like this writing called jsx or tsx.

And how does that component works? Return a <div> is illegal. That's because the compiler would convert it to some normal JS codes,

<div></div> might convert to document.createElement('div').

We can did it by some compiler as like Babel or SWC.

SWC

SWC is an extensible Rust-based platform for the next generation of fast developer tools. We can use it to convert JavaScript/TypeScrpt and supperting jsx/tsx.

You can conveniently look into how SWC convert jsx/tsx to normal JavaScript: SWC playground.

Source jsx:

function App() {
  return <div>Component</div>;
}

Convert to:

function App() {
    return /*#__PURE__*/ React.createElement("div", null, "Component");
}

As you see above <div></div> become React.createElement('div', null, "Component") by default.

Also you can config it, use Dvorak instead of React. It's very simple, just need create a file named .swcrc, and config it:

{
  "jsc": {
    "parser": {
      "syntax": "typescript",
      "tsx": true
    },
    "target": "es5",
    "loose": false,
    "minify": {
      "compress": false,
      "mangle": false
    },
    "transform": {
      "react": {
        "pragma": "Dvorak.createElement",
        "pragmaFrag": "Dvorak.createFragment"
      }
    }
  },
  "module": {
    "type": "es6"
  },
  "minify": false,
  "isModule": true
}

The field transform - react specified how to convert the jsx. So we can implement our own logic of Dvorak.createElement, and that's I did.

How reactivity

Reactivity, the data in the view would be changed when we updated the data.

The Angular did that through binding variable, the React did that through VDOM. or like Solid used Signal. I'll going to use Signal cause I don't like VDOM, Signal writting like below:

const [count, setCount] = createSignal(0);
  const interval = setInterval(
    () => setCount(count => count + 1),
    1000
  );
  onCleanup(() => clearInterval(interval));
  return <div>Count value is {count()}</div>;

It created a signal by createSignal and it returns two variables count, setCount, use count into HTML, and while calling setCount, the count in HTML would update. I like this writting, so I declaring a function useSignal, but how to implement it?

function useSignal<T>(default: T): [getter, setter] {
	// ???
}

It just is a simplest publisher-subscriber partten, imagine the setter useSignal returned is a publisher, while we update the value by calling setter, every place we used getter in HTML would update the value, every place we used getter in HTML would subscribing the value.

How subscribing it? That is what the Dvorak.createElement should do.

<div>{count}</div>

would be translated to Dvorak.createElement('div', null, count);

Attributes

The first of parameters of it, div if element node name; the second is attributes, for example: name="name", id="theId", class="container"; third are children, they are the rest parameters, so you can passing as like Dvorak.createElement('div', null, count, count2, count3);

Then we can let our function createElement to subscribe the value. There are a couple situation: attribute, children.

If we need to reactive the attributes, like <div name={count}></div>, it's easy, just simply bind the attribute value, and subscribe the publisher:

function createElement(nodeName, attributes, ...children) {
	const ele = document.createElement(nodeName);
	
	const attribute = attributes.find(t => t[0] === 'name');
	
	let changeAttribute = () => {
	  ele.setAttribute('name', attribute[1]);
	};
	
	publisher.subscribe(changeAttribute);
}

The code above is just a demonstration for how a simple reactive do. A publisher observes a value, and changeAttribute subscribes publisher, when publisher received a new value, it will notifies every subscriber as like calling the changeAttribute, then would update the attribute of element.

What about the Children? Okay let's wrap a function reactiveChildren for demonstration. Children of element have three situation, Text or element or function.

Children

If the child of element is a function that reactively, we will thought it's returns something we need, and it would be changed at any time. So we can just replace the old element to the new element.

And if is Text or Element it's static that means don't need update at any time.

function reactiveChildren(host, child) {
  if (typeof child === 'function') {
		let preNode = child();
		host.appendChild(preNode);
		
		let changeChild = () => {
			let newNode = child();
			host.replace(newNode, preNode);
			preNode = newNode;
		};
		
		publisher.subscribe(changeChild);
		
	} else {
	// ...
}

You could see the function changeChild do the replace node, and it subscribed the publish, so it would be notified while publisher received a new value.

Conclusion

So you can implement a simple, signal based reactively library.