From c6256a6b375d17d0f1e5d8cae81f1732529c4016 Mon Sep 17 00:00:00 2001 From: "Sebastian \"Sebbie\" Silbermann" Date: Wed, 1 Oct 2025 18:41:42 +0200 Subject: [PATCH] [react-dom] Add Canary types for partial-prerendering (#73765) --- types/react-dom/canary.d.ts | 67 +++++++++++++++++++++++++++ types/react-dom/test/canary-tests.tsx | 31 +++++++++++++ types/react/ts5.0/index.d.ts | 3 +- 3 files changed, 100 insertions(+), 1 deletion(-) diff --git a/types/react-dom/canary.d.ts b/types/react-dom/canary.d.ts index c7d77864415d0bb..363a6aec6695436 100644 --- a/types/react-dom/canary.d.ts +++ b/types/react-dom/canary.d.ts @@ -1,3 +1,5 @@ +/* eslint-disable @definitelytyped/no-self-import -- self-imports in module augmentations aren't self-imports */ +/* eslint-disable @definitelytyped/no-declare-current-package -- The module augmentations are optional */ /** * These are types for things that are present in the upcoming React 18 release. * @@ -35,3 +37,68 @@ declare module "react" { // eslint-disable-next-line @typescript-eslint/no-empty-interface interface CacheSignal extends AbortSignal {} } + +declare const POSTPONED_STATE_SIGIL: unique symbol; +declare module "react-dom/static" { + /** + * This is an opaque type i.e. users should not make any assumptions about its structure. + * It is JSON-serializeable to be a able to store it and retrvieve later for use with {@link https://react.dev/reference/react-dom/server/resume `resume`}. + */ + interface PostponedState { + [POSTPONED_STATE_SIGIL]: never; + } + + interface ResumeOptions { + nonce?: string; + signal?: AbortSignal; + onError?: (error: unknown) => string | undefined | void; + } + + interface PrerenderResult { + postponed: null | PostponedState; + } + /** + * @see {@link https://react.dev/reference/react-dom/static/resumeAndPrerender `resumeAndPrerender` reference documentation} + * @version 19.2 + */ + function resumeAndPrerender( + children: React.ReactNode, + postponedState: PostponedState, + options?: Omit, + ): Promise; + + interface PrerenderToNodeStreamResult { + postponed: null | PostponedState; + } + /** + * @see {@link https://react.dev/reference/react-dom/static/resumeAndPrerenderToNodeStream `resumeAndPrerenderToNodeStream`` reference documentation} + * @version 19.2 + */ + function resumeAndPrerenderToNodeStream( + children: React.ReactNode, + postponedState: PostponedState, + options?: Omit, + ): Promise; +} + +import { PostponedState, ResumeOptions } from "react-dom/static"; +declare module "react-dom/server" { + /** + * @see {@link https://react.dev/reference/react-dom/server/resume `resume`` reference documentation} + * @version 19.2 + */ + function resume( + children: React.ReactNode, + postponedState: PostponedState, + options?: ResumeOptions, + ): Promise; + /** + * @see {@link https://react.dev/reference/react-dom/server/resumeToPipeableStream `resumeToPipeableStream`` reference documentation} + * @version 19.2 + */ + function resumeToPipeableStream( + children: React.ReactNode, + postponedState: PostponedState, + options?: ResumeOptions, + ): Promise; +} diff --git a/types/react-dom/test/canary-tests.tsx b/types/react-dom/test/canary-tests.tsx index 23a1d259ac8546c..6a226d9c29acb19 100644 --- a/types/react-dom/test/canary-tests.tsx +++ b/types/react-dom/test/canary-tests.tsx @@ -2,6 +2,8 @@ import React = require("react"); import ReactDOM = require("react-dom"); import ReactDOMClient = require("react-dom/client"); +import ReactDOMServer = require("react-dom/server"); +import ReactDOMStatic = require("react-dom/static"); function cacheSignalTest() { const cacheSignal = React.cacheSignal; @@ -15,3 +17,32 @@ function cacheSignalTest() { } } } + +async function resumeTest(children: React.ReactNode) { + const prerenderController = new AbortController(); + setTimeout(prerenderController.abort, 1); + const { prelude, postponed } = await ReactDOMStatic.prerender(children, { signal: prerenderController.signal }); + + let stream: ReadableStream; + if (postponed !== null) { + const resumeController = new AbortController(); + const reactStream = await ReactDOMServer.resume(children, postponed, { + nonce: "nonce", + signal: resumeController.signal, + onError(error) { + return (error as { digest?: string | undefined }).digest; + }, + }); + await reactStream.allReady; + // In a real app you'd also have to flush the prelude first + stream = reactStream; + } else { + stream = prelude; + } + + await ReactDOMServer.resume( + children, + // @ts-expect-error Requires the opaque postponed state + {}, + ); +} diff --git a/types/react/ts5.0/index.d.ts b/types/react/ts5.0/index.d.ts index 5a94eeafbdf41e7..02a1f914f434e06 100644 --- a/types/react/ts5.0/index.d.ts +++ b/types/react/ts5.0/index.d.ts @@ -1126,7 +1126,8 @@ declare namespace React { interface ComponentClass

extends StaticLifecycle { // constructor signature must match React.Component new( - props: P, /** + props: P, + /** * Value of the parent {@link https://react.dev/reference/react/Component#context Context} specified * in `contextType`. */