Skip to content

Move Scripts to the Initially Loaded Page Content

Hyvä Checkout uses Magewire to dynamically load and update components, but strict Content Security Policy (CSP) prevents script evaluation after initial page load. All <script> tags must exist in the page source when first rendered to receive proper nonce authorization. Inline scripts within Magewire component templates must be extracted into separate files and loaded during the initial page render, not during dynamic component updates.

Multistep checkout behavior

For multistep checkouts, even though only one step displays at a time, all scripts used across any step must load with the initial page. Dynamic step transitions update only DOM structure, not the authorized script set.

Understanding the CSP Script Constraint

Content Security Policy requires all inline scripts to be present in the initial page source for nonce authorization. When Magewire dynamically loads a component, any scripts within that component arrive too late in the page lifecycle to receive proper nonce protection. This means inline scripts must be separated from component templates and included during the initial checkout page load, even if the component itself loads dynamically later.

The separation follows three distinct steps: extract the inline script into a separate template file, include that template in the initial page load using layout XML, and update the component template to use Alpine.js initialization instead of inline script code.

Example: Refactoring the Payment Method List Component

This example demonstrates extracting an inline script from a Magewire component template. The payment method list component originally included inline script for activating the selected payment method. This script must be extracted and loaded with the initial page to work under strict CSP.

Step 1: Extract the Script into a Separate File

The original inline script is copied into a new file method-list-activate.phtml and refactored to work as an Alpine.js constructor function. The script registers itself with Alpine.data() and receives data through HTML dataset attributes instead of PHP template variables.

The key changes in this step are:

  • Wrap the script logic in a function that registers with Alpine.data()
  • Use this.$el.dataset to access data passed from the HTML element
  • Register the inline script with $hyvaCsp->registerInlineScript() for nonce authorization

Hyva_Checkout::checkout/payment/method-list-activate.phtml

/** @var Hyva\Theme\ViewModel\HyvaCsp $hyvaCsp */
?>
<script>
    "use strict";

    function hyvaCheckoutPaymentMethodListActivate() {
        const method = this.$el.dataset.method;

        window.addEventListener('checkout:step:loaded', () => {
            if (method && document.getElementById('payment-method-list')) {
                window.dispatchEvent(
                    new CustomEvent(
                        'checkout:payment:method-activate',
                        {detail: {method: method}}
                    )
                )
            }
        }, { once: true })

        return {}
    }
    window.addEventListener(
        'alpine:init',
        () => Alpine.data('hyvaCheckoutPaymentMethodListActivate', hyvaCheckoutPaymentMethodListActivate),
        {once: true}
    )
</script>
<?php $hyvaCsp->registerInlineScript() ?>

Step 2: Include the Script in the Initial Checkout Page Load

The extracted script must be available in all steps of the checkout. This is achieved by declaring a block for the template using the hyva_checkout layout handle, which loads on every checkout page.

The magewire.plugin.scripts container is specifically designed to hold scripts that need to be present on every checkout page. Add the extracted script block to this container.

layout/hyva_checkout.xml

<!-- ... -->
<body>
    <!-- ... -->
    <!-- this container loads on every checkout page in the footer -->
    <referenceContainer name="magewire.plugin.scripts">
        <!-- add the script -->
        <block name="hyva-checkout.checkout.payment.method-list-activate"
               template="Hyva_Checkout::checkout/payment/method-list-activate.phtml"
        />
    </referenceContainer>
    <!-- ... -->
</body>

Step 3: Use Alpine to Trigger the Code

The final step is activating the Alpine component on the payment method list element. The original inline script is removed, and the element is initialized with the Alpine component using x-data. Data is passed to the script using HTML data attributes.

Note the changes to the escaper method: escapeJs() becomes escapeHtmlAttr() because the method code is now passed through an HTML attribute instead of inline JavaScript.

Hyva_Checkout::checkout/payment/method-list.phtml

?>
<div id="payment-methods">
    <?php if ($methods): ?>
        <?php /** script moved to Hyva_Checkout::checkout/payment/method-list-activate.phtml */ ?>
        <ol id="payment-method-list"
            x-data="hyvaCheckoutPaymentMethodListActivate"
            data-method="<?= $escaper->escapeHtmlAttr($magewire->method) ?>"
            class="space-y-4">
<?php

Original Combined Component and Script

For comparison, this excerpt shows the original component template before refactoring. The inline script was directly embedded in the component template, which works without CSP but violates strict CSP policies.

Hyva_Checkout::checkout/payment/method-list.phtml (before refactoring)

?>
<div id="payment-methods">
    <?php if ($methods): ?>
        <script>
            window.addEventListener('checkout:step:loaded', () => {
                if ('<?= $escaper->escapeJs($magewire->method) ?>' && document.getElementById('payment-method-list')) {
                    window.dispatchEvent(new CustomEvent('checkout:payment:method-activate', { detail: { method: '<?= $escaper->escapeJs($magewire->method) ?>'} }))
                }
            }, { once: true })
        </script>

        <ol id="payment-method-list" class="space-y-4">
<?php