You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(web): send without a microtask when prepareRequest is sync - #3988
Fixes#3959. With any prepareRequest hook configured, even init => init, a server-function read that hydration adopted was fetched again from the browser.
Hydration runs an adopted source's compute once inside subFetch, which swaps window.fetch for a never-settling mock only for that synchronous call. createRequest always awaited the hook, so even a synchronous hook pushed send(base, init) one microtask later, after the real fetch was restored, and the request reached the network.
createRequest now awaits the hook's result only when it is a thenable. A synchronous hook sends in the calling tick, like the transport does without a hook. An async hook is still awaited and keeps its current behavior; the prepareRequest docs now say that an async hook makes hydrated reads fetch again.
Left open: calls whose arguments need the rich-argument codec still await serializeArguments(args) before sending (the POST path in initializeResponse and the GET path in fetchServerFunction), so a hydrated read with such arguments refetches with or without a hook. Fixing that needs synchronous serialization, which is a larger change.
How did you test this change?
test/server/server-functions-extensions.spec.tsx adds a case that checks a synchronous hook sends before the call returns, with and without arguments, and that an async hook still sends after the await. It fails on next (0 synchronous sends) and passes with the fix: pnpm build in packages/web, then npx vitest run --config vite.config.server.mjs test/server/server-functions-extensions.spec.tsx.
test/hydration/prepare-request-hydration-3959.spec.tsx hydrates a createMemo(() => read()) over adopted data, once without a hook and once with init => init, and asserts no request is sent. On next the hook case sends /_server/data/prepare-request-3959; with the fix neither case sends anything: npx vitest run --config vite.config.hydrate.mjs test/hydration/prepare-request-hydration-3959.spec.tsx.
⚠️2 benchmarks spent significant time in system calls
System calls cannot be consistently instrumented, so they are not included in the measure, which understates the real cost. Please switch to the Walltime instrument to accurately measure system calls.
With that change test-types is clean and the spec still passes.
The fix itself is right. createRequest reaches the hook with no await in between, so a synchronous return now sends within the subFetch window, and a thenable is still awaited. I checked it locally on 84ee3a5, with web rebuilt:
Check
Result
Both new tests with server-functions/src/client.ts reverted to next
fail
Both new tests on the PR
pass (13/13 server, 2/2 hydration)
The gap you left open is worth its own issue: a hydrated read whose arguments go through the rich-argument codec still refetches, because of await serializeArguments(args), with or without a hook.
Merged through #3990. This session could not push to your fork, so your commit went in unchanged and still authored by you (c2d4571 on next), followed by the solid-js/internal import fix for test-types (4101c67). Closing this one as superseded. Thanks!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #3959. With any
prepareRequesthook configured, eveninit => init, a server-function read that hydration adopted was fetched again from the browser.Hydration runs an adopted source's compute once inside
subFetch, which swapswindow.fetchfor a never-settling mock only for that synchronous call.createRequestalways awaited the hook, so even a synchronous hook pushedsend(base, init)one microtask later, after the realfetchwas restored, and the request reached the network.createRequestnow awaits the hook's result only when it is a thenable. A synchronous hook sends in the calling tick, like the transport does without a hook. An async hook is still awaited and keeps its current behavior; theprepareRequestdocs now say that an async hook makes hydrated reads fetch again.Left open: calls whose arguments need the rich-argument codec still
await serializeArguments(args)before sending (the POST path ininitializeResponseand the GET path infetchServerFunction), so a hydrated read with such arguments refetches with or without a hook. Fixing that needs synchronous serialization, which is a larger change.How did you test this change?
test/server/server-functions-extensions.spec.tsxadds a case that checks a synchronous hook sends before the call returns, with and without arguments, and that an async hook still sends after the await. It fails onnext(0 synchronous sends) and passes with the fix:pnpm buildinpackages/web, thennpx vitest run --config vite.config.server.mjs test/server/server-functions-extensions.spec.tsx.test/hydration/prepare-request-hydration-3959.spec.tsxhydrates acreateMemo(() => read())over adopted data, once without a hook and once withinit => init, and asserts no request is sent. Onnextthe hook case sends/_server/data/prepare-request-3959; with the fix neither case sends anything:npx vitest run --config vite.config.hydrate.mjs test/hydration/prepare-request-hydration-3959.spec.tsx.@solidjs/webclient, server and hydration suites before and after the change. The local compiler build is older than Client element claims fire before dynamic attributes are applied, and onlyhref/actionwrites re-claim #3923 and Select loses its bound value when options load asynchronously #3928, so some element-claim and server specs fail onnextas well; the failing test lists are identical before and after, apart from the new test.node size.mjsinscripts/size: the server-function scenarios pass. "app: CSR, observe tier" is over its cap by 5 B, with the same byte count onnext.🤖 Generated with Claude Code