sifrr-dom ·

A ~6KB :zap: fast DOM framework for creating web user interfaces using Custom Elements with state management, one way/two way data bindings etc.
Sifrr-Dom is best of both worlds: write components in pure HTML, CSS, JS with ease-of-use and features of a full fledged JS framework like css scoping (shadow root), events, reconciliation etc.
Size
Minified (dist/sifrr.dom.min.js ) |  |
Minified + Gzipped (dist/sifrr.dom.min.js ) |  |
Tradeoffs
- :+1: Uses @sifrr/template and adds custom elements, prop/state management on top of it
- :+1: Use latest web API standards (custom elements v1)
- :+1: CSS scoping with shadow root
- :-1: hence will not work in older browsers without polyfills
- :+1: In-built Synthetic event listeners and custom events
Performance Comparison
Check Performance Here
Note: These might not be exact and should only be taken as a reference.
How to use
Directly in Browser using standalone distribution
Add script tag in your website.
<script src="https://unpkg.com/@sifrr/template@{version}/dist/sifrr.template.min.js"></script>
<script src="https://unpkg.com/@sifrr/dom@{version}/dist/sifrr.dom.min.js"></script>
Browser API support needed for
If custom elements v1 API is supported by browsers, it is very likely that other APIs are supported as well.
Using npm
Do npm i @sifrr/template @sifrr/dom
or yarn add @sifrr/template @sifrr/dom
or add the package to your package.json
file.
Put in your frontend js module (compatible with webpack/rollup/etc).
Importing
const { setup } = require('@sifrr/dom');
import { setup } from '@sifrr/dom';
import SifrrDom from '@sifrr/dom';
const { setup } = SifrrDom;
const { setup } = Sifrr.Dom;
Basic API usage
Setting Up
const config = {
events: ['input', 'change', 'update'],
urls: {
'element-name': '/element/name.js'
},
url: null
};
Sifrr.Dom.setup(config);
Sifrr element
import { html } from '@sifrr/template';
import { Element } from '@sifrr/template';
class CustomTag extends Element {
static get template() {
return html`
<style media="screen">
p {
color: blue;
}
</style>
<p>${el => el.data()}</p>
`;
}
data() {
return this.getAttribute('data');
}
}
Sifrr.Dom.register(CustomTag);
module.exports = CustomTag;
Template
template
should be return value of Sifrr.Template.html
. check out sifrr-template for more details.
Bindings work as it does in Sifrr.Template
, difference being instead of props, Sifrr.Dom
passes element itself in binding functions' first argument
Loading element
Note: Sifrr.Dom.load
requires Fetch API to work.
- Sifrr.Dom.load() - downloads element file (recommended for async loading)
const config = {
urls: {
'custom-tag': '/custom-tag.js'
}
};
Sifrr.Dom.load('custom-tag');
const config = {
url: name => `/elements/${name}.js`
};
Sifrr.Dom.load('custom-tag');
Sifrr.Dom.load('custom-tag', 'https://www.elements.com/custom-tag.js');
Priority Order: url
in load function call, url from urls
in config, then url from url
function.
class DependantElement extends Sifrr.Dom.Element {}
Sifrr.Dom.load('some-element').then(() => {
Sifrr.Dom.register(DependantElement);
});
<script type="module">
import '/elements/custom-tag';
</script>
<script src="/elements/custom-tag" type="module">
- Normal script tag (recommended for best browser support)
<script src="/elements/custom-tag" />
Rendering
This html
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<custom-tag></custom-tag>
<script src="custom-tag.js" charset="utf-8"></script>
<script src="index.js" charset="utf-8"></script>
</body>
</html>
with
class CustomTag extends Element {
static get template() {
return html`
<style media="screen">
p {
color: blue; // Only applies to p inside this element
}
</style>
<p>${el => el.state.}</p>
<p attr=${el => el.state.attribute}>${el => el.state.number}</p>
`;
}
constructor() {
super();
this.state = {
number: 1,
attribute: 'abcd'
}
}
}
Sifrr.Dom.register(CustomTag);
will render to
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<custom-tag>
#shadow-root
<style media="screen">
p {
color: blue; // Only applies to p inside this element
}
</style>
<p attr="abcd">1</p>
</custom-tag>
<script src="index.js" charset="utf-8"></script>
</body>
</html>
Changing state of element
const customtag = window.querySelector('custom-tag');
customtag.setState({ number: 2, attribute: 'xyz' });
This will change custom-tag to
<custom-tag>
#shadow-root
<style media="screen">
p {
color: blue;
}
</style>
<p attr="xyz">2</p>
</custom-tag>
Changing state automatically triggers element.update()
which updates the bindings.
Force update element bindings
customtag.update();
Components Without shadow root
<template>
<style media="screen">
// Style here will be global
</style>
</template>
<script type="text/javascript">
class CustomTag {
static get useShadowRoot {
return false;
}
}
CustomTag.useShadowRoot = false;
</script>
props
- props do not trigger re-renders, unless set by
:
or ::
prop bindings of Sifrr.Template
- first argument in props binding function is parent sifrr element
:
or ::
prop bindings don't work when the element has no parent sifrr element
- props are case insensitive
- props in hyphen-case will be conveted to camel-case property name, i.e.
some-thing
=> someThing
<custom-tag :prop="${parentElement => parentElement.data}"></custom-tag>
Sifrr Element (Sifrr.Dom.Element) Methods
Callbacks
Sifrr wraps some of the original callbacks of Custom Elements API
class CustomTag extends Sifrr.Dom.Element {
static observedAttributes() {
return ['custom-attr'];
}
onConnect() {
}
onDisconnect() {
}
onAttributeChange(attrName, oldVal, newVal) {
}
onStateChange(newState) {
}
beforeUpdate() {
}
onUpdate() {
}
}
Clearing state of element (use only if you know what you are doing)
customtag.clearState();
Query selectors for custom element content
customtag.$(selector );
customtag.$$(selector );
Sifrr adds $ and $$ as alias for querySelector and querySelectorAll to all HTMLElements and document. eg. document.$('div').$$('p')
Synthetic events
Sifrr.Dom.Event.add('click');
el._click = fxn;
Sifrr.Dom.Event.addListener('click', selector, fxn);
Sifrr.Dom.Event.addListener('click', element, fxn);
Sifrr.Dom.Event.trigger(target, 'custom:event', options);
Note: Synthetic event listeners are always passive, hence, event.preventDefault()
can not be called inside the function. Use html event listener properties (eg. onclick
) if you need event.preventDefault()
.
More complex apis
Controlled inputs
import { memo } from '@sifrr/template'
<input :value="${el => el.state.input}" :_input=${memo(el => value => el.setState({ input: value }))} />
<select :value="${el => el.state.select}" :_input=${memo(el => value => el.setState({ select: value }))}>
</select>
<textarea :_input=${memo(el => value => el.setState({ textarea: value }))} :value=${el => el.state.textarea}></textarea>
<div contenteditable :_input=${memo(el => value => el.setState({ elements: value }))}>
${el => el.state.elements}
</div>
Controlled State
<some-element :state="${el => el.state.someElementState}"></some-element>
<some-element
:_update="${Sifrr.Template.memo(el => newState => el.setState({ someElementState: newState }))}"
></some-element>
<some-element
:state="${el => el.state.someElementState}"
:_update="${Sifrr.Template.memo(el => newState => el.setState({ someElementState: newState }))}"
></some-element>
Creating another sifrr element programmatically
Sifrr.Dom.createElement(Sifrr.Dom.Element class or Tag name, props to be set, oldValue)
`${(parent, oldValue) =>
Sifrr.Dom.createElement(CustomTag || 'custom-tag', { prop: 'value' }, oldValue)}`;
Extending another html element (doesn't work in safari yet)
Sifrr element can extend other html elements also, eg:
CustomTag extends HTMLButtonElement here, note that register call has { extends: 'button' } as second argument
class CustomTag extends Sifrr.Dom.Element.extends(HTMLButtonElement) {}
Sifrr.Dom.register(SifrrSmaller, {
extends: 'button'
});
then you can use custom-tag as button in html like:
<button is="custom-tag"></button>
Global Stores
import { html, Store } from '@sifrr/template';
import { Element } from '@sifrr/template';
const someStore = new Sifrr.Template.Store({ a: 'b' });
class CustomTag extends Element {
static get template() {
return html`
<style media="screen">
p {
color: blue;
}
</style>
<p>${() => someStore.value.a}</p>
`;
}
constructor() {
super();
someStore.addListener(() => {
this.update();
});
}
}
Sifrr.Dom.register(CustomTag);
module.exports = CustomTag;
someStore.set({ a: 'c' });
Execute JS File
Execute a JS file
Sifrr.Dom.Loader.executeJS(url);
slots
- Slots work same as it would in web components, but note that bindings in slot elements won't work
Example elements
More readings
Special thanks to