Zpět na domů

Problémy webových komponent: paměť a rychlost

Článek analyzuje klíčové nevýhody webových komponent: vysoká spotřeba paměti kvůli DOM uzlům, pomalé operace ve srovnání s JS objekty, problémy push-reaktivity. Srovnání s $mol ukazuje rozdíl 8000x při 100k prvcích.

Webové komponenty: 124 bajtů na prvek a zpoždění ve seznamech
Advertisement 728x90

Proč webové komponenty zaostávají ve výkonu a vývojářském pohodlí

Webové komponenty, jako jsou Lit a Symbiote, vytvářejí každý prvek jako HTMLElement, což vede k alokaci přibližně 124 bajtů na C++ heap. To je 8krát více než 16 bajtů pro JS-objekt v $mol. Pro 1000 úkolů spotřebují Lit/Symbiote přibližně 124 KB pouze na DOM uzly, zatímco $mol pouze přibližně 16 KB. Při 10 000 úkolech rozdíl dosahuje 1,2 MB oproti 160 KB.

Bez virtualizace začne rozhraní s tisíci prvky zpožďovat. Specifikace Custom Elements vyžaduje dědění od HTMLElement, což nevyhnutelně vytváří DOM uzel i pro neviditelné prvky.

// Lit: každý todo-item je HTMLElement (C++ heap, ~124 bajtů minimum)
@customElement("todo-item")
export class TodoItem extends LitElement { ... }
// <todo-item> okamžitě alokuje DOM uzel při createElement

// Symbiote: podobně, každý <todo-item> = HTMLElement
class TodoItem extends Symbiote { ... }
TodoItem.reg('todo-item');

// $mol: komponenta je JS-objekt. DOM se vytváří POUZE při renderování.
// 1000 úkolů v modelu ≠ 1000 DOM prvků
// $mol renderuje pouze viditelné komponenty (virtualizace)
task_rows() {
    return this.task_ids_filtered().map(id => this.Task_row(id))
}

Autor SolidJS potvrzuje: pro maximální výkon nejsou webové komponenty vhodné, protože minimalizace DOM uzlů není možná.

Google AdInline article slot

Ztráty v rychlosti kvůli DOM operacím

DOM operace jsou mnohonásobně pomalejší než JS vlastnosti kvůli C++ vazbám a absenci JIT optimalizace:

| Operace | Čas | Kolikrát horší |

|---------------------------|------------|----------------|

Google AdInline article slot

| obj.title = x (JS) | 1–2 ns | 1x |

| element.textContent = x | 30–60 ns | 30x |

| element.setAttribute | 50–100 ns | 50x |

Google AdInline article slot

| element.style.color = x | 80–150 ns | 80x |

Při hromadné aktualizaci (např. označení všech úkolů v TodoMVC) se výkon zhoršuje:

| Úkolů | Lit | $mol | Rozdíl |

|---------|-----------|---------|--------|

| 100 | 60 µs | 3 µs | 20x |

| 1 000 | 600 µs | 8 µs | 75x |

| 10 000 | 8 ms | 12 µs | 650x |

| 100 000 | 120 ms | 15 µs | 8000x |

// Lit: aktualizace přes DOM property
render() {
    return html`<label> ${this.text} </label>`;
    // Lit v pozadí provádí: node.textContent = value
}

// $mol: aktualizace přes JS property
// this.title() — čtení z memo-cache (JS heap)
// DOM se aktualizuje dávkově přes requestAnimationFrame
task_title(id, next?) {
    return this.task(id, ...)!.title ?? ''
}

Parsování HTML v Lit přidává režii.

Rozdíly v reaktivitě

Webové komponenty používají push sémantiku: události se rozesílají všem odběratelům při jakékoli změně.

// Lit: Push přes EventTarget + requestUpdate
export class Todos extends EventTarget {
    #notifyChange() {
        this.dispatchEvent(new Event("change")); // push!
    }
    add(text) {
        this.#todos.push({ ... });
        this.#notifyChange(); // ← ruční push
    }
}
// Každá komponenta se přihlašuje přes @updateOnEvent("change")
// Při JAKÉKOLI změně VŠICHNI odběratelé dostanou oznámení

// $mol: Pull — automatický graf závislostí
@$mol_mem
groups_completed() {
    // Automaticky sleduje závislosti:
    // task_ids() → task() → groups
    // Přepočítává se POUZE když se změnily závislosti
    for (let id of this.task_ids()) {
        var task = this.task(id)!;
        groups[String(task.completed)].push(id);
    }
    return groups;
}
// Není potřeba ani EventTarget, ani ruční odběry

Pull reaktivita ($mol, Vue) minimalizuje zbytečné aktualizace a snižuje objem kódu. Push zvyšuje riziko chyb v odběrech.

Testování a dědění

Testování webových komponent vyžaduje DOM: vytváření prvků, connectedCallback, čekání na updateComplete. V $mol testy pracují s JS-objekty bez DOM.

// $mol: testy BEZ DOM
'task add'($) {
    const app = $hyoo_todomvc.make({ $ })
    app.Add().value('test')
    app.Add().submit()
    $mol_assert_equal(app.task_rows().at(-1)!.title(), 'test')
}

Dědění v Lit vyžaduje kopírování celého render()-bloku:

class MyTodoApp extends TodoApp {
    render() {
        // Kopírujeme 40+ řádků HTML, měníme pouze footer
    }
}

V $mol dědění nahrazuje pouze potřebné sub-prvky:

$my_custom_todo $hyoo_todomvc
    foot <= My_footer $mol_view
// Vše ostatní se dědí

Co je důležité

  • Webové komponenty spotřebují přibližně 124 bajtů/prvek na DOM, což je kritické pro seznamy.
  • DOM operace jsou 30–80krát pomalejší než JS vlastnosti, degradace při škálování.
  • Push reaktivita vyžaduje ruční odběry, pull je jednodušší a efektivnější.
  • Testy webových komponent jsou pomalé kvůli DOM, $mol testuje čisté objekty.
  • Dědění ve webových komponentách vede ke kopírování šablon.

— Editorial Team

Advertisement 728x90

Číst dál