Web Components – komponenty bez frameworków
Przez ostatnie lata ekosystem JavaScriptu był zdominowany przez frameworki takie jak React, Vue czy Angular. Każdy z nich oferuje własny system komponentów, własny cykl życia i własne abstrakcje. Jednak od dłuższego czasu istnieje coś, co daje nam podobne możliwości bez instalowania ani jednej zależności – Web Components. To natywny standard wbudowany bezpośrednio w przeglądarki, który dojrzał na tyle, że warto mu poświęcić poważną uwagę.
Czym są Web Components?
Web Components to zbiór trzech niezależnych specyfikacji W3C, które razem tworzą kompletny system komponentów:
- Custom Elements – pozwalają definiować własne tagi HTML wraz z ich zachowaniem
- Shadow DOM – umożliwia enkapsulację struktury, stylów i logiki komponentu
- HTML Templates – znaczniki
<template>i<slot>do definiowania szablonów wielokrotnego użytku
Wszystkie trzy technologie są obsługiwane przez wszystkie nowoczesne przeglądarki – Chrome, Firefox, Safari, Edge – bez żadnych polyfilli. To ogromna zmiana w porównaniu z sytuacją sprzed kilku lat, gdy wsparcie było fragmentaryczne i problematyczne.
Custom Elements – Twoje własne tagi HTML
Pierwszym filarem Web Components są Custom Elements. Dzięki nim możesz zarejestrować własny element HTML i nadać mu dowolne zachowanie przy użyciu czystego JavaScriptu.
class MojPrzycisk extends HTMLElement {
constructor() {
super();
this.addEventListener('click', () => {
console.log('Kliknięto!');
});
}
connectedCallback() {
this.innerHTML = `<button>${this.getAttribute('label') || 'Kliknij mnie'}</button>`;
}
}
customElements.define('moj-przycisk', MojPrzycisk);
Po zarejestrowaniu takiego elementu możesz go używać w HTML jak każdego innego tagu:
<moj-przycisk label="Zapisz"></moj-przycisk>
Ważna zasada: nazwy Custom Elements muszą zawierać myślnik (np. moj-przycisk, app-header). To celowy zabieg, który pozwala przeglądarce odróżnić elementy natywne od niestandardowych i zapewnia, że przyszłe elementy HTML nigdy nie wejdą w konflikt z Twoimi komponentami.
Lifecycle callbacks
Custom Elements posiadają bogaty zestaw metod cyklu życia:
connectedCallback()– wywoływana, gdy element zostaje dodany do DOMdisconnectedCallback()– wywoływana, gdy element zostaje usunięty z DOMattributeChangedCallback(name, oldValue, newValue)– wywoływana przy zmianie atrybutuadoptedCallback()– wywoływana, gdy element zostaje przeniesiony do innego dokumentu
Aby attributeChangedCallback działało, należy zadeklarować obserwowane atrybuty:
static get observedAttributes() {
return ['label', 'disabled'];
}
Shadow DOM – enkapsulacja na poziomie przeglądarki
Shadow DOM to mechanizm, który pozwala tworzyć "ukryte" drzewo DOM wewnątrz elementu, odizolowane od reszty strony. Style zdefiniowane wewnątrz Shadow DOM nie wpływają na zewnętrzny dokument i odwrotnie – style globalne nie "wnikają" do środka komponentu.
class KartaUzytkownika extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
.karta {
background: #f0f4ff;
border-radius: 12px;
padding: 16px;
font-family: sans-serif;
}
h2 { color: #333; margin: 0 0 8px; }
p { color: #666; margin: 0; }
</style>
<div class="karta">
<h2>Jan Kowalski</h2>
<p>Frontend Developer</p>
</div>
`;
}
}
customElements.define('karta-uzytkownika', KartaUzytkownika);
Parametr mode: 'open' oznacza, że Shadow DOM jest dostępny z zewnątrz przez element.shadowRoot. Tryb closed całkowicie blokuje zewnętrzny dostęp – przydatne w bibliotekach komponentów, gdzie nie chcemy, aby użytkownicy ingerowali w wewnętrzną strukturę.
HTML Templates i Sloty
Element <template> pozwala zdefiniować fragment HTML, który nie jest renderowany podczas ładowania strony, ale może być wielokrotnie klonowany i wstawiany do DOM:
<template id="szablon-karty">
<style>
.karta { border: 1px solid #ddd; padding: 16px; border-radius: 8px; }
</style>
<div class="karta">
<slot name="tytul"></slot>
<slot name="tresc">Brak treści</slot>
</div>
</template>
Sloty (<slot>) to miejsca, w których użytkownik komponentu może wstrzyknąć własną treść – to odpowiednik koncepcji "children" znanych z Reacta czy "ng-content" z Angulara. Użycie takiego szablonu:
<moja-karta>
<h3 slot="tytul">Tytuł karty</h3>
<p slot="tresc">To jest treść mojej karty.</p>
</moja-karta>
A w klasie komponentu:
connectedCallback() {
const szablon = document.getElementById('szablon-karty');
const klon = szablon.content.cloneNode(true);
this.attachShadow({ mode: 'open' }).appendChild(klon);
}
Komunikacja z komponentem
Jednym z kluczowych aspektów pracy z Web Components jest komunikacja – zarówno "do środka" (przekazywanie danych do komponentu), jak i "na zewnątrz" (reagowanie na zdarzenia komponentu).
Atrybuty i właściwości
Dane przekazujemy do komponentu przez atrybuty HTML (zawsze ciągi znaków) lub przez właściwości JavaScriptowe (mogą być dowolnego typu). Dobrą praktyką jest synchronizowanie obu:
get value() {
return this._value;
}
set value(val) {
this._value = val;
this.setAttribute('value', val);
}
Własne zdarzenia (Custom Events)
Komponent powinien komunikować się z zewnętrznym światem przez zdarzenia:
this.dispatchEvent(new CustomEvent('zmiana-wartosci', {
detail: { nowaWartosc: this._value },
bubbles: true,
composed: true // ważne! pozwala zdarzeniu przebić się przez Shadow DOM
}));
Parametr composed: true jest kluczowy – bez niego zdarzenie zatrzyma się na granicy Shadow DOM i nie dotrze do elementów nadrzędnych.
Web Components a frameworki – kiedy co wybrać?
To pytanie, które zadaje sobie wielu deweloperów. Odpowiedź, jak to zwykle w programowaniu bywa, brzmi: "to zależy".
Web Components sprawdzają się doskonale, gdy:
- Tworzysz Design System lub bibliotekę komponentów UI, która ma być używana w różnych projektach – niezależnie od frameworka
- Chcesz mieć trwałe komponenty odporne na zmiany frameworków (Web Components będą działać za 10 lat)
- Budujesz aplikacje o niskiej złożoności, gdzie overhead frameworka byłby nieuzasadniony
- Pracujesz nad projektami CMS-owymi lub stronami, gdzie trzeba wstrzykiwać interaktywność w istniejące HTML
- Chcesz tworzyć mikrofrontendy, gdzie różne teamsy używają różnych technologii
Frameworki mają przewagę, gdy:
- Potrzebujesz zaawansowanego zarządzania stanem na poziomie aplikacji
- Budujesz duże SPA z kompleksowym routingiem
- Chcesz korzystać z bogatego ekosystemu gotowych narzędzi i bibliotek
- Twój zespół już dobrze zna dany framework
Warto podkreślić, że Web Components i frameworki nie wykluczają się wzajemnie. React, Vue i Angular doskonale współpracują z natywną specyfikacją – możesz używać Web Components wewnątrz aplikacji frameworkowej.
Lit – gdy czyste Web Components to za mało
Praca z czystym API Web Components bywa czasami rozwlekła i powtarzalna. Dlatego warto wspomnieć o Lit – ultralekkiej bibliotece stworzonej przez Google (wcześniej znana jako LitElement / Polymer), która dodaje wygodne abstrakcje na wierzchu natywnego API.
Lit waży zaledwie ~5KB minified+gzip, a oferuje reaktywne właściwości, efektywny system szablonów i wygodne dekoratory. Przykładowy komponent w Lit:
import { LitElement, html, css } from 'lit';
import { customElement, property } from 'lit/decorators.js';
@customElement('licznik-klikniec')
class LicznikKlikniec extends LitElement {
static styles = css`
button { background: #6200ee; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; }
span { font-size: 1.5rem; margin-left: 12px; }
`;
@property({ type: Number }) licznik = 0;
render() {
return html`
<button @click=${() => this.licznik++}>Kliknij</button>
<span>${this.licznik}</span>
`;
}
}
Lit jest szczególnie polecany do budowania Design Systemów – używają go m.in. Adobe (Spectrum Web Components) czy Google (Material Web).
SEO i dostępność
Jedną z obaw deweloperów jest wpływ Web Components na SEO. Boty Google doskonale radzą sobie z renderowaniem Custom Elements i Shadow DOM – Googlebot wykonuje JavaScript i indeksuje treść. Niemniej jednak, dla krytycznych treści SEO warto zadbać o Server-Side Rendering (SSR) lub Declarative Shadow DOM (DSD) – nowy standard pozwalający serializować Shadow DOM do HTML po stronie serwera.
Jeśli chodzi o dostępność (a11y), Web Components wymagają takiej samej dbałości jak każda inna technologia – poprawne atrybuty ARIA, obsługa klawiatury i kompatybilność z czytnikami ekranowymi leżą w gestii dewelopera, nie w samej specyfikacji.
Podsumowanie
Web Components to dojrzała, stabilna technologia, która rozwiązuje prawdziwy problem – potrzebę tworzenia wielokrotnie używalnych, hermetycznych komponentów UI bez uzależniania się od konkretnego frameworka. Ich największa siłą jest interoperacyjność i długowieczność.
Nie są złotym środkiem na każdy problem i nie zastąpią Reacta czy Vue w budowaniu skomplikowanych aplikacji. Jednak w kontekście Design Systemów, mikrofrontendów czy małych interaktywnych widgetów, Web Components to po prostu najlepsze narzędzie dostępne natywnie w przeglądarce.
Jeśli jeszcze ich nie znasz – czas to zmienić. Wiedza o Web Components to inwestycja, która nie zestarzeją się razem z kolejną wersją ulubionego frameworka.