From 58cd6ef10fd328d5e1d1a31370f5cf6ee0a721be Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Mon, 19 Sep 2022 11:56:28 +0200 Subject: [PATCH 1/2] Refactor fetchServerResponse --- packages/next/client/app-index.tsx | 6 ++---- .../next/client/components/app-router.client.tsx | 11 ++++++----- .../next/client/components/layout-router.client.tsx | 13 ++++++++----- packages/next/client/components/reducer.ts | 10 ++++------ packages/next/server/app-render.tsx | 6 ++---- packages/next/types/index.d.ts | 3 +++ 6 files changed, 25 insertions(+), 24 deletions(-) diff --git a/packages/next/client/app-index.tsx b/packages/next/client/app-index.tsx index 552b2c00f739887..f23b59429329334 100644 --- a/packages/next/client/app-index.tsx +++ b/packages/next/client/app-index.tsx @@ -2,7 +2,8 @@ import '../build/polyfills/polyfill-module' // @ts-ignore react-dom/client exists when using React 18 import ReactDOMClient from 'react-dom/client' -import React from 'react' +// TODO-APP: change to React.use once it becomes stable +import React, { experimental_use as use } from 'react' import { createFromReadableStream } from 'next/dist/compiled/react-server-dom-webpack' import measureWebVitals from './performance-relayer' @@ -16,9 +17,6 @@ declare global { const __webpack_require__: any } -// TODO-APP: change to React.use once it becomes stable -const use = (React as any).experimental_use - // eslint-disable-next-line no-undef const getChunkScriptFilename = __webpack_require__.u const chunkFilenameMap: any = {} diff --git a/packages/next/client/components/app-router.client.tsx b/packages/next/client/components/app-router.client.tsx index e2950f45588eab4..9bd71e4376d6d48 100644 --- a/packages/next/client/components/app-router.client.tsx +++ b/packages/next/client/components/app-router.client.tsx @@ -32,11 +32,11 @@ import { useReducerWithReduxDevtools } from './use-reducer-with-devtools' /** * Fetch the flight data for the provided url. Takes in the current router state to decide what to render server-side. */ -export function fetchServerResponse( +export async function fetchServerResponse( url: URL, flightRouterState: FlightRouterState, prefetch?: true -): Promise { +): Promise<[FlightData: FlightData]> { const flightUrl = new URL(url) const searchParams = flightUrl.searchParams // Enable flight response @@ -50,9 +50,10 @@ export function fetchServerResponse( searchParams.append('__flight_prefetch__', '1') } - const promise = fetch(flightUrl.toString()) + const res = await fetch(flightUrl.toString()) // Handle the `fetch` readable stream that can be unwrapped by `React.use`. - return createFromFetch(promise) + const flightData: FlightData = await createFromFetch(Promise.resolve(res)) + return [flightData] } /** @@ -197,7 +198,7 @@ export default function AppRouter({ window.history.state?.tree || initialTree, true ) - const flightData = await r + const [flightData] = await r // @ts-ignore startTransition exists React.startTransition(() => { dispatch({ diff --git a/packages/next/client/components/layout-router.client.tsx b/packages/next/client/components/layout-router.client.tsx index 5dfa3fb80fd4b58..8de34509af60f84 100644 --- a/packages/next/client/components/layout-router.client.tsx +++ b/packages/next/client/components/layout-router.client.tsx @@ -1,6 +1,12 @@ 'client' -import React, { useContext, useEffect, useRef } from 'react' +import React, { + useContext, + useEffect, + useRef, + // TODO-APP: change to React.use once it becomes stable + experimental_use as use, +} from 'react' import type { ChildProp, //Segment @@ -19,9 +25,6 @@ import { import { fetchServerResponse } from './app-router.client' // import { matchSegment } from './match-segments' -// TODO-APP: change to React.use once it becomes stable -const use = (React as any).experimental_use - /** * Check if every segment in array a and b matches */ @@ -230,7 +233,7 @@ export function InnerLayoutRouter({ * Flight response data */ // When the data has not resolved yet `use` will suspend here. - const flightData = use(childNode.data) + const [flightData] = use(childNode.data) // Handle case when navigating to page in `pages` from `app` if (typeof flightData === 'string') { diff --git a/packages/next/client/components/reducer.ts b/packages/next/client/components/reducer.ts index d8c6cb2c3bec889..3eaaf13e52204f0 100644 --- a/packages/next/client/components/reducer.ts +++ b/packages/next/client/components/reducer.ts @@ -6,13 +6,11 @@ import type { FlightSegmentPath, Segment, } from '../../server/app-render' -import React from 'react' +// TODO-APP: change to React.use once it becomes stable +import { experimental_use as use } from 'react' import { matchSegment } from './match-segments' import { fetchServerResponse } from './app-router.client' -// TODO-APP: change to React.use once it becomes stable -const use = (React as any).experimental_use - /** * Invalidate cache one level down from the router state. */ @@ -233,7 +231,7 @@ function fillCacheWithDataProperty( newCache: CacheNode, existingCache: CacheNode, segments: string[], - fetchResponse: any + fetchResponse: () => ReturnType ): { bailOptimistic: boolean } | undefined { const isLastEntry = segments.length === 1 @@ -734,7 +732,7 @@ export function reducer( cache, state.cache, segments.slice(1), - (): Promise => fetchServerResponse(url, optimisticTree) + () => fetchServerResponse(url, optimisticTree) ) // If optimistic fetch couldn't happen it falls back to the non-optimistic case. diff --git a/packages/next/server/app-render.tsx b/packages/next/server/app-render.tsx index ae12b0335b5b404..3ca720d87058079 100644 --- a/packages/next/server/app-render.tsx +++ b/packages/next/server/app-render.tsx @@ -2,7 +2,8 @@ import type { IncomingHttpHeaders, IncomingMessage, ServerResponse } from 'http' import type { LoadComponentsReturnType } from './load-components' import type { ServerRuntime } from '../types' -import React from 'react' +// TODO-APP: change to React.use once it becomes stable +import React, { experimental_use as use } from 'react' import { ParsedUrlQuery, stringify as stringifyQuery } from 'querystring' import { createFromReadableStream } from 'next/dist/compiled/react-server-dom-webpack' import { renderToReadableStream } from 'next/dist/compiled/react-server-dom-webpack/writer.browser.server' @@ -29,9 +30,6 @@ import { FlushEffectsContext } from '../shared/lib/flush-effects' import { stripInternalQueries } from './internal-utils' import type { ComponentsType } from '../build/webpack/loaders/next-app-loader' -// TODO-APP: change to React.use once it becomes stable -const use = (React as any).experimental_use - // this needs to be required lazily so that `next-server` can set // the env before we require const ReactDOMServer = shouldUseReactRoot diff --git a/packages/next/types/index.d.ts b/packages/next/types/index.d.ts index 25b63fe25533161..28866d91b134c3b 100644 --- a/packages/next/types/index.d.ts +++ b/packages/next/types/index.d.ts @@ -39,6 +39,9 @@ declare module 'react' { interface LinkHTMLAttributes extends HTMLAttributes { nonce?: string } + + // TODO-APP: check if this is the right type. + function experimental_use(promise: Promise): Awaited } export type Redirect = From 1f8749e0242c7fa8c3773b1948df81d734a052a4 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Mon, 19 Sep 2022 13:44:40 +0200 Subject: [PATCH 2/2] Fix type errors --- packages/next/client/app-index.tsx | 6 +++--- packages/next/client/components/reducer.ts | 4 ++-- packages/next/server/app-render.tsx | 23 ++++++++++++---------- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/packages/next/client/app-index.tsx b/packages/next/client/app-index.tsx index f23b59429329334..c8ede9eaa67dac4 100644 --- a/packages/next/client/app-index.tsx +++ b/packages/next/client/app-index.tsx @@ -125,7 +125,7 @@ function createResponseCache() { } const rscCache = createResponseCache() -function useInitialServerResponse(cacheKey: string) { +function useInitialServerResponse(cacheKey: string): Promise { const response = rscCache.get(cacheKey) if (response) return response @@ -141,7 +141,7 @@ function useInitialServerResponse(cacheKey: string) { return newResponse } -function ServerRoot({ cacheKey }: { cacheKey: string }) { +function ServerRoot({ cacheKey }: { cacheKey: string }): JSX.Element { React.useEffect(() => { rscCache.delete(cacheKey) }) @@ -169,7 +169,7 @@ function Root({ children }: React.PropsWithChildren<{}>): React.ReactElement { return children as React.ReactElement } -function RSCComponent(props: any) { +function RSCComponent(props: any): JSX.Element { const cacheKey = getCacheKey() return } diff --git a/packages/next/client/components/reducer.ts b/packages/next/client/components/reducer.ts index 3eaaf13e52204f0..60c33a9916938cf 100644 --- a/packages/next/client/components/reducer.ts +++ b/packages/next/client/components/reducer.ts @@ -763,7 +763,7 @@ export function reducer( } // Unwrap cache data with `use` to suspend here (in the reducer) until the fetch resolves. - const flightData = use(cache.data) + const [flightData] = use(cache.data) // Handle case when navigating to page in `pages` from `app` if (typeof flightData === 'string') { @@ -954,7 +954,7 @@ export function reducer( 'refetch', ]) } - const flightData = use(cache.data) + const [flightData] = use(cache.data) // Handle case when navigating to page in `pages` from `app` if (typeof flightData === 'string') { diff --git a/packages/next/server/app-render.tsx b/packages/next/server/app-render.tsx index 3ca720d87058079..f115df782da17ef 100644 --- a/packages/next/server/app-render.tsx +++ b/packages/next/server/app-render.tsx @@ -137,6 +137,10 @@ function preloadDataFetchingRecord( return record } +interface FlightResponseRef { + current: Promise | null +} + /** * Render Flight stream. * This is only used for renderToHTML, the Flight response does not need additional wrappers. @@ -145,19 +149,18 @@ function useFlightResponse( writable: WritableStream, req: ReadableStream, serverComponentManifest: any, - flightResponseRef: { - current: ReturnType | null - }, + flightResponseRef: FlightResponseRef, nonce?: string -) { - if (flightResponseRef.current) { +): Promise { + if (flightResponseRef.current !== null) { return flightResponseRef.current } const [renderStream, forwardStream] = readableStreamTee(req) - flightResponseRef.current = createFromReadableStream(renderStream, { + const res = createFromReadableStream(renderStream, { moduleMap: serverComponentManifest.__ssr_module_mapping__, }) + flightResponseRef.current = res let bootstrapped = false // We only attach CSS chunks to the inlined data. @@ -195,7 +198,7 @@ function useFlightResponse( } process() - return flightResponseRef.current + return res } /** @@ -222,7 +225,7 @@ function createServerComponentRenderer( > }, nonce?: string -) { +): () => JSX.Element { // We need to expose the `__webpack_require__` API globally for // react-server-dom-webpack. This is a hack until we find a better way. if (ComponentMod.__next_app_webpack_require__ || ComponentMod.__next_rsc__) { @@ -249,10 +252,10 @@ function createServerComponentRenderer( return RSCStream } - const flightResponseRef = { current: null } + const flightResponseRef: FlightResponseRef = { current: null } const writable = transformStream.writable - return function ServerComponentWrapper() { + return function ServerComponentWrapper(): JSX.Element { const reqStream = createRSCStream() const response = useFlightResponse( writable,