11# Grease & Livewire
22
3- ## Livewire is a better fit than the benchmarks suggest, not a worse one
3+ ## Livewire stacks every tier Grease accelerates — and fires them on every click
44
55Grease's headline numbers come from API-shaped endpoints — query, hydrate, serialize to
6- JSON, respond. [ Livewire] ( https://livewire.laravel.com ) doesn't work that way: it has no
7- traditional REST endpoint. Every interaction posts a component snapshot back to a single
8- update route, re-hydrates the component's models, re-renders its Blade template, and
9- re-serializes the result into a new snapshot. A fair question is whether a package tuned
10- on JSON endpoints does anything at all for that.
11-
12- It does — and arguably * more* than for a plain API, because a Livewire round-trip stacks
13- the tiers Grease accelerates and fires them on ** every interaction** rather than once:
6+ JSON, respond. [ Livewire] ( https://livewire.laravel.com ) works differently, and that
7+ difference is exactly why it gets * more* out of Grease, not less. Every interaction posts a
8+ component snapshot back to a single update route, re-hydrates the component's models,
9+ re-renders its Blade template, and re-serializes the result into a new snapshot. A single
10+ round-trip stacks the tiers Grease accelerates and fires them on ** every interaction**
11+ rather than once:
1412
1513- ** Model hydration / casting** — every update re-queries the component's models.
1614- ** The Blade compiler** — a Livewire component * is* a Blade view, re-rendered in full on
17- every ` wire:click ` , ` wire:model ` update, or polled refresh. An API never touches Blade.
15+ every ` wire:click ` , ` wire:model ` update, or polled refresh.
1816- ** ` toArray() ` + date serialization** — the snapshot Livewire ships to the browser is a
1917 serialized view of component state.
2018
2119A REST endpoint exercises the model and serialization tiers once per request. A Livewire
2220component exercises model + Blade + serialization on every interaction for the life of the
2321page. The work Grease removes is the same; Livewire just asks for it more often.
2422
25- ## The one tier that buys less
26-
27- [ The Request input memoization] ( /guide/request ) is tuned for the form-field read pattern —
28- ` $request->input('email') ` funnelled through repeatedly. Livewire's update requests don't
29- read input that way: they post a single ` components ` payload that Livewire's own machinery
30- parses, not via ` $request->input() ` . So on the * update* path that tier has less to amortize
31- (the initial full-page load is a normal request and benefits normally). It doesn't hurt —
32- invalidation stays correct — it just isn't the lever it is on a classic form post. Every
33- other tier lands.
34-
35- ## The part that could have broken — and didn't
23+ ## Byte-identical through the snapshot — and the checksum
3624
3725Livewire serializes component state into a ** snapshot** between requests and seals it with
38- an HMAC ** checksum** . The model attributes in that snapshot are produced by the exact
26+ an HMAC ** checksum** . The model attributes in that snapshot come from the exact
3927serialization path Grease optimizes — ` toArray() ` , the
40- [ ` datetime ` /timestamp date tiers] ( /guide/serialization-helpers ) . If a greased model
41- serialized even one byte differently from vanilla — an ISO date formatted differently, a
42- decimal string rendered another way — the snapshot would differ, the checksum would differ,
43- and the next request would reject the payload with a corruption exception. In a rolling
44- deploy with some workers greased and some not, * every* request would fail.
45-
46- That's the same promise Grease makes everywhere —
47- [ byte-identical output] ( /guide/why#the-one-rule-byte-identical-output ) — and Livewire is no
48- exception. It's proved, not asserted: ` tests/Livewire/LivewireParityTest ` mounts a greased
49- component and its vanilla twin (differing only in whether the model has ` HasGrease ` ) and
50- checks, across mount → action → update:
28+ [ ` datetime ` /timestamp date tiers] ( /guide/serialization-helpers ) . Grease's one rule is that
29+ this output stays [ byte-identical to vanilla] ( /guide/why#the-one-rule-byte-identical-output )
30+ — the same ISO date, the same ` decimal:2 ` string, down to the byte — so the snapshot is
31+ identical, the checksum is identical, and the payload round-trips exactly as Livewire
32+ expects. A mixed greased/vanilla fleet is safe for the same reason: the snapshots match, so
33+ there's no boundary for a checksum to straddle.
34+
35+ That's proved, not asserted. ` tests/Livewire/LivewireParityTest ` mounts a greased component
36+ and its vanilla twin (differing only in whether the model has ` HasGrease ` ) and checks,
37+ across mount → action → update:
5138
5239- the dehydrated snapshot ** data** is byte-identical — ISO dates, the ` decimal:2 ` string,
5340 the loaded relation and all;
5441- Livewire's own checksum generator produces the ** identical HMAC** once the per-request
5542 random component id is held constant;
5643- the rendered HTML is byte-identical.
5744
58- Because the snapshots are identical, a mixed greased/vanilla fleet is safe: there's no
59- boundary for a checksum to straddle.
60-
61- ## What it's worth — and where the win actually is
45+ ## Where the win is
6246
63- The same ` benchmarks/livewire_ab.php ` that gates the parity above then times a
47+ The same ` benchmarks/livewire_ab.php ` that gates that parity then times a
6448` Livewire::mount() ` — hydrate the model, render the component, dehydrate its ` toArray() `
6549payload into a checksummed snapshot — across four corners, so you can see * which* tier the
6650saving comes from rather than just a headline:
@@ -71,21 +55,18 @@ saving comes from rather than just a headline:
7155| ` + ` container / view / event tiers only | ≈ −0 to −5% |
7256| full greased stack | ≈ −48 to −50% |
7357
74- The surprise is that ** the model trait alone carries nearly the whole delta** — the
75- foundation tiers are a thin slice on top, because a single component resolve is a small
76- fraction of the work (the [ container tier] ( /guide/container ) shaves a request elsewhere,
77- not here). Why is one trait worth half a mount? Because Grease's model tier kills
78- * per-instance* overhead — reflection, the [ class-attribute resolution] ( /guide/how-it-works ) ,
79- the ` initialize* ` booters — which is a ** fixed cost per ` new Model() ` ** that doesn't shrink
80- when the row is small. A Livewire mount hydrates the component's model ** and its loaded
81- relations** (the bench's fixture is one user + eight posts — nine models), so that fixed
82- overhead is paid nine times and greasing it lands hard. It's the same mechanism behind the
83- [ ` index_users ` macro] ( /guide/benchmarks ) .
84-
85- Trust the direction, not the digits. This reproduces at the same magnitude on both macOS and
86- Linux+JIT (` benchmarks/docker ` ), but — in the spirit of the
87- [ Octane page] ( /guide/octane#why-there-s-no-benchmark-table-on-this-page ) — the honest number
88- is the one you measure on your own components and database:
58+ The model trait alone carries nearly the whole delta, and the reason is mechanical: Grease's
59+ model tier kills * per-instance* overhead — reflection, the
60+ [ class-attribute resolution] ( /guide/how-it-works ) , the ` initialize* ` booters — which is a
61+ ** fixed cost per ` new Model() ` ** that doesn't shrink when the row is small. A Livewire mount
62+ hydrates the component's model ** and its loaded relations** (the bench's fixture is one user
63+ + eight posts — nine models), so that fixed overhead is paid nine times and greasing it lands
64+ hard. It's the same mechanism behind the [ ` index_users ` macro] ( /guide/benchmarks ) .
65+
66+ This reproduces at the same magnitude on both macOS and Linux+JIT (` benchmarks/docker ` ). As
67+ everywhere in Grease — and in the spirit of the
68+ [ Octane page] ( /guide/octane#why-there-s-no-benchmark-table-on-this-page ) — the number that
69+ counts is the one you measure on your own components and database:
8970
9071``` bash
9172php benchmarks/livewire_ab.php
@@ -100,7 +81,7 @@ There's nothing Livewire-specific to install or configure. Add `HasGrease` to th
10081[ Eloquent models] ( /guide/getting-started ) your components use — that's it. The
10182[ Blade] ( /guide/blade ) , [ container] ( /guide/container ) , and [ event] ( /guide/events ) tiers all
10283apply unchanged (Livewire renders Blade and resolves through the container like everything
103- else), and they're worth turning on — but the four-corner number above is unambiguous about
104- the priority: ** if you take nothing else from this package, put ` HasGrease ` on the models your
105- components touch. ** On a component-heavy page that single trait is where the request goes; the
106- rest is compounding upside.
84+ else) and compound on top. The four-corner number is unambiguous about the priority: ** if you
85+ take nothing else from this package, put ` HasGrease ` on the models your components touch. ** On
86+ a component-heavy page that single trait is where the request goes; the rest is compounding
87+ upside.
0 commit comments