Slide 1

Slide 1 text

Web Components:
 Native Komponenten für’s Web Ohne und mit Framework Manuel Rauber @ManuelRauber Consultants Patrick Jahr @jahr_patrick Developer

Slide 2

Slide 2 text

Manuel Rauber Speakers Patrick Jahr [email protected] @jahr_patrick [email protected] @manuelrauber https://manuel-rauber.com Microsoft MVP

Slide 3

Slide 3 text

• 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?! • Micro Frontends 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

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

DRY

Slide 11

Slide 11 text

Components Components Components Components

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

!"div>

Slide 14

Slide 14 text

!"div> !"script> !"head> <body> !"body> !"html> • No semantic • Intransparent • Change are difficult • Framework dependent

Slide 15

Slide 15 text

!"script> !"head> <body> !"body> !"html> <span id="label">!"span> <button id="incButton">+!"button> <button id="decButton"!#!"button> !"div> • Global Scoping • Naming conflicts • Styling conflicts

Slide 16

Slide 16 text

!"my-counter> !"script> !"head> <body> !"body> !"html> • Semantic • Local Scoping • Configuration • Bundle Import

Slide 17

Slide 17 text

Web Components

Slide 18

Slide 18 text

• 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?

Slide 19

Slide 19 text

Can I use - Custom Elements?

Slide 20

Slide 20 text

Can I use - Shadow DOM?

Slide 21

Slide 21 text

Can I use - HTML templates?

Slide 22

Slide 22 text

Custom Elements

Slide 23

Slide 23 text

• Create your own HTML tags • Lifecycle model (“connected”, “disconnected”, “attribute changed”, “adopted”) • Reusable • Decouple behavior from the document Custom Elements !"my-rating>

Slide 24

Slide 24 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 25

Slide 25 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 26

Slide 26 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 27

Slide 27 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 28

Slide 28 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 29

Slide 29 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}`; } } } !"my-rating>

Slide 30

Slide 30 text

HTML Templates

Slide 31

Slide 31 text

• HTML tag: • Parsed, but not rendered • Instantiated via JavaScript HTML Templates My Image Card!"header> 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);

Slide 32

Slide 32 text

Shadow DOM

Slide 33

Slide 33 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 34

Slide 34 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 35

Slide 35 text

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

Slide 36

Slide 36 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 37

Slide 37 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; } !"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>

Slide 38

Slide 38 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 39

Slide 39 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; } !"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>';

Slide 40

Slide 40 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 41

Slide 41 text

• :host: Selects the shadow host element Shadow DOM - CSS Selectors - Inside Shadow DOM document shadow host Document Tree Shadow Tree shadow root Shadow boundary
!"div>

Slide 42

Slide 42 text

• :host: Selects the shadow host element • :host(): Selects the shadow host element only, if it has a certain class Shadow DOM - CSS Selectors - Inside Shadow DOM document shadow host Document Tree Shadow Tree shadow root Shadow boundary
!"div>

Slide 43

Slide 43 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 Shadow DOM - CSS Selectors - Inside Shadow DOM document shadow host Document Tree Shadow Tree shadow root Shadow boundary
!"div> !"body>

Slide 44

Slide 44 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
!"div> !-slotted(div) {} !"style> <slot name="my-slot">!"slot> !"template>

Slide 45

Slide 45 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; } !"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>

Slide 46

Slide 46 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 47

Slide 47 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> <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>

Slide 48

Slide 48 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; } !"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>

Slide 49

Slide 49 text

Constructable Stylesheets

Slide 50

Slide 50 text

• W3C: “A collection of interesting ideas” • Currently, styling must be specified in each shadow DOM • As we know, current web pages have hundreds and thousands CSS styles • Long parsing time and memory costs! • But most Web Components (or at least within a component library) will use the same/shared stylings • However, component libraries may update CSS during runtime, which may thwart style sheet sharing • API allows to create, remove, update, replace style sheet rules Constructable Stylesheets - Motivation

Slide 51

Slide 51 text

Constructable Stylesheets - Demo Code const myElementSheet = new CSSStyleSheet(); class MyElement extends HTMLElement { constructor() { super(); const shadowRoot = this.attachShadow({ mode: "open" }); shadowRoot.adoptedStyleSheets = [myElementSheet]; } connectedCallback() { !% Only actually parse the stylesheet when the first instance is connected. if (myElementSheet.cssRules.length !. 0) { myElementSheet.replaceSync(styleText); } } } https://wicg.github.io/construct-stylesheets/

Slide 52

Slide 52 text

Importing Web Components

Slide 53

Slide 53 text

• ES6 Import • Via Code • Deprecated: HTML Import Importing Web Components !"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);

Slide 54

Slide 54 text

CustomElementRegistry

Slide 55

Slide 55 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 56

Slide 56 text

Take Care! A sneak peek into the future

Slide 57

Slide 57 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 58

Slide 58 text

• 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

Slide 59

Slide 59 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 60

Slide 60 text

• 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

Slide 61

Slide 61 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 62

Slide 62 text

• 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

Slide 63

Slide 63 text

No built-in template engine - Lit Element example get previewTemplate() { return html`
${this.preview.image ? html`Preview ${this.preview.title}!"header> ${this.preview.description ? html`

${this.preview.description}!"p>` : html`` } !"div> !"div> `; }

Slide 64

Slide 64 text

• Currently there is no auto completion support for Web Components • Stencil tries to solve this by generating TypeScript Definition files • VS Code & W3C are discussing possible solutions • https://github.com/w3c/webcomponents/issues/776 • Stencil is currently generating a possible solution with its compiler Editor support // type definitions

Slide 65

Slide 65 text

• There is no standard build process • You can use any tool you want, e.g. webpack, parcel, etc. • Be aware: • CSS post processing • Asset management Build process // custom asset management

Slide 66

Slide 66 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 67

Slide 67 text

• Elements can not escape their stacking context • Highly needed for (modal) dialogs, tooltips, right-click menus, select, … • Current element does not fit for all use cases • Possible workaround: API in the shell Stacking UI !!!* Will be transformed, but should be a top layer modal dialog !!+ !"my-modal-dialog> !"my-wc-a> !"my-wc-b> !"body>

Slide 68

Slide 68 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 69

Slide 69 text

What about frameworks?!

Slide 70

Slide 70 text

• 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?!

Slide 71

Slide 71 text

• 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

Slide 72

Slide 72 text

• Angular Ivy could be used for creating Web Components • … as long as you don’t consume other Web Components! • Ivy will complain about other Web Components not knowing about properties and throw an exception during compilation time
 
 
 • Be aware, that Ivy is not yet production ready! • Think of removing zone.js and handle everything OnPush What about frameworks?! - Angular Property ‘propertyName' does not exist on type 'HTMLElement'.

Slide 73

Slide 73 text

DEMO

Slide 74

Slide 74 text

Micro Frontends

Slide 75

Slide 75 text

Micro Frontends Feature A Feature B Application Web API Web API UI UI

Slide 76

Slide 76 text

• Web Component != Micro Frontend • A Micro Frontend can be built by multiple Web Components • The whole Micro Frontend could be wrapped into a Web Component • Micro Frontend(s) are wrapped into a (SPA) shell Micro Frontends https://example.com Micro Frontend Dashboard (SPA) Shell

Slide 77

Slide 77 text

• Eager Loading • Server-side composition • Build-time integration • Lazy Loading • iFrames • JavaScript • Web Components Micro Frontends - Composition

Slide 78

Slide 78 text

• “Does this approach fit my company structure?” • SPA-Shell API depending on the composition approach • Data exchange / Data API • Routing • A single Micro Frontend routing is easy • A multi Micro Frontend routing is complex • Let the user F5 your application! • Routing is also a possibility for data exchange Micro Frontends - Challenges

Slide 79

Slide 79 text

Micro Frontends - Challenges https://example.com?mf1=/customer/1&mf2=balance/1 Micro Frontend 1 Micro Frontend 2 Dashboard

Slide 80

Slide 80 text

• Interesting possibility to have small customized UIs per feature • Think of how you’ll compose Micro Frontends (Micro Frontend Architecture) • Think of data exchange in Micro Frontends (if needed) • Think of single or multi routing possibilities (let the user F5 your app!) • Good for decoupling teams! Micro Frontends - Summary

Slide 81

Slide 81 text

Conclusion

Slide 82

Slide 82 text

• Web Components help in giving a semantic structure in apps • They encapsulate HTML and CSS • 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 • Micro Frontends help decouple teams Web Components - Summary

Slide 83

Slide 83 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: https://speakerdeck.com/manuelrauber • Repository: https://github.com/thinktecture/angular-days-2019-fall-web-components • Contact: [email protected], [email protected]