From 6f565dde8ac0e4e5426e10b20a2ca3442e92f547 Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Wed, 2 Mar 2022 01:25:11 +0100 Subject: [PATCH 1/2] refactor rsc export marks --- packages/next/build/utils.ts | 2 +- .../loaders/next-flight-server-loader.ts | 73 ++++++------------- .../next-middleware-ssr-loader/index.ts | 4 +- .../next-middleware-ssr-loader/render.ts | 7 +- packages/next/client/index.tsx | 7 +- packages/next/server/load-components.ts | 6 +- packages/next/server/render.tsx | 36 ++++++--- packages/next/shared/lib/router/router.ts | 6 +- .../app/pages/partial-hydration.server.js | 2 +- 9 files changed, 71 insertions(+), 72 deletions(-) diff --git a/packages/next/build/utils.ts b/packages/next/build/utils.ts index 4fd4d8c9a19a..081ca1edada7 100644 --- a/packages/next/build/utils.ts +++ b/packages/next/build/utils.ts @@ -881,7 +881,7 @@ export async function isPageStatic( throw new Error('INVALID_DEFAULT_EXPORT') } - const hasFlightData = !!(Comp as any).__next_rsc__ + const hasFlightData = !!(mod as any).__next_rsc__ const hasGetInitialProps = !!(Comp as any).getInitialProps const hasStaticProps = !!mod.getStaticProps const hasStaticPaths = !!mod.getStaticPaths 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 1beb921a9d07..006e5fe960aa 100644 --- a/packages/next/build/webpack/loaders/next-flight-server-loader.ts +++ b/packages/next/build/webpack/loaders/next-flight-server-loader.ts @@ -42,20 +42,18 @@ function isImageImport(importSource: string) { async function parseImportsInfo({ resourcePath, source, - imports, isClientCompilation, isServerComponent, isClientComponent, }: { resourcePath: string source: string - imports: Array isClientCompilation: boolean isServerComponent: (name: string) => boolean isClientComponent: (name: string) => boolean }): Promise<{ source: string - defaultExportName: string + imports: string }> { const opts = getBaseSWCOptions({ filename: resourcePath, @@ -63,9 +61,11 @@ async function parseImportsInfo({ }) const ast = await parse(source, { ...opts.jsc.parser, isModule: true }) const { body } = ast + let transformedSource = '' let lastIndex = 0 - let defaultExportName + let imports = '' + for (let i = 0; i < body.length; i++) { const node = body[i] switch (node.type) { @@ -86,7 +86,7 @@ async function parseImportsInfo({ // A client component. It should be loaded as module reference. transformedSource += importDeclarations transformedSource += JSON.stringify(`${importSource}?__sc_client__`) - imports.push(`require(${JSON.stringify(importSource)})`) + imports += `require(${JSON.stringify(importSource)});` } else { // This is a special case to avoid the Duplicate React error. // Since we already include React in the SSR runtime, @@ -116,27 +116,12 @@ async function parseImportsInfo({ continue } - imports.push(`require(${JSON.stringify(importSource)})`) + imports += `require(${JSON.stringify(importSource)});` } lastIndex = node.source.span.end break } - case 'ExportDefaultDeclaration': { - const def = node.decl - if (def.type === 'Identifier') { - defaultExportName = def.name - } else if (def.type === 'FunctionExpression') { - defaultExportName = def.identifier.value - } - break - } - case 'ExportDefaultExpression': - const exp = node.expression - if (exp.type === 'Identifier') { - defaultExportName = exp.value - } - break default: break } @@ -146,7 +131,7 @@ async function parseImportsInfo({ transformedSource += source.substring(lastIndex) } - return { source: transformedSource, defaultExportName } + return { source: transformedSource, imports } } export default async function transformSource( @@ -181,44 +166,34 @@ export default async function transformSource( } } - const imports: string[] = [] - const { source: transformedSource, defaultExportName } = - await parseImportsInfo({ - resourcePath, - source, - imports, - isClientCompilation, - isServerComponent, - isClientComponent, - }) + const { source: transformedSource, imports } = await parseImportsInfo({ + resourcePath, + source, + isClientCompilation, + isServerComponent, + isClientComponent, + }) /** * For .server.js files, we handle this loader differently. * * Server compilation output: - * export default function ServerComponent() { ... } - * export const __rsc_noop__ = () => { ... } - * ServerComponent.__next_rsc__ = 1 - * ServerComponent.__webpack_require__ = __webpack_require__ + * (The content of the Server Component module will be kept.) + * export const __next_rsc__ = { __webpack_require__, _: () => { ... } } * * Client compilation output: - * The function body of Server Component will be removed + * (The content of the Server Component module will be removed.) + * export const __next_rsc__ = { __webpack_require__, _: () => { ... } } */ - const noop = `export const __rsc_noop__=()=>{${imports.join(';')}}` + let rscExports = `export const __next_rsc__={ + __webpack_require__, + _: () => {${imports}} + }` - let defaultExportNoop = '' if (isClientCompilation) { - defaultExportNoop = `export default function ${ - defaultExportName || 'ServerComponent' - }(){}\n${defaultExportName || 'ServerComponent'}.__next_rsc__=1;` - } else { - if (defaultExportName) { - // It's required to have the default export for pages. For other components, it's fine to leave it as is. - defaultExportNoop = `${defaultExportName}.__next_rsc__=1;${defaultExportName}.__webpack_require__=__webpack_require__;` - } + rscExports += '\nexport default function RSC () {}' } - const transformed = transformedSource + '\n' + noop + '\n' + defaultExportNoop - return transformed + return transformedSource + '\n' + rscExports } diff --git a/packages/next/build/webpack/loaders/next-middleware-ssr-loader/index.ts b/packages/next/build/webpack/loaders/next-middleware-ssr-loader/index.ts index 870a6b901693..51caf137f478 100644 --- a/packages/next/build/webpack/loaders/next-middleware-ssr-loader/index.ts +++ b/packages/next/build/webpack/loaders/next-middleware-ssr-loader/index.ts @@ -28,9 +28,9 @@ export default async function middlewareSSRLoader(this: any) { import { getRender } from 'next/dist/build/webpack/loaders/next-middleware-ssr-loader/render' - import App from ${stringifiedAppPath} import Document from ${stringifiedDocumentPath} + const appMod = require(${stringifiedAppPath}) const pageMod = require(${stringifiedPagePath}) const errorMod = require(${stringifiedErrorPath}) const error500Mod = ${stringified500Path} ? require(${stringified500Path}) : null @@ -48,10 +48,10 @@ export default async function middlewareSSRLoader(this: any) { const render = getRender({ dev: ${dev}, page: ${JSON.stringify(page)}, + appMod, pageMod, errorMod, error500Mod, - App, Document, buildManifest, reactLoadableManifest, diff --git a/packages/next/build/webpack/loaders/next-middleware-ssr-loader/render.ts b/packages/next/build/webpack/loaders/next-middleware-ssr-loader/render.ts index 8fa95529ea3a..77b553d2b6da 100644 --- a/packages/next/build/webpack/loaders/next-middleware-ssr-loader/render.ts +++ b/packages/next/build/webpack/loaders/next-middleware-ssr-loader/render.ts @@ -17,11 +17,11 @@ process.cwd = () => '' export function getRender({ dev, page, + appMod, pageMod, errorMod, error500Mod, Document, - App, buildManifest, reactLoadableManifest, serverComponentManifest, @@ -31,11 +31,11 @@ export function getRender({ }: { dev: boolean page: string + appMod: any pageMod: any errorMod: any error500Mod: any Document: DocumentType - App: AppType buildManifest: BuildManifest reactLoadableManifest: ReactLoadableManifest serverComponentManifest: any | null @@ -48,7 +48,8 @@ export function getRender({ buildManifest, reactLoadableManifest, Document, - App, + App: appMod.default as AppType, + AppMod: appMod, } const server = new WebServer({ diff --git a/packages/next/client/index.tsx b/packages/next/client/index.tsx index f7d90774551e..6c2de5960162 100644 --- a/packages/next/client/index.tsx +++ b/packages/next/client/index.tsx @@ -179,7 +179,10 @@ const appElement: HTMLElement | null = document.getElementById('__next') let lastRenderReject: (() => void) | null let webpackHMR: any export let router: Router + let CachedApp: AppComponent, onPerfEntry: (metric: any) => void +let isAppRSC: boolean + headManager.getIsSsr = () => { return router.isSsr } @@ -291,6 +294,7 @@ export async function initNext( const { component: app, exports: mod } = appEntrypoint CachedApp = app as AppComponent + isAppRSC = !!mod.__next_rsc__ const exportedReportWebVitals = mod && mod.reportWebVitals onPerfEntry = ({ id, @@ -419,6 +423,7 @@ export async function initNext( defaultLocale, domainLocales, isPreview, + isRsc: rsc, }) const renderCtx: RenderRouteInfo = { @@ -640,7 +645,7 @@ function AppContainer({ } function renderApp(App: AppComponent, appProps: AppProps) { - if (process.env.__NEXT_RSC && (App as any).__next_rsc__) { + if (process.env.__NEXT_RSC && isAppRSC) { const { Component, err: _, router: __, ...props } = appProps return } else { diff --git a/packages/next/server/load-components.ts b/packages/next/server/load-components.ts index b4d4b02a0ac0..8ccafc60a75d 100644 --- a/packages/next/server/load-components.ts +++ b/packages/next/server/load-components.ts @@ -36,6 +36,7 @@ export type LoadComponentsReturnType = { getStaticPaths?: GetStaticPaths getServerSideProps?: GetServerSideProps ComponentMod: any + AppMod: any } export async function loadDefaultErrorComponents( @@ -46,7 +47,8 @@ export async function loadDefaultErrorComponents( require(`next/dist/pages/_document` + (hasConcurrentFeatures ? '-concurrent' : '')) ) - const App = interopDefault(require('next/dist/pages/_app')) + const AppMod = require('next/dist/pages/_app') + const App = interopDefault(AppMod) const ComponentMod = require('next/dist/pages/_error') const Component = interopDefault(ComponentMod) @@ -58,6 +60,7 @@ export async function loadDefaultErrorComponents( buildManifest: require(join(distDir, `fallback-${BUILD_MANIFEST}`)), reactLoadableManifest: {}, ComponentMod, + AppMod, } } @@ -124,6 +127,7 @@ export async function loadComponents( reactLoadableManifest, pageConfig: ComponentMod.config || {}, ComponentMod, + AppMod, getServerSideProps, getStaticProps, getStaticPaths, diff --git a/packages/next/server/render.tsx b/packages/next/server/render.tsx index 2dd64eb1b402..5641bdcc94b8 100644 --- a/packages/next/server/render.tsx +++ b/packages/next/server/render.tsx @@ -184,11 +184,12 @@ function enhanceComponents( } function renderFlight( + AppMod: any, App: AppType, Component: React.ComponentType, props: any ) { - const AppServer = (App as any).__next_rsc__ + const AppServer = AppMod.__next_rsc__ ? (App as React.ComponentType) : React.Fragment return ( @@ -357,6 +358,8 @@ const useRSCResponse = createRSCHook() function createServerComponentRenderer( App: AppType, OriginalComponent: React.ComponentType, + AppMod: any, + ComponentMod: any, { cachePrefix, transformStream, @@ -374,14 +377,15 @@ function createServerComponentRenderer( // globally for react-server-dom-webpack. // This is a hack until we find a better way. // @ts-ignore - globalThis.__webpack_require__ = OriginalComponent.__webpack_require__ + 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( - renderFlight(App, OriginalComponent, props), + renderFlight(AppMod, App, OriginalComponent, props), serverComponentManifest ) @@ -463,6 +467,8 @@ export async function renderToHTML( images, reactRoot, runtime, + ComponentMod, + AppMod, } = renderOpts const hasConcurrentFeatures = !!runtime @@ -473,7 +479,7 @@ export async function renderToHTML( const isServerComponent = !!serverComponentManifest && hasConcurrentFeatures && - (OriginalComponent as any).__next_rsc__ + ComponentMod.__next_rsc__ let Component: React.ComponentType<{}> | ((props: any) => JSX.Element) = renderOpts.Component @@ -485,12 +491,18 @@ export async function renderToHTML( if (isServerComponent) { serverComponentsInlinedTransformStream = new TransformStream() const search = stringifyQuery(query) - Component = createServerComponentRenderer(App, OriginalComponent, { - cachePrefix: pathname + (search ? `?${search}` : ''), - transformStream: serverComponentsInlinedTransformStream, - serverComponentManifest, - runtime, - }) + Component = createServerComponentRenderer( + App, + AppMod, + OriginalComponent, + ComponentMod, + { + cachePrefix: pathname + (search ? `?${search}` : ''), + transformStream: serverComponentsInlinedTransformStream, + serverComponentManifest, + runtime, + } + ) } const getFontDefinition = (url: string): string => { @@ -1178,7 +1190,7 @@ export async function renderToHTML( if (renderServerComponentData) { const stream: ReadableStream = renderToReadableStream( - renderFlight(App, OriginalComponent, { + renderFlight(AppMod, App, OriginalComponent, { ...props.pageProps, ...serverComponentProps, }), @@ -1318,7 +1330,7 @@ export async function renderToHTML( ) : ( - {renderOpts.serverComponents && (App as any).__next_rsc__ ? ( + {renderOpts.serverComponents && AppMod.__next_rsc__ ? ( ) : ( diff --git a/packages/next/shared/lib/router/router.ts b/packages/next/shared/lib/router/router.ts index d3e172431880..30214fa6a073 100644 --- a/packages/next/shared/lib/router/router.ts +++ b/packages/next/shared/lib/router/router.ts @@ -656,6 +656,7 @@ export default class Router implements BaseRouter { defaultLocale, domainLocales, isPreview, + isRsc, }: { subscription: Subscription initialProps: any @@ -670,6 +671,7 @@ export default class Router implements BaseRouter { defaultLocale?: string domainLocales?: DomainLocale[] isPreview?: boolean + isRsc?: boolean } ) { // represents the current component key @@ -688,7 +690,7 @@ export default class Router implements BaseRouter { err, __N_SSG: initialProps && initialProps.__N_SSG, __N_SSP: initialProps && initialProps.__N_SSP, - __N_RSC: !!(Component as any)?.__next_rsc__, + __N_RSC: !!isRsc, } } @@ -1527,7 +1529,7 @@ export default class Router implements BaseRouter { styleSheets: res.styleSheets, __N_SSG: res.mod.__N_SSG, __N_SSP: res.mod.__N_SSP, - __N_RSC: !!(res.page as any).__next_rsc__, + __N_RSC: !!res.mod.__next_rsc__, }))) const { Component, __N_SSG, __N_SSP, __N_RSC } = routeInfo diff --git a/test/integration/react-streaming-and-server-components/app/pages/partial-hydration.server.js b/test/integration/react-streaming-and-server-components/app/pages/partial-hydration.server.js index 5214fccb70d3..afd39e8d601d 100644 --- a/test/integration/react-streaming-and-server-components/app/pages/partial-hydration.server.js +++ b/test/integration/react-streaming-and-server-components/app/pages/partial-hydration.server.js @@ -23,7 +23,7 @@ function Data() { throw promise } -export default function Page() { +export default function () { return ( <> Current Runtime:{' '} From e4e708cc9134c90733a1d061bfa45b2cc122772c Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Wed, 2 Mar 2022 19:05:35 +0100 Subject: [PATCH 2/2] fix failed test --- .../loaders/next-flight-server-loader.ts | 4 ++-- packages/next/server/render.tsx | 17 +++++------------ 2 files changed, 7 insertions(+), 14 deletions(-) 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 006e5fe960aa..a8315fdc5080 100644 --- a/packages/next/build/webpack/loaders/next-flight-server-loader.ts +++ b/packages/next/build/webpack/loaders/next-flight-server-loader.ts @@ -86,7 +86,7 @@ async function parseImportsInfo({ // A client component. It should be loaded as module reference. transformedSource += importDeclarations transformedSource += JSON.stringify(`${importSource}?__sc_client__`) - imports += `require(${JSON.stringify(importSource)});` + imports += `require(${JSON.stringify(importSource)})\n` } else { // This is a special case to avoid the Duplicate React error. // Since we already include React in the SSR runtime, @@ -116,7 +116,7 @@ async function parseImportsInfo({ continue } - imports += `require(${JSON.stringify(importSource)});` + imports += `require(${JSON.stringify(importSource)})\n` } lastIndex = node.source.span.end diff --git a/packages/next/server/render.tsx b/packages/next/server/render.tsx index 5641bdcc94b8..f86b9c3a10a3 100644 --- a/packages/next/server/render.tsx +++ b/packages/next/server/render.tsx @@ -183,14 +183,9 @@ function enhanceComponents( } } -function renderFlight( - AppMod: any, - App: AppType, - Component: React.ComponentType, - props: any -) { +function renderFlight(AppMod: any, Component: React.ComponentType, props: any) { const AppServer = AppMod.__next_rsc__ - ? (App as React.ComponentType) + ? (AppMod.default as React.ComponentType) : React.Fragment return ( @@ -356,7 +351,6 @@ const useRSCResponse = createRSCHook() // Create the wrapper component for a Flight stream. function createServerComponentRenderer( - App: AppType, OriginalComponent: React.ComponentType, AppMod: any, ComponentMod: any, @@ -385,7 +379,7 @@ function createServerComponentRenderer( const ServerComponentWrapper = (props: any) => { const id = (React as any).useId() const reqStream: ReadableStream = renderToReadableStream( - renderFlight(AppMod, App, OriginalComponent, props), + renderFlight(AppMod, OriginalComponent, props), serverComponentManifest ) @@ -492,9 +486,8 @@ export async function renderToHTML( serverComponentsInlinedTransformStream = new TransformStream() const search = stringifyQuery(query) Component = createServerComponentRenderer( - App, - AppMod, OriginalComponent, + AppMod, ComponentMod, { cachePrefix: pathname + (search ? `?${search}` : ''), @@ -1190,7 +1183,7 @@ export async function renderToHTML( if (renderServerComponentData) { const stream: ReadableStream = renderToReadableStream( - renderFlight(AppMod, App, OriginalComponent, { + renderFlight(AppMod, OriginalComponent, { ...props.pageProps, ...serverComponentProps, }),