Refactor fetchServerResponse (#40674)
This commit is contained in:
parent
356b6cec43
commit
c7e2619313
6 changed files with 43 additions and 39 deletions
|
@ -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 = {}
|
||||
|
@ -127,7 +125,7 @@ function createResponseCache() {
|
|||
}
|
||||
const rscCache = createResponseCache()
|
||||
|
||||
function useInitialServerResponse(cacheKey: string) {
|
||||
function useInitialServerResponse(cacheKey: string): Promise<JSX.Element> {
|
||||
const response = rscCache.get(cacheKey)
|
||||
if (response) return response
|
||||
|
||||
|
@ -143,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)
|
||||
})
|
||||
|
@ -171,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 <ServerRoot {...props} cacheKey={cacheKey} />
|
||||
}
|
||||
|
|
|
@ -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<FlightData> {
|
||||
): 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({
|
||||
|
|
|
@ -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') {
|
||||
|
|
|
@ -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<typeof fetchServerResponse>
|
||||
): { bailOptimistic: boolean } | undefined {
|
||||
const isLastEntry = segments.length === 1
|
||||
|
||||
|
@ -734,7 +732,7 @@ export function reducer(
|
|||
cache,
|
||||
state.cache,
|
||||
segments.slice(1),
|
||||
(): Promise<FlightData> => fetchServerResponse(url, optimisticTree)
|
||||
() => fetchServerResponse(url, optimisticTree)
|
||||
)
|
||||
|
||||
// If optimistic fetch couldn't happen it falls back to the non-optimistic case.
|
||||
|
@ -765,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') {
|
||||
|
@ -956,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') {
|
||||
|
|
|
@ -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
|
||||
|
@ -139,6 +137,10 @@ function preloadDataFetchingRecord(
|
|||
return record
|
||||
}
|
||||
|
||||
interface FlightResponseRef {
|
||||
current: Promise<JSX.Element> | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Flight stream.
|
||||
* This is only used for renderToHTML, the Flight response does not need additional wrappers.
|
||||
|
@ -147,19 +149,18 @@ function useFlightResponse(
|
|||
writable: WritableStream<Uint8Array>,
|
||||
req: ReadableStream<Uint8Array>,
|
||||
serverComponentManifest: any,
|
||||
flightResponseRef: {
|
||||
current: ReturnType<typeof createFromReadableStream> | null
|
||||
},
|
||||
flightResponseRef: FlightResponseRef,
|
||||
nonce?: string
|
||||
) {
|
||||
if (flightResponseRef.current) {
|
||||
): Promise<JSX.Element> {
|
||||
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.
|
||||
|
@ -197,7 +198,7 @@ function useFlightResponse(
|
|||
}
|
||||
process()
|
||||
|
||||
return flightResponseRef.current
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -224,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__) {
|
||||
|
@ -251,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,
|
||||
|
|
3
packages/next/types/index.d.ts
vendored
3
packages/next/types/index.d.ts
vendored
|
@ -39,6 +39,9 @@ declare module 'react' {
|
|||
interface LinkHTMLAttributes<T> extends HTMLAttributes<T> {
|
||||
nonce?: string
|
||||
}
|
||||
|
||||
// TODO-APP: check if this is the right type.
|
||||
function experimental_use<T>(promise: Promise<T>): Awaited<T>
|
||||
}
|
||||
|
||||
export type Redirect =
|
||||
|
|
Loading…
Reference in a new issue