Move Scripts to the Initially Loaded Page Content
Hyvä Checkout uses Magewire to dynamically load and update components within the checkout page. With strict Content Security Policy (CSP), scripts included in dynamically loaded Magewire components cannot be evaluated after page load. All <script> tags must be present in the page source when initially loaded to receive proper nonce authorization.
This CSP requirement means that inline scripts within Magewire component templates must be extracted into separate files and loaded with the initial page, even if the component itself loads dynamically. The script authorization happens during the initial page render, not during dynamic component updates.
Multistep checkout
For multistep checkouts, even though only one step at a time is visible, all scripts used on any step must be loaded with the initial page. Dynamic step transitions update only the DOM structure, not the authorized script set.
Separating Inline Scripts from Magewire Components
Having scripts directly in a Magewire component template is convenient for co-locating logic with markup, but strict CSP prohibits this pattern. Scripts must be extracted into separate template files that load with the initial page.
The separation process involves three key steps:
- Extract the inline script into a separate template file and register it as an Alpine.js data function
- Include the extracted script template in the initial page load using layout XML
- Update the component template to use Alpine component initialization instead of inline script
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.datasetto 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