Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Web Components: native Komponenten fürs Web – das Ende der SPA Frameworks?

Web Components: native Komponenten fürs Web – das Ende der SPA Frameworks?

Komponenten – ein Begriff der uns Entwicklern sehr geläufig ist, egal ob als Windows-, Desktop- oder Webentwickler. Durch Kapselung von UI-Definition und Code-behind erhalten wir modulare und wiederverwendbare Bausteine zum Zusammensetzen der Oberfläche unserer Anwendung. Während wir damals im Web mit jQueryUI erste Komponenten entwickeln konnten, bedienen wir uns heute moderner Frameworks wie Angular, React oder Vue.js. Doch mit den Webstandards für HTML-Templates, Shadow DOM und Custom Elements erhalten wir die Möglichkeit, ein natives Komponentenmodell im Browser zu nutzen, gänzlich ohne Frameworks: Es leben die Web Components! In dieser Session klärt Manuel Rauber, wie Web Components funktionieren, wo die Vorteile und Nachteile liegen und ob wir heutzutage schon in der Lage sind, Single Page Applications zu entwickeln, die gänzlich ohne Frameworks auskommen. Sind Sie auch schon auf die Antworten gespannt?

https://github.com/thinktecture/basta-spring-2020-web-components

Manuel Rauber

February 25, 2020
Tweet

More Decks by Manuel Rauber

Other Decks in Programming

Transcript

  1. Web Components Native Komponenten für’s Web - das Ende der

    SPA Frameworks? Manuel Rauber @ManuelRauber Consultant @ Thinktecture AG
  2. Special Day “Modern Business Applications” Thema Sprecher Datum, Uhrzeit Raum

    Pragmatische Microservices mit .NET Core 3 – mehr als nur gRPC Christian Weyer DI, 25. Februar 2020, 10.30 bis 11.30 Ballsaal 2 Web Components: native Komponenten fürs Web – das Ende der SPA Frameworks? Manuel Rauber DI, 25. Februar 2020, 11.45 bis 12.45 Ballsaal 2 Progressive Web Apps – die Grundlagen Sebastian Springer DI, 25. Februar 2020, 14.15 bis 15.15 Ballsaal 2 Progressive Web-Apps mit Angular: Tipps für Fortgeschrittene Christian Liebel DI, 25. Februar 2020, 15.30 bis 16.30 Ballsaal 2 Blazor: SPAs im Browser mit C# und WebAssembly Christian Weyer DI, 25. Februar 2020, 17.15 bis 18.15 Ballsaal 2
  3. • The challenge • Web Components • What are they?

    • How to use them? • How to create them? • Take Care & a peek into the future • What about frameworks?! Agenda
  4. DRY

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

    !</html> • No semantic • Intransparent • Changes are difficult • Framework dependent
  6. <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
  7. <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
  8. • Web Components are not a standard, but a collection

    of 3 technologies • Custom Elements • HTML templates • Shadow DOM • (HTML Imports) • Bring a native component model to web instead of having to use frameworks Web Components - What are they?
  9. Can I use …? IE11 Edge Chrome Android Opera Safari

    iOS Custom Elements ❌ ✅ ✅ ✅ ✅ ⚠ ⚠ Shadow DOM ❌ ✅ ✅ ✅ ✅ ⚠ ⚠ HTML Templates ❌ ✅ ✅ ✅ ✅ ✅ ✅
  10. • 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>
  11. • 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);
  12. • 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>
  13. • 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);
  14. • 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
  15. • 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
  16. • 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>
  17. • 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
  18. • 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>
  19. • 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 });
  20. • 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>';
  21. • 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
  22. • :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>
  23. 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>
  24. • 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
  25. • 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>
  26. • 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>
  27. • 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);
  28. • 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
  29. 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.
  30. • Problem: Custom Element Registry is a window global object

    • Possible solution currently discussed by W3C • Scoped Custom Element Registry • https://github.com/w3c/webcomponents/issues/716 • Current usable solutions • Version your HTML tags • The host application is the only one loading WCs, even 3rd-party, but this could break other Web Components expecting a certain version Versioning
  31. • 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
  32. • Shadow DOM encapsulates the DOM only • JavaScript is

    still global scoped • Use common techniques for scoping JavaScript • May use the global scope to your advantage for app-wide configurations • But global polyfills can cause problems (e.g. if you load zone.js twice) JavaScript is still global scoped
  33. • 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
  34. • 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
  35. • 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
  36. • Web Components are an uprising good alternative to build

    frontends without frameworks involved • But! A lot of stuff is done easier with frameworks, e.g. Form Validation, State Handling, Data Management, CSS post processing • Why not wrap framework components into Web Components? • Angular: Angular Elements, @angular/elements • React: Wrap manually or use a community wrapper • Vue: Vue Web Components, @vue/web-components What about frameworks?!
  37. • Web Components help in giving a semantic structure in

    apps • Improves maintainability and reusability • Take care for stuff not working yet (e.g. Form Participation API) • SPA Frameworks can help in building Web Components • … with the odd of having bigger bundles • Web Components will make you think more about your APIs! Web Components - Summary
  38. 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