Repository navigation
Replies: 2 comments
|
What the server sends for the dashboard of the example. Each widget is this component, trimmed of its markup: function Widget(props: { index: number }) {
const seed = props.index;
const [items, setItems] = createSignal(
Array.from({ length: 6 }, (_, i) => ((seed * 7 + i * 13) % 50) + 1)
); // named 001001~0
const [mode, setMode] = createSignal(seed % 3); // named 001001~1
const sorted = createMemo(() => [...items()].sort((a, b) => a - b));
const summary = createMemo(() => {
const values = sorted();
const sum = values.reduce((a, b) => a + b, 0) + work(seed);
return mode() === 0 ? sum : mode() === 1 ? sum / values.length : values[5] - values[0];
});
const label = createMemo(() => `${NAMES[mode()]}: ${summary().toFixed(2)}`);
const bars = createMemo(() =>
sorted().map(n => "#".repeat(Math.round(n / 10) + work(n))).join(" ")
);
// <section> with {label()}, {bars()} and the Run and Mode buttons
}For two widgets, the server sends each memo's value and the names of the signals it read, under its hydration id plus _$HY.r["0010010#g"] = [[1,3,14,16,27,40], ["001001~0"]] // sorted, widget 0
_$HY.r["0010011#g"] = [101, ["001001~0","001001~1"]] // summary, widget 0
_$HY.r["0010012#g"] = ["Total: 101.00", ["001001~1","001001~0"]] // label, widget 0
_$HY.r["0010013#g"] = ["# ### # #### ######### #########", ["001001~0"]] // bars, widget 0
_$HY.r["0010018#g"] = [[8,10,21,23,34,47], ["001001~0"]] // sorted, widget 1One limit of the prototype that this shows: the signal names come from the server-component edges, |
|
Uh oh!
There was an error while loading. Please reload this page.
Can hydration skip the computes of memos, if the server sends each memo's value with the signals it read? I prototyped this on
nextand measured it. Short answer: it works, and it halves hydration for pages full of expensive synchronous memos, but in apps built like the 2.0 examples there is little for it to skip.The branch, with the prototype, a findings document and the scripts to reproduce every number: https://github.com/lxsmnsyc/solid/tree/experiment/graph-hydration/examples/graph-hydration
How hydration works today
fetchandPromise(subFetch) to find its dependencies, then throws that result away.insert()renders the components inside it and claims their DOM.The prototype
About 100 lines, mostly reusing the dependency edges that server components already have (
server/edges.tsandcore/identities.ts). Every change is markedEXPERIMENT(graph-hydration).[value, signal names]under<memo id>#g, if its compute took at least a set time on the server. A read through another memo records that memo's signals.Results
Chrome with the CPU slowed 4 times, median of 9 loads, Apple M2. Each page is checked with a click after hydration.
A synthetic dashboard of 200 widgets, 4 memos each. "Busy" memos run a loop of 2000 steps.
The example apps of the repository, with the graph off. "Memos with data" are memos that return no nodes, an upper bound on what a graph could skip.
What I take from it
urlreturns aURLandmatchesreturns matches that hold components, so neither is plain data, and routepreloadhas to run. Much of their time is a first run that a skip would only move to the first navigation.insert()that renders and claims the components of the page. A graph cannot skip that.So I don't think it is worth adding for apps built like the examples. It could make sense as an opt-in for a memo with an expensive compute, such as
createMemo(fn, { ssrSource: "graph" }), for apps that hydrate heavy derived data. I'd be glad to hear whether that case comes up, or whether these numbers match what you see in larger apps.All reactions