Skip to content

Unify SSRState, RequestState and the dev context #16519

Description

@Nic-Polumeyv

Describe the problem

Following Rich's comment in #16507, I mapped everything that carries server-side state, since a unification needs the full picture. Line refs are from version-3 at b596610.

There are three carriers now, not two. Besides SSRState and RequestState, the dev rework keeps a context in import.meta.hot.data that feeds state.emulator and state.before_handle on every request. Eight functions take both state and event_state, plus two closures in respond.js that capture both, which is half the state-carrying functions in the server runtime. SSRState.fallback has no readers or writers left and can be deleted whatever else happens.

The overlap isn't just duplication, the two objects have opposite semantics that nothing documents. SSRState is mutated in place and is the only carrier that survives event.fetch; RequestState is readonly, copy-varied, and rebuilt per sub-request, with three fields hand-copied from the other side at respond.js:179. That snapshot is why faking state.prerendering in dev couldn't work in #16507, prerender_default only works because of which side of the fetch boundary it happens to live on, and the hmr: false breakage in #16464 is the same shape again, load-bearing state in a carrier that isn't guaranteed to exist.

Describe the proposed solution

A single object has to keep the behaviours the split currently implies:

  • sub-requests inherit prerendering/error/depth but reset the remote caches (fetch.js:219 vs respond.js:177), so it needs an explicit fork rather than spread-one-rebuild-the-other
  • copy-on-write for the request flags, since the webcontainer fallback in event.js:74 serializes requests and is only safe because nothing mutates the stored object
  • remote_responses is per prerender pass while dependencies is per visit, so lifetimes become explicit instead of implied
  • Server.respond's public shape stays put
  • dev values must be re-derivable after module re-evaluation, so hot.data can back the injection but can't be the only copy
  • read exists per request (SSRState.read) and per server (read_implementation), pick one home

What I'd do: a request state created per request with a fork() for event.fetch, and the server-lifetime values (emulator, before_handle, read, the hooks-derived fields) on an object created once in Server.init and referenced, not copied. Dev builds that once from its context module instead of writing SSRState fields per request. The eight dual signatures collapse to one parameter, no public API changes.

Hoping this could be the thread to work this out

Importance

nice to have

Additional Information

If this sounds right I'll implement it in stages, starting with the dead field and the fork semantics.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions