2 av 10 bruker web-komponenter: – Mindre avhengigheter!

Vi fikk frontendsjef Martin Klingenberg i Alv til å forklare web components for oss, ved å stille både dumme og ikke fullt så dumme spørsmål.

Martin Klingenberg, head of frontend i Alv. 📸: Kurt Lekanger
Martin Klingenberg, head of frontend i Alv. 📸: Kurt Lekanger Vis mer

Da Microsoft skulle lage en ny versjon av app-butikken sin, valgte de å bygge alt sammen fra bunnen av med web-komponenter i stedet for React. Og i det siste har stadig flere tatt i bruk denne «mystiske» teknologien som gjør det mulig å lage dine egne html-tagger.

Teknologien er en del av web-standarden og støttes av alle nettlesere. I det siste har den funnet veien blant annet inn i ulike frontendrammeverk, og en sjekk på Chromestatus.com viser at bortimot 20 prosent av alle nettsider som lastes nå bruker web-komponenter:

Andel av nettsider som bruker CustomElementRegistryDefine, som er en indikasjon på at nettsiden bruker Web Components. 📸: Chromestatus
Andel av nettsider som bruker CustomElementRegistryDefine, som er en indikasjon på at nettsiden bruker Web Components. 📸: Chromestatus Vis mer

Martin Klingenberg er «head of frontend» i Alv og er over gjennomsnittlig opptatt av web-komponenter.

Vi har stilt han alle de dumme spørsmålene om web-komponenter, så du slipper.

«Om en teknologi er bra nok for YouTube, er den bra nok for din app.»

– Martin, hva er web-komponenter?

– Webcomponents er en nettleserteknologi som lar oss definere egne html-tags ved hjelp av JavaScript.

– En tag kan være så komplisert som man ønsker. En komponent kan være en hel applikasjon om man vil. Eller så kan det være en liten knapp.

– Du kan jo lage gjenbrukbare komponenter i for eksempel React også? Hva er forskjellen?

– For å lage en React-komponent trenger man å importere React i nettleseren. Det kan gjøres på en så enkel måte som dette:

<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>

– Siden webcomponents er en nettleserteknologi, så trenger man ikke å importere noe som helst. Du kan definere komponenter helt uten noen som helst avhengighet.

– De aller fleste moderne nettlesere har støtte for webcomponents. Om nettleseren din ikke støtter webcomponents, så vil jeg anbefale at du fjerner Internet Explorer!

– Hva er "shadow DOM", og har det noe å gjøre med Reacts "virtual DOM"?

– Vi kan starte med å forklare hva “virtual dom” er. Det er en teknologi React bruker. React har en virtuell versjon av DOM-en i minnet. Når det skjer en endring i den virtuelle DOM-en så blir tilsvarende endringer påført i den faktiske DOM-en og brukeren vil se at ting har endret seg.

Shadow DOM er faktisk noe helt annet. En shadow DOM ligger faktisk i den vanlige DOM-en i nettleseren!

– Shadow DOM oppfører seg helt som den vanlige DOM-en som vi alle kjenner og elsker, med noen særegenheter. Man kan bruke shadow DOM-er til å enkapsulere CSS. Man vil også oppleve at enkelte DOM-operasjoner ikke går inn i shadow DOM-en uten at du gjør det eksplisitt.

image: 2 av 10 bruker web-komponenter: – Mindre avhengigheter!

– Jeg har implementert uvar.no med webcomponents og jeg kommer med et par eksempler på hva begrensningene er. Om man ikke har shadow-roots i DOM-en så vil man kunne spørre etter elementer på følgende måte:

document.getElementsByTagName(”div”); 

– På uvar.no så er det en div med ID view-container, men når jeg forsøker å spørre etter den med document.getElementById('view-container'); så får jeg returnert null. Om man skulle ønske å interagere med elementet direkte så må man spørre direkte mot webkomponentens DOM-API element.getElementById('view-container');

– Du har også noe som kalles "templates". Forklar...

Templates er ikke noe man egentlig trenger å forholde seg til når man skriver web-componenter. Template-taggen <template></template> er rett og slett en html-tag med innhold som ikke vises på nettsiden. Dette innholdet kan veldig enkelt gjenbrukes.

– Jeg har personlig aldri brukt html-templates i produksjon, så jeg har troen på at man kan lage bra web-components uten templates. Men for ordens skyld la oss ha et eksempel på bruken av templates. Eksempelet under lager en template og lager ti instanser av samme template.


// Vi lager et template element
const template = document.createElement("template");

// Vi gir templaten litt innhold og en ID
template.innerHTML = "

Klingen liker web-components

"; template.id = "templates-er-gøy"; // Vi legger templaten til i DOM'en document.body.appendChild(template); const templateContent = document.getElementById("templates-er-gøy"); for (let i = 0; i < 10; i++) { document.body.appendChild(templateContent.content.cloneNode(true)); }

– Et bruksområde for template elements, er at man rendrer html-templates med innhold med den backend-teknologien man ønsker. Om det er PHP- eller Razor-templates er ikke så farlig.

– Så kan man bruke web-komponenter til å rendre denne dataen på en god måte og gi brukeren interaktivitet. Fordelen ved å gjøre noe slikt, er at man viser dataen først når JavaScripten har blitt lastet skikkelig.

– Hva kan jeg bruke web-komponenter til?

– Det er jo en rekke bruksområder, men det mest opplagte bruksområdet er å lage komponent-biblioteker som fungerer på tvers av rammeverk. Det er flere selskaper som har gjort dette. IBM har Carbon, Adobe har laget sine egne Spectrum-components.

– Jeg tenker jo også at webcomponents kan brukes til å lage hele applikasjoner. Dermed kan man bruke webcomponents som en ren drop-in replacement for React, Angular og Vue. Eksempler kan være min tjeneste uvar.no eller nye Microsoft Teams.

– YouTube er kanskje den første store nettsiden som brukte webcomponents. Om en teknologi er bra nok for YouTube, er den bra nok for din app.

– Styrkene ligger i at man får en veldig lett applikasjon der det blir veldig lett å bytte ut deler av siden. I mine webcomponent-prosjekter føler jeg at jeg får mindre pakkeavhengigheter. Man har gjerne bare et lite bibliotek som gjør det enklere å lage komponenter.

– En annen fordel ved at man bygger små, enkle komponenter, er at om man strukturerer applikasjonen sin riktig så vil man kunne bruke hele eller deler av applikasjonen som en micro-frontend. Altså man kan legge til masse funksjonalitet på en nettside med to tags. En script tag + en custom html tag.

– Hvordan lager man web-komponenter?

– En web-komponent kan lages så enkelt som demonstrert her:

class MinFørsteWebcomponent extends HTMLElement {
  constructor() {
		 super();
    this.counter = 0;
  }

  connectedCallback() {
    const shadow = this.attachShadow({ mode: "open" });
    const button = this.makeButtonElement();
		 shadow.appendChild(button);
    
  }

  makeButtonElement() {
    const button = document.createElement("button");
    button.innerText = this.makeButtonText();
    button.addEventListener('click', () => {
      this.counter++;
      button.innerText = this.makeButtonText();
    });
    return button;
  }

  makeButtonText() {
    return `Du har trykket ${this.counter} ganger`;
  }
}
customElements.define("test-element", MinFørsteWebcomponent);

Nå kan denne legges til i DOM-en som en helt vanlig komponent:

document.body.appendChild(new MinFørsteWebcomponent());

– Dette er jo mye kode for å lage en basic counter. Og jeg ville nok aldri laget en komponent uten et bibliotek. La oss se hvor mye enklere denne koden kan bli gjennom å bruke mitt favoritt-bibliotek Lit for å lage webcomponents. Vi skal se samme funksjonalitet implementert i Lit:

import { html, LitElement, TemplateResult } from "lit";
import { customElement } from "lit/decorators/custom-element.js";
import { state } from "lit/decorators/state.js";

@customElement("click-component")
export class ClickComponent extends LitElement {

  @state()
  clickCount = 0;

  render(): TemplateResult {
    return html`<button @click=${() => this.clickCount++}>Du har trykket ${this.clickCount} ganger</button>`;
  }
}

– Som du ser, er denne koden betraktelig mye enklere å lese. Jeg foretrekker å skrive komponentene i TypeScript for å gjøre koden enda enklere å lese. Variabler med en @state-dekoratør vil trigge en oppdatering av DOM-en når verdien endres.

– Anbefaler du at man bruker et rammeverk?

Jeg anbefaler først og fremst Lit. Biblioteket er passe lett og har god dokumentasjon. Oppdatering av versjoner har så langt vært veldig enkelt.

– Det finnes mange biblioteker som hjelper deg med å lage webcomponents. En react-utvikler vil jo kanskje like Stencil da de benytter seg av JSX-templates.

– Ellers har jo Microsoft kommet på banen med fast-element som ligner veldig på Lit, men man kan enkelt definere templates utenfor komponenten. Litt som Angular.

– Er det noe som er vanskelig i web-komponenter?

Fordelen med godt enkapsulerte stiler, er at komponentenes stiler ikke lekker utover hele applikasjonen. Samtidig så kan man oppleve at det er mer krevende å endre på stilene til komponentene.

– Kanskje du ønsker å ha litt ekstra margin på et inputfelt eller ha komponenter du kan tweake fargene på litt avhengig av situasjon. Vi har i hovedsak to fremgangsmåter for å oppnå slike tweaks:

Alternativ 1: bruk CSS-variabler

– Om vi ser på vårt tidligere counter-eksempel, så kan vi legge til en gråfarge på knappen. Med følgende kode-snutt:

:host {
	--button-color: #999;
}

button {
	background: var(--button-color);
}

– Vi lager en variabel med en gråfarge, og vi bruker den gjennom å bruke var-keywordet. Variable må starte med -- Resultatet blir da seende slik ut:

image: 2 av 10 bruker web-komponenter: – Mindre avhengigheter!

– Om jeg nå vil overstyre denne fargen fra “utsiden” av komponenten, så kan jeg gjøre det på følgende måte:

click-component {
  --button-color: white;
}

Og knappen ser slik ut:

image: 2 av 10 bruker web-komponenter: – Mindre avhengigheter!

Alternativ 2: Bruk CSS parts

– CSS-variabler var jo ganske smooth for å overstyre en verdi. Hva om du vil gjøre større endringer på en komponent? Jo det er da CSS-parts kommer inn. Se på vår enkle komponent nå. Jeg har lagt til en part-attributt på knappen:

import { html, LitElement, TemplateResult } from "lit";
import { customElement } from "lit/decorators/custom-element.js";
import { state } from "lit/decorators/state.js";
import style from "./clickComponent.styles.scss";

@customElement("click-component")
export class ClickComponent extends LitElement {

  @state()
  clickCount = 0;

  static styles = [style];

  render(): TemplateResult {
    return html`<button part="button" @click=${() => this.clickCount++}>Du har trykket ${this.clickCount} ganger</button>`;
  }
}

– Nå kan vi gå hardere til verks for å få knappen til å se nice ut:

click-component::part(button) {
  background-color: #008CBA;
  border: none;
  color: white;
  padding: 15px 32px;
  text-align: center;
  text-decoration: none;
  display: inline-block;
  font-size: 16px;
}
image: 2 av 10 bruker web-komponenter: – Mindre avhengigheter!

– Vi ender jo da opp med en helt annen knapp. Jeg vil tro at man stort sett vil holde seg til enkle CSS-variabler, men kjekt å vite om muligheten for å kunne overstyre større deler av en komponent.

– Hva med fonter?

– Om man lager en hel applikasjon basert på webcomponents så vil man jo veldig gjerne bruke de samme fontene på tvers av hele applikasjonen. Man har kanskje en default font og et par ekstra fonter som brukes en gang i blant.

– Dette har vært noe jeg har knotet mye med selv. Man trenger ikke å bruke shadow-DOM. Man kan “åpne” komponenten, men det kan gjøre livet vanskelig senere. Man vil ha en lukket shadow-DOM og fortsatt ha samme fonter på tvers. Det er vanskelig. Men jeg har brukt to forskjellige løsninger for å komme rundt problemet. Den ene er å bruke CSS-variabler til å spesifisere fonter.

– Som regel bruker jeg SCSS til å style mine webcomponent-applikasjoner og da lager jeg som regel én eller flere mixins.

– Hvordan bruker en web-komponenter andre har laget?

Man finner webcomponents der du finner npm-pakker, men det er to plasser jeg ofte faller tilbake til når jeg skal bruke andre sine webcomponents.

– På Awesome Web Components er det en liste med web-component-ressurser. Noen guider, biblioteker og undersøkelser. Den siden har svar på alt du lurer på når det gjelder web-components.

– Så har man jo webcomponents.org. Det er basically en side dedikert til å vise publiserte webcomponents. Denne siden var "awesome" i 2017, men jeg føler ofte mange av eksemplene har sluttet å fungere de siste årene.

– Noe mer kode24-lesere bør vite om web-komponenter?

Jeg tenker at leserne må få litt "hands on"-tid med webcomponents. Lag et lit-prosjekt med vite:

npm create vite@latest hands-on --template lit-ts

– Jeg liker også å gjøre integrasjonen med editoren litt bedre gjennom ts-lit-pluginen

– Ellers er det jo en pågående diskusjon om en utvidelse av webcomponent-speccen. Rett og slett muligheten til at man utvider eksisterende tags. Det skal kunne gjøres på denne måten:

<p is="klingens-paragraph">Mitt spesielle paragraph element</p>

– Dette har ikke blitt implementert i Safari, men alle andre nettlesere støtter denne teknologien. Det er selvfølgelig en lang diskusjon rundt dette på Github.