RWC is a unique mix of Shadow DOM + Virtual DOM + Redux to create web-components. This approach is an attempt to find a balance between a scalable paradigm and performance.
npm install rwc --save
Components are composed of three functions —
init(component)
: The function takes in the current instance of the component and returns the initial state.update(state, action)
: A reducer function like that in Redux that takes an inputstate
and based on theaction
returns a new output state. Additionally it can return a tuple (an array) of two elements containing both thestate
and an object of CustomEvent type, which is dispatched as a DOM event.view(state, dispatch)
: The view function converts thestate
into a virtual DOM tree. Additionally it also gets adispatch()
function which can dispatch actions on DOM events.
All these three functions are essentially pure functions, ie. they have no side effects and should remain referentially transparent for all practical purposes.
// CounterComponent.js
import h from 'snabbdom/h'
// Creates an initial state
const init = () => {
return {count: 0}
}
// Reducer function for redux
const update = (state, {type, params}) => {
switch (type) {
case 'INC': return {count: state.count + 1}
case 'DEC': return {count: state.count - 1}
default: return state
}
}
// Creates virtual DOM elements
const view = ({count}, dispatch) => {
return h('div', [
h('h1', [count]),
h('button', {on: {click: dispatch('INC')}}, ['Increment']),
h('button', {on: {click: dispatch('DEC')}}, ['Decrement'])
])
}
export default {init, view, update}
import rwc from 'rwc'
import CounterComponent from './CounterComponent'
function virtualDOMPatcher (shadowRoot) {
return (vnode) => /* patches the shadowRoot with vNode */
}
// create prototype object
const proto = rwc.createWCProto(virtualDOMPatcher, CounterComponent)
// create an HTMLElement instance
const html = Object.create(HTMLElement.prototype)
// extend html element with the created prototype
const CounterHTMLComponent = Object.assign(html, proto)
// register as usual
document.registerElement('x-counter', {prototype: CounterHTMLComponent})
The virtualDOMPatcher
function argument gives the to ability to customize how the shadow DOM is updated.
import snabbdom from 'snabbdom'
function virtualDOMPatcher (shadowRoot) {
const patch = snabbdom.init()
let __vNode = shadowRoot.appendChild(document.createElement('div'))
return function (vNode) {
__vNode = patch(__vNode, vNode)
}
}
import { render } from 'preact'
import { createElement as h } from 'preact-hyperscript'
function virtualDOMPatcher (shadowRoot) {
let __vNode
return function (vNode) {
__vNode =render(vNode, shadowRoot, __vNode)
}
}
import {h, createProjector} from 'maquette'
function virtualDOMPatcher (root) {
let __vNode
const projector = createProjector()
const render = () => projector.append(root, () => __vNode)
return function (vNode) {
if(!__vNode) {
__vNode = vNode
render()
}
__vNode = vNode
}
}
For components to communicate with the outside world the component can dispatch a CustomEvent via the update()
function.
export const update = (state, {type, params}) => {
switch (type) {
case 'INC':
{count: state.count + 1},
return [
{count: state.count + 1},
new CustomEvent('changed', {detail: _state.count})
]
case 'DEC':
const _state = {count: state.count - 1}
return [
_state,
new CustomEvent('changed', {detail: _state.count})
]
default: return state
}
}
Attribute changes are fired as actions and are namspaced with @@attr
. For example —
<x-counter some-custom-attribute="100" />
The changes can be observed inside the update function using @@attr/some-custom-attribute
—
update (state, {type, params}) {
switch (type) {
case '@@attr/some-custom-attribute':
return {count: state.count + parseInt(params)}
default: return state
}
}
A special action @@attached
is fired when the component is attached into the DOM.
The param
for this action is the instance of the web component.
update (state, {type, params}) {
switch (type) {
case '@@attached':
return {width: params.getBoundingClientRect().width}
default: return state
}
}
Creates the prototype for the web component element.
Kind: inner property of raf
Returns: Object
- prototype object for creating HTMLElements
Param | Type | Description |
---|---|---|
virtualDOMPatcher | function |
patches the virtual dom on shadowRoot. |
component | Object |
|
component.init | function |
returns the initial state of the component. |
component.update | function |
a redux reducer for updating component state. |
component.view | function |
takes in the state and returns a dom tree. |