Developer Addiction

Developers are lazy. This is a good and bad thing. When it's good, developers look for the quickest and most efficient way to solve a problem. When it's bad, they become dependent on tools to help them achieve those results. This dependency is so strong that they won't consider alternatives, even if they might be better.

Developers have an addiction problem. They learn a library, get accostumed to it and do not want to consider anything else. We see it on forums and articles all the time:

Libary X is awesome. Library Y is garbage. Those guys need to switch to X . There should only be one front end library so everyone is on the same page. Why waste time and energy supporting a library other than X ??? Libary Y has a bigger company behind it, so you should switch from X . More people ask questions on Stack Overflow about Y than X , therefore it must be better.

Having a big company behind a library does not mean that it will be the best. Look at Flash. It had Adobe behind it. And Silverlight had Microsoft behind it. The fact that more people are asking questions about how to do basic things with a library to me means it has a problem. A good library should be easy to learn and have self-evident ways of doing things. If library X has more questions than library Y , may mean several things:

1. X has poor documentation

2. X has few or poor examples

3. X might be over-engineered, making it difficult to do simple things

4. the developers behind X may not understand their users: how they use the library or what they want to use it for

Freddie Kruger Power Tools

If you want to hang a small, framed picture in your living room, do you go rent a boom lift and pneumatic nail gun? I don't think so. A single nail and a small hammer will do fine. You might already have those lying around the house. Similary, if you want to plant a small rose bush in your front yard, do you go rent a jackhammer and backhoe? No, you just need a simple shovel or hoe. You could event get by with a sturdy hand trowel.

Hello From Hell

I absolutely hate seeing Hello World demos built with popular libraries and frameworks. Why do you need a linter, tanspiler, types, event emitter, state manager, etc.? The JavaScript for a Hello World demo should be a few bytes, not 50KB or 150KB. I shouldn't need a backhoe to plant a petunia.

End of rant, I promise.

DIY

Now we're going to look at how to make our own tools. Tool making is what defines us as human. We take simple resources and combine them in ingenuous ways to create tools that do things the resources alone would not be capable of.

Choosing Our Resources

Right now React pretty much rules the roost with developer mindshare. It's got some nice patterns: one way data flow, stateless and stateful components, etc. What made React stand out from the pack from the beginning was its use of JSX . This is a dialect of XML that gets converted into functions that create DOM nodes. Most developers have embraced JSX for declarative markup because it uses standard JavaScript--no special template language to learn, no `ng*` directives.

The Goal

What we're going to create a simple library that uses JSX to let us define components, use inline events or event listeners, create functional components, create nodes and insert them in the DOM. This will be, in effect, a React lite. We are going to do this in less than 1KB.

Since we're going to use JSX , we need to be able to transpile it. Babel has a plugin that does that. But we need a way to turn JSX into something we can use. When you use JSX with React, it has Babel convert it into hyperscript functions that return virtual nodes. These are objects that describe the nodes to be created.

It Starts with H

So the first thing we need is a hyperscript function, which we'll call h . We'll use it with Babel to create virtual nodes. This is actually really easy. We need to define a function that takes three arguments:

1. type--the type of node: div, h1, p, etc.

2. props--an object of key-value pairs defining properties and attributes for the element.

3. children--this could be a string or an array of other type objects.

Below is how we expect to be able to use this function:

// Simple tag:

h('h1', {class: 'title'}, 'This is the title') // Tag with multiple children:

h('ul', {class: 'list'}, [

h('li', {}, 'Item One'),

h('li', {}, 'Item Two'),

h('li', {}, 'Item Three')

])

So, from the above you can see that the child might be a string or number. Or it might be an array of other h functions creating elements. Our function will need to test whether the child is a string, number or array and deal with it. Here's our basic call structure:

function h(type, props, ...args) {

// stuff here

})

We are expecting a tag type, some props and a child, possibly as an array. We'll check the args array, looping over its content to create children. Finally our function will create an object representing this. If we take our previous example of h from above, it should return two objects:

{

children: 'This is the title',

props: {

class: 'title'

},

type: 'h1',

} {

children: [

{

children: 'Item One',

props: {},

type: 'li'

},

{

children: 'Item Two',

props: {},

type: 'li'

},

{

children: 'Item Three',

props: {},

type: 'li'

}

],

props: {

class: 'list'

},

type: 'ul',

}

Here is our h function. We loop over the args to check if they are other nodes or just strings. We also need to check if the value is a boolean. You can use boolean values to determine whether a property or attribute should be created or not.

function h(type, props, ...args) {

let node

const children = [] // Go thru arguments from front.

while (args.length) {

// If child is array, process.

if (Array.isArray((node = args.shift()))) {

node.map(item => args.push(item))

// Else check if child is string or number.

} else if (node != null && typeof node !== 'boolean') {

typeof node === "number" ? node = node + '' : node

children.push(node)

}

}



return typeof type === "string"

? {type, props: props || {}, children }

: type(props || {}, children)

}

Babel will be able to use our h function to create virtual nodes. For such a simple function, this is pretty awesome. But we need a way to take those virtual nodes and create real DOM nodes. We'll call this function createElement . This will have the same purpose as React.createElement or Vue's createElement .

Creating Elements

We'll start with a basic shell for our `createElement` function:

function createElement(node) {

// Do stuff to create element

}

Setting Props

The first thing our createElement function needs to be able to do is set properties on elements. We'll define this as a helper function inside createElement . We'll call it setProps . When setting props, we'll have to check if the value is 0. If it is, we'll convert it to a string. Why? Because in JavaScript 0 is truthy. A property set to 0 won't get output. It's like setting it to false. We want to start by trying to set the provided prop as a property. This might throw and arrow, so we use a try/catch . Then we check that the value is not a function. This is to prevent an inline event from being reset. If the value is positive, we set the attribute, otherwise we remove it.

This function allows us to use normal HTML attributes, such as class , for , onclick , etc., avoiding the annoyance that React has where you have to use className and htmlFor , etc. in your JSX . We also need a way to merge one object into another. We'll call that function `mixin`.

// We need a mixin function:

function mixin(obj1, obj2) {

const result = {}

for (let i in obj1) {

result[i] = obj1[i]

}

for (let i in obj2) {

result[i] = obj2[i]

}

return result

} function createElement(node) {

// Define setProps funtion:

function setProps(element, name, value, oldValue) {

if (name === 'key') {

} else if (name === 'style') {

for (let name in mixin(oldValue, (value = value || {}))) {

element.style[name] = String(value[name]) || ''

}

} else {

try {

if (value === 0) value = String(value)

element[name] = value

} catch (err) {} if (typeof value !== 'function') {

if (!!value) {

element.setAttribute(name, value)

} else {

element.removeAttribute(name)

}

}

}

}

}

Creating a Node

Next we need a function to create a node. To do that by using the virtual node created by the h function. We create a node based on its type. Then we add any props defined on the virtual node to the node. Lastly we check the virtual node children. If it's an array, we loop over it and create the children by calling this function. Here's the createNode function:

function createElement(node) {

function setProps(element, name, value, oldValue) {

if (name === 'key') {

} else if (name === 'style') {

for (let name in mixin(oldValue, (value = value || {}))) {

element.style[name] = String(value[name]) || ''

}

} else {

try {

if (value === 0) value = String(value)

element[name] = value

} catch (err) {} if (typeof value !== 'function') {

if (!!value) {

element.setAttribute(name, value)

} else {

element.removeAttribute(name)

}

}

}

} function createNode(node) {

const element = document.createElement(node.type) Object.keys(node.props).forEach(key => setProps(element, key, node.props[key]))

node.children.map(child => element.appendChild(createElement(child)))

return element

}

}

And lastly, we need to return the result. Before we do so, we check if the child is a string. If so, we create a text node, otherwise we pass it to the createNode function we just created. Here's our fininished createElement function:

function createElement(node) { function setProps(element, name, value) {

try {

// Handle normal properties like disabled, checked or inline events:

if (value === 0) value = String(value)

element[name] = value

} catch (err) {}

// Handle attributes:

if (typeof value !== 'function') {

if (!!value) {

element.setAttribute(name, value)

} else {

element.removeAttribute(name)

}

}

} function createNode(node) {

const element = document.createElement(node.type)

node.props && Object.keys(node.props).forEach(key => setProps(element, key, node.props[key]))

node.children && node.children.map(child => element.appendChild(createElement(child)))

return element

} // Create element:

return typeof node === "string" ? document.createTextNode(node) : createNode(node)

}

In Action

Now that we have the createElement function, we can do something practical:

function Title({message}) {

return (

<nav>

<h1>Hello, {message}!</h1>

</nav>

)

} // Create node from virtual node:

const title = createElement(<Title message='World' />) // Inject node into DOM:

document.querySelector('header').appendChild(title)

Codepen example:

Render

So, now we can define elements with the h function and convert them into nodes with the createElement function. However, it would be nice to have a convenient way to inject the result into the DOM. For that we will create a render function.

This function will expect two arguments: a virtual node to create and a container in which to inject it. The virtual node can be a JSX tag, or the result of one:

function Title({message}) {

return <h1>{message}</h1>

} render(<Title message='World' />, 'nav') // or

const title = Title({message: 'World'})

render(title, 'nav')

The first thing we need to do is check that the user provided a tag to render. Then we check if a container was provided, if no container was provided, we default to the document body. Next we check if the container is a string. This allows the user to provide a query selector for the container. If it's a string, we try to get the selector from the DOM, otherwise, the user gave us a node and we use that.

Next we create a document fragment. We use it to append the nodes we create. Then we empty our container before appending the final result. Here's the function:

function render(tag, container) {

if (undefined === tag) return

if (undefined === container) container = document.body

if (typeof container === 'string') container = document.querySelector(container)

const ret = document.createDocumentFragment('div')

ret.appendChild(createElement(tag))

container.textContent = ''

container.appendChild(ret)

}

With this render function we can now create a simple Hello Wsorld example:

function Title({message}) {

return (

<nav>

<h1 style='color:red'>Hello, {message}!</h1>

</nav>

)

} // Render tag to document:

render(<Title message='World' />, 'header')

Codepen examle:

This Hello World when gzipped will weigh in at 617 bytes , well within our target of 1KB limit.

With this we can create rather complex components. Here's the code to create an interactive list:

function List({data}) { function announce(e) {

alert(e.target.textContent.trim())

} const state = {

fruits: fruits

} if (data) state.fruits = data

return (

<ul class='list'>

{

state.fruits.map(item => (

<li>

<p onclick={(e) => announce(e)} class='name-container'>Name: {item.name}</p>

<p class='amount-container'>Ammount: {item.amount}</p>

</li>

))

}

</ul>

)

} const fruits = [

{

name:'Apples',

amount: 12

},

{

name:'Oranges',

amount: 6

},

{

name:'Cherries',

amount: '32'

}

] function renderList(state) {

if (Array.isArray(state)) {

return render(<List data={state}/>, 'section')

} else {

return render(<List />, 'section')

}



} renderList()

Now we've got a list with inline events and state. We could update the fruits array and then re-render the list with the renderList function. Easy-peasy.

SVG

As it stands you can create any type of html with this. However if you need to also occasionally output SVG, you would need to update the functions to handle SVG:

function createElement(node, isSVG) {

function setProps(element, name, value) {

try {

// Handle normal properties like disabled, checked or inline events:

if (value === 0) value = String(value)

element[name] = value

} catch (err) {}

// Handle attributes:

if (typeof value !== 'function') {

if (!!value) {

element.setAttribute(name, value)

} else {

element.removeAttribute(name)

}

}

}

let element

if (isSVG = isSVG || node.type === "svg") {

element = document.createElementNS("

} else {

element = document.createElement(node.type)

}

node.props && Object.keys(node.props).forEach(key => setProps(element, key, node.props[key]))

node.children && node.children.map(child => element.appendChild(createElement(child, isSVG)))

return element

} function createNode(node, isSVG) {let elementif (isSVG = isSVG || node.type === "svg") {element = document.createElementNS(" http://www.w3.org/2000/svg ", node.type)} else {element = document.createElement(node.type)node.props && Object.keys(node.props).forEach(key => setProps(element, key, node.props[key]))node.children && node.children.map(child => element.appendChild(createElement(child, isSVG)))return element return typeof node === "string" ? document.createTextNode(node) : createNode(node, isSVG)

}

Here’s a Codepen example that includes the interactive list from above and an SVG element:

Virtual Dom

This does not provide a virtual dom. As you saw in our render function, each time we call it, it empties the container before outputting the result. Maybe that’s all you need. However, there’s nothing stopping your from using a virtual dom with this. You would need to update the render function here to handle a virtual dom.

The Code

We’ve made this library available on Github and NPM as nano-byte .

Source code on Github.

Install from NPM: