80% van de Magento-extensies in je shop werkt niet out-of-the-box met Hyvä. Ze leveren .phtml-templates met jQuery, Knockout en RequireJS. Hyvä gooit dat allemaal weg en draait op Tailwind plus Alpine.js.
Een Hyvä compat module schrijven voor incompatibele extensies
80% van de Magento-extensies in je shop werkt niet out-of-the-box met Hyvä. Ze leveren .phtml-templates met jQuery, Knockout en RequireJS. Hyvä gooit dat allemaal weg en draait op Tailwind plus Alpine.js. Het resultaat: na een Hyvä-migratie zie je lege blokken, kapotte widgets of stille JavaScript-errors in de console.
De oplossing is geen fork van de extensie. Het is een aparte compatibility module die de frontend van de oude extensie overschrijft. De backend-logica, de events en de PHP-classes laat je intact. Je vervangt alleen de presentatielaag.
Dit artikel laat zien hoe je dat doet, regel voor regel. Voor developers die zelf een Luma-only extensie Hyvä-compatibel willen maken.
Waarom een aparte module en geen fork
Forken voelt sneller. Het is het niet. Een fork koppelt jou aan de extensie voor altijd. Elke update van de vendor moet je opnieuw mergen. Bij security-patches is dat een nachtmerrie.
Een compat module draait dit om:
- De originele extensie blijft ongewijzigd en update-baar via Composer.
- Jouw module overschrijft alleen de templates en layout XML voor het Hyvä-theme.
- Je kunt de compat module los versioneren en hergebruiken in meerdere projecten.
Hyvä zelf werkt met dit patroon. Officiële compatibility modules registreren zich in een centraal bestand: hyva_modules.json. Daarmee weet het Hyvä-theme welke modules een eigen Hyvä-compatibele frontend leveren.
Wil je eerst begrijpen waarom Hyvä zoveel anders is? Lees onze vergelijking van Hyvä en Luma op performance.
Stap 1: de module-skeleton
Je begint met een standaard Magento 2 module. Naamgeving: Vendor_HyvaCompatExtensienaam. Houd het herkenbaar.
app/code/Coding/HyvaCompatVendorReviews/registration.php:
<?php
use Magento\Framework\Component\ComponentRegistrar;
ComponentRegistrar::register(
ComponentRegistrar::MODULE,
'Coding_HyvaCompatVendorReviews',
__DIR__
);
app/code/Coding/HyvaCompatVendorReviews/etc/module.xml:
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="Coding_HyvaCompatVendorReviews">
<sequence>
<module name="Hyva_Theme"/>
<module name="Vendor_Reviews"/>
</sequence>
</module>
</config>
De sequence is hier het belangrijkste detail. Je module moet na zowel Hyva_Theme als de originele Vendor_Reviews laden. Anders winnen hun layout-instructies en zie je niets veranderen.
Stap 2: registreren in hyva_modules.json
Het Hyvä-theme leest tijdens de build welke compat-modules actief zijn. Dat doet het via hyva_modules.json. Dit bestand hoort niet in je module zelf, maar Hyvä levert een mechanisme om je registratie aan te melden.
In de praktijk plaats je een hyva_modules.json in de root van je theme of laat je de officiële hyva/compat-module-fallback het beheren. De inhoud is simpel:
{
"extensions": {
"Coding_HyvaCompatVendorReviews": {
"version": "1.0.0",
"compat": "1.0.0"
}
}
}
Dit doet twee dingen. Het signaleert aan andere compat-modules dat deze extensie al is afgedekt. En het voorkomt dat de community-fallback dezelfde extensie nog eens probeert te patchen. Dubbele compat-modules voor dezelfde extensie geven onvoorspelbare output.
Heb je de migratie nog niet gedaan? Bekijk hoe je Hyvä installeert op een bestaande Magento-shop voordat je aan compat-modules begint.
Stap 3: templates overschrijven via layout XML
Nu het echte werk. De originele extensie laadt zijn .phtml-template via een layout-handle, bijvoorbeeld catalog_product_view. Jij overschrijft dat template met een Hyvä-variant.
app/code/Coding/HyvaCompatVendorReviews/view/frontend/layout/catalog_product_view.xml:
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceBlock name="vendor.reviews.list">
<action method="setTemplate">
<argument name="template" xsi:type="string">Coding_HyvaCompatVendorReviews::reviews/list.phtml</argument>
</action>
</referenceBlock>
</body>
</page>
Je hergebruikt het bestaande block (de PHP-class met alle data-logica) en vervangt enkel het template. Het block levert nog steeds $block->getReviews(). Jij rendert die data alleen met Tailwind en Alpine in plaats van Knockout.
Soms zit de markup vast in een UI Component of in een widget. Dan overschrijf je geen setTemplate, maar verwijder je het oude block en plaats je een eigen block:
<referenceBlock name="vendor.reviews.list" remove="true"/>
<referenceContainer name="content">
<block class="Vendor\Reviews\Block\ReviewList"
name="hyva.vendor.reviews.list"
template="Coding_HyvaCompatVendorReviews::reviews/list.phtml"/>
</referenceContainer>
Stap 4: Knockout en jQuery vervangen door Alpine
Dit is waar de meeste tijd in gaat zitten. Het oude template ziet er ongeveer zo uit:
<div data-bind="scope: 'reviewList'">
<!-- ko foreach: reviews -->
<div class="review" data-bind="text: title"></div>
<!-- /ko -->
</div>
<script type="text/x-magento-init">
{"*": {"Magento_Ui/js/core/app": {"components": {"reviewList": {...}}}}}
</script>
Knockout-bindings, een x-magento-init blok en een RequireJS-component. Niets daarvan werkt in Hyvä. Je herschrijft het naar server-side rendering met Alpine voor de interactie:
<div x-data="reviewList()" class="space-y-4">
<template x-for="review in reviews" :key="review.id">
<div class="border rounded p-4">
<h4 class="font-bold" x-text="review.title"></h4>
<p x-text="review.body"></p>
</div>
</template>
<button @click="loadMore()"
class="btn btn-primary"
x-show="hasMore">
Meer reviews laden
</button>
</div>
<script>
function reviewList() {
return {
reviews: <?= /* @noEscape */ $block->getReviewsJson() ?>,
hasMore: <?= $block->getHasMore() ? 'true' : 'false' ?>,
loadMore() {
fetch('<?= $block->getLoadMoreUrl() ?>')
.then(r => r.json())
.then(data => {
this.reviews.push(...data.items);
this.hasMore = data.hasMore;
});
}
}
}
</script>
De vertaalregels die je telkens toepast:
| Luma-patroon | Hyvä-equivalent |
|---|---|
data-bind="text: x" | x-text="x" |
data-bind="foreach: items" | |
data-bind="click: fn" | @click="fn()" |
data-bind="visible: x" | x-show="x" |
x-magento-init / data-mage-init | x-data="component()" |
| jQuery AJAX | native fetch() |
| Knockout observable | Alpine reactive property |
Belangrijk: render zoveel mogelijk server-side. Alpine is voor interactie, niet voor de eerste paint. Dit is precies waarom Hyvä sneller is. Wie alles client-side rendert, gooit de winst weg. Zie ook onze analyse van Hyvä checkout versus Luma op conversie.
Stap 5: data uit het block naar de frontend
Het oude block leverde data vaak als geserialiseerde JSON voor Knockout. Vaak kun je die methode hergebruiken. Soms moet je een eigen getter toevoegen via een ViewModel.
Een ViewModel is netter dan het block direct uitbreiden:
app/code/Coding/HyvaCompatVendorReviews/ViewModel/Reviews.php:
<?php
namespace Coding\HyvaCompatVendorReviews\ViewModel;
use Magento\Framework\View\Element\Block\ArgumentInterface;
use Vendor\Reviews\Model\ReviewRepository;
class Reviews implements ArgumentInterface
{
public function __construct(
private ReviewRepository $reviewRepository,
private \Magento\Framework\Serialize\Serializer\Json $json
) {}
public function getReviewsJson(int $productId): string
{
$reviews = $this->reviewRepository->getByProduct($productId);
return $this->json->serialize(array_map(fn($r) => [
'id' => $r->getId(),
'title' => $r->getTitle(),
'body' => $r->getBody(),
], $reviews));
}
}
Je koppelt het ViewModel via layout XML aan je block en roept het aan in het template met $viewModel->getReviewsJson($productId). Zo blijft je .phtml dun en testbaar.
Stap 6: CSS en de Tailwind build
Hyvä laadt geen externe CSS-bestanden van de oude extensie. Alle styling gaat via Tailwind, en Tailwind purge-t klassen die niet in je templates voorkomen.
Twee opties:
- Gebruik bestaande Tailwind-utility-klassen in je nieuwe templates. Voorkeur. Geen extra build-stap.
- Voeg een eigen CSS-entry toe als de extensie unieke styling nodig heeft. Plaats die in de Tailwind-source van het theme zodat purge je klassen meeneemt.
Vergeet niet de Tailwind-config te wijzen op je module-templates, anders worden je klassen weggepurged:
// tailwind.config.js in het Hyvä-theme
content: [
'../../../../../app/code/Coding/HyvaCompatVendorReviews/view/frontend/templates/**/*.phtml',
]
Daarna npm run build in de theme-directory en je klassen overleven de purge.
Stap 7: testen en uitrollen
Schakel de cache uit tijdens development:
bin/magento module:enable Coding_HyvaCompatVendorReviews
bin/magento setup:upgrade
bin/magento cache:flush
Check daarna systematisch:
- Console is leeg, geen Knockout- of RequireJS-errors.
- De data van de originele extensie verschijnt correct.
- Interactie (laden, filteren, submitten) werkt via Alpine.
- De
x-magento-init-resten zijn volledig weg. - Lighthouse scoort niet lager dan de rest van je Hyvä-shop.
Twijfel je of een extensie het waard is om compat-matig te bouwen of dat je beter een native Hyvä-alternatief kiest? Dat is een afweging die we bij klanten dagelijks maken. Soms is een bestaande extensie zo verweven met Luma dat herbouwen goedkoper is dan compat. Neem contact op als je wilt sparren over die keuze.
Wanneer je géén compat module moet schrijven
Eerlijk zijn hoort erbij. Een compat module is niet altijd het antwoord.
- De extensie heeft een officiële Hyvä-versie. Koop die. Klaar.
- De functionaliteit is triviaal. Een simpel blokje bouw je sneller native dan dat je een compat-laag onderhoudt.
- De extensie is zwaar client-side. Een full SPA-checkout-plugin herschrijven kost meer dan een Hyvä-native oplossing.
- De vendor leeft niet meer. Dan is forken of vervangen verstandiger dan een compat-laag op dode code.
Meer weten over de bredere afwegingen rond Hyvä en Magento maatwerk? Wij zijn Hyvä Certified en bouwen deze compat-modules regelmatig voor klanten die met legacy-extensies zitten.
Veelgestelde vragen
Moet ik de originele extensie aanpassen?
Nee. Dat is juist het hele punt. Je laat de PHP-logica, events en backend-classes intact en overschrijft alleen de frontend via een aparte module. Zo blijft de extensie update-baar via Composer.
Wat is hyva_modules.json precies?
Het is een registratiebestand dat het Hyvä-theme vertelt welke modules een Hyvä-compatibele frontend leveren. Het voorkomt dubbele compat-modules voor dezelfde extensie en geeft versie-informatie door tijdens de build.
Kan ik jQuery laten staan in een Hyvä-shop?
Liever niet. Hyvä laadt jQuery en RequireJS bewust niet, dat scheelt honderden kilobytes. Eén extensie die jQuery terug inlaadt, ondermijnt de performancewinst. Herschrijf de interactie naar Alpine of native fetch.
Hoeveel tijd kost een compat module gemiddeld?
Voor een eenvoudige extensie met één of twee templates: een halve tot een hele dag. Voor extensies met meerdere UI Components, AJAX-flows en widgets loopt dat snel op naar meerdere dagen. De Knockout-naar-Alpine-vertaling is het zwaarste deel.
Hoe weet ik of een extensie incompatibel is met Hyvä?
Check de console na de migratie op Knockout- en RequireJS-errors, en zoek in de extensie naar data-bind, x-magento-init en Magento_Ui/js. Komen die voor in frontend-templates, dan heb je vrijwel zeker een compat module nodig.

Geschreven door Ruthger Idema
15+ jaar ervaring in e-commerce development. Gespecialiseerd in Magento, Shopify en Laravel maatwerk.
Meer over ons team →