livewire/livewire: Upmerge 4.x into main for release prep
Contributed by calebporzio
Part of the livewire v4.4.7 release
This pull request is the release-prep upmerge that folds the finished 4.x state into main, so if you are coming from Livewire v3.8.10, this is effectively your preview of what ships in v4.0. It preserves the main-only feature work while carrying over the current 4.x fixes and a refreshed Alpine lockfile. There are no breaking API removals here, but there are several behavior-level changes worth knowing about before you upgrade: a defensive fix for cached computed properties ahead of Laravel 13's stricter cache unserialization, new guardrails around $wire.$refresh() and reactive bindings, and a batch of fixes for islands, script assets, and nested file uploads.
What this merge delivers
Beyond the branch reconciliation itself, the diff bundles a dependency refresh (Alpine 3.16.2), a resilience fix in the #[Computed] feature for Laravel 13's new cache restrictions, JavaScript hardening in the evaluator and action layer, and correctness fixes in islands rendering and file upload handling. It was validated with npm ci, 155 passing JavaScript tests, a production asset build, and 223 focused PHP tests with 474 assertions, with the full GitHub Actions matrix as the final gate.
Alpine bumps to 3.16.2
Every bundled Alpine package — alpinejs plus the @alpinejs/* plugins — moves from 3.16.1 to 3.16.2, and @vue/reactivity moves to 3.5.41. A nice side effect: Alpine core and the CSP build now depend on @vue/reactivity ~3.5.40 instead of ~3.1.1, so the nested 3.1.x copy is deduplicated out of the lockfile. If you rely on Livewire's bundled Alpine, you get this automatically. If you install Alpine yourself, align your version with 3.16.2 to avoid double-loading.
Cached computed properties now survive unserialization failures
New Laravel 13 applications only unserialize explicitly allowed PHP classes from cache. Previously, if a #[Computed(cache: true)] or #[Computed(persist: true)] property returned an object whose class wasn't allowed, you'd get a __PHP_Incomplete_Class back and a fatal error downstream. Now Livewire detects this — including inside arrays — re-evaluates the property so the value stays correct, and (in debug mode) logs a warning explaining that the value was not served from cache:
// config/cache.php — only needed if you intentionally cache objects
'serializable_classes' => [
App\Models\User::class,
],
The safest upgrade path remains what the refreshed docs recommend: cache scalars or arrays.
#[Computed(persist: true, seconds: 3600)]
public function userName()
{
return User::find($this->userId)->name; // scalar — cache-safe
}
JavaScript guardrails: promises and reactive bindings
Two behavior changes to note:
$wire.$refresh()and$wire.$commit()now always return a promise, and synchronous errors surface as rejections instead of escaping as exceptions. If you previously wrapped calls intry/catch, move to.catch():
$wire.$refresh().catch(error => console.error(error))
- Calling server methods from reactive bindings is now blocked. Expressions on
wire:text,wire:show, andwire:bindre-evaluate on renders, so a call likewire:text="save()"would silently fire network requests. Livewire now logs a console warning and resolves without an action. Move the call towire:clickor a lifecycle hook instead.
Additionally, a component's script module (its $js actions) is now re-loaded after back/forward navigation, so handlers no longer vanish when the browser restores the page.
Islands, asset loading, and file upload fixes
This merge lands several correctness fixes: island fragments render sequentially and in order (including repeated explicit renderIsland() calls), a #[Renderless] action only skips its own implicit island render — not a parent's — and morph interceptors run sequentially instead of in parallel. Script assets injected into <head> are iterated over a static copy of the children, fixing a bug where the asset following a non-script asset was silently skipped. On the PHP side, removeUpload now supports multi-file properties stored as Collections, preserving the collection type after removal, and nested uploads (e.g., data.sections.first.rows.one.image) survive a wholesale set() on an ancestor path, so repeater-style UIs no longer lose their TemporaryUploadedFile references.
Upgrading from v3.8.10
For most apps this upgrade is transparent: bump the package, then clear config and view caches. The changes that may require action are the ones above — cached computed properties returning objects, promise handling around $refresh/$commit, and server calls in reactive bindings. If you're on Laravel 13, audit any #[Computed(persist: true, cache: true)] property that returns Eloquent models or other objects first.
Takeaways:
- Ships Alpine 3.16.2 and a deduplicated
@vue/reactivitydependency tree. - Cached/persisted computed properties auto re-evaluate when Laravel 13 refuses to unserialize them; prefer scalars/arrays or whitelist classes via
cache.serializable_classes. $refresh()/$commit()always return promises; handle errors with.catch().- Server methods called from
wire:text,wire:show, orwire:bindare blocked with a console warning — move them to explicit actions. - Nested file uploads and Collection-based multi-file properties are now handled correctly.
- Island renders, script asset injection, and back/forward navigation script module loading all got sequencing and reliability fixes.