Pro Yearly is on sale from $80 to $50! »

WebComponents: Native Komponenten fürs Web – mal ohne Framework?

WebComponents: Native Komponenten fürs Web – mal ohne Framework?

Die Begriffe UI-Komponenten oder Controls kennen wir Entwickler seit "Ewigkeiten". Durch Kapselung erhalten wir modulare und wiederverwendbare Baukastenelemente, aus denen unsere UI-Anwendungen zusammengesetzt werden. Bisher mussten wir uns im Web eines SPA Frameworks bedienen. Ob Angular, React, Vue.js oder Polymer. Doch keines dieser Frameworks wollte oder konnte so recht mit dem anderen zusammenarbeiten. Dies könnte sich in Zukunft ändern mit WebComponents. Diese beschreiben anhand von Standards wie CustomElements ein Komponentenmodell für das Web und bietet damit erstmal die Möglichkeit, native Komponenten im Browser gänzlich ohne ein Framework oder aber über Framework-Grenzen hinweg einzusetzen. In dieser Session klärt Manuel Rauber wie WebComponents funktionieren, wo die Vorteile und Nachteile liegen – und, vermutlich am spannendsten: er geht der Frage in gewohnt praktischer Manier nach, ob WebComponents mit aktuellen Single-Page Application (SPA) Frameworks wie Angular oder React genutzt und erstellt werden können. Abgerundet wird das Ganze mit einem Ausblick, wie wir künftig Web-Anwendungen mit Web Components entwickeln könnten. Auf zum nächsten Schritt!

GitHub: https://github.com/thinktecture-labs/web-components-demo

667fbca1f58bc0215c744b5ae8f8e5d2?s=128

Manuel Rauber

November 19, 2019
Tweet

Transcript

  1. Web Components: Native Komponenten für’s Web Mal ohne Framework Manuel

    Rauber @ManuelRauber Consultant
  2. Manuel Rauber Consultant @ Thinktecture AG manuel.rauber@thinktecture.com @manuelrauber https://manuel-rauber.com Microsoft

    MVP Who Am I?
  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. The challenge

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

  6. DRY

  7. Components Components Components Components

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

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

    • No semantic • Intransparent • Changes are difficult • Framework dependent
  11. <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
  12. <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
  13. Web Components

  14. • 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?
  15. Can I use …? IE11 Edge Chrome Android Opera Safari

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

  17. • 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>
  18. • 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);
  19. • 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>
  20. HTML Templates

  21. • 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);
  22. Shadow DOM

  23. • 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
  24. • 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
  25. • 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>
  26. • 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
  27. • 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>
  28. • 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 });
  29. • 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>';
  30. • 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
  31. • :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>
  32. 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>
  33. • 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
  34. • 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>
  35. • 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>
  36. Importing Web Components

  37. • 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);
  38. CustomElementRegistry

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

  41. 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.
  42. • 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
  43. • 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
  44. • 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
  45. • 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
  46. • There is no built-in template engine, you may be

    used to from SPA frameworks like Angular, React, Vue etc. • JavaScript template strings are preferred for better readability • Can get complex and unreadable when conditions are used heavily No built-in template engine
  47. No built-in template engine - Lit Element example get previewTemplate()

    { return html` <div @click="${this.clickHandler}" class="preview"> ${this.preview.image ? html`<img src="${this.preview.image}" alt="Preview" !/>` : html`` } <div> <header>${this.preview.title}!</header> ${this.preview.description ? html`<p>${this.preview.description}!</p>` : html`` } !</div> !</div> `; }
  48. • 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
  49. • 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
  50. What about frameworks?!

  51. • 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?!
  52. • Bundle sizes per framework is quite high for small

    components • Possible long loading and parsing times • A solution could be: • Bundle Web Components without their framework • Load the framework in the hosting application • Watch out! All Web Components have to use the same framework version • Will get better over time with less and less framework involved • or frameworks compile to much smaller bundle sizes (e.g. Angular Ivy) What about frameworks?! - Bundle Sizes
  53. DEMO

  54. Conclusion

  55. • 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
  56. 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: https://speakerdeck.com/manuelrauber • Repository: https://github.com/thinktecture-labs/web-components-chat • Contact: manuel.rauber@thinktecture.com - @manuelrauber