Web components through the eyes of a newcomer

A presentation at Decoupled Days 2021 in July 2021 in by Brian Perry

Slide 1

Slide 1

Slide 2

Slide 2

Brian Perry » Sr. Technology Consultant, Decoupled Architectures @ Pantheon » Initiative Coordinator: Decoupled Menus Initiative » Rocking the Chicago ‘burbs » Lover of all things components… and Nintendo d.o: brianperry @bricomedy brianperry.dev

Slide 3

Slide 3

Slide 4

Slide 4

Slide 5

Slide 5

Components have taken over the web

Slide 6

Slide 6

We’re here to talk about Web Components

Slide 7

Slide 7

Like React and Angular, right?

Slide 8

Slide 8

Web components A set of web platform APIs, not tied to a specific framework » Custom elements » Shadow DOM » HTML Templates

Slide 9

Slide 9

I ❤ components. So I should ❤ Web Components. ! So why weren’t they part of my workflow?

Slide 10

Slide 10

Warning: not an expert

Slide 11

Slide 11

I look to: @castastrophee @salem_cobalt @btopro @illepic

Slide 12

Slide 12

Can I Use Web Components? 1 IE 11 can be supported using polyfills 1

Slide 13

Slide 13

Can Can I Use Use Web Components? 2 Find many more examples at https://wild.open-wc.org/ 2

Slide 14

Slide 14

I should build an example web component. But what could be a relevant example in November 2020?

Slide 15

Slide 15

Slide 16

Slide 16

Slide 17

Slide 17

Slide 18

Slide 18

Slide 19

Slide 19

Slide 20

Slide 20

Slide 21

Slide 21

Slide 22

Slide 22

Slide 23

Slide 23

My own personal election tracker

Slide 24

Slide 24

Using <election-tracker> » Import script as JS module. Could be: » local file » NPM dependency (@backlineint/results-tracker) » Use your custom element in markup » Pass data in using attributes Note: If your component has external dependencies, you’ll need to use a bundler (Webpack, Rollup, Parcel, etc.)

Slide 25

Slide 25

Custom Elements 3 3 https://codepen.io/brianperry/pen/RwGPLBx

Slide 26

Slide 26

Shadow DOM » Encapsulated DOM Tree » Separate from main DOM » Elements won’t collide » Scoped styles » Super spooky

Slide 27

Slide 27

Building <election-tracker> Take 1: Vanilla JS

Slide 28

Slide 28

Rendering a Headline index.html <html> <head> <title>Results Tracker Heading</title> <meta charset=”UTF-8” /> </head> <body> <results-tracker headline=”Race Between Old Men Too Close To Call” /> <script type=”module” src=”src/results-tracker.js”></script> </body> </html>

Slide 29

Slide 29

results-tracker.js class ResultsTracker extends HTMLElement { constructor() { // Always call super first in constructor super(); // Create a shadow root const shadow = this.attachShadow({ mode: “open” }); // sets and returns ‘this.shadowRoot’ // Create wrapping element const wrapper = document.createElement(“div”); wrapper.setAttribute(“class”, “results-tracker”); this.headlineElement = document.createElement(“h2”); this.headlineElement.setAttribute(“class”, “results-tracker__headline”); this.headlineElement.textContent = this.getAttribute(“headline”); wrapper.appendChild(this.headlineElement); // Attach the results tracker to the shadow DOM. shadow.appendChild(wrapper); } } // Define the new element customElements.define(“results-tracker”, ResultsTracker);

Slide 30

Slide 30

Refactoring to use <template> class ResultsTracker extends HTMLElement { constructor() { super(); this.shadow = this.attachShadow({ mode: “open” }); // Templates are not referenced in the DOM, but can be referenced / cloned using js const template = document.createElement(“template”); template.innerHTML = <div class="results-tracker"> <div class="results-tracker__headline"> <h2>${this.getAttribute("headline")}</h2> </div> </div>; // Attach the template to the Shadow DOM this.shadow.appendChild(template.content); } } customElements.define(“results-tracker”, ResultsTracker);

Slide 31

Slide 31

Add scoped styling class ResultsTracker extends HTMLElement { constructor() { super(); /* Removed for brevity… / // Create CSS to apply to the shadow dom const style = document.createElement(“style”); style.textContent = :host { font-family: 'Libre Franklin', helvetica, arial, sans-serif; } h2 { margin: .5rem 0; font-family: 'Domine', serif; font-weight: 700; font-size: 36px; text-align: center; }; // Attach the styles to the shadow dom shadow.appendChild(style); / Removed for brevity… */ } }

Slide 32

Slide 32

Observe attributes and re-render if changed class ResultsTracker extends HTMLElement { // Specify observed attributes for attributeChangedCallback static get observedAttributes() { return [“headline”]; } constructor() { /* Removed for brevity */ } // Custom element lifecycle callback function attributeChangedCallback(name, oldValue, newValue) { // Compare old to new to prevent unnecessary re-rendering if (oldValue !== newValue && name === “headline”) { this.shadow.querySelector(“.results-tracker__headline h2”).textContent = newValue; } } } // Define the new element customElements.define(“results-tracker”, ResultsTracker);

Slide 33

Slide 33

That was too much work for a headline… 4 Shouldn’t this be easier? 4 https://codesandbox.io/s/results-tracker-heading-vanilla-js-lunes

Slide 34

Slide 34

Wait for it…

Slide 35

Slide 35

So. Many. Libraries.

Slide 36

Slide 36

» Lit » Stencil » FastElement » Haunted » Hybrids » Many more… Aren’t we just back where we started?

Slide 37

Slide 37

Only one way to find out… ! Building <election-tracker> Take 2: Lit

Slide 38

Slide 38

Rendering a headline (Lit version) index.html <html> <head> <title>Results Tracker Heading - Lit</title> <meta charset=”UTF-8” /> </head> <body> <results-tracker headline=”Race Between Old Men Too Close To Call” /> <script type=”module” src=”src/results-tracker.js”></script> </body> </html> (same as vanilla js version)

Slide 39

Slide 39

results-tracker.js import { LitElement, html } from “lit”; export class ResultsTracker extends LitElement { static get properties() { return { headline: { type: String }, }; } render() { return html<div class="results-tracker"> <div class="results-tracker__headline"> <h2>${this.headline}</h2> </div> </div>; } } window.customElements.define(“results-tracker”, ResultsTracker);

Slide 40

Slide 40

A lot with less » Renders custom element » Templating » Observes updates to attributes Just need to add scoped styles.

Slide 41

Slide 41

Add scoped styling import { LitElement, html, css } from “lit”; export class ResultsTracker extends LitElement { static get styles() { return css:host { font-family: "Libre Franklin", helvetica, arial, sans-serif; } .results-tracker__headline h2 { margin: 0.5rem 0; font-family: "Domine", serif; font-weight: 700; font-size: 36px; text-align: center; }; } // Properties… // Render method… } window.customElements.define(“results-tracker”, ResultsTracker);

Slide 42

Slide 42

Converting attributes // Vanilla JS - have to manually transform string attributes processCandidates() { this.candidates = JSON.parse(this.getAttribute(‘candidates’)); // … } // Define the type of your property, and LitElement will automatically // handle conversion for you. static get properties() { return { /** * An array of objects containing data for each candidate */ candidates: {type: Array}, }; } (And many other DX niceties)

Slide 43

Slide 43

Feels like a more appropriate amount of work for a headline… and especially the full results-tracker 5 https://codesandbox.io/s/election-results-tracker-thk26 5

Slide 44

Slide 44

Stencil “Compiler that generates web components” Provides extra capabilities on top of Web Components: » Prerendering » Objects-as-properties » Virtual DOM » JSX » Async Rendering

Slide 45

Slide 45

Vue Supports web components as a build target. But… It still requires the Vue library as a global dependency. !

Slide 46

Slide 46

Odds N’ Ends

Slide 47

Slide 47

Scoped Styles 6 6 https://codesandbox.io/s/election-results-tracker-global-styling-options-w0i3e?file=/src/styles.css

Slide 48

Slide 48

Here’s how I’ve been making sense of this… » Only inherited properties pierce the shadow DOM » Everything else requires the component to expose a styling hook: » CSS custom properties (variables) » Classes » Shadow Parts » Slots

Slide 49

Slide 49

Using web components with a framework custom-elementseverywhere.com outlines support for many frameworks. Support is pretty solid across the board. React has some notable limitations. !

Slide 50

Slide 50

React + Custom Elements 7 https://codesandbox.io/s/web-components-with-react-tvnwd 7

Slide 51

Slide 51

@lit-labs/react 8 https://codesandbox.io/s/lit-react-358ez 8

Slide 52

Slide 52

My (slightly) more educated views on web components » This was pretty hard to learn! » I’d turn to this for special purpose components today. » Close to getting comfortable enough to use this for a full app/design system. » I would use a library, but one close to the vanilla API.

Slide 53

Slide 53

On an infinite timescale… » I think some version of this concept will win out. » But how infinite is that timescale? » And will it be this take on web components? » Things are looking better every day…

Slide 54

Slide 54

Generic Drupal Web Components (GDWC)

Slide 55

Slide 55

Thanks! brian.perry@pantheon.io d.o: brianperry @bricomedy brianperry.dev