From d71de204d315d51629f7279151dd262badc0cc25 Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Sat, 26 Mar 2022 13:57:06 +0100 Subject: [PATCH 01/12] merge conflicts --- packages/next/build/index.ts | 17 ++- packages/next/build/swc/options.js | 2 +- packages/next/build/utils.ts | 9 +- .../loaders/next-flight-server-loader.ts | 32 ++++- packages/next/client/index.tsx | 8 +- packages/next/client/page-loader.ts | 6 +- packages/next/server/base-server.ts | 10 +- packages/next/server/render.tsx | 129 ++++++++++++++---- packages/next/shared/lib/router/router.ts | 39 ++++-- .../pages/edge-rsc.server.js | 8 +- .../switchable-runtime/pages/edge.js | 8 +- .../pages/node-rsc-ssg.server.js | 8 +- .../pages/node-rsc-ssr.server.js | 8 +- .../pages/node-rsc.server.js | 8 +- .../switchable-runtime/pages/node-ssg.js | 8 +- .../switchable-runtime/pages/node-ssr.js | 8 +- .../switchable-runtime/pages/node.js | 30 +++- .../switchable-runtime/pages/static.js | 8 +- .../switchable-runtime/utils/runtime.js | 13 +- .../switchable-runtime/utils/time.js | 13 +- 20 files changed, 271 insertions(+), 101 deletions(-) diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts index f96f20f6228a0fa..907323182dd5378 100644 --- a/packages/next/build/index.ts +++ b/packages/next/build/index.ts @@ -98,6 +98,7 @@ import { copyTracedFiles, isReservedPage, isCustomErrorPage, + isFlightPage, } from './utils' import getBaseWebpackConfig from './webpack-config' import { PagesManifest } from './webpack/plugins/pages-manifest-plugin' @@ -161,7 +162,6 @@ export default async function build( // using React 18 or experimental. const hasReactRoot = shouldUseReactRoot() const hasConcurrentFeatures = hasReactRoot - const hasServerComponents = hasReactRoot && !!config.experimental.serverComponents @@ -287,6 +287,7 @@ export default async function build( .traceAsyncFn(() => collectPages(pagesDir, config.pageExtensions)) // needed for static exporting since we want to replace with HTML // files + const allStaticPages = new Set() let allPageInfos = new Map() @@ -956,6 +957,7 @@ export default async function build( let isSsg = false let isStatic = false + let isServerComponent = false let isHybridAmp = false let ssgPageRoutes: string[] | null = null let isMiddlewareRoute = !!page.match(MIDDLEWARE_ROUTE) @@ -973,6 +975,12 @@ export default async function build( ) : undefined + if (hasServerComponents && pagePath) { + if (isFlightPage(config, pagePath)) { + isServerComponent = true + } + } + if ( !isMiddlewareRoute && !isReservedPage(page) && @@ -1042,11 +1050,16 @@ export default async function build( serverPropsPages.add(page) } else if ( workerResult.isStatic && - !workerResult.hasFlightData && + !isServerComponent && (await customAppGetInitialPropsPromise) === false ) { staticPages.add(page) isStatic = true + } else if (isServerComponent) { + // This is a static server component page that doesn't have + // gSP or gSSP. We still treat it as a SSG page. + ssgPages.add(page) + isSsg = true } if (hasPages404 && page === '/404') { diff --git a/packages/next/build/swc/options.js b/packages/next/build/swc/options.js index 33ed7b9d00eaa72..dd98795412ecf2c 100644 --- a/packages/next/build/swc/options.js +++ b/packages/next/build/swc/options.js @@ -104,7 +104,7 @@ function getBaseSWCOptions({ reactRemoveProperties: nextConfig?.compiler?.reactRemoveProperties, modularizeImports: nextConfig?.experimental?.modularizeImports, relay: nextConfig?.compiler?.relay, - emotion: getEmotionOptions(nextConfig, development), + // emotion: getEmotionOptions(nextConfig, development), } } diff --git a/packages/next/build/utils.ts b/packages/next/build/utils.ts index 110ef701c36ca1a..7ed0d77a61e4f1d 100644 --- a/packages/next/build/utils.ts +++ b/packages/next/build/utils.ts @@ -859,7 +859,6 @@ export async function isPageStatic( isStatic?: boolean isAmpOnly?: boolean isHybridAmp?: boolean - hasFlightData?: boolean hasServerProps?: boolean hasStaticProps?: boolean prerenderRoutes?: string[] @@ -882,7 +881,6 @@ export async function isPageStatic( throw new Error('INVALID_DEFAULT_EXPORT') } - const hasFlightData = !!(mod as any).__next_rsc__ const hasGetInitialProps = !!(Comp as any).getInitialProps const hasStaticProps = !!mod.getStaticProps const hasStaticPaths = !!mod.getStaticPaths @@ -970,11 +968,7 @@ export async function isPageStatic( const isNextImageImported = (global as any).__NEXT_IMAGE_IMPORTED const config: PageConfig = mod.pageConfig return { - isStatic: - !hasStaticProps && - !hasGetInitialProps && - !hasServerProps && - !hasFlightData, + isStatic: !hasStaticProps && !hasGetInitialProps && !hasServerProps, isHybridAmp: config.amp === 'hybrid', isAmpOnly: config.amp === true, prerenderRoutes, @@ -982,7 +976,6 @@ export async function isPageStatic( encodedPrerenderRoutes, hasStaticProps, hasServerProps, - hasFlightData, isNextImageImported, traceIncludes: config.unstable_includeFiles || [], traceExcludes: config.unstable_excludeFiles || [], diff --git a/packages/next/build/webpack/loaders/next-flight-server-loader.ts b/packages/next/build/webpack/loaders/next-flight-server-loader.ts index b87f234690b154a..b6843e411463b39 100644 --- a/packages/next/build/webpack/loaders/next-flight-server-loader.ts +++ b/packages/next/build/webpack/loaders/next-flight-server-loader.ts @@ -41,6 +41,8 @@ async function parseModuleInfo({ source: string imports: string isEsm: boolean + __N_SSG: boolean + __N_SSP: boolean }> { const ast = await parse(source, { filename: resourcePath, isModule: true }) const { body } = ast @@ -48,6 +50,8 @@ async function parseModuleInfo({ let lastIndex = 0 let imports = '' let isEsm = false + let __N_SSG = false + let __N_SSP = false for (let i = 0; i < body.length; i++) { const node = body[i] @@ -111,6 +115,26 @@ async function parseModuleInfo({ lastIndex = node.source.span.end break } + case 'ExportDeclaration': { + if (isClientCompilation) { + // Keep `__N_SSG` and `__N_SSP` exports. + if (node.declaration?.type === 'VariableDeclaration') { + for (const declaration of node.declaration.declarations) { + if (declaration.type === 'VariableDeclarator') { + if (declaration.id?.type === 'Identifier') { + const value = declaration.id.value + if (value === '__N_SSP') { + __N_SSP = true + } else if (value === '__N_SSG') { + __N_SSG = true + } + } + } + } + } + break + } + } default: break } @@ -120,7 +144,7 @@ async function parseModuleInfo({ transformedSource += source.substring(lastIndex) } - return { source: transformedSource, imports, isEsm } + return { source: transformedSource, imports, isEsm, __N_SSG, __N_SSP } } export default async function transformSource( @@ -159,6 +183,8 @@ export default async function transformSource( source: transformedSource, imports, isEsm, + __N_SSG, + __N_SSP, } = await parseModuleInfo({ resourcePath, source, @@ -187,7 +213,9 @@ export default async function transformSource( } if (isClientCompilation) { - rscExports['default'] = 'function RSC() {}' + rscExports.default = 'function RSC() {}' + if (__N_SSG) rscExports.__N_SSG = 'true' + if (__N_SSP) rscExports.__N_SSP = 'true' } const output = transformedSource + '\n' + buildExports(rscExports, isEsm) diff --git a/packages/next/client/index.tsx b/packages/next/client/index.tsx index 941c51bcd9dbe05..84f1a8e7aaed7b2 100644 --- a/packages/next/client/index.tsx +++ b/packages/next/client/index.tsx @@ -797,16 +797,16 @@ if (process.env.__NEXT_RSC) { rscCache.delete(cacheKey) }) const response = useServerResponse(cacheKey, serialized) - const root = response.readRoot() - return root + return response.readRoot() } RSCComponent = (props: any) => { const cacheKey = getCacheKey() - const { __flight_serialized__ } = props + const { __flight__ } = props const [, dispatch] = useState({}) const startTransition = (React as any).startTransition const rerender = () => dispatch({}) + // If there is no cache, or there is serialized data already function refreshCache(nextProps?: any) { startTransition(() => { @@ -822,7 +822,7 @@ if (process.env.__NEXT_RSC) { return ( - + ) } diff --git a/packages/next/client/page-loader.ts b/packages/next/client/page-loader.ts index 0ec4e6945827b0d..a601d96c285cb2a 100644 --- a/packages/next/client/page-loader.ts +++ b/packages/next/client/page-loader.ts @@ -133,13 +133,13 @@ export default class PageLoader { href, asPath, ssg, - rsc, + flight, locale, }: { href: string asPath: string ssg?: boolean - rsc?: boolean + flight?: boolean locale?: string | false }): string { const { pathname: hrefPathname, query, search } = parseRelativeUrl(href) @@ -147,7 +147,7 @@ export default class PageLoader { const route = normalizeRoute(hrefPathname) const getHrefForSlug = (path: string) => { - if (rsc) { + if (flight) { return path + search + (search ? `&` : '?') + '__flight__=1' } diff --git a/packages/next/server/base-server.ts b/packages/next/server/base-server.ts index 68d1eca05ffad91..18b35312b31ee35 100644 --- a/packages/next/server/base-server.ts +++ b/packages/next/server/base-server.ts @@ -1127,9 +1127,12 @@ export default abstract class Server { const hasServerProps = !!components.getServerSideProps const hasStaticPaths = !!components.getStaticPaths const hasGetInitialProps = !!components.Component?.getInitialProps + const isServerComponent = !!components.ComponentMod?.__next_rsc__ // Toggle whether or not this is a Data request - const isDataReq = !!query._nextDataReq && (isSSG || hasServerProps) + const isDataReq = + !!query._nextDataReq && (isSSG || hasServerProps || isServerComponent) + delete query._nextDataReq // Don't delete query.__flight__ yet, it still needs to be used in renderToHTML later const isFlightRequest = Boolean( @@ -1601,7 +1604,10 @@ export default abstract class Server { if (isDataReq) { return { type: 'json', - body: RenderResult.fromStatic(JSON.stringify(cachedData.props)), + body: RenderResult.fromStatic( + // @TODO: Handle flight data. + JSON.stringify(cachedData.props) + ), revalidateOptions, } } else { diff --git a/packages/next/server/render.tsx b/packages/next/server/render.tsx index c3d1b2849d319f7..58d27d4d320e9e1 100644 --- a/packages/next/server/render.tsx +++ b/packages/next/server/render.tsx @@ -308,12 +308,19 @@ function checkRedirectValues( const rscCache = new Map() function createRSCHook() { - return ( - writable: WritableStream, - id: string, - req: ReadableStream, + return ({ + id, + req, + inlinedDataWritable, + staticDataWritable, + bootstrap, + }: { + id: string + req: ReadableStream bootstrap: boolean - ) => { + inlinedDataWritable: WritableStream + staticDataWritable: WritableStream | null + }) => { let entry = rscCache.get(id) if (!entry) { const [renderStream, forwardStream] = readableStreamTee(req) @@ -321,13 +328,18 @@ function createRSCHook() { rscCache.set(id, entry) let bootstrapped = false + const forwardReader = forwardStream.getReader() - const writer = writable.getWriter() + const inlinedDataWriter = inlinedDataWritable.getWriter() + const staticDataWriter = staticDataWritable + ? staticDataWritable.getWriter() + : null + function process() { forwardReader.read().then(({ done, value }) => { if (bootstrap && !bootstrapped) { bootstrapped = true - writer.write( + inlinedDataWriter.write( encodeText( `` ) ) + if (staticDataWriter) { + staticDataWriter.write(value) + } process() } }) @@ -365,11 +383,13 @@ function createServerComponentRenderer( ComponentMod: any, { cachePrefix, - transformStream, + inlinedTransformStream, + staticTransformStream, serverComponentManifest, }: { cachePrefix: string - transformStream: TransformStream + inlinedTransformStream: TransformStream + staticTransformStream: null | TransformStream serverComponentManifest: NonNullable } ) { @@ -378,7 +398,6 @@ function createServerComponentRenderer( // @ts-ignore globalThis.__webpack_require__ = ComponentMod.__next_rsc__.__webpack_require__ - const writable = transformStream.writable const ServerComponentWrapper = (props: any) => { const id = (React as any).useId() const reqStream: ReadableStream = renderToReadableStream( @@ -386,12 +405,16 @@ function createServerComponentRenderer( serverComponentManifest ) - const response = useRSCResponse( - writable, - cachePrefix + ',' + id, - reqStream, - true - ) + const response = useRSCResponse({ + id: cachePrefix + ',' + id, + req: reqStream, + inlinedDataWritable: inlinedTransformStream.writable, + staticDataWritable: staticTransformStream + ? staticTransformStream.writable + : null, + bootstrap: true, + }) + const root = response.readRoot() rscCache.delete(id) return root @@ -479,6 +502,11 @@ export async function renderToHTML( Uint8Array, Uint8Array > | null = null + let serverComponentsPageDataTrasnformStream: TransformStream< + Uint8Array, + Uint8Array + > | null = + isServerComponent && !process.browser ? new TransformStream() : null if (isServerComponent) { serverComponentsInlinedTransformStream = new TransformStream() @@ -489,7 +517,8 @@ export async function renderToHTML( ComponentMod, { cachePrefix: pathname + (search ? `?${search}` : ''), - transformStream: serverComponentsInlinedTransformStream, + inlinedTransformStream: serverComponentsInlinedTransformStream, + staticTransformStream: serverComponentsPageDataTrasnformStream, serverComponentManifest, } ) @@ -1167,7 +1196,11 @@ export async function renderToHTML( // Avoid rendering page un-necessarily for getServerSideProps data request // and getServerSideProps/getStaticProps redirects if ((isDataReq && !isSSG) || (renderOpts as any).isRedirect) { - return RenderResult.fromStatic(JSON.stringify(props)) + // For server components, we still need to render the page to get the flight + // data. + if (!serverComponentsPageDataTrasnformStream) { + return RenderResult.fromStatic(JSON.stringify(props)) + } } // We don't call getStaticProps or getServerSideProps while generating @@ -1185,16 +1218,17 @@ export async function renderToHTML( if (isResSent(res) && !isSSG) return null if (renderServerComponentData) { - const stream: ReadableStream = renderToReadableStream( - renderFlight(AppMod, OriginalComponent, { - ...props.pageProps, - ...serverComponentProps, - }), - serverComponentManifest - ) - return new RenderResult( - pipeThrough(stream, createBufferedTransformStream()) + pipeThrough( + renderToReadableStream( + renderFlight(AppMod, OriginalComponent, { + ...props.pageProps, + ...serverComponentProps, + }), + serverComponentManifest + ), + createBufferedTransformStream() + ) ) } @@ -1379,6 +1413,35 @@ export async function renderToHTML( return flushed } + // Handle static data for server components. + async function generateStaticFlightDataIfNeeded() { + if (serverComponentsPageDataTrasnformStream) { + // If it's a server component with the Node.js runtime, we also + // statically generate the page data. + let data = '' + const readable = serverComponentsPageDataTrasnformStream.readable + const reader = readable.getReader() + while (true) { + const { done, value } = await reader.read() + if (done) { + break + } + data += decodeText(value) + } + ;(renderOpts as any).pageData = { + ...(renderOpts as any).pageData, + __flight__: data, + } + return data + } + } + + // @TODO: A potential improvement would be to reuse the inlined + // data stream, or pass a callback inside as this doesn't need to + // be streamed. + // Do not use `await` here. + generateStaticFlightDataIfNeeded() + return await renderToStream({ ReactDOMServer, element: content, @@ -1567,6 +1630,14 @@ export async function renderToHTML( await documentResult.bodyResult(renderTargetSuffix), ] + if ( + serverComponentsPageDataTrasnformStream && + ((isDataReq && !isSSG) || (renderOpts as any).isRedirect) + ) { + await streamToString(streams[1]) + return RenderResult.fromStatic((renderOpts as any).pageData) + } + const postProcessors: Array<((html: string) => Promise) | null> = ( generateStaticHTML ? [ diff --git a/packages/next/shared/lib/router/router.ts b/packages/next/shared/lib/router/router.ts index 4abfb6c2e07c6db..8be3b512e569f17 100644 --- a/packages/next/shared/lib/router/router.ts +++ b/packages/next/shared/lib/router/router.ts @@ -1546,18 +1546,25 @@ export default class Router implements BaseRouter { let dataHref: string | undefined + // For server components, non-SSR pages will have statically optimized + // flight data in a production build. + // So only development and SSR pages will always have the real-time + // generated and streamed flight data. + const useStreamedFlightData = + (process.env.NODE_ENV !== 'production' || __N_SSP) && __N_RSC + if (__N_SSG || __N_SSP || __N_RSC) { dataHref = this.pageLoader.getDataHref({ href: formatWithValidation({ pathname, query }), asPath: resolvedAs, ssg: __N_SSG, - rsc: __N_RSC, + flight: useStreamedFlightData, locale, }) } const props = await this._getData(() => - __N_SSG || __N_SSP + __N_SSG || __N_SSP || (__N_RSC && !useStreamedFlightData) ? fetchNextData( dataHref!, this.isSsr, @@ -1580,13 +1587,25 @@ export default class Router implements BaseRouter { ) if (__N_RSC) { - const { fresh, data } = (await this._getData(() => - this._getFlightData(dataHref!) - )) as { fresh: boolean; data: string } - ;(props as any).pageProps = Object.assign((props as any).pageProps, { - __flight_serialized__: data, - __flight_fresh__: fresh, - }) + if (useStreamedFlightData) { + const { data } = (await this._getData(() => + this._getFlightData(dataHref!) + )) as { data: string } + ;(props as any).pageProps = Object.assign((props as any).pageProps, { + __flight__: data, + }) + } else { + const { __flight__ } = props as any + delete (props as any).__N_SSG + delete (props as any).__N_SSP + ;(props as any).pageProps = Object.assign( + {}, + (props as any).pageProps, + { + __flight__, + } + ) + } } routeInfo.props = props @@ -1851,7 +1870,7 @@ export default class Router implements BaseRouter { // Do not cache RSC flight response since it's not a static resource return fetchNextData(dataHref, true, true, this.sdc, false).then( (serialized) => { - return { fresh: true, data: serialized } + return { data: serialized } } ) } diff --git a/test/integration/react-streaming-and-server-components/switchable-runtime/pages/edge-rsc.server.js b/test/integration/react-streaming-and-server-components/switchable-runtime/pages/edge-rsc.server.js index 83dc8c219e84ef0..0c05e6023b4d59b 100644 --- a/test/integration/react-streaming-and-server-components/switchable-runtime/pages/edge-rsc.server.js +++ b/test/integration/react-streaming-and-server-components/switchable-runtime/pages/edge-rsc.server.js @@ -1,14 +1,14 @@ -import getRuntime from '../utils/runtime' -import getTime from '../utils/time' +import Runtime from '../utils/runtime' +import Time from '../utils/time' export default function Page() { return (
This is a SSR RSC page.
- {'Runtime: ' + getRuntime()} +
- {'Time: ' + getTime()} +
) } diff --git a/test/integration/react-streaming-and-server-components/switchable-runtime/pages/edge.js b/test/integration/react-streaming-and-server-components/switchable-runtime/pages/edge.js index c3425c7e64af7cd..9f99eaecaa15c81 100644 --- a/test/integration/react-streaming-and-server-components/switchable-runtime/pages/edge.js +++ b/test/integration/react-streaming-and-server-components/switchable-runtime/pages/edge.js @@ -1,14 +1,14 @@ -import getRuntime from '../utils/runtime' -import getTime from '../utils/time' +import Runtime from '../utils/runtime' +import Time from '../utils/time' export default function Page() { return (
This is a SSR page.
- {'Runtime: ' + getRuntime()} +
- {'Time: ' + getTime()} +
) } diff --git a/test/integration/react-streaming-and-server-components/switchable-runtime/pages/node-rsc-ssg.server.js b/test/integration/react-streaming-and-server-components/switchable-runtime/pages/node-rsc-ssg.server.js index 362e634644eb2ba..7f9804f683e8f3a 100644 --- a/test/integration/react-streaming-and-server-components/switchable-runtime/pages/node-rsc-ssg.server.js +++ b/test/integration/react-streaming-and-server-components/switchable-runtime/pages/node-rsc-ssg.server.js @@ -1,14 +1,14 @@ -import getRuntime from '../utils/runtime' -import getTime from '../utils/time' +import Runtime from '../utils/runtime' +import Time from '../utils/time' export default function Page({ type }) { return (
This is a {type} RSC page.
- {'Runtime: ' + getRuntime()} +
- {'Time: ' + getTime()} +
) } diff --git a/test/integration/react-streaming-and-server-components/switchable-runtime/pages/node-rsc-ssr.server.js b/test/integration/react-streaming-and-server-components/switchable-runtime/pages/node-rsc-ssr.server.js index 1b8b01526a3cede..d064a5cbbfd5bdc 100644 --- a/test/integration/react-streaming-and-server-components/switchable-runtime/pages/node-rsc-ssr.server.js +++ b/test/integration/react-streaming-and-server-components/switchable-runtime/pages/node-rsc-ssr.server.js @@ -1,14 +1,14 @@ -import getRuntime from '../utils/runtime' -import getTime from '../utils/time' +import Runtime from '../utils/runtime' +import Time from '../utils/time' export default function Page({ type }) { return (
This is a {type} RSC page.
- {'Runtime: ' + getRuntime()} +
- {'Time: ' + getTime()} +
) } diff --git a/test/integration/react-streaming-and-server-components/switchable-runtime/pages/node-rsc.server.js b/test/integration/react-streaming-and-server-components/switchable-runtime/pages/node-rsc.server.js index f3563039b63bdfe..4c373d397b58c30 100644 --- a/test/integration/react-streaming-and-server-components/switchable-runtime/pages/node-rsc.server.js +++ b/test/integration/react-streaming-and-server-components/switchable-runtime/pages/node-rsc.server.js @@ -1,14 +1,14 @@ -import getRuntime from '../utils/runtime' -import getTime from '../utils/time' +import Runtime from '../utils/runtime' +import Time from '../utils/time' export default function Page() { return (
This is a static RSC page.
- {'Runtime: ' + getRuntime()} +
- {'Time: ' + getTime()} +
) } diff --git a/test/integration/react-streaming-and-server-components/switchable-runtime/pages/node-ssg.js b/test/integration/react-streaming-and-server-components/switchable-runtime/pages/node-ssg.js index d555009acfcdb83..ccd6832cf2e37ad 100644 --- a/test/integration/react-streaming-and-server-components/switchable-runtime/pages/node-ssg.js +++ b/test/integration/react-streaming-and-server-components/switchable-runtime/pages/node-ssg.js @@ -1,14 +1,14 @@ -import getRuntime from '../utils/runtime' -import getTime from '../utils/time' +import Runtime from '../utils/runtime' +import Time from '../utils/time' export default function Page({ type }) { return (
This is a {type} page.
- {'Runtime: ' + getRuntime()} +
- {'Time: ' + getTime()} +
) } diff --git a/test/integration/react-streaming-and-server-components/switchable-runtime/pages/node-ssr.js b/test/integration/react-streaming-and-server-components/switchable-runtime/pages/node-ssr.js index e58276b47a76386..1f7ee2e0b14c6ad 100644 --- a/test/integration/react-streaming-and-server-components/switchable-runtime/pages/node-ssr.js +++ b/test/integration/react-streaming-and-server-components/switchable-runtime/pages/node-ssr.js @@ -1,14 +1,14 @@ -import getRuntime from '../utils/runtime' -import getTime from '../utils/time' +import Runtime from '../utils/runtime' +import Time from '../utils/time' export default function Page({ type }) { return (
This is a {type} page.
- {'Runtime: ' + getRuntime()} +
- {'Time: ' + getTime()} +
) } diff --git a/test/integration/react-streaming-and-server-components/switchable-runtime/pages/node.js b/test/integration/react-streaming-and-server-components/switchable-runtime/pages/node.js index bf065da478ba866..4e469a337bcb648 100644 --- a/test/integration/react-streaming-and-server-components/switchable-runtime/pages/node.js +++ b/test/integration/react-streaming-and-server-components/switchable-runtime/pages/node.js @@ -1,14 +1,36 @@ -import getRuntime from '../utils/runtime' -import getTime from '../utils/time' +import Runtime from '../utils/runtime' +import Time from '../utils/time' + +import Link from 'next/link' export default function Page() { return (
This is a static page.
- {'Runtime: ' + getRuntime()} + +
+
) } diff --git a/test/integration/react-streaming-and-server-components/switchable-runtime/pages/static.js b/test/integration/react-streaming-and-server-components/switchable-runtime/pages/static.js index e44edfd7952325d..ea410104fb5dff4 100644 --- a/test/integration/react-streaming-and-server-components/switchable-runtime/pages/static.js +++ b/test/integration/react-streaming-and-server-components/switchable-runtime/pages/static.js @@ -1,14 +1,14 @@ -import getRuntime from '../utils/runtime' -import getTime from '../utils/time' +import Runtime from '../utils/runtime' +import Time from '../utils/time' export default function Page() { return (
This is a static page.
- {'Runtime: ' + getRuntime()} +
- {'Time: ' + getTime()} +
) } diff --git a/test/integration/react-streaming-and-server-components/switchable-runtime/utils/runtime.js b/test/integration/react-streaming-and-server-components/switchable-runtime/utils/runtime.js index 444f1ee8b498bbb..b7782af7e6f76f5 100644 --- a/test/integration/react-streaming-and-server-components/switchable-runtime/utils/runtime.js +++ b/test/integration/react-streaming-and-server-components/switchable-runtime/utils/runtime.js @@ -1,3 +1,12 @@ -export default function getRuntime() { - return process.version ? `Node.js ${process.version}` : 'Edge/Browser' +export default function Runtime() { + let runtime + + if (typeof window !== 'undefined') { + // We have to make sure it matches the existing markup when hydrating. + runtime = document.getElementById('__runtime').textContent + } else { + runtime = process.version ? `Node.js ${process.version}` : 'Edge/Browser' + } + + return {runtime} } diff --git a/test/integration/react-streaming-and-server-components/switchable-runtime/utils/time.js b/test/integration/react-streaming-and-server-components/switchable-runtime/utils/time.js index cf78549b9a7c124..046ac6f1f49f0cb 100644 --- a/test/integration/react-streaming-and-server-components/switchable-runtime/utils/time.js +++ b/test/integration/react-streaming-and-server-components/switchable-runtime/utils/time.js @@ -1,3 +1,12 @@ -export default function getTime() { - return Date.now() +export default function Time() { + let time + + if (typeof window !== 'undefined') { + // We have to make sure it matches the existing markup when hydrating. + time = document.getElementById('__time').textContent + } else { + time = Date.now() + } + + return {time} } From 1f81ba6ab4e77306d3de4fce05f6dd418701bb19 Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Sat, 26 Mar 2022 16:40:11 +0100 Subject: [PATCH 02/12] merge conflicts --- packages/next/build/swc/options.js | 2 +- .../loaders/next-flight-server-loader.ts | 16 ++++---- packages/next/client/index.tsx | 37 ++++++++++--------- packages/next/shared/lib/router/router.ts | 4 +- .../switchable-runtime/utils/runtime.js | 4 +- .../switchable-runtime/utils/time.js | 2 +- 6 files changed, 33 insertions(+), 32 deletions(-) diff --git a/packages/next/build/swc/options.js b/packages/next/build/swc/options.js index dd98795412ecf2c..33ed7b9d00eaa72 100644 --- a/packages/next/build/swc/options.js +++ b/packages/next/build/swc/options.js @@ -104,7 +104,7 @@ function getBaseSWCOptions({ reactRemoveProperties: nextConfig?.compiler?.reactRemoveProperties, modularizeImports: nextConfig?.experimental?.modularizeImports, relay: nextConfig?.compiler?.relay, - // emotion: getEmotionOptions(nextConfig, development), + emotion: getEmotionOptions(nextConfig, development), } } diff --git a/packages/next/build/webpack/loaders/next-flight-server-loader.ts b/packages/next/build/webpack/loaders/next-flight-server-loader.ts index b6843e411463b39..5646c09922ae04b 100644 --- a/packages/next/build/webpack/loaders/next-flight-server-loader.ts +++ b/packages/next/build/webpack/loaders/next-flight-server-loader.ts @@ -41,7 +41,6 @@ async function parseModuleInfo({ source: string imports: string isEsm: boolean - __N_SSG: boolean __N_SSP: boolean }> { const ast = await parse(source, { filename: resourcePath, isModule: true }) @@ -50,7 +49,6 @@ async function parseModuleInfo({ let lastIndex = 0 let imports = '' let isEsm = false - let __N_SSG = false let __N_SSP = false for (let i = 0; i < body.length; i++) { @@ -125,8 +123,6 @@ async function parseModuleInfo({ const value = declaration.id.value if (value === '__N_SSP') { __N_SSP = true - } else if (value === '__N_SSG') { - __N_SSG = true } } } @@ -144,7 +140,7 @@ async function parseModuleInfo({ transformedSource += source.substring(lastIndex) } - return { source: transformedSource, imports, isEsm, __N_SSG, __N_SSP } + return { source: transformedSource, imports, isEsm, __N_SSP } } export default async function transformSource( @@ -183,7 +179,6 @@ export default async function transformSource( source: transformedSource, imports, isEsm, - __N_SSG, __N_SSP, } = await parseModuleInfo({ resourcePath, @@ -214,8 +209,13 @@ export default async function transformSource( if (isClientCompilation) { rscExports.default = 'function RSC() {}' - if (__N_SSG) rscExports.__N_SSG = 'true' - if (__N_SSP) rscExports.__N_SSP = 'true' + if (__N_SSP) { + rscExports.__N_SSP = 'true' + } else { + // Server component pages are always considered as SSG by default because + // the flight data is needed for client navigation. + rscExports.__N_SSG = 'true' + } } const output = transformedSource + '\n' + buildExports(rscExports, isEsm) diff --git a/packages/next/client/index.tsx b/packages/next/client/index.tsx index 84f1a8e7aaed7b2..3bb415aad890f8a 100644 --- a/packages/next/client/index.tsx +++ b/packages/next/client/index.tsx @@ -547,14 +547,17 @@ function renderReactElement( const reactEl = fn(shouldHydrate ? markHydrateComplete : markRenderComplete) if (process.env.__NEXT_REACT_ROOT) { - const ReactDOMClient = require('react-dom/client') if (!reactRoot) { // Unlike with createRoot, you don't need a separate root.render() call here - reactRoot = (ReactDOMClient as any).hydrateRoot(domEl, reactEl) + const ReactDOMClient = require('react-dom/client') + reactRoot = ReactDOMClient.hydrateRoot(domEl, reactEl) // TODO: Remove shouldHydrate variable when React 18 is stable as it can depend on `reactRoot` existing shouldHydrate = false } else { - reactRoot.render(reactEl) + const startTransition = (React as any).startTransition + startTransition(() => { + reactRoot.render(reactEl) + }) } } else { // The check for `.hydrate` is there to support React alternatives like preact @@ -675,6 +678,7 @@ if (process.env.__NEXT_RSC) { const { createFromFetch, + createFromReadableStream, } = require('next/dist/compiled/react-server-dom-webpack') const encoder = new TextEncoder() @@ -764,22 +768,19 @@ if (process.env.__NEXT_RSC) { if (response) return response if (initialServerDataBuffer) { - const t = new TransformStream() - const writer = t.writable.getWriter() - response = createFromFetch(Promise.resolve({ body: t.readable })) - nextServerDataRegisterWriter(writer) + const { readable, writable } = new TransformStream() + response = createFromReadableStream(readable) + nextServerDataRegisterWriter(writable.getWriter()) } else { - const fetchPromise = serialized - ? (() => { - const t = new TransformStream() - const writer = t.writable.getWriter() - writer.ready.then(() => { - writer.write(new TextEncoder().encode(serialized)) - }) - return Promise.resolve({ body: t.readable }) - })() - : fetchFlight(getCacheKey()) - response = createFromFetch(fetchPromise) + if (serialized) { + const { readable, writable } = new TransformStream() + const writer = writable.getWriter() + writer.write(new TextEncoder().encode(serialized)) + writer.close() + response = createFromReadableStream(readable) + } else { + response = createFromFetch(fetchFlight(getCacheKey())) + } } rscCache.set(cacheKey, response) diff --git a/packages/next/shared/lib/router/router.ts b/packages/next/shared/lib/router/router.ts index 8be3b512e569f17..2816ab180e804a4 100644 --- a/packages/next/shared/lib/router/router.ts +++ b/packages/next/shared/lib/router/router.ts @@ -1564,7 +1564,7 @@ export default class Router implements BaseRouter { } const props = await this._getData(() => - __N_SSG || __N_SSP || (__N_RSC && !useStreamedFlightData) + (__N_SSG || __N_SSP || __N_RSC) && !useStreamedFlightData ? fetchNextData( dataHref!, this.isSsr, @@ -1596,8 +1596,6 @@ export default class Router implements BaseRouter { }) } else { const { __flight__ } = props as any - delete (props as any).__N_SSG - delete (props as any).__N_SSP ;(props as any).pageProps = Object.assign( {}, (props as any).pageProps, diff --git a/test/integration/react-streaming-and-server-components/switchable-runtime/utils/runtime.js b/test/integration/react-streaming-and-server-components/switchable-runtime/utils/runtime.js index b7782af7e6f76f5..bea2a3fe18adcae 100644 --- a/test/integration/react-streaming-and-server-components/switchable-runtime/utils/runtime.js +++ b/test/integration/react-streaming-and-server-components/switchable-runtime/utils/runtime.js @@ -5,7 +5,9 @@ export default function Runtime() { // We have to make sure it matches the existing markup when hydrating. runtime = document.getElementById('__runtime').textContent } else { - runtime = process.version ? `Node.js ${process.version}` : 'Edge/Browser' + runtime = + 'Runtime: ' + + (process.version ? `Node.js ${process.version}` : 'Edge/Browser') } return {runtime} diff --git a/test/integration/react-streaming-and-server-components/switchable-runtime/utils/time.js b/test/integration/react-streaming-and-server-components/switchable-runtime/utils/time.js index 046ac6f1f49f0cb..c0894628bb9936e 100644 --- a/test/integration/react-streaming-and-server-components/switchable-runtime/utils/time.js +++ b/test/integration/react-streaming-and-server-components/switchable-runtime/utils/time.js @@ -5,7 +5,7 @@ export default function Time() { // We have to make sure it matches the existing markup when hydrating. time = document.getElementById('__time').textContent } else { - time = Date.now() + time = 'Time: ' + Date.now() } return {time} From 9781d61df03a5689e240e789717e62a2a257a71b Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Mon, 28 Mar 2022 15:52:46 +0200 Subject: [PATCH 03/12] fix lint errors and test --- package.json | 2 +- .../build/webpack/loaders/next-flight-server-loader.ts | 8 +++----- .../test/switchable-runtime.test.js | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index fc5da08d6f4ac3f..c812f741b673f51 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "lint-staged": "lint-staged", "next-with-deps": "./scripts/next-with-deps.sh", "next": "node --trace-deprecation --enable-source-maps packages/next/dist/bin/next", - "next-react-18": "node --trace-deprecation --enable-source-maps -r ./test/integration/react-18/test/require-hook.js packages/next/dist/bin/next", + "next-react-18": "node --inspect --trace-deprecation --enable-source-maps -r ./test/integration/react-18/test/require-hook.js packages/next/dist/bin/next", "next-no-sourcemaps": "node --trace-deprecation packages/next/dist/bin/next", "clean-trace-jaeger": "rm -rf test/integration/basic/.next && TRACE_TARGET=JAEGER node --trace-deprecation --enable-source-maps packages/next/dist/bin/next build test/integration/basic", "debug": "node --inspect packages/next/dist/bin/next", diff --git a/packages/next/build/webpack/loaders/next-flight-server-loader.ts b/packages/next/build/webpack/loaders/next-flight-server-loader.ts index 5646c09922ae04b..258715b9fef7d21 100644 --- a/packages/next/build/webpack/loaders/next-flight-server-loader.ts +++ b/packages/next/build/webpack/loaders/next-flight-server-loader.ts @@ -55,7 +55,7 @@ async function parseModuleInfo({ const node = body[i] isEsm = isEsm || isEsmNodeType(node.type) switch (node.type) { - case 'ImportDeclaration': { + case 'ImportDeclaration': const importSource = node.source.value if (!isClientCompilation) { // Server compilation for .server.js. @@ -112,8 +112,7 @@ async function parseModuleInfo({ lastIndex = node.source.span.end break - } - case 'ExportDeclaration': { + case 'ExportDeclaration': if (isClientCompilation) { // Keep `__N_SSG` and `__N_SSP` exports. if (node.declaration?.type === 'VariableDeclaration') { @@ -128,9 +127,8 @@ async function parseModuleInfo({ } } } - break } - } + break default: break } diff --git a/test/integration/react-streaming-and-server-components/test/switchable-runtime.test.js b/test/integration/react-streaming-and-server-components/test/switchable-runtime.test.js index d5cabd5f9a4ed6b..cc4b5e234c189cb 100644 --- a/test/integration/react-streaming-and-server-components/test/switchable-runtime.test.js +++ b/test/integration/react-streaming-and-server-components/test/switchable-runtime.test.js @@ -117,7 +117,7 @@ describe('Without global runtime configuration', () => { ├ ℇ /edge ├ ℇ /edge-rsc ├ ○ /node - ├ ○ /node-rsc + ├ ● /node-rsc ├ ● /node-rsc-ssg ├ λ /node-rsc-ssr ├ ● /node-ssg From c42d543a3e93ebb2904e80c67d2a765d1c45daf5 Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Mon, 28 Mar 2022 19:02:15 +0200 Subject: [PATCH 04/12] fix ssg case --- packages/next/server/base-server.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/next/server/base-server.ts b/packages/next/server/base-server.ts index 18b35312b31ee35..4c0cdb66d35fc41 100644 --- a/packages/next/server/base-server.ts +++ b/packages/next/server/base-server.ts @@ -1123,11 +1123,13 @@ export default abstract class Server { const isLikeServerless = typeof components.ComponentMod === 'object' && typeof (components.ComponentMod as any).renderReqToHTML === 'function' - const isSSG = !!components.getStaticProps const hasServerProps = !!components.getServerSideProps const hasStaticPaths = !!components.getStaticPaths const hasGetInitialProps = !!components.Component?.getInitialProps const isServerComponent = !!components.ComponentMod?.__next_rsc__ + const isSSG = + !!components.getStaticProps || + (isServerComponent && !hasServerProps && !hasGetInitialProps) // Toggle whether or not this is a Data request const isDataReq = From 434b8025cb63a55fe90c7d309f2f2831ef1b5558 Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Mon, 28 Mar 2022 19:14:48 +0200 Subject: [PATCH 05/12] add isr --- .../pages/node-rsc-isr.server.js | 27 +++++++++++++++++++ .../switchable-runtime/pages/node.js | 4 +++ 2 files changed, 31 insertions(+) create mode 100644 test/integration/react-streaming-and-server-components/switchable-runtime/pages/node-rsc-isr.server.js diff --git a/test/integration/react-streaming-and-server-components/switchable-runtime/pages/node-rsc-isr.server.js b/test/integration/react-streaming-and-server-components/switchable-runtime/pages/node-rsc-isr.server.js new file mode 100644 index 000000000000000..70a54e4368cf638 --- /dev/null +++ b/test/integration/react-streaming-and-server-components/switchable-runtime/pages/node-rsc-isr.server.js @@ -0,0 +1,27 @@ +import Runtime from '../utils/runtime' +import Time from '../utils/time' + +export default function Page({ type }) { + return ( +
+ This is a {type} RSC page. +
+ +
+
+ ) +} + +export function getStaticProps() { + return { + props: { + type: 'ISR', + }, + revalidate: 3, + } +} + +export const config = { + runtime: 'nodejs', +} diff --git a/test/integration/react-streaming-and-server-components/switchable-runtime/pages/node.js b/test/integration/react-streaming-and-server-components/switchable-runtime/pages/node.js index 4e469a337bcb648..160ed02f6a38570 100644 --- a/test/integration/react-streaming-and-server-components/switchable-runtime/pages/node.js +++ b/test/integration/react-streaming-and-server-components/switchable-runtime/pages/node.js @@ -24,6 +24,10 @@ export default function Page() { to /node-rsc-ssr
+ + to /node-rsc-isr + +
to /node-ssg From 28397e5d0649ec50f5ae9852f6be616ac3385d9f Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Mon, 28 Mar 2022 19:34:44 +0200 Subject: [PATCH 06/12] fix ssg condition for the edge runtime --- packages/next/server/base-server.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/next/server/base-server.ts b/packages/next/server/base-server.ts index 4c0cdb66d35fc41..76e46a52eadc330 100644 --- a/packages/next/server/base-server.ts +++ b/packages/next/server/base-server.ts @@ -1129,7 +1129,12 @@ export default abstract class Server { const isServerComponent = !!components.ComponentMod?.__next_rsc__ const isSSG = !!components.getStaticProps || - (isServerComponent && !hasServerProps && !hasGetInitialProps) + // For static server component pages, we currently always consider them + // as SSG since we also need to handle the next data. + (isServerComponent && + !hasServerProps && + !hasGetInitialProps && + !process.browser) // Toggle whether or not this is a Data request const isDataReq = From 93996cc5cb71d9700f8fe53b478cf776431854bc Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Tue, 29 Mar 2022 17:14:08 +0200 Subject: [PATCH 07/12] fix edge runtime should be always ssr --- package.json | 2 +- .../loaders/next-flight-server-loader.ts | 28 +++++++++++++++---- .../test/switchable-runtime.test.js | 1 + 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index c812f741b673f51..fc5da08d6f4ac3f 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "lint-staged": "lint-staged", "next-with-deps": "./scripts/next-with-deps.sh", "next": "node --trace-deprecation --enable-source-maps packages/next/dist/bin/next", - "next-react-18": "node --inspect --trace-deprecation --enable-source-maps -r ./test/integration/react-18/test/require-hook.js packages/next/dist/bin/next", + "next-react-18": "node --trace-deprecation --enable-source-maps -r ./test/integration/react-18/test/require-hook.js packages/next/dist/bin/next", "next-no-sourcemaps": "node --trace-deprecation packages/next/dist/bin/next", "clean-trace-jaeger": "rm -rf test/integration/basic/.next && TRACE_TARGET=JAEGER node --trace-deprecation --enable-source-maps packages/next/dist/bin/next build test/integration/basic", "debug": "node --inspect packages/next/dist/bin/next", diff --git a/packages/next/build/webpack/loaders/next-flight-server-loader.ts b/packages/next/build/webpack/loaders/next-flight-server-loader.ts index 75959923eb60929..18e908e19190126 100644 --- a/packages/next/build/webpack/loaders/next-flight-server-loader.ts +++ b/packages/next/build/webpack/loaders/next-flight-server-loader.ts @@ -42,6 +42,7 @@ async function parseModuleInfo({ imports: string isEsm: boolean __N_SSP: boolean + pageRuntime: 'edge' | 'nodejs' | null }> { const ast = await parse(source, { filename: resourcePath, @@ -52,6 +53,7 @@ async function parseModuleInfo({ let lastIndex = 0 let imports = '' let __N_SSP = false + let pageRuntime = null const isEsm = type === 'Module' @@ -125,6 +127,15 @@ async function parseModuleInfo({ const value = declaration.id.value if (value === '__N_SSP') { __N_SSP = true + } else if (value === 'config') { + const props = declaration.init.properties + const runtimeKeyValue = props.find( + (prop: any) => prop.key.value === 'runtime' + ) + const runtime = runtimeKeyValue?.value?.value + if (runtime === 'nodejs' || runtime === 'edge') { + pageRuntime = runtime + } } } } @@ -141,7 +152,7 @@ async function parseModuleInfo({ transformedSource += source.substring(lastIndex) } - return { source: transformedSource, imports, isEsm, __N_SSP } + return { source: transformedSource, imports, isEsm, __N_SSP, pageRuntime } } export default async function transformSource( @@ -181,6 +192,7 @@ export default async function transformSource( imports, isEsm, __N_SSP, + pageRuntime, } = await parseModuleInfo({ resourcePath, source, @@ -211,12 +223,18 @@ export default async function transformSource( if (isClientCompilation) { rscExports.default = 'function RSC() {}' - if (__N_SSP) { + + if (pageRuntime === 'edge') { + // Currently for the Edge runtime, we treat all RSC pages as SSR pages. rscExports.__N_SSP = 'true' } else { - // Server component pages are always considered as SSG by default because - // the flight data is needed for client navigation. - rscExports.__N_SSG = 'true' + if (__N_SSP) { + rscExports.__N_SSP = 'true' + } else { + // Server component pages are always considered as SSG by default because + // the flight data is needed for client navigation. + rscExports.__N_SSG = 'true' + } } } diff --git a/test/integration/react-streaming-and-server-components/test/switchable-runtime.test.js b/test/integration/react-streaming-and-server-components/test/switchable-runtime.test.js index cc4b5e234c189cb..1a4334e37bbc0a9 100644 --- a/test/integration/react-streaming-and-server-components/test/switchable-runtime.test.js +++ b/test/integration/react-streaming-and-server-components/test/switchable-runtime.test.js @@ -118,6 +118,7 @@ describe('Without global runtime configuration', () => { ├ ℇ /edge-rsc ├ ○ /node ├ ● /node-rsc + ├ ● /node-rsc-isr ├ ● /node-rsc-ssg ├ λ /node-rsc-ssr ├ ● /node-ssg From b9ca920e0d55b596bc3861060b72c71bcb13530f Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Tue, 29 Mar 2022 17:50:23 +0200 Subject: [PATCH 08/12] add tests --- .../switchable-runtime/pages/node.js | 12 +-- .../test/switchable-runtime.test.js | 93 ++++++++++++++++++- 2 files changed, 98 insertions(+), 7 deletions(-) diff --git a/test/integration/react-streaming-and-server-components/switchable-runtime/pages/node.js b/test/integration/react-streaming-and-server-components/switchable-runtime/pages/node.js index 160ed02f6a38570..d0857a09aebaaa8 100644 --- a/test/integration/react-streaming-and-server-components/switchable-runtime/pages/node.js +++ b/test/integration/react-streaming-and-server-components/switchable-runtime/pages/node.js @@ -13,27 +13,27 @@ export default function Page() {