diff --git a/packages/next/client/app-index.tsx b/packages/next/client/app-index.tsx index 84fa2cb088d5ce9..fa475942a2f963b 100644 --- a/packages/next/client/app-index.tsx +++ b/packages/next/client/app-index.tsx @@ -14,6 +14,9 @@ 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 = {} @@ -143,7 +146,7 @@ function ServerRoot({ cacheKey }: { cacheKey: string }) { rscCache.delete(cacheKey) }) const response = useInitialServerResponse(cacheKey) - const root = response.readRoot() + const root = use(response) return root } diff --git a/packages/next/client/components/app-router.client.tsx b/packages/next/client/components/app-router.client.tsx index e3d4766652cfa96..edc4cf2e77dc79d 100644 --- a/packages/next/client/components/app-router.client.tsx +++ b/packages/next/client/components/app-router.client.tsx @@ -65,8 +65,8 @@ export function fetchServerResponse( url: URL, flightRouterState: FlightRouterState, prefetch?: true -): { readRoot: () => FlightData } { - // Handle the `fetch` readable stream that can be read using `readRoot`. +): Promise { + // Handle the `fetch` readable stream that can be unwrapped by `React.use`. return createFromReadableStream(fetchFlight(url, flightRouterState, prefetch)) } @@ -212,20 +212,15 @@ export default function AppRouter({ window.history.state?.tree || initialTree, true ) - try { - r.readRoot() - } catch (e) { - await e - const flightData = r.readRoot() - // @ts-ignore startTransition exists - React.startTransition(() => { - dispatch({ - type: ACTION_PREFETCH, - url, - flightData, - }) + const flightData = await r + // @ts-ignore startTransition exists + React.startTransition(() => { + dispatch({ + type: ACTION_PREFETCH, + url, + flightData, }) - } + }) } catch (err) { console.error('PREFETCH ERROR', err) } diff --git a/packages/next/client/components/layout-router.client.tsx b/packages/next/client/components/layout-router.client.tsx index 197764187e727a8..c1521955d3a5e61 100644 --- a/packages/next/client/components/layout-router.client.tsx +++ b/packages/next/client/components/layout-router.client.tsx @@ -17,6 +17,9 @@ 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 */ @@ -218,14 +221,14 @@ export function InnerLayoutRouter({ throw new Error('Child node should not have both subTreeData and data') } - // If cache node has a data request we have to readRoot and update the cache. + // If cache node has a data request we have to unwrap response by `use` and update the cache. if (childNode.data) { // TODO-APP: error case /** * Flight response data */ - // When the data has not resolved yet readRoot will suspend here. - const flightData = childNode.data.readRoot() + // When the data has not resolved yet `use` will suspend here. + 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 f4b013528ceb027..d8c6cb2c3bec889 100644 --- a/packages/next/client/components/reducer.ts +++ b/packages/next/client/components/reducer.ts @@ -6,9 +6,13 @@ import type { FlightSegmentPath, Segment, } from '../../server/app-render' +import React 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. */ @@ -730,8 +734,7 @@ export function reducer( cache, state.cache, segments.slice(1), - (): { readRoot: () => FlightData } => - fetchServerResponse(url, optimisticTree) + (): Promise => fetchServerResponse(url, optimisticTree) ) // If optimistic fetch couldn't happen it falls back to the non-optimistic case. @@ -761,8 +764,8 @@ export function reducer( cache.data = fetchServerResponse(url, state.tree) } - // readRoot to suspend here (in the reducer) until the fetch resolves. - const flightData = cache.data.readRoot() + // Unwrap cache data with `use` to suspend here (in the reducer) until the fetch resolves. + const flightData = use(cache.data) // Handle case when navigating to page in `pages` from `app` if (typeof flightData === 'string') { @@ -953,7 +956,7 @@ export function reducer( 'refetch', ]) } - const flightData = cache.data.readRoot() + const flightData = use(cache.data) // Handle case when navigating to page in `pages` from `app` if (typeof flightData === 'string') { diff --git a/packages/next/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.browser.development.server.js b/packages/next/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.browser.development.server.js index b5e1d259afd5267..d059e1b5573b58b 100644 --- a/packages/next/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.browser.development.server.js +++ b/packages/next/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.browser.development.server.js @@ -162,6 +162,7 @@ function processErrorChunk(request, id, message, stack) { return stringToChunk(row); } function processModelChunk(request, id, model) { + // $FlowFixMe: `json` might be `undefined` when model is a symbol. var json = stringify(model, request.toJSON); var row = serializeRowHeader('J', id) + json + '\n'; return stringToChunk(row); @@ -172,6 +173,7 @@ function processReferenceChunk(request, id, reference) { return stringToChunk(row); } function processModuleChunk(request, id, moduleMetaData) { + // $FlowFixMe: `json` might be `undefined` when moduleMetaData is a symbol. var json = stringify(moduleMetaData); var row = serializeRowHeader('M', id) + json + '\n'; return stringToChunk(row); @@ -894,8 +896,8 @@ function readContext(context) { return value; } -// Corresponds to ReactFiberWakeable module. Generally, changes to one module -// should be reflected in the other. +// Corresponds to ReactFiberWakeable and ReactFizzWakeable modules. Generally, +// changes to one module should be reflected in the others. // TODO: Rename this module and the corresponding Fiber one to "Thenable" // instead of "Wakeable". Or some other more appropriate name. // TODO: Sparse arrays are bad for performance. @@ -917,11 +919,6 @@ function trackSuspendedWakeable(wakeable) { // a listener that will update its status and result when it resolves. switch (thenable.status) { - case 'pending': - // Since the status is already "pending", we can assume it will be updated - // when it resolves, either by React or something in userspace. - break; - case 'fulfilled': case 'rejected': // A thenable that already resolved shouldn't have been thrown, so this is @@ -933,9 +930,13 @@ function trackSuspendedWakeable(wakeable) { default: { - // TODO: Only instrument the thenable if the status if not defined. If - // it's defined, but an unknown value, assume it's been instrumented by - // some custom userspace implementation. + if (typeof thenable.status === 'string') { + // Only instrument the thenable if the status if not defined. If + // it's defined, but an unknown value, assume it's been instrumented by + // some custom userspace implementation. We treat it as "pending". + break; + } + var pendingThenable = thenable; pendingThenable.status = 'pending'; pendingThenable.then(function (fulfilledValue) { @@ -990,7 +991,9 @@ function prepareToUseHooksForComponent(prevThenableState) { thenableState = prevThenableState; } function getThenableStateAfterSuspending() { - return thenableState; + var state = thenableState; + thenableState = null; + return state; } function readContext$1(context) { @@ -1149,6 +1152,9 @@ function use(usable) { } } } + } else if (usable.$$typeof === REACT_SERVER_CONTEXT_TYPE) { + var context = usable; + return readContext$1(context); } } // eslint-disable-next-line react-internal/safe-string-coercion @@ -1159,7 +1165,8 @@ function use(usable) { var ContextRegistry = ReactSharedInternals.ContextRegistry; function getOrCreateServerContext(globalName) { if (!ContextRegistry[globalName]) { - ContextRegistry[globalName] = React.createServerContext(globalName, REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED); + ContextRegistry[globalName] = React.createServerContext(globalName, // $FlowFixMe function signature doesn't reflect the symbol value + REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED); } return ContextRegistry[globalName]; @@ -1292,7 +1299,8 @@ function attemptResolveElement(type, key, ref, props, prevThenableState) { if (extraKeys.length !== 0) { error('ServerContext can only have a value prop and children. Found: %s', JSON.stringify(extraKeys)); } - } + } // $FlowFixMe issue discovered when updating Flow + return [REACT_ELEMENT_TYPE, type, key, // Rely on __popProvider being serialized last to pop the provider. { @@ -1677,7 +1685,8 @@ function resolveModelToJSON(request, parent, key, value) { } } } - } + } // $FlowFixMe + return value; } @@ -1708,12 +1717,14 @@ function resolveModelToJSON(request, parent, key, value) { if (existingId !== undefined) { return serializeByValueID(existingId); - } + } // $FlowFixMe `description` might be undefined + - var name = value.description; + var name = value.description; // $FlowFixMe `name` might be undefined if (Symbol.for(name) !== value) { - throw new Error('Only global symbols received from Symbol.for(...) can be passed to client components. ' + ("The symbol Symbol.for(" + value.description + ") cannot be found among global symbols. ") + ("Remove " + describeKeyForErrorMessage(key) + " from this object, or avoid the entire object: " + describeObjectForErrorMessage(parent))); + throw new Error('Only global symbols received from Symbol.for(...) can be passed to client components. ' + ("The symbol Symbol.for(" + // $FlowFixMe `description` might be undefined + value.description + ") cannot be found among global symbols. ") + ("Remove " + describeKeyForErrorMessage(key) + " from this object, or avoid the entire object: " + describeObjectForErrorMessage(parent))); } request.pendingChunks++; @@ -1772,6 +1783,7 @@ function emitErrorChunk(request, id, error) { } function emitModuleChunk(request, id, moduleMetaData) { + // $FlowFixMe ModuleMetaData is not a ReactModel var processedChunk = processModuleChunk(request, id, moduleMetaData); request.completedModuleChunks.push(processedChunk); } diff --git a/packages/next/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.browser.production.min.server.js b/packages/next/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.browser.production.min.server.js index 892fa9bace9be57..e1e75332dacc9e3 100644 --- a/packages/next/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.browser.production.min.server.js +++ b/packages/next/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-writer.browser.production.min.server.js @@ -8,43 +8,43 @@ * LICENSE file in the root directory of this source tree. */ 'use strict';var e=require("react"),l=null,m=0;function n(a,c){if(0!==c.length)if(512