Skip to content

Exclude Blaze scope variables from Volt fragment arguments#188

Open
calebporzio wants to merge 1 commit into
mainfrom
fix/volt-fragment-scope-leak
Open

Exclude Blaze scope variables from Volt fragment arguments#188
calebporzio wants to merge 1 commit into
mainfrom
fix/volt-fragment-scope-leak

Conversation

@calebporzio

Copy link
Copy Markdown
Contributor

Fixes #187

The problem

Using Blaze on an app with a Laravel Folio page that embeds a Volt component via @volt throws CorruptComponentPayloadException on any interaction with the component.

Root cause

Volt compiles @volt fragments into a Livewire mount call that captures the entire surrounding template scope:

ExtractFragments::componentArguments([...get_defined_vars(), ...])

Volt filters out the framework variables it knows about (__env, __data, errors, etc.), but Blaze introduces new variables into template scope that aren't on that list:

  • $__blaze — the shared runtime, injected into every view
  • $__attrs{hash} / $__slots{hash} (and their stacks) — call-site temporaries emitted around optimized component calls (e.g. a layout component wrapping the fragment)

Volt forwards these to Livewire as component arguments. Since they don't match any public property, Livewire stores them as HTML attributes in the snapshot memo. The serialized runtime contains empty objects ({}) that decode to [] on the next request, so the re-encoded snapshot no longer matches its checksum — and Livewire throws the corrupt payload exception.

This also explains why scoping Blaze to specific directories didn't help ($__blaze is injected into every view while Blaze is enabled) and why only disabling Blaze entirely worked.

The fix

When Blaze's pre-compilation hook sees Volt's generated scope capture (Volt's precompiler always runs first, since Blaze registers its hook after the app is booted), it wraps get_defined_vars() so Blaze's internal variables are filtered out before Volt ever sees them:

ExtractFragments::componentArguments([...\Livewire\Blaze\Support\Utils::exceptBlazeVariables(get_defined_vars()), ...])

User variables in scope (e.g. data passed from Folio's render()) still flow through exactly as before.

Verification

Reproduced against a fresh Laravel 13 app (Livewire 4.3.3, Volt 1.10.5, Folio 1.1) with a Folio page embedding a @volt counter inside a Blaze-optimized layout component:

  • Before: POST /livewire/update → 500 CorruptComponentPayloadException
  • After: POST /livewire/update → 200, counter increments, and the snapshot memo is byte-for-byte identical in shape to running with Blaze disabled (including intentionally captured scope variables like Folio render() data)

🤖 Generated with Claude Code

Volt compiles @Volt fragments (used in Folio pages) into a Livewire
mount call that captures the surrounding template scope via
get_defined_vars(). Blaze introduces variables into that scope (the
$__blaze runtime plus call-site temporaries like $__attrs{hash} and
$__slots{hash}) that Volt then forwards to Livewire as component
arguments. Livewire stores the unrecognized arguments in the snapshot
memo, and the serialized objects don't survive the JSON round trip on
the next request, corrupting the snapshot checksum and throwing
CorruptComponentPayloadException on any interaction.

Fixes #187

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Benchmark Result: Default

Attempt Blade Blaze Improvement
#1 355.48ms 15.62ms 95.6%
#2 350.74ms 15.99ms 95.4%
#3 348.37ms 15.76ms 95.5%
#4 350.38ms 15.76ms 95.5%
#5 353.94ms 15.65ms 95.6%
#6 * 365.34ms 15.58ms 95.7%
#7 351.27ms 15.86ms 95.5%
#8 355.10ms 15.83ms 95.5%
#9 * 358.35ms 16.22ms 95.5%
#10 354.24ms 15.77ms 95.5%
Snapshot 353.35ms 15.96ms 95.5%
Result 352.61ms (~) 15.77ms (~) 95.5% (~)

Median of 10 attempts (* = outlier, excluded from result), 5000 iterations x 10 rounds, 46.92s total

To run a specific benchmark, comment /benchmark <name> where name is one of: attributes, aware, class, default, forwarding, merge, named-slots, no-attributes, slot

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant