Web Components – das Ende der SPA-Framework-Ära?

Web Components – das Ende der SPA-Framework-Ära?

Web Components – ein Begriff, den wir immer öfter lesen und lesen werden. Dahinter stehen die Technologien Custom Elements, Shadow DOM und HTML Templates. Zusammen ergeben sie ein natives Komponentenmodell für unseren Browser. Es hilft uns, wiederverwendbare UI-Komponenten zu entwickeln, die wir in jeder App benutzen können. Völlig gleich, ob diese Anwendungen mit einem SPA-Framework entwickelt wurden.

Bedeutet das aber auch, dass wir eigentlich gar kein SPA-Framework mehr benötigen, sondern alles mit Web Components entwickeln können? Dieser spannenden Frage geht Manuel Rauber in diesem Webinar nach anhand einer Real-World-Demo-Applikation, Live-Coding und den Vor- und Nachteilen von Web Components.

667fbca1f58bc0215c744b5ae8f8e5d2?s=128

Manuel Rauber

May 20, 2020
Tweet

Transcript

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

  2. • The challenge • Web Components • What are they?

    • How to use them? • How to create them? Agenda
  3. The challenge

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

  5. DRY

  6. Components

  7. None
  8. <div>!</div>

  9. <div id="fancyWidget"> !</div> <html> <head> <script src="fancyFramework.js">!</script> !</head> <body> !</body>

    !</html> • No semantic • Intransparent • Changes are difficult • Framework dependent
  10. <div id="fancyWidget"> <html> <head> <script src="fancyFramework.js">!</script> !</head> <body> !</body> !</html>

    <span id="label">!</span> <button id="incButton">+!</button> <button id="decButton"!>-!</button> !</div> • Global Scoping • Naming conflicts • Styling conflicts
  11. <my-counter value="1" minimum="1" maximum="5">!</my-counter> <html> <head> <script type="module" src=“my-counter.js”>!</script> !</head>

    <body> !</body> !</html> • Semantic • Local Scoping • Configuration • Bundle Import
  12. Web Components

  13. Bring a native component model to web instead of having

    to use frameworks Web Components - What are they? Custom Element <my-element> Shadow DOM HTML Template <template>
  14. Can I use …? IE11 Edge Chrome Android Opera Safari

    iOS Custom Elements ❌ ✅ ✅ ✅ ✅ ⚠ ⚠ Shadow DOM ❌ ✅ ✅ ✅ ✅ ✅ ⚠ HTML Templates ❌ ✅ ✅ ✅ ✅ ✅ ✅
  15. Custom Elements

  16. • Create your own HTML tags • Lifecycle model (“connected”,

    “disconnected”, “attribute changed”, “adopted”) • Reusable • Decouple behavior from the document Custom Elements <my-rating value="3" stars="10">!</my-rating>
  17. • 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);
  18. • 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}`; } } } <my-rating value="5">!</my-rating>
  19. HTML Templates

  20. • HTML tag: <template> • Parsed, but not rendered •

    Instantiated via JavaScript HTML Templates <template> <header>My Image Card!</header> <img src="!!..." !/> <p>Description!</p> !</template> 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);
  21. Shadow DOM

  22. • 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
  23. • 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
  24. • attachShadow() attaches a shadow DOM to any HTML element

    • open and closed mode Shadow DOM <html> <head>!</head> <body> <div>!</div> <script> const shadowHost = document.querySelector('div'); const shadowRoot = shadowHost.attachShadow({ mode: 'open' }); shadowRoot.innerHTML = '<h1>Hello World!!</h1>'; !</script> !</body> !</html>
  25. • 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
  26. • A (named) placeholder to fill with your own markup

    • Basically mixes together two DOM trees: the shadow & light tree Shadow DOM - Slots <template> <style> :host { border: 1px solid black; display: block; } !</style> <div> <slot>!</slot> <slot name="another-slot">With Default Content!</slot> !</div> !</template> <my-element>!</my-element> !!!<!-- No slot filled !!--> <my-element> <div>Unnamed Slot Content!</div> !</my-element> <my-element> <div>Unnamed Slot Content 2!</div> <div slot="another-slot">Another Slot Content!</div> !</my-element>
  27. • 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 });
  28. • Styling is done via <style>-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; } !</style> <h1>Hello World!!</h1> !</template> const shadowRoot = document.querySelector('div') .attachShadow({ mode: 'open' }); shadowRoot.innerHTML = '<h1>Hello World!!</h1>' + '<style>h1 { color: red; }!</style>';
  29. • 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
  30. • :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 <div class="my-class">!</div> <body class="my-host-context"> <div>!</div> !</body> <div>!</div> <div slot="my-slot">!</div> <template> <style> !::slotted(div) {} !</style> <slot name="my-slot">!</slot> !</template>
  31. Shadow DOM - CSS Selectors - Inside Shadow DOM <template>

    <style> :host { display: block; background-color: blue; } :host(.red) { background-color: red; } :host-context(main) { background-color: yellow; } !::slotted(article) { background-color: black; } !</style> <div> <p>Before slot!</p> <slot>Default Slot content!</slot> <p>After slot!</p> !</div> !</template> <my-element>!</my-element> <my-element class="red">!</my-element> <main> <my-element>!</my-element> !</main> <my-element> <article> Article Content !</article> !</my-element>
  32. • 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
  33. • Allows to selectively expose elements from the shadow tree

    to the outside page for styling purposes Shadow DOM - CSS Selectors - Outside Shadow DOM - ::part <template> <style> header { background-color: crimson; } !</style> <div> <header part="my-header">Style me!</header> !</div> !</template> <style> my-element.styled!::part(my-header) { background-color: black; color: white; } !</style> <my-element>!</my-element> <my-element class="styled">!</my-element>
  34. • 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 <style> my-element!::part(textspan) { color: red; } !</style> <template id="my-element-outer-template"> <my-element-inner exportparts="innerspan textspan”> !</my-element-inner> !</template> <template id="my-element-inner-template"> <span part="innerspan">Innerspan!</span> <span part="textspan">Textspan!</span> !</template> <my-element>!</my-element>
  35. Importing Web Components

  36. • ES6 Import • Via Code • Deprecated: HTML Import

    Importing Web Components <link rel="import" href="my-element.html"> <script type="module" src="my-element.js">!</script> !// 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);
  37. CustomElementRegistry

  38. • 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
  39. Take Care! A sneak peek into the future

  40. Versioning My library A My library B 3rd party library

    1.0 3rd party library 2.0 Application <my-web-component /> <my-web-component /> defines defines Uncaught DOMException: Failed to execute ‘define’ on ‘CustomElementRegistry’: the name “my-web- component” has already been used with this registry.
  41. • 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
  42. • 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
  43. • Forms and Web Components don’t play well yet •

    FormData is not populated • <button type=“submit”> 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
  44. • 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
  45. • Blog Series: “The Perks & Flaws of Web Components”

    • https://bit.ly/web-components-series • Web Components Cheat Sheet • Coming soon: https://thinktecture.com/newsletter More info about Web Components
  46. 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: manuel.rauber@thinktecture.com - @manuelrauber