A take on using lit-html and using methods coming from Polymer.
This is based on https://github.com/PolymerLabs/lit-element and added my own take on creating my own simple library. You can freely use it on your own projects.
Most of the methods and its internals came from Polymer's properties-mixin, properties-changed, property-accessors mixin, and property-effects mixin.
This is the recommended way. To install, just do this:
npm i --save @littleq/element-lite
You can add this to your JS
// main.js
import { ElementLite } from 'node_modules/@littleq/element-lite/element-lite.js';
class Component extends ElementLite(HTMLElement) {
template () {
return 'Whatever you want to put in the ShadowDom';
}
}
customElement.define('web-component', Component);
and add this to your HTML
<script src="main.js" type="module"></script>
If you are going to use it with lit-html...
// main.js
import { ElementLite } from 'node_modules/@littleq/element-lite/element-lite.js';
import { render, html } from 'node_modules/lit-html/lit-html.js';
class Component extends ElementLite(HTMLElement) {
static get renderer () { return render; }
template () {
return html`Whatever you want to put in the ShadowDom`;
}
}
customElement.define('web-component', Component);
Same as above.
You need to add this additional script for polyfill
<script src="node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js">
And then wrap around the files after the WebComponentsReady
event has been fired
<script>
window.addEventListener('WebComponentsReady', function() {
var component = document.createElement('script');
component.src = 'main.js';
component.type = 'module';
document.head.appendChild(component);
});
</script>
if you are using Webpack and you have bundled it in ES5 for older browsers, you also need:
<script src="node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js">
<script src="/node_modules/@littleq/element-lite/dist/element-lite.umd.es5.min.js">
<script>
window.addEventListener('WebComponentsReady', function() {
var component = document.createElement('script');
component.src = 'main.js';
document.head.appendChild(component);
});
</script>
// main.js
// wrap in anonymous function
// Bundle this in ES5 either using Rollup or Webpack
(function(){
// get ElementLite from window.ElementLite;
var ElementLite = window.ElementLite.ElementLite;
class Component extends ElementLite(HTMLElement) {
render () {
return html`Whatever you want to put in the ShadowDom`;
}
}
})();
You can put a default value on a property.
class Component extends ElementLite(HTMLElement) {
static get properties () {
return {
prop1: {
type: String,
value: 'default value'
}
}
}
}
You can put auto reflect it on the attribute
class Component extends ElementLite(HTMLElement) {
static get properties () {
return {
prop1: {
type: String,
reflectToAttribute: true
}
}
}
}
You can set the property to be read only (only settable by _setProperty method)
class Component extends ElementLite(HTMLElement) {
static get properties () {
return {
prop1: {
type: String,
readOnly: true
}
}
}
}
You can call a method when the property changes...
class Component extends ElementLite(HTMLElement) {
static get properties () {
return {
prop1: {
type: String,
observer: '_prop1Changed'
}
}
}
_prop1Changed (newValue, oldValue) {
// method goes here
}
}
You can call a method when a set of property changes...
class Component extends ElementLite(HTMLElement) {
static get properties () {
return {
prop1: {
type: String
},
prop2: {
type: Object
}
}
}
static get observers () {
return [
'_propChanges(prop1, prop2.attr1)'
];
}
_propChanges (prop1, attr1) {
// method goes here
}
}
// ...
el.prop1 = 'a'
el.set('prop2.attr1', 'b')
// _propChanges('new', 'b')
It has helper methods for array mutations...
class Component extends ElementLite(HTMLElement) {
static get properties () {
return {
prop1: {
type: Array,
value: [],
observer: '_arrayChanged'
}
}
}
_arrayChanged (newArray, oldArray) {
// method goes here
}
}
// ...
el.prop1 = ['a', 'b', 'c'];
// ...
el.push('prop1', 'd') // el.prop1 = ['a', 'b', 'c', 'd']
// ...
el.pop('prop1') // el.prop1 = ['a', 'b', 'c']
// ...
el.unshift('prop1', 'z') // el.prop1 = ['z', 'a', 'b', 'c']
// ...
el.shift('prop1') // el.prop1 = ['a', 'b', 'c']
// ...
el.splice('prop1', 1, 0, '1', '2') // el.prop1 = ['a', '1', '2', 'b', 'c']
// ...
el.splice('prop1', 1, 1) // el.prop1 = ['a', '2', 'b', 'c']
index.html
<!doctype html>
<html lang="en">
<head>
<title>Testing</title>
</head>
<body>
<test-element></test-element>
<script type="module" src="test-element.js" async></script>
</body>
</html>
test-element.js
// you can get lit-html's `html` from the bundle as well
import { ElementLite } from './node_modules/@littleq/element-lite/element-lite.js';
import { html, render } from './node_modules/lit-html/lit-html.js';
const { customElements } = window;
class Component extends ElementLite(HTMLElement) {
static get is () { return 'test-element'; }
static get renderer () { return render; }
static get properties () {
return {
test: {
type: Object,
value: {}
},
testtwo: {
type: Boolean,
value: false
},
number: {
type: Number,
value: 0
}
}
}
/**
* Return a template result to render using lit-html.
*/
template () {
const {test: { name }, testFx, testtwo, number, addNumber} = this;
return html`
Hello World ${testFx(name)} ${testtwo} ${number}
<button @click=${addNumber.bind(this)}>
Click
</button>
`;
}
// We can use functions as variables in the rendered system
testFx (name) {
return `${name}!`
}
// We can call functions on event-handlers. Remember to add .bind(this)
addNumber () {
this.number++;
}
}
// defines the element
customElements.define(Component.is, Component);
// Just changes the test object inside the test-element
setTimeout(() => {
document.querySelector('test-element').test = {
name: 'hello'
};
}, 1000)
If you need only the power to track changes in properties without the need of shadow DOM,
use PropertiesLite
// main-base.js
import { PropertiesLite } from 'node_modules/@littleq/element-lite/properties-lite.js'
class Component extends PropertiesLite(HTMLElement) {
static get properties () {
return {
prop1: {
type: String,
// sets up an event propagation upwards with the event name called 'prop1-change', anyone can listen to this to get change on the value of prop1 by using this:
// thisElement.addEventListener('prop1-change', (event) => { console.log(event.detail) })
// the value can be found in detail
notify: true,
// sets up the changes on the attribute itself. Will show up on DOM.
reflectToAttribute: true,
value: 'default'
},
prop2: {
type: String,
value: 'prop2',
// will not setup a setter from the outside.
// to change this value, you can only do thisElement._setProperty('prop2', value)
// which is considered as a private method
readOnly: true
}
}
}
}
customElement.define('web-component-properties', Component);
If you need only the power of PropertiesLite
and use the additional parts like providing default and (in the future) call functions based on observed variables without the need of shadow DOM. Automatically extends PropertiesLite
// main-base.js
import { ObserversLite } from 'node_modules/@littleq/element-lite/observers-lite.js'
class Component extends ObserversLite(HTMLElement) {
static get properties () {
return {
prop1: {
type: String,
observer: 'prop1Changed', // calls this method if there are changes in prop1
value: 'default'
},
prop2: {
type: String,
value: 'prop2',
}
}
}
static get observers () {
return [
'propsChanged(prop1, prop2)' // calls this method if there are changes in prop1 or prop2
];
}
// if called in observer only, it will put in both newValue and oldValue
prop1Changed (newValue, oldValue) {
console.log(newValue, oldValue)
}
// if calle in observers, it will put only the newValue of the parameters
propsChanged (prop1, prop2) {
console.log(prop1, prop2)
}
}
customElement.define('web-component-observers', Component);
If you need the power of templating without tracking properties,
use TemplateLite
// main-static.js
import { TemplateLite } from 'node_modules/@littleq/element-lite/template-lite.js'
class Component extends TemplateLite(HTMLElement) {
template () {
return `Hello World!`
}
}
customElement.define('web-component-template', Component);
If you want to use lit-html
, you need to define the renderer
of TemplateLite
to use lit-html
's render
method
// main-static.js
import { TemplateLite } from 'node_modules/@littleq/element-lite/template-lite.js'
import { render, html } from 'node_modules/@littleq/lit-html/lit-html.js'
class Component extends TemplateLite(HTMLElement) {
static get renderer () { return render; }
template () {
return html`Hello World!`
}
}
customElement.define('web-component-template', Component);
Documentation to be added...
Documentation to be added...
If you are going to use it on Evergreen Browsers that allows <script type="module">
,
then you can just do this on your js
files
import { ElementLite } from './node_modules/@littleq/element-lite/element-lite.js';
If you are going to use it on Webpack or Rollup, you can do any of these
// provided that node_modules is resolved in your configurations
import { ElementLite } from '@littleq/element-lite.js';
or
import { ElementLite } from './node_modules/@littleq/element-lite';
If you are going to use require
and not import
you can do any of these
// provided that node_modules is resolved in your configurations
var ElementLite = require('@littleq/element-lite').ElementLite;
or
// provided that node_modules is resolved in your configurations
var ElementLite = require('@littleq/element-lite/dist/element-lite.cjs.js').ElementLite;
If you are going to load it via the <script>
tag, you need to do these
For ES6
<script src="/node_modules/@littleq/element-lite/dist/element-lite.umd.js">
<!-- <script src="/node_modules/@littleq/element-lite/dist/element-lite.umd.min.js">
if you need the minified file -->
For ES5
<script src="/node_modules/@littleq/element-lite/dist/element-lite.umd.es5.js">
<!-- <script src="/node_modules/@littleq/element-lite/dist/element-lite.umd.es5.min.js">
if you need the minified file -->
and then can access it here
var ElementLite = window.ElementLite.ElementLite
It works on all major evergreen Browsers (Edge, Safari, Chrome, Firefox) as long as you have the Polyfills
set (make sure to add webcomponents-lite
or webcomponents-loader
and create the components after the
WebComponentsReady
event has been fired)
It also works on Safari 11, Safari 10.1, and Safari 9.
This doesn't work on IE 11 below as of the moment. (It should work on IE 11 but the polyfill is different and currently on the works on creating a smooth documentation for it).
For IE 11. You need to use lib/native-shim.es5.js
instead of custom-element-es5-adapter
for it to work.
Based on size-limit
properties-lite.js
Package size: 1.78 KB
Size limit: 2 KB
observers-lite.js
Package size: 2.25 KB
Size limit: 2.5 KB
object-accessors-lite.js // this is without properties-lite
Package size: 775 B // should be around 2.6 KB if with properties-lite
Size limit: 1 KB
array-accessors-lite.js // this is without properties-lite
Package size: 970 B // should be around 3 KB if with properties-lite
Size limit: 1 KB
template-lite.js // this is without lit-html
Package size: 669 B // should be around 3.5 KB if with lit-html
Size limit: 1 KB
element-lite.js
Package size: 3.2 KB
Size limit: 3.5 KB
element.js // using element-lite and lit-html
Package size: 6.09 KB
Size limit: 6.5 KB