ADR-DS-004: Web Components
Beslutning
Komponentlogikk implementeres som Web Components i indeks-web og eksponeres til React-konsumenter via indeks-react. Shadow DOM unngås som hovedregel.
Eksempel: ix-field er ikke et input-felt — det er et omgivende lag som knytter label, input og feilmelding sammen med korrekte ARIA-relasjoner. Alt dette kan også gjøres med ren HTML; Web Component-en gjør det bare enklere og feilsikkert.
Shadow DOM: Vi unngår Shadow DOM som hovedregel. Shadow DOM isolerer markup fra resten av dokumentet, noe som gjør tilgjengelighetsarbeid vanskeligere — skjermlesere og ARIA-relasjoner må fungere på tvers av shadow boundaries, og konsumenter mister muligheten til å style med CSS-variabler. Unntaket er komponenter som viser visuelt innhold basert på universelt utformet data i markup — for eksempel et chart-element som rendres visuelt i Shadow DOM mens tabelldata forblir tilgjengelig i vanlig DOM.
Arkitektur: indeks-web (Web Components) → indeks-react (wrapper).
React-pakken eksponerer komponentene med idiomatisk React API. Det meste av funksjonaliteten ligger i Web Component-en — React-laget håndterer React-spesifikke ting som event-mapping og prop-konvertering, slik at en ren-HTML-konsument får nøyaktig samme fullverdige komponent.
Hvordan indeks-react bygges og distribueres er beskrevet i ADR-DS-005.
Bevisste rammeverk-tilpasninger: React-laget kan ha målrettede tilpasninger for å fungere godt med utbredte React-verktøy — for eksempel props som gjør en komponent kompatibel med React Hook Form (register()), slik at skjemakobling blir idiomatisk. Slike tilpasninger handler utelukkende om integrasjon — å få React til å fungere — aldri om å gi React-konsumenter mer funksjonalitet eller en annen brukeropplevelse.
UI og UX skal være identisk uansett konsument. En bruker skal ikke kunne se eller kjenne — verken visuelt eller via tastatur og skjermleser — om et felt er bygget med ren HTML eller React. HTML-versjonen av hver komponent er fullverdig i seg selv; React-wrapperen legger ikke til noe ekstra, den kabler bare det som allerede finnes lenger ned i stacken. Det som skal skille seg, er API-flaten: React får idiomatiske props, typer og JSX, mens HTML får attributter og web component. Ulikt grensesnitt, lik komponent.
Tilpasninger er tillatt så lenge to ting holder: (1) Web Component-en forblir kilden til sannhet — logikken legges i WC-en når den også gagner ikke-React-konsumenter (f.eks. native change-events og fokushåndtering på riktig element), og React-laget begrenser seg til tynn kabling; og (2) HTML-pariteten bevares — en ren-HTML- eller Vue/Angular-konsument får samme fullverdige komponent, ikke en redusert variant. En React-only-egenskap som ikke lar seg uttrykke i WC-en, skal være liten, veldokumentert og ikke bryte de andre konsumentene.
Drivere for beslutningen
- Rammeverk-uavhengighet — webstandard fungerer i React, Vue, Angular, Astro og ren HTML
- Én kilde til sannhet for komponentlogikk
- Fremtidssikring uavhengig av rammeverk-trender
- Nettleser-native uten ekstra runtime
Bakgrunn
De fleste komponentene i Indeks er ren HTML-markup med CSS-styling — React wrapper bare dette, og man kan like gjerne bruke ren HTML. Men noen komponenter trenger mer: logikk, tilstandshåndtering eller sammensatt atferd som knytter flere elementer sammen.
SpareBank 1 har team som bruker ulike teknologier — ikke bare React. En løsning som låser kompleks komponentlogikk til React er ikke fremtidssikret.
Problemstilling
Hvordan implementerer vi komponentlogikk slik at den er tilgjengelig for alle team, uavhengig av hvilke JavaScript-rammeverk de bruker?
Konsekvenser
Hvem påvirkes?
- Team Designsystem — implementerer Web Components
- React-konsumenter — bruker React-wrapperen og slipper å forholde seg til maskineriet under; for team som vil bygge frontend uten å tenke på hvordan det fungerer, er dette et godt valg
- Andre team — får tilgang til komponentlogikk uten React-avhengighet; team som bruker andre rammeverk eller vil tilpasse mer, ser naturlig mer av kompleksiteten bak — men får samme komponent
- Designere — ingen endring
Abstraksjonsnivået skalerer altså med hvor mye konsumenten vil involvere seg: React skjuler mest, ren HTML og andre rammeverk eksponerer mer av maskineriet. Uansett nivå er komponenten — og brukeropplevelsen — den samme.
Ulemper
- SSR:
customElements.define()er en nettleser-API som ikke finnes i Node.js. Vi har testet SSR i de tilfellene vi har prøvd, og det har fungert godt — men vi har ikke verifisert alle rammeverk (Next.js o.l.) uttømmende - Team Designsystem må beherske Web Components-standarden (Custom Elements API) — i praksis en liten kostnad, siden komponentene vi skriver er forholdsvis enkle og konsumenter merker lite (de bruker React-wrapperen eller ren HTML)
- Konsumenter er selv ansvarlige for å laste
indeks-webén gang — dette forhindrer kollisjoner ved registrering av custom elements
Tiltak mot ulemper
- Etablere klare mønstre for Web Components-utvikling tidlig
- SSR-støtte må kartlegges per komponent — dette er et åpent spørsmål som vil bli adressert når aktuelle komponenter nærmer seg produksjon
Forkastede alternativer
Kun React-komponenter
Fortsette med React-only komponenter for all logikk.
Forkastet fordi: Begrenser gjenbruk til React-prosjekter. Vi skal støtte team som bruker andre teknologier enn React, og disse teamene ville vært utestengt fra de mest komplekse komponentene.
Lit
Et lett bibliotek fra Google for å skrive Web Components med deklarativ template-syntaks og reaktivitetssystem. Brukes av blant andre Adobe (Spectrum), SAP og ING.
Forkastet fordi: Vi ønsker å holde indeks-web fri for eksterne bibliotek- og rammeverk-avhengigheter. Lits verdi ligger i deklarativ templating og reaktiv rendering inne i komponenten — noe vi ikke trenger, fordi vi ikke bruker Shadow DOM. Våre Web Components er tynne wrappere som manipulerer lys-DOM direkte, så native Custom Elements er håndterbart for komponentene vi faktisk skriver. Vi ser også til designsystemet.no som gjør det samme med native Web Components — det gir oss mulighet til å lære av og bygge videre på deres erfaringer.
Stencil.js (Ionic)
Et kompilatorbasert rammeverk for Web Components med TypeScript-first API, JSX-syntaks og innebygd støtte for å generere React- og Vue-wrappers automatisk.
Forkastet fordi: Introduserer et kompilatorlag og en egen komponentmodell som gjør koden avhengig av Stencil — native Custom Elements er håndterbart uten dette. Automatisk wrapper-generering er en fordel vi ikke trenger siden vi uansett skriver React-wrapperen manuelt.
FAST (Microsoft)
Et bibliotek fra Microsoft for å bygge Web Components, brukt som fundament for Fluent UI Web Components.
Forkastet fordi: Samme begrunnelse som Lit — vi ønsker å holde indeks-web fri for eksterne avhengigheter. FAST introduserer i tillegg en egen komponentmodell og attributt-system som binder komponentene tettere til biblioteket enn native Custom Elements.
Multi-framework med delt TypeScript-kjerne
Dele forretningslogikk i et rammeverk-agnostisk TypeScript-bibliotek, og skrive rammeverk-spesifikke wrappers for React, Vue og Angular.
Forkastet fordi: Kompleks build-pipeline med mange wrappers å vedlikeholde. Fortsatt rammeverk-spesifikk kode i alle wrappers. Web Components løser det samme med én implementasjon.
Mitosis (Builder.io)
Et verktøy som kompilerer én komponentdefinisjon til React, Vue, Angular og Web Components.
Forkastet fordi: Umodent verktøy med begrenset adopsjon. Abstraksjonslaget over komponentdefinisjonene gjør feilsøking vanskeligere, og output-koden er ikke idiomatisk i noe av målrammeverket.
Deltakere