Powrót do strony głównej

Problemy web-komponentów: wydajność i API

Artykuł analizuje kluczowe problemy web-komponentów: nadmierny boilerplate dla właściwości, niską wydajność z powodu host-proxy, globalną rejestrację z konfliktami i niedogodności Shadow DOM. Podane są benchmarki i przykłady kodu.

Dlaczego web-komponenty spowalniają: benchmarki i kod
Advertisement 728x90

Problemy wydajności i API w web-components

Tworzenie web-componenta z jedną właściwością wymaga znacznej ilości kodu przez Custom Elements API. Podstawowa implementacja ograniczona jest do atrybutów tekstowych, co prowadzi do konieczności serializacji dla innych typów danych.

Przykład minimalnego komponentu:

class MyFoo extends HTMLElement {

	_bar = 0
	get bar() {
		return this._bar
	}
	set bar( next ) {
		this.setAttribute( 'bar', next )
	}
	
	static observedAttributes = [ 'bar' ]
	attributeChangedCallback( name, prev, next ) {
		this[ '_' + name ] = next
	}
	
}

globalThis.customElements.define( 'my-foo', MyFoo )

Aby wspierać liczby, dodaje się serializację JSON z flagą _muted_, aby uniknąć rekursji:

Google AdInline article slot
class MyFoo extends HTMLElement {

	_bar = 0
	get bar() {
		return this._bar
	}
	set bar( next ) {
		this._bar = next
		this._muted_ = true; try {
			this.setAttribute( 'bar', JSON.stringify( next ) )
		} finally { this._muted_ = false }
	}
	
	_muted_ = false
	static observedAttributes = [ 'bar' ]
	attributeChangedCallback( name, prev, next ) {
		if( this._muted_ ) return
		this[ '_' + name ] = JSON.parse( next )
	}
	
}

To prowadzi do problemów z ucieczką znaków w atrybutach i wymaga dodatkowych warstw abstrakcji dla dat, obiektów i innych typów.

Wydajność: mikrosekundy zabijają klatki

Web-components są o rzędy wolniejsze niż zwykłe obiekty JS ze względu na przekazywanie danych przez runtime hosta. Porównanie czasu tworzenia:

  • Zwykły obiekt JS: ~0,01 μs
  • Web-component: ~1–3 μs

Tysiąc komponentów zajmuje 1–3 ms na utworzenie, co zużywa istotną część 16 ms klatki. Zmiana właściwości również wymaga mikrosekund:

Google AdInline article slot
  • Proste przypisanie w JS: nanosekundy
  • Setter web-componenta: mikrosekundy

Przyczyna — ciągła wymiana danych między JS a C++ runtime przeglądarki, co blokuje JIT-inlining. Pamięć: 124 bajtów na prosty web-component w porównaniu do 16 bajtów dla obiektu JS.

Globalne rejestrowanie i kolizje nazw

Rejestrowanie komponentów jest globalne i nieodwracalne do zamknięcia karty:

customElements.define( 'ya-button', YandexButton )
// ...
customElements.define( 'ya-button', YahooButton ) // 💥 Błąd: nazwa już użyta

Kolizje nazw niszczą modułowość. CustomElementRegistry w Shadow DOM częściowo rozwiązuje ten problem, ale polyfills są niedostępne, a scalanie rejestrow wymaga namespace’u.

Google AdInline article slot

Alternatywy:

  • Globalny fraktalny rejestr typu DNS
  • VerLess — aktualizacja bez naruszania zgodności

Hooki cyklu życia i Shadow DOM

connectedCallback/disconnectedCallback wywołują się przy każdym przeniesieniu komponentu, nawet jeśli pozostaje na tym samym miejscu, powodując kaskadowe zadania asynchroniczne.

Shadow DOM zapewnia izolację, ale utrudnia stylizację: adoptedStyleSheets wymagają dynamicznego importowania stylów z zewnątrz. Nazwy elementów muszą być dokładnie w formacie kebab-case, co koliduje ze standardowymi elementami DOM.

Co ważne

  • Wydajność: web-components są 100–1000 razy wolniejsze niż obiekty JS z powodu proxy hosta
  • Boilerplate: serializacja/deserializacja jest wymagana dla niestandardowych typów danych
  • Rejestracja: globalna, nieodwracalna, z kolizjami nazw
  • Cykl życia: hooki wywołują się przy przenoszeniach, powodując kaskady
  • Shadow DOM: utrudnia dostosowanie stylów i polyfillowanie

Web-components nie obsługują statycznej typizacji TypeScript ani reaktywności od razu, co wymaga frameworków do aplikacji realnych.

— Editorial Team

Advertisement 728x90

Czytaj dalej