Package Exports
- rwc
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (rwc) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
rwc (BETA)
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.
Installation
npm install rwc --saveFeatures
- Small footprint (~100 SLOC) with no dependencies at all.
- Reactive approach —
actionstrigger anupdateon thestatewhich triggersviewupdation. - Can integrate with multiple virtual dom implementations like preact or snabbdom simulataneously.
- Caches event handler between renders.
- Passes proper JS objects to components using
props. - Support for custom side-effects using
Tasks. - Uses shadow dom v1 API.
Paradigm
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 inputstateand based on theactionreturns a new output state.view(state, dispatch): The view function converts thestateinto a virtual DOM tree. Additionally it also gets adispatch()function as an argument which can dispatch custom actions.
All these three functions are essentially pure functions, ie. they have no side effects and should remain referentially transparent for all practical purposes.
Create Component
// CounterComponent.js
import h from 'snabbdom/h'
// Creates an initial state
const init = () => {
return {count: 0}
}
// Reducer function like that in 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 = (state, dispatch) => {
return h('div', [
h('h1', [state.count]),
h('button', {on: {click: dispatch('INC')}}, ['Increment']),
h('button', {on: {click: dispatch('DEC')}}, ['Decrement'])
])
}
export default {init, view, update}Register Web Component
import rwc from 'rwc'
import CounterComponent from './CounterComponent'
function virtualDOMPatcher (shadowRoot) {
return (vnode) => {/* patches the shadowRoot with vNode */}
}
// create prototype object
const ReactiveProto = rwc.createWCProto(virtualDOMPatcher, CounterComponent)
// register as usual
document.registerElement('x-counter', {prototype: ReactiveProto})Virtual DOM Patcher
The virtualDOMPatcher function argument gives the to ability to customize how the shadow DOM is updated.
Examples:
import snabbdom from 'snabbdom'
import h from 'snabbdom/h'
const patch = snabbdom.init([
require('snabbdom/modules/class'),
require('snabbdom/modules/props'),
require('snabbdom/modules/style'),
require('snabbdom/modules/eventlisteners')
])
function virtualDOMPatcher (shadowRoot) {
// create wrapper element
let __vNode = shadowRoot.appendChild(document.createElement('div'))
return function (vNode) {
__vNode = patch(__vNode, h('div', [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
}
}RWC Tasks
Web components can have custom side-effects using Tasks. A task is a simple object which has a run() function. This function gets called by RWC with the current component as the first param and the dispatch function as the second. Tasks have to be created inside the update() function and returned as a part of the tuple's second value as shown below.
For instance a counter web component might want to dispatch an event whenever the counter changes. This can be done by creating an event dispatching task.
// Create a event dispatching task
export class DispatchEventTask {
constructor (name, detail) {
this.detail = detail
this.name = name
}
run (component) {
const ev = new CustomEvent(this.name, {
detail: this.detail,
bubbles: true
})
component.dispatch(ev)
}
}
export const update = (state, {type, params}) => {
switch (type) {
case 'INC':
// Return a tuple of state + task
return [
{count: state.count + 1},
new DispatchEventTask('count-changed', _state.count)
]
case 'DEC':
const _state = {count: state.count - 1}
return [
_state,
new DispatchEventTask('count-changed', _state.count)
]
default: return state
}
}@@rwc actions
RWC dispatches custom actions during the lifecycle of the web component, which can be used inside the update() function.
@@rwc/created: Fired when the web component is initialized. Theparamsfor this action is the instance of the web component.@@rwc/attached: Dispatched when the web component is inserted into the DOM. This is a good time to call something likeparams.getBoundingClientRect()to get the dimensions of the web component and keep it in the state.@@rwc/detached: Dispatched when the component is removed from the DOM.@@rwc/attr/<attr name>: This is fired whenever a web component's attribute is changed. Theparamis the current value of the attribute.@@rwc/prop/<prop name>: Attributes have a limitation of passing data that is ofstringtype only. For this purpose you can predefine somepropsthatrwcwill attach hooks on and whenever they are changed, this particular action will be fired.// create a list of props const props = ['aa', 'bb'] // pass props to the factory function const proto = rwc.createWCProto(patcher, {update, init, view, props}) // an instance is automatically created by the browser const wc = Object.create(proto) // this will fire action {type: '@@rwc/prop/aa', param: wc} wc.aa = new Date()
Mutating DOM Events
Might get deprecated in favour of tasks
In certain scenarios one might want to call preventDefault() or stopPropagation() on the events that are emitted. This can be easily done by passing additional params to the dispatch function in the view.
const view = (state, dispatch) =>
h('div', [
h('a', {
href: '/example.com',
on: {click: dispatch('CLICK', {preventDefault: true, stopPropagation: true})}
}, [
'Click Me!'
])
])Show your support
⭐ this repo
Would greatly appreciate if you could provide feedback.