Slide 1

Slide 1 text

Web Components Das Ende der SPA-Framework-Ära? Manuel Rauber @ManuelRauber Consultant

Slide 2

Slide 2 text

Manuel Rauber Consultant @ Thinktecture AG [email protected] @manuelrauber https://manuel-rauber.com

Slide 3

Slide 3 text

• The challenge • Web Components • What are they? • How to use them? • How to create them? Agenda

Slide 4

Slide 4 text

The challenge

Slide 5

Slide 5 text

The challenge https://dribbble.com/shots/7169781-Homestay-Web-Design https://dribbble.com/shots/7164322-Food-delivery-Landing-page-design

Slide 6

Slide 6 text

DRY

Slide 7

Slide 7 text

Components

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

<

Slide 10

Slide 10 text

<
< < < < • No semantic • Intransparent • Changes are difficult • Framework dependent

Slide 11

Slide 11 text

< < < < < +< --< <
• Global Scoping • Naming conflicts • Styling conflicts

Slide 12

Slide 12 text

< < < < < • Semantic • Local Scoping • Configuration • Bundle Import

Slide 13

Slide 13 text

Web Components

Slide 14

Slide 14 text

Bring a native component model to web instead of having to use frameworks Web Components - What are they? Custom Element Shadow DOM HTML Template

Slide 15

Slide 15 text

Can I use …? IE11 Edge Chrome Android Opera Safari iOS Custom Elements ❌ ✅ ✅ ✅ ✅ ⚠ ⚠ Shadow DOM ❌ ✅ ✅ ✅ ✅ ✅ ⚠ HTML Templates ❌ ✅ ✅ ✅ ✅ ✅ ✅

Slide 16

Slide 16 text

Custom Elements

Slide 17

Slide 17 text

• Create your own HTML tags • Lifecycle model (“connected”, “disconnected”, “attribute changed”, “adopted”) • Reusable • Decouple behavior from the document Custom Elements <

Slide 18

Slide 18 text

• ES6 Class • Inherit from HTMLElement or any other HTML element • Need to be defined in a CustomElementRegistry Custom Elements class MyRating extends HTMLElement { constructor() { super(); console.log('component constructed'); } connectedCallback() { console.log('component added to DOM'); } adoptedCallback() { console.log('component was moved in DOM'); } disconnectedCallback() { console.log('component removed from DOM'); } attributeChangedCallback(name, oldVal, newVal) { console.log('a attribute has been changed'); } } window.customElements.define('my-rating', MyRating); const myRating = document.createElement('my-rating'); document.body.appendChild(myRating); /// this moves the node! otherElement.appendChild(myRating); otherElement.removeChild(myRating);

Slide 19

Slide 19 text

• Static getter observedAttributes returns all attributes which should be observed • All those attributes will trigger attributeChangedCallback upon a change • Does not execute on property change! Custom Elements - Observed Attributes class MyRating extends HTMLElement { constructor() { super(); } static get observedAttributes() { return [ 'value' ]; } attributeChangedCallback(name, oldVal, newVal) { if (name ===== 'value') { this.innerHTML = `Rating value ${newVal}`; } } } <

Slide 20

Slide 20 text

HTML Templates

Slide 21

Slide 21 text

• HTML tag: • Parsed, but not rendered • Instantiated via JavaScript HTML Templates My Image Card<

Description<

< const template = document.querySelector('template'); const templateInstance = document.importNode(template.content, true); const img = templateInstance.querySelector('img'); img.setAttribute('src', 'http:///a-link.com/foo.png'); document.body.appendChild(templateInstance);

Slide 22

Slide 22 text

Shadow DOM

Slide 23

Slide 23 text

• Encapsulates HTML and CSS • Isolated DOM: elements within Shadow DOM are not selectable from outside • e.g. document.querySelector() won’t return a result • Scoped CSS: CSS styles stay within Shadow DOM, no leakage, no bleed-in • Composition: declarative, markup-based API, put elements from outside in • Simplifies CSS: no conflicts with existing classes and IDs • Productivity: app consists of chunks of components instead of one big page Shadow DOM https://developers.google.com/web/fundamentals/web-components/shadowdom

Slide 24

Slide 24 text

• Shadow host DOM node that the shadow DOM is attached to • Shadow tree DOM tree inside shadow DOM • Shadow boundary Place where the shadow DOM ends and the regular DOM begins (“Light DOM”) • Shadow root Root node of the shadow tree Shadow DOM document shadow host Document Tree Shadow Tree shadow root Shadow boundary

Slide 25

Slide 25 text

• attachShadow() attaches a shadow DOM to any HTML element • open and closed mode Shadow DOM <
<
const shadowHost = document.querySelector('div'); const shadowRoot = shadowHost.attachShadow({ mode: 'open' }); shadowRoot.innerHTML = '<h1>Hello World!<</h1>'; < < <

Slide 26

Slide 26 text

• open allows access to the HTML element’s shadow DOM • closed does not allow access to the HTML element’s shadow DOM Shadow DOM /// ..... shadowHost.attachShadow({ mode: 'open' }); document.querySelector('div').shadowRoot.querySelector('h1'); /// ->- [HTMLHeadingElement] /// ..... shadowHost.attachShadow({ mode: 'closed '}); document.querySelector('div').shadowRoot.querySelector('h1'); /// ->- Can not read 'querySelector' of null

Slide 27

Slide 27 text

• A (named) placeholder to fill with your own markup • Basically mixes together two DOM trees: the shadow & light tree Shadow DOM - Slots :host { border: 1px solid black; display: block; } <
< With Default Content< <
< < -
Unnamed Slot Content<
<
Unnamed Slot Content 2<
Another Slot Content<
<

Slide 28

Slide 28 text

• Slots emit an event, whenever its content changes: slotchange • Get the slotted elements via assignedNodes() • https://drafts.csswg.org/css-scoping/#slotted-pseudo Shadow DOM - Slots const slot = this.shadowRoot.querySelector('slot'); slot.addEventListener('slotchange', () =>= { const assignedNodes = slot.assignedNodes(); /// Do something with assignedNodes });

Slide 29

Slide 29 text

• Styling is done via -tags • All stylings are local and do not overwrite styles from other shadow DOMs • No other stylings will bleed into the shadow DOM Shadow DOM - Styling <template> <style> h1 { color: red; } <

Hello World!<

< const shadowRoot = document.querySelector('div') .attachShadow({ mode: 'open' }); shadowRoot.innerHTML = '

Hello World!<

' + 'h1 { color: red; }<';

Slide 30

Slide 30 text

• Inside shadow DOM • :host, :host(), :host-context(), ::slotted() • Outside shadow DOM • CSS Shadow Parts: ::part() • In Discussion: ::theme() • Deprecated: >>> (“shadow piercing”) Shadow DOM - CSS Selectors

Slide 31

Slide 31 text

• :host: Selects the shadow host element • :host(): Selects the shadow host element only, if it has a certain class • :host-context(): Selects the shadow host element only, if the selector given as the function's parameter matches the shadow host's ancestor(s) in the place it sits inside the DOM hierarchy • :::slotted(): Selects a slotted element if it matches the selector Shadow DOM - CSS Selectors - Inside Shadow DOM document shadow host Document Tree Shadow Tree shadow root Shadow boundary
<
<
<
<
<
:::slotted(div) {} < < <

Slide 32

Slide 32 text

Shadow DOM - CSS Selectors - Inside Shadow DOM :host { display: block; background-color: blue; } :host(.red) { background-color: red; } :host-context(main) { background-color: yellow; } :::slotted(article) { background-color: black; } <

Before slot<

Default Slot content<

After slot<

<
< < < < < Article Content < <

Slide 33

Slide 33 text

• CSS Shadow Parts (Working Draft): :::part() • Allows to selectively expose elements from the shadow tree to the outside page for styling purposes • https://www.w3.org/TR/css-shadow-parts-1/ • CSS Shadow Parts (Unofficial Draft): :::theme() • Matches all given parts, no matter how deeply nested they are • http://tabatkins.github.io/specs/css-shadow-parts/#part-theme Shadow DOM - CSS Selectors - Outside Shadow DOM

Slide 34

Slide 34 text

• Allows to selectively expose elements from the shadow tree to the outside page for styling purposes Shadow DOM - CSS Selectors - Outside Shadow DOM - ::part header { background-color: crimson; } <
Style me< <
< my-element.styled:::part(my-header) { background-color: black; color: white; } < < <

Slide 35

Slide 35 text

• If a nested Web Component will expose an inner Web Component’s part, it has to use exportparts=“part1 part2 …” instead of part=“name” Shadow DOM - CSS Selectors - Outside Shadow DOM - ::part my-element:::part(textspan) { color: red; } < Innerspan< Textspan< < <

Slide 36

Slide 36 text

Importing Web Components

Slide 37

Slide 37 text

• ES6 Import • Via Code • Deprecated: HTML Import Importing Web Components < /// static import import './my-element.js'; /// dynamic import import('./my-element.js') .then(.....); /// or lazy-loading via const script = document.createElement('script'); script.src = './my-element.js'; document.body.appendChild(script); const myElement = document.createElement('my-element'); document.body.appendChild(myElement);

Slide 38

Slide 38 text

CustomElementRegistry

Slide 39

Slide 39 text

• Window global registry for all custom elements • API • define(): defines a new element • get(): returns the constructor of an element (or undefined) • whenDefined(): returns a promise which is fulfilled, when the custom element is available • upgrade(): upgrades an element before it is connected to its shadow root CustomElementRegistry

Slide 40

Slide 40 text

Take Care! A sneak peek into the future

Slide 41

Slide 41 text

Versioning My library A My library B 3rd party library 1.0 3rd party library 2.0 Application defines defines Uncaught DOMException: Failed to execute ‘define’ on ‘CustomElementRegistry’: the name “my-web- component” has already been used with this registry.

Slide 42

Slide 42 text

• If you wish to sync them, then you’ll need to sync them! • “Reflecting properties to attributes” or “Reflected DOM Attributes” • Helps to keep DOM representation in sync with its JavaScript state • Watch out! Properties can hold objects, numbers etc. Attributes only strings • Attributes vs Properties input = document.createElement('input'); console.log(input.getAttribute('value')); /// null input.value = 'one'; console.log(input.getAttribute('value')); /// null input.setAttribute('value', 'two'); console.log(input.value); /// one

Slide 43

Slide 43 text

• Easy things may require more code • Attributes vs Properties • addEventListener etc. • Bigger component libraries need a strong foundation • Think of when and how to render your component • There is no virtual DOM like in React or Angular • You may need to think of partial DOM updates for bigger components Boilerplate & render loops

Slide 44

Slide 44 text

• Forms and Web Components don’t play well yet • FormData is not populated • does not work • Form Participation API aka “Form Attached Controls” is discussion • https://github.com/w3c/webcomponents/issues/815 • https://github.com/w3c/webcomponents/issues/814 • https://github.com/github/image-crop-element/pull/15 • https://github.com/axa-ch/patterns-library/issues/1258 Form Participation API

Slide 45

Slide 45 text

• Already good possibilities to create framework-independent components • Decide between view-only and data-controlling Web Components • Keep in mind, what is not working yet, but will come in the future • Leverage the shadow DOM to encapsulate your Web Components • Think of your Web Components API! Web Components - Summary

Slide 46

Slide 46 text

• Blog Series: “The Perks & Flaws of Web Components” • https://bit.ly/web-components-series • Web Components Cheat Sheet • https://www.thinktecture.com/de/cheatsheet/web-components/ More info about Web Components

Slide 47

Slide 47 text

And… now? • Try it out! Fiddle around with Web Components • Use them! Include first small Web Components into your application • Be prepared! Keep up to date with the development of Web Components • Slides & Repo: https://thinktecture.com/manuel-rauber • Contact: [email protected] - @manuelrauber