Web components through the eyes of a newcomer

A presentation at Florida Drupal Camp in February 2021 in by Brian Perry

Slide 1

Slide 1

Slide 2

Slide 2

Slide 3

Slide 3

Brian Perry » Front End Architect at Bounteous » Rocking the Chicago ‘burbs » Lover of all things components… and Nintendo @bricomedy brianperry.dev

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 aren’t they part of my workflow?

Slide 10

Slide 10

Warning: not an expert

Slide 11

Slide 11

I look to: @btopro @salem_cobalt @castastrophee @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

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

Slide 28

Slide 28

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 29

Slide 29

In case of emergency break glass opt out of the Shadow DOM

Slide 30

Slide 30

Building <election-tracker> Take 1: Vanilla JS

Slide 31

Slide 31

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 32

Slide 32

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 33

Slide 33

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 34

Slide 34

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 35

Slide 35

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 36

Slide 36

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

Slide 37

Slide 37

Wait for it…

Slide 38

Slide 38

So. Many. Libraries.

Slide 39

Slide 39

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

Slide 40

Slide 40

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

Slide 41

Slide 41

Rendering a headline (LitElement 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 42

Slide 42

results-tracker.js import { LitElement, html } from “lit-element”; 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 43

Slide 43

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

Slide 44

Slide 44

Add scoped styling import { LitElement, html, css } from “lit-element”; 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 45

Slide 45

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 46

Slide 46

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

Slide 47

Slide 47

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

Slide 48

Slide 48

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

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

Managing Application state » Didn’t come accross any clear pattern or best practice. » Could roll your own. » Could use any JS based framework or state management library. » Would be nice if a default standard existed (think React context) » Maybe this is an unreasonable expectation…

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. » Not yet comfortable enough for a full app/design system. » I would use a library, but one close to the vanilla API.

Slide 53

Slide 53

A (possibly unusual?) use case

Slide 54

Slide 54

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?

Slide 55

Slide 55

At least I’ve got this cool election tracker. Thanks! brian.perry@bounteous.com @bricomedy brianperry.dev

Slide 56

Slide 56