Skip to content

Commit ad57296

Browse files
serpentbladeclaude
andcommitted
docs(livewire): lead with the win, drop the defensive framing
Reopen the page on the verdict — "Livewire stacks every tier Grease accelerates — and fires them on every click" — instead of rebutting a doubt the reader never raised. Reframe each section from negation to fact: byte-identical-through-the-checksum as a guarantee, not a near-miss; "where the win is" without the hedge. Cut the request-input caveat entirely — it doesn't matter to the reader. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 744c11d commit ad57296

1 file changed

Lines changed: 38 additions & 57 deletions

File tree

docs/guide/livewire.md

Lines changed: 38 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,50 @@
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

55
Grease'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

2119
A REST endpoint exercises the model and serialization tiers once per request. A Livewire
2220
component exercises model + Blade + serialization on every interaction for the life of the
2321
page. 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

3725
Livewire 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
3927
serialization 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()`
6549
payload into a checksummed snapshot — across four corners, so you can see *which* tier the
6650
saving 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
9172
php 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
10283
apply 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

Comments
 (0)