Een standaard Luma-checkout laadt voor honderden kilobytes aan JavaScript voordat een bezoeker iets ziet. Alpine.js, de motor achter de interactie in Hyvä, weegt ongeveer 15 KB gzipped. Dat verschil is niet cosmetisch.
Hyvä en Alpine.js: interactieve componenten zonder framework-overhead
Een standaard Luma-checkout laadt voor honderden kilobytes aan JavaScript voordat een bezoeker iets ziet. Alpine.js, de motor achter de interactie in Hyvä, weegt ongeveer 15 KB gzipped. Dat verschil is niet cosmetisch. Het bepaalt of je Largest Contentful Paint onder de 2,5 seconde blijft of er ruim overheen schiet.
In dit artikel duiken we de code in. Hoe werkt Alpine.js binnen Hyvä? Wat zijn de x-data patterns? Hoe bouw je een eigen component zoals een quantity-selector of mini-cart? En waarom is dit fundamenteel lichter dan Knockout of React?
Wat Alpine.js is, en wat het niet is
Alpine.js is geen framework. Het is een verzameling van 15 directives die je direct in je HTML schrijft. Geen build-step, geen virtual DOM, geen component-tree. Je markup blijft je markup.
Een component is gewoon een stuk HTML met een x-data attribuut:
<div x-data="{ open: false }">
<button @click="open = !open">Toon details</button>
<div x-show="open">Verborgen content</div>
</div>
Geen import. Geen registratie. Geen JSX-compilatie. De browser leest dit, Alpine pakt het op zodra de DOM klaar is, en de interactie werkt.
Dat is het mentale model. Alpine vult de gaten in server-gerenderde HTML met gedrag. Het probeert niet je hele UI te bezitten zoals React doet.
Het verschil met Knockout en React
Magento Luma draait op Knockout.js plus RequireJS. Die combinatie is het zwaarste deel van de Luma-frontend. RequireJS laadt asynchroon tientallen modules, Knockout bouwt observables op, en de virtuele DOM van Knockout-templates moet worden geparsed. We zien bij klanten dat alleen al de JS-payload van een Luma-pagina vaak 800 KB tot 1,2 MB bedraagt.
Hyvä gooit dat hele blok weg. Geen RequireJS, geen Knockout, geen jQuery. De volledige JavaScript-bundel van een Hyvä-pagina blijft doorgaans onder de 50 KB.
| Luma (Knockout) | Hyvä (Alpine) | |
|---|---|---|
| JS-bibliotheken | Knockout + RequireJS + jQuery | Alpine.js |
| JS-payload (typisch) | 800 KB - 1,2 MB | 30 - 50 KB |
| Build-step nodig | Ja (Grunt/Webpack) | Nee voor Alpine zelf |
| DOM-strategie | Virtual DOM templates | Directe DOM |
| Leercurve | Steil | Vlak |
React is in theorie sneller dan Knockout, maar lost het verkeerde probleem op. Een productpagina is geen single-page application. Je hebt geen hydratie van een complete component-tree nodig om een quantity-knop te laten werken. Alpine voegt precies genoeg toe, op precies de plek waar je het nodig hebt. Voor de meeste e-commerce-interactie is dat de juiste keuze.
Wanneer is Alpine NIET genoeg? Bij echt complexe state die over tientallen componenten gedeeld wordt, of een configurator met diepe afhankelijkheden, loop je tegen grenzen aan. Dan is een gerichte React-island een betere keuze. Wees daar eerlijk over.
We zetten de performance-cijfers naast elkaar in onze vergelijking Hyvä vs Luma.
De kern: x-data en reactive state
x-data is het hart van elk Alpine-component. Het definieert een scope met state en methodes. Alles binnen dat element heeft toegang tot die data.
<div x-data="{
count: 1,
increment() { this.count++ },
decrement() { if (this.count > 1) this.count-- }
}">
<button @click="decrement()">-</button>
<span x-text="count"></span>
<button @click="increment()">+</button>
</div>
De state is reactive. Verander count en elke x-text, x-show of x-bind die ervan afhangt update automatisch. Geen setState, geen re-render van een tree. Alpine houdt per expressie bij welke data wordt gebruikt en werkt alleen die plekken bij.
Voor herbruikbare componenten haal je de logica uit de HTML met Alpine.data():
document.addEventListener('alpine:init', () => {
Alpine.data('quantitySelector', (initial = 1, max = 99) => ({
count: initial,
max: max,
increment() { if (this.count < this.max) this.count++ },
decrement() { if (this.count > 1) this.count-- }
}))
})
In je Hyvä .phtml-template wordt het dan compact:
<div x-data="quantitySelector(1, <?= (int) $maxQty ?>)">
<button @click="decrement()" :disabled="count <= 1">-</button>
<input type="number" name="qty" x-model="count" min="1" :max="max">
<button @click="increment()" :disabled="count >= max">+</button>
</div>
x-model koppelt het input-veld tweerichtings aan de state. :disabled is de korte schrijfwijze voor x-bind:disabled en zet de knop uit op de grenzen. Dit is volledig server-gerenderd en server-gevuld; Alpine voegt alleen het gedrag toe.
Hyvä's structuur: waar de magie zit
Hyvä registreert Alpine niet zomaar. Het levert Alpine via een centrale module en stelt het in view/frontend/web/js/alpine/ beschikbaar. Componenten en plugins worden gebundeld via hyva_modules en geladen in een enkel script vlak voor
