A super fast, lightweight (roughly 1kb gzipped) and reactive series of utility functions, used to build HTML node trees and stateful functional components. Can be used either directly via the h
function, or from transpiled JSX
.
Due to it's size, hypnode
is a useful companion utility for writing WebComponents, simplifying HTML
structures, element references and state management. See section entitled "WebComponents" for an example, click to jump there.
To install hypnode
, you can use one of the following in your project:
yarn add hypnode
or npm install hypnode
hypode
can be used in one of two ways, either as a target for JSX
transpilation, or directly using the exposed h
function. It's exported as ES6, so if you need to support older environments, you'll need to transpile down to ES5 in your build tasks.
The h
function can be imported in one of the following ways:
import { h } from 'hypnode';
const { h } = require('hypnode');
Once imported, use the function to generate your tree of DOM Nodes. The function takes 3 arguments, the last two of which are optional:
h([type]: string | Function, [attributes]?: object, [children]?: array[]);
The code below:
import { h, render } from 'hypnode';
/*[...]*/
const result = render(
h('div', { title: 'A DIV!' }, [
h('h1', { class: 'title' }, 'Hypnode'),
h('p', { id: 'text'}, 'My text value'),
)
);
console.log(result.outerHTML);
Will produce the following:
<div title="A DIV!">
<h1 class="title">Hypnode</h1>
<p id="text">My text value</p>
</div>
hypnode
can be used with JSX
to provide a more familiar API when building DOM structures. This will need a transpilation step, see below for examples.
Transpilation of JSX
is provided out of the box by custom factories (TypeScript 1.8+), to apply this, add the following to your tsconfig.json
file:
"compilerOptions": {
"jsx": "react",
"jsxFactory": "h",
/*[...]*/
}
This tells the TypeScript compiler to convert all JSX
elements into function calls, in this case using our exported h
function. You'll still need to import the h
function in every file where you're using JSX
.
To apply the correct types, all files that contain JSX
must have the extension .tsx
.
The code below:
import { h, render } from 'hypnode';
/*[...]*/
const root = document.getElementId('root');
const result = render(
<div class="wrapper">
<a id="link" onClick={(ev = console.log(ev))}>
Click here
</a>
</div>
);
root.appendChild(result);
Will produce the following:
<div class="wrapper">
<a id="link">
Click here
</a>
</div>
As the render()
function exported by hypnode
returns a fully formed HTMLElement
, you can handle it's output easily using native DOM
API's like root.appendChild
or root.replaceChild
. There is, however, an optional secondary argument that can handle this for you, e.g:
const result = h('div', { class: 'wrapper' }, 'Lorem ipsum');
/*[...]*/
render(result, document.getElementById('root'));
or, with JSX
:
const result = <div class="wrapper">Lorem ipsum</div>;
/*[...]*/
render(result, document.getElementById('root'));
hypnode
provides a set of properties for you to apply DOM events. All native events are supported, formatted in camelCase and prefixed with on
. For example:
h('a', { onClick: (ev) => console.log(ev) }, 'Click Here');
or, with JSX
:
<input onKeyUp={(ev) => console.log(ev)} />
If you need access to a particular node in your tree, use the ref
property. For example:
let myElement;
/*[...]*/
h('div', { id: 'container' }, [
h('p', { ref: (el) => (myElement = el) }, 'Lorem ipsum dolor sit amet, consectetur'),
]);
or with JSX
:
let myElement;
/*[...]*/
<div class="wrapper">
<a href="//link.co" ref={(el) => (myElement = el)}>
Click here
</a>
</div>;
hypnode
can be used to create re-usable, functional components, below is a simple example:
import { h, render } from 'hypnode';
/*[...]*/
function Button({ className = '', children }) {
return h('a', { class: `button ${className}` }, children);
}
/*[...]*/
const button = h(Button, { className: 'big' }, buttonText));
render(button, document.getElementById('root'))
or with JSX
:
import { h, render } from 'hypnode';
/*[...]*/
function Button({ className = '', children }) {
return <a class={`button ${className}`}>{children}</a>;
}
/*[...]*/
const button = <Button className="big">Click here</Button>;
render(button, document.getElementById('root'));
Creating and managing complex HTML structures using WebComponents can become tricky as their size increases. hypnode
simplifies this by incorporating a familiar component and state pattern into your custom elements. You can find more info on WebComponents here. As hypnode
is fast and lightweight, WebComponents can be more easily shared between projects without significant dependency overhead.
A quick example can be found below:
// myComponent.tsx (TypeScript + JSX)
import { h, render, useState, State } from 'hypnode';
/*[...]*/
class MyComponent extends HTMLElement {
private state: State<number>;
public connectedCallback() {
const Button = () => this.renderButton();
this.appendChild(render(<Button />));
}
private renderButton = () => {
const [counter] = (this.state = useState(0));
return (
<div class="my-component">
<p>Counter: {counter}</p>
<button onClick={this.onClick}>Click Here</button>
</div>
);
}
private onClick = (ev: Event) => {
const [counter, setState] = this.state;
setState(counter + 1);
}
}
hypnode
exposes a simple, declarative hook to provide state into your functional application.
First, you'll need to import the hook function:
import { h, useState } from 'hypnode';
Once imported, you can initialize a state registry in your components by doing the following:
const [state, setState] = useState(([value]: any));
The useState
function takes a single argument, the initial value you wish to assign to the state. This can be anything, a primitive or something more complex like an object. For example:
function Button({ buttonText }) {
const [state, setState] = useState(10);
return h('button', { onClick: () => setState(state + 1) }, `${buttonText}: ${state}`);
}
You provide mutations to your state via the setState
function, this accepts a new value you wish to assign. Whenever this function is called, the component will be re-rendered with the replaced state value.
hypnode
exports a simple effect hook, useEffect
. This takes a callback as it's only argument which will be called on "mount". You can optionally return another function from it that will be called on "un-mount". The lifecycle of this function works in a similar way to React's hook by the same name.
See example below:
import { h, useEffect } from 'hypnode';
/*[...]*/
function Banner({ children }) {
useEffect(() => {
console.log('onMount');
return () => console.log('onUnMount (optional)');
});
return h('div', { class: 'banner' }, children);
}
hypnode
provides a Virtual DOM
representation for server side rendering (universal rendering). You can use this output to generate a complete HTML
representation of your app. A prebuilt utility that can convert this into an HTML
string can be found here: hypnode-server
This utility was created with TypeScript and comes pre-bundled with a definition file.