A Idea for a base class for web components in 2020

Jochen Kühner
Mar 22 · 2 min read

Hello people,
may I introduce our base class we use for web components in the year 2020.
It requires a browser with “Constructible Stylesheet” (https://github.com/WICG/construct-stylesheets/blob/gh-pages/explainer.md) support, or the include of the following polyfill (https://github.com/calebdwilliams/construct-style-sheets#readme).

export const html = function html(strings: TemplateStringsArray, ...values: any[]): HTMLTemplateElement {
const template = document.createElement('template');
template.innerHTML = strings.raw[0];
return template;
};
export const css = function html(strings: TemplateStringsArray, ...values: any[]): CSSStyleSheet {
const cssStyleSheet = new CSSStyleSheet();
//@ts-ignore
cssStyleSheet.replaceSync(strings.raw[0]);
return cssStyleSheet;
};
export class BaseCustomWebComponent extends HTMLElement {
static readonly style: CSSStyleSheet;
static readonly template: HTMLTemplateElement;
protected _getDomElement<T extends Element>(id: string): T {
if (this.shadowRoot.children.length > 0)
return <T>(<any>this.shadowRoot.getElementById(id));
return <T>(<any>this._rootDocumentFragment.getElementById(id));
}
//@ts-ignore
private static _propertiesDictionary: Map<string, string>;
protected _parseAttributesToProperties() {
//@ts-ignore
if (!this.constructor._propertiesDictionary) {
//@ts-ignore
this.constructor._propertiesDictionary = new Map<string, [string, any]>();
//@ts-ignore
for (let i in this.constructor.properties) {
//@ts-ignore
this.constructor._propertiesDictionary.set(i.replace(/([A-Z])/g, (g) => `-${g[0].toLowerCase()}`), [i, this.constructor.properties[i]]);
}
}
for (const a of this.attributes) {
//@ts-ignore
let pair = this.constructor._propertiesDictionary.get(a.name);
if (pair) {
if (pair[1] === Boolean)
this[pair[0]] = true;
else
this[pair[0]] = a.value;
}
}
}
private _rootDocumentFragment: DocumentFragment;constructor() {
super();
this.attachShadow({ mode: 'open' });//@ts-ignore
if (this.constructor.template) {
//@ts-ignore
this._rootDocumentFragment = this.constructor.template.content.cloneNode(true);
}
//@ts-ignore
if (this.constructor.style) {
//@ts-ignore
this.shadowRoot.adoptedStyleSheets = [this.constructor.style];
}
queueMicrotask(() => {
if (this._rootDocumentFragment)
this.shadowRoot.appendChild(this._rootDocumentFragment);
//@ts-ignore
if (this.ready)
//@ts-ignore
this.ready();
})
}
}

This class can then easily be used like this

import { BaseCustomWebComponent, html, css } from 'BaseCustomWebComponent';export class AB extends BaseCustomWebComponent {

static readonly style = css`
* {
font-size: 20px
}`;
static readonly template = html`
<div id="inner"></div>
`;
static readonly properties = {
objectName: String
}

private objectName: string;
constructor() {
super();
this._parseAttributesToProperties();
const inner = this._getDomElement<HTMLDivElement>('inner');
inner.innerText = this.objectName;
}
}
customElements.define('a-b', AB);

We use this component where we need high performance web components.

The “properties” list is there to tell wich attributes are reflected to properties and also in our designer framework (https://github.com/node-projects/web-component-designer) so we can show editors.

There is no binding mechanism (or something similar) at the moment, we only use the “template-literal-strings” so we have a nice editor experience in vs-code when using the lit-html extension (https://marketplace.visualstudio.com/items?itemName=bierner.lit-html).

Hopefully this component is useful for you, leave your suggestions or comments.

Written by

More From Medium

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade