Skip to content

Commit 2367645

Browse files
authored
feat(package/react): streaming SSR on react-dom@>=18 (#2049)
* feat(package/react): SSR uses renderToPipeableStream on react-dom@>=18 * feat: support web standard runtimes * chore(package/react): add peers for React 19
1 parent 46b8a4d commit 2367645

File tree

5 files changed

+48
-10
lines changed

5 files changed

+48
-10
lines changed

.changeset/fluffy-kings-smell.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@gqty/react': minor
3+
---
4+
5+
streaming SSR on react-dom@>=18

packages/react/package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@
114114
"jest-environment-jsdom": "^29.7.0",
115115
"lodash-es": "^4.17.21",
116116
"react": "^18.3.1",
117+
"react-dom": "^18.3.1",
117118
"react-test-renderer": "^18.3.1",
118119
"test-utils": "workspace:^",
119120
"type-fest": "^4.24.0",
@@ -124,7 +125,8 @@
124125
"graphql": "*",
125126
"graphql-sse": "^2.5.3",
126127
"graphql-ws": "^5.16.0",
127-
"react": "^16.14.0 || ^17 || ^18"
128+
"react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
129+
"react-dom": "^18.0.0 || ^19.0.0"
128130
},
129131
"peerDependenciesMeta": {
130132
"graphql-sse": {

packages/react/src/ssr/ssr.ts

+32-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import {
33
type GQtyClient,
44
type LegacyHydrateCacheOptions,
55
} from 'gqty';
6-
import { useEffect, useMemo, type ReactNode } from 'react';
6+
import { type ReactNode, useEffect, useMemo } from 'react';
7+
import { version } from 'react-dom/server';
78
import { getDefault, type ReactClientOptionsWithDefaults } from '../utils';
89

910
export interface UseHydrateCacheOptions
@@ -25,7 +26,7 @@ export interface UseHydrateCacheOptions
2526
* Props with `cacheSnapshot` that would be returned from `prepareReactRender`
2627
*/
2728
export type PropsWithServerCache<
28-
T extends Record<string | number, unknown> = Record<string | number, unknown>
29+
T extends Record<string | number, unknown> = Record<string | number, unknown>,
2930
> = {
3031
/**
3132
* Cache snapshot, returned from `prepareReactRender`
@@ -51,10 +52,37 @@ export function createSSRHelpers<TSchema extends BaseGeneratedSchema>(
5152
) {
5253
const prepareReactRender: PrepareReactRender =
5354
async function prepareReactRender(element: ReactNode) {
54-
const ssrPrepass = getDefault(await import('react-ssr-prepass'));
55+
const majorVersion = +version.split('.')[0];
5556

56-
return prepareRender(() => ssrPrepass(element));
57+
if (majorVersion >= 18) {
58+
const { renderToPipeableStream, renderToReadableStream } = await import(
59+
'react-dom/server'
60+
);
61+
62+
if (renderToReadableStream !== undefined) {
63+
return prepareRender(async () => {
64+
const stream = await renderToReadableStream(element);
65+
66+
await stream.allReady;
67+
});
68+
} else {
69+
return prepareRender(
70+
() =>
71+
new Promise((resolve, reject) => {
72+
renderToPipeableStream(element, {
73+
onAllReady: resolve,
74+
onError: reject,
75+
});
76+
})
77+
);
78+
}
79+
} else {
80+
const ssrPrepass = getDefault(await import('react-ssr-prepass'));
81+
82+
return prepareRender(() => ssrPrepass(element));
83+
}
5784
};
85+
5886
const useHydrateCache: UseHydrateCache = function useHydrateCache({
5987
cacheSnapshot,
6088
shouldRefetch = refetchAfterHydrate,

packages/react/src/useThrottledAsync.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -65,31 +65,31 @@ export function useThrottledAsync<Result, Args extends unknown[] = unknown[]>(
6565
): [
6666
AsyncState<Result>,
6767
UseAsyncActions<Result, Args>,
68-
UseAsyncMeta<Result, Args>
68+
UseAsyncMeta<Result, Args>,
6969
];
7070
export function useThrottledAsync<Result, Args extends unknown[] = unknown[]>(
7171
asyncFn: (...params: Args) => Promise<Result>,
7272
initialValue?: Result
7373
): [
7474
AsyncState<Result | undefined>,
7575
UseAsyncActions<Result, Args>,
76-
UseAsyncMeta<Result, Args>
76+
UseAsyncMeta<Result, Args>,
7777
];
7878
export function useThrottledAsync<Result, Args extends unknown[] = unknown[]>(
7979
asyncFn: (...params: Args) => Promise<Result>,
8080
initialValue?: Result
8181
): [
8282
AsyncState<Result | undefined>,
8383
UseAsyncActions<Result, Args>,
84-
UseAsyncMeta<Result, Args>
84+
UseAsyncMeta<Result, Args>,
8585
] {
8686
const [state, setState] = useState<AsyncState<Result | undefined>>({
8787
status: 'not-executed',
8888
error: undefined,
8989
result: initialValue,
9090
});
91-
const promiseRef = useRef<Promise<Result>>();
92-
const argsRef = useRef<Args>();
91+
const promiseRef = useRef<Promise<Result> | undefined>(undefined);
92+
const argsRef = useRef<Args | undefined>(undefined);
9393

9494
const methods = useSyncedRef({
9595
execute: (...params: Args) => {

pnpm-lock.yaml

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)