Een Hyvä compat module schrijven voor incompatibele extensies
Terug naar blog

Een Hyvä compat module schrijven voor incompatibele extensies

AuthorRuthger Idema
13 juni 20267 min leestijd

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
<?php
use Magento\Framework\Component\ComponentRegistrar;

ComponentRegistrar::register(
    ComponentRegistrar::MODULE,
    'Coding_HyvaCompatVendorReviews',
    __DIR__
);
app/code/Coding/HyvaCompatVendorReviews/etc/module.xml:
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:

json
{
    "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
<?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:

xml
<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:

html
<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:

html
<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-patroonHyvä-equivalent
data-bind="text: x"x-text="x"
data-bind="foreach: items"