Livewire v3 Internals: Morph Markers, JS Hooks, and Alpine Integration
Livewire v3 rewrote its JavaScript core from scratch. If you have ever wrestled with unexpected DOM resets, Alpine state loss, or mysterious re-renders, the answer is almost always in three places: morph markers, the JS hook system, and the Alpine entangle bridge. Understanding these internals turns debugging from guesswork into precision.
How Morph Markers Work
When a Livewire component re-renders, the server sends back a fresh HTML snapshot. The client-side morpher (@livewire/morph) walks both the old and new DOM trees and patches only what changed — similar to morphdom, but Livewire-aware.
To anchor this diff, Livewire injects invisible HTML comments around dynamic regions:
<!-- [if BLOCK]><![endif] -->
<div>{{ $count }}</div>
<!-- [if ENDBLOCK]><![endif] -->
These markers tell the morpher where a keyed block starts and ends. If you remove them — for example by stripping HTML comments in a CDN or build pipeline — the morpher falls back to a full subtree replacement, which destroys Alpine component state.
Practical rule: never strip HTML comments from Livewire responses. Add an exception in your Vite/webpack HTML minifier:
// vite.config.js
export default defineConfig({
build: {
minify: 'terser',
terserOptions: {
format: { comments: /\[if (BLOCK|ENDBLOCK)\]/ },
},
},
});
The JS Hook System
Livewire exposes a first-class hook API that lets you intercept the full request/response lifecycle from JavaScript. This is the correct place for things like global loading indicators, analytics events, or third-party widget re-initialisation.
import { Livewire } from '../../vendor/livewire/livewire/dist/livewire.esm';
Livewire.hook('request', ({ uri, options, payload, respond, succeed, fail }) => {
// Mutate outgoing payload
payload.fingerprint.extra = { timezone: Intl.DateTimeFormat().resolvedOptions().timeZone };
succeed(({ snapshot, effects }) => {
// Runs after a successful server round-trip
console.debug('Component updated:', snapshot.memo.name);
});
fail(() => {
document.dispatchEvent(new CustomEvent('livewire:error'));
});
});
Available hooks include component.init, element.init, element.updating, element.updated, commit, request, and message. The commit hook is particularly useful — it fires synchronously before the morph, giving you a chance to snapshot third-party widget state and restore it after.
Livewire.hook('commit', ({ component, commit, respond, succeed, fail }) => {
const chartInstance = Chart.getChart(component.el.querySelector('canvas'));
const zoom = chartInstance?.getZoomLevel();
succeed(() => {
if (zoom) chartInstance?.zoomScale('x', zoom);
});
});
Alpine Integration: Entangle and $wire
Livewire v3 ships Alpine as a first-class dependency. Every Livewire component automatically gets an Alpine scope, and $wire is a reactive proxy to the server-side component.
<div x-data="{ open: $wire.entangle('modalOpen') }">
<button @click="open = true">Open</button>
<div x-show="open">Modal content</div>
</div>
entangle creates a two-way reactive binding. By default it defers the server sync until the next Livewire network request. Pass .live to push changes immediately:
<div x-data="{ query: $wire.entangle('search').live }">
<input x-model="query" />
</div>
For read-only access without a network round-trip, use $wire.get('property') inside Alpine expressions. It reads the last known snapshot value synchronously.
Avoid this anti-pattern: duplicating state in both x-data and a Livewire property without entangle. You end up with two sources of truth that diverge on every re-render.
Key Takeaways
- Morph markers are HTML comments — protect them from minifiers or suffer full subtree replacements.
- Use
Livewire.hook('commit')to snapshot and restore third-party widget state around re-renders. $wire.entangleis the correct bridge for two-way Alpine ↔ Livewire state;.livepushes changes immediately.$wire.get()is synchronous and reads the snapshot — use it for read-only Alpine expressions to avoid extra requests.- The
requesthook lets you inject metadata into every outgoing payload without touching PHP.