What are Web Components?
Web Platform APIs
Libraries & Tools
Patterns & Techniques
Hands-on
Web components are a set of web platform APIs that allow you to create new custom, reusable, encapsulated HTML tags to use in web pages and web apps. Custom components and widgets build on the Web Component standards, will work across modern browsers, and can be used with any JavaScript library or framework that works with HTML.
Quelle: webcomponents.org
APIs for UI components*
Available everywhere
Future-ready
No lock-in
Interoperable
No frameworks & compilers
*complex HTML with associated JS & CSS
Support in all mainstream browsers since January 2020 🎉
Evolving standards
Thriving ecosystem
👉 und Cryptopus, Puzzle Docs, ...
Register custom "HTML tag"
Behaves like built-in HTML element
Input: attributes/properties
Output: DOM events
Render something:
Add children to host element
(internal DOM tree)
Implement logic:
Add event listeners, update children etc.
class MyComponent extends HTMLElement {
constructor() {
super();
// ...
}
}
window.customElements.define(
"my-component", // Must contain hyphen!
MyComponent
)Markup:
<my-component></my-component>class MyComponent extends HTMLButtonElement {
// ...
}
window.customElements.define(
'my-component',
MyComponent,
{ extends: "button" }
)Markup:
<button is="my-component"></button>class MyComponent extends HTMLElement {
connectedCallback() {} // Added to DOM
disconnectedCallback() {} // Removed
// from DOM
adoptedCallback() {} // Moved to new
// document
// Attribute changed
static get observedAttributes() {
return ['x', 'y'];
}
attributeChangedCallback(
name, oldValue, newValue) {}
}
customElements.whenDefined("my-component")
.then(() => {
console.log("my-component is defined");
});
tl;dr:
All evergreen browsers, Safari only autonomous elements
"Old thing" → <video>
Encapsulation
Hide markup structure & style
Avoid clashes & leaking
Optional
Regular DOM we see
DOM that is hidden
Source: MDN
class MyComponent extends HTMLElement {
constructor() {
super();
this.append(
document.createElement("p")
);
}
}class MyComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
// Use shadow root
// like regular DOM node
this.shadowRoot.append(
document.createElement("p")
);
}
}class MyComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
// Use shadow root
// like regular DOM node
this.shadowRoot.append(
document.createElement("p")
);
}
}Mode closed means no access to Element.shadowRoot
👉 irrelevant in practice
class MyComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
// ...
const style = document.createElement("style");
style.textContent = `
.red {
color: red;
}
`;
this.shadowRoot.append(style);
}
}// Host element itself
:host {}
// Host element with "active" class
:host(.active) {}
// When element has ancestor with "dark" class
:host-context(.dark) {}Shadow Tree can be inspected:
tl;dr:
All evergreen browsers
Contents not rendered in the DOM
Can be referenced with JS
Useful besides Web Components
<template id="my-template">
<p>My Component</p>
</template>
<script>
class MyComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
const template =
document.getElementById('my-template');
this.shadowRoot.append(
template.content.cloneNode(true)
);
}
}
</script><template id="my-template">
<style>
:host {
display: block;
background: gray;
}
.red {
color: red;
}
</style>
<p class="red">My Component</p>
</template>
tl;dr:
All evergreen browsers
Project custom element's content into template
Unnamed or named
<!-- Text node: -->
<my-component>Hello</my-component>
<!-- Children tree: -->
<my-component>
<ul>
<li>Hello</li>
</ul>
</my-component>
<template id="my-template">
<div class="container">
<slot></slot>
</div>
</template>
<my-component>
<p slot="message">Hello</p>
<img slot="image" src="img.png" />
</my-component>
<template id="my-template">
<div class="container">
<slot name="image"></slot>
<slot name="message"></slot>
</div>
</template>
👉 Slotted elements are outside of Shadow Tree
(in light DOM)
/* Style slotted element itself */
::slotted {}
::slotted([slot="image"]) {}
/*
Won't work:
style children of slotted element
*/
::slotted li {}
/*
Won't work:
style slot itself
*/
slot {}
slot[name="image"] {}
const slot = this.shadowRoot
.querySelector('#slot');
const nodes = slot.assignedNodes();
slot.addEventListener(
'slotchange',
(event) => {
console.log('Light DOM children changed');
}
);tl;dr:
All evergreen browsers
That's it —
these are the Web Component APIs
🙋 Wait —
there is one more thing...
import { add } from "./utils.js";
import add from "./utils/add.js";
import { uniq } from "lodash-es";export function add(a, b) { return a + b; }
export default function add(a, b) {}<script type="module" src="main.js"></script>tl;dr:
Script tag & dynamic import → all evergreen browsers
Less support for ESM in workers
🚀 Build modern web apps with interactive JavaScript components
❌ JavaScript framework
❌ bundler
❌ transpiler
👉 Just browser APIs
Native Web Components
Less boilerplate
Shadow DOM per default
Reactive properties & declarative templates
Fast rendering
~5kb footprint
import {html, css, LitElement} from 'lit';
export class SimpleGreeting extends LitElement {
static get styles() {
return css`p { color: blue }`;
}
static get properties() {
return {
name: {type: String}
}
}
constructor() {
super();
this.name = 'Somebody';
}
render() {
return html`<p>Hello, ${this.name}!</p>`;
}
}
customElements.define('simple-greeting', SimpleGreeting);Source: lit.dev
import {html, css, LitElement} from 'lit';
import {customElement, property} from 'lit/decorators.js';
@customElement('simple-greeting')
export class SimpleGreeting extends LitElement {
static styles = css`p { color: blue }`;
@property()
name = 'Somebody';
render() {
return html`<p>Hello, ${this.name}!</p>`;
}
}Source: lit.dev
https://modern-web.dev/docs/dev-server/overview/
ES Modules support & resolving
Fast reloads
Extensible
esbuild/Rollup Plugins
👉 ...or Vite
npm init @open-wcGenerator by open-wc.org
Scaffold app or component
Best practices
Build setup
Linting
Testing
Demo & documentation
👉 ...or the scaffolding tool of your lib
https://github.com/open-wc/custom-elements-manifest
Spec to describe custom elements (JSON)
For tooling/IDEs etc.
Analyzer:
npm i -D @custom-elements-manifest/analyzer
custom-elements-manifest analyzeexport class Menu extends LitElement {
// ...
connectedCallback() {
super.connectedCallback();
document.addEventListener(
"pzsh-menu-toggle",
this.toggleMenu,
true
);
}
toggleMenu(e) {
this.open = !this.open;
}
// ...
}export class Topbar extends LitElement {
// ...
toggleMenu() {
this.dispatchEvent(
new CustomEvent(
"pzsh-menu-toggle"
)
);
}
// ...
}<my-component
style="--paragraph-bg: gold;"
></my-component>
<template id="my-template">
<style>
p {
background: var(--paragraph-bg, salmon);
}
</style>
<p>My Component</p>
</template>:host {
display: block;
}
:host([hidden]) {
display: none;
}
*with theory
This work is licensed under a
Creative Commons Attribution 4.0 International License.