Add SSR Suspense support#2837
Closed
edkimmel wants to merge 10 commits into
Closed
Conversation
Adds a setForHydration flag so that callers can pre-seed `dimensions` from the server-rendered values without `Dimensions.get` immediately calling update() and clobbering them on the first client-side read. After hydration completes, unsafe_restoreFromHydration() clears the flag and runs update() to switch back to live browser values. Ports the existing patch-package fix to the upstream source. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the import from `styleq/transform-localize-style` with a local copy in `src/exports/StyleSheet/localizeStyle.js`. styleq's published copy lags this implementation; vendoring decouples our writing-direction handling from styleq releases. Ports the existing patch-package fix to the upstream source. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`distanceFromEnd = contentLength - visibleLength - offset` treats the footer as part of the scrollable content, so onEndReached fires before the user has actually reached the last item — the footer's height counts against the threshold. Subtracting `this._footerLength` makes the trigger point relative to the end of real list content. Applied in both `_adjustCellsAroundViewport` and `_maybeCallOnEdgeReached`. Ports the existing patch-package fix to the upstream source. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a new internal module that exposes runInRequestScope + getScopedState
for isolating module-level mutable state across concurrent SSR renders.
On Node, `runInRequestScope(fn)` opens an AsyncLocalStorage scope so any
state read via `getScopedState(key, factory)` during `fn` is per-request;
outside any scope, calls fall through to a process-default singleton.
On the client and React Native, AsyncLocalStorage is unavailable — the
module degrades to a no-op singleton store, matching today's behavior
of RNW's module-level `let` bindings. `node:async_hooks` is loaded via
`eval('require')` so bundlers targeting browser/native do not attempt
to resolve it.
Phase 1b of the streaming-SSR migration will route module state inside
Dimensions, StyleSheet, and AppRegistry through this module so each
request gets its own isolated copy.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Routes the module-level `dimensions` object through getScopedState so each SSR request gets its own `window` / `screen` viewport values. Concurrent renders with different breakpoints no longer overwrite each other's metrics. `listeners`, `shouldInit`, and `setForHydration` stay module-level: - listeners are runtime subscriptions added post-mount; no resize fires on the server. - shouldInit = canUseDOM, so it is false on the server and never reads through update(). - setForHydration only changes inside unsafe_setForHydration, which throws on the server. Adds a Node-environment test (asyncContext-test.node.js) verifying that concurrent runInRequestScope scopes do not see each other's Dimensions.set values, and that scope writes do not leak to the process default. Existing jsdom tests pass unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a dual-write per-request delta channel to the shared StyleSheet so
streaming SSR consumers can emit only the rules that actually landed
during a given chunk, while the process-wide sheet stays a singleton
(matching today's module-load-time accumulation behavior).
How it works:
- createOrderedCSSStyleSheet.insert now returns { ruleAdded, groupCreated }
so callers can detect dedup hits (rule was already present) without
re-implementing the selectors map.
- The dom/index.js wrapper forwards the primary sheet's InsertResult.
- StyleSheet.insertRules mirrors only genuine adds (ruleAdded === true)
into an ALS-scoped per-request delta buffer via getScopedState. If no
request scope is active (client / module-load / legacy two-pass
renderer) the dual-write is a no-op and behavior is unchanged.
New StyleSheet APIs:
- takeRequestDelta(): drains the buffer to a CSS text fragment, with
[stylesheet-group="N"]{} markers emitted only on the first flush for
each group per request.
- resetRequestDelta(): clears the buffer without emitting. The streaming
pipeline calls this after the shell head dump, since the full sheet
text already covered everything inserted up to that point.
- markGroupsAsEmitted(groups): pre-marks groups whose markers are
present in the shell head so subsequent delta flushes don't duplicate
them.
Tests (node env):
- takeRequestDelta returns "" outside any scope.
- A scope captures created rules; markers appear once per group per
request, not on every flush.
- Concurrent runInRequestScope renders see only their own rules.
- A duplicate `create()` across two requests appears in the first
delta and is empty in the second (dedup via the shared sheet).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes the singular `<style id="react-native-stylesheet">` assumption so streaming SSR can emit per-chunk `<style data-rnw-delta="N">` tags without leaving the client-side dedup map / groups records out of sync. Changes: - createOrderedCSSStyleSheet accepts an optional `additionalSheets` array alongside the primary CSSStyleSheet. The hydration loop is refactored so the same logic walks each source; only the primary records absolute rule indices in `groups[g].start`. Groups discovered only in additional sheets get `start: null` and let sheetInsert compute the position lazily on first runtime add. - dom/index.js#createSheet scans the rootNode for `style[data-rnw-delta]` elements during the initial sheet bootstrap and passes their CSSStyleSheets to createOrderedCSSStyleSheet. Effect: when the streaming SSR pipeline emits per-chunk delta tags, their rules apply to the document immediately (no FOUC), and on hydration RNW's runtime sheet learns about them too. A subsequent StyleSheet.create call for a rule already in a delta tag dedups correctly and avoids inserting a duplicate runtime rule into the primary sheet. Tests: - New unit case in dom-createOrderedCSSStyleSheet-test.js exercises the multi-sheet hydration path: primary + two delta sheets across four distinct groups; verifies the unified text, dedup of delta-sourced rules, and admission of brand-new rules. - New dom-delta-hydration-test.js plants delta `<style>` elements in the document head before booting createSheet and verifies the module-scope sheets array hydrates from all of them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Streaming SSR can emit `<style data-rnw-delta="N">` tags interspersed
with body content as suspense boundaries resolve. The previous commit
made initial hydration walk every existing delta tag, but tags that
arrive AFTER RNW's first sheet bootstrap (i.e. with later chunks)
were not getting their rules into the dedup map / groups records.
This commit adds the inline-script handshake pattern:
<style data-rnw-delta="N">...rules...</style>
<script>
(window.__RNW_DELTA__ = window.__RNW_DELTA__ || []).push("N");
if (window.__RNW_INGEST_DELTA__) window.__RNW_INGEST_DELTA__();
</script>
createSheet installs __RNW_INGEST_DELTA__ on its first boot. The hook
drains window.__RNW_DELTA__: for each id, it finds the corresponding
`<style data-rnw-delta="N">` element, walks its cssRules, and calls
registerExisting on every sheet in the wrapper's sheets array.
New OrderedCSSStyleSheet.registerExisting:
- Same dedup + records semantics as `insert`, but skips the CSSOM
`insertRule` call. Used because the rule is already in the document
via the delta tag — we only need to teach the bookkeeping about it
so subsequent runtime StyleSheet.create dedups instead of injecting
a duplicate into the primary sheet.
The handshake works whether the inline script runs before or after
RNW boots:
- Before: __RNW_INGEST_DELTA__ is undefined, so the if-call is a noop;
the id stays queued. On boot, installDeltaIngest drains the queue.
- After: the if-call invokes the hook directly, which processes the
new id (and any other queued ids).
Re-processing the same delta id is a no-op (processed-ids Set).
If RNW never boots (e.g. JS-disabled crawler), nothing is lost — the
browser already applied the rules via the `<style data-rnw-delta>`
element. The bookkeeping is purely an optimization to avoid duplicate
CSSOM inserts at runtime; it has no effect on rendered output.
Tests (jest.isolateModules per case for fresh dom/index module state):
- Queue drained on first boot.
- Delta arriving AFTER boot is ingested when the hook is called.
- Reprocessing the same id is a no-op (no duplicate rules).
- Pre-boot script that ran before RNW loaded still gets drained.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Upstream styleq now returns a 3-tuple [classNames, inlineStyles, debugString] instead of the 2-tuple the StyleSheet inline snapshots were captured against. The third element is currently always "" — a debug-only slot that doesn't change rendered output but causes the snapshot equality check to fail. Necolas has a pending `update-styleq` branch upstream with the same fix; we just regenerate locally rather than waiting for it to merge. `jest -u` against packages/react-native-web/src/exports/StyleSheet/__tests__/index-test.js re-captures all 18 affected snapshots. No code change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
--
Also fixing upstream issues against newer styleq versions