Skip to content

Commit

Permalink
Refactor export marks of server components (#34945)
Browse files Browse the repository at this point in the history
* refactor rsc export marks

* fix failed test
  • Loading branch information
shuding committed Mar 2, 2022
1 parent d9d494a commit a518036
Show file tree
Hide file tree
Showing 9 changed files with 71 additions and 79 deletions.
2 changes: 1 addition & 1 deletion packages/next/build/utils.ts
Expand Up @@ -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
Expand Down
73 changes: 24 additions & 49 deletions packages/next/build/webpack/loaders/next-flight-server-loader.ts
Expand Up @@ -27,26 +27,26 @@ const createServerComponentFilter = (pageExtensions: string[]) => {
async function parseImportsInfo({
resourcePath,
source,
imports,
isClientCompilation,
isServerComponent,
isClientComponent,
}: {
resourcePath: string
source: string
imports: Array<string>
isClientCompilation: boolean
isServerComponent: (name: string) => boolean
isClientComponent: (name: string) => boolean
}): Promise<{
source: string
defaultExportName: string
imports: string
}> {
const ast = await parse(source, { filename: resourcePath, 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) {
Expand All @@ -67,7 +67,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)})\n`
} else {
// This is a special case to avoid the Duplicate React error.
// Since we already include React in the SSR runtime,
Expand Down Expand Up @@ -97,27 +97,12 @@ async function parseImportsInfo({
continue
}

imports.push(`require(${JSON.stringify(importSource)})`)
imports += `require(${JSON.stringify(importSource)})\n`
}

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
}
Expand All @@ -127,7 +112,7 @@ async function parseImportsInfo({
transformedSource += source.substring(lastIndex)
}

return { source: transformedSource, defaultExportName }
return { source: transformedSource, imports }
}

export default async function transformSource(
Expand Down Expand Up @@ -162,44 +147,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
}
Expand Up @@ -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
Expand All @@ -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,
Expand Down
Expand Up @@ -17,11 +17,11 @@ process.cwd = () => ''
export function getRender({
dev,
page,
appMod,
pageMod,
errorMod,
error500Mod,
Document,
App,
buildManifest,
reactLoadableManifest,
serverComponentManifest,
Expand All @@ -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
Expand All @@ -48,7 +48,8 @@ export function getRender({
buildManifest,
reactLoadableManifest,
Document,
App,
App: appMod.default as AppType,
AppMod: appMod,
}

const server = new WebServer({
Expand Down
7 changes: 6 additions & 1 deletion packages/next/client/index.tsx
Expand Up @@ -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
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -419,6 +423,7 @@ export async function initNext(
defaultLocale,
domainLocales,
isPreview,
isRsc: rsc,
})

const renderCtx: RenderRouteInfo = {
Expand Down Expand Up @@ -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 <Component {...props} />
} else {
Expand Down
6 changes: 5 additions & 1 deletion packages/next/server/load-components.ts
Expand Up @@ -36,6 +36,7 @@ export type LoadComponentsReturnType = {
getStaticPaths?: GetStaticPaths
getServerSideProps?: GetServerSideProps
ComponentMod: any
AppMod: any
}

export async function loadDefaultErrorComponents(
Expand All @@ -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)

Expand All @@ -58,6 +60,7 @@ export async function loadDefaultErrorComponents(
buildManifest: require(join(distDir, `fallback-${BUILD_MANIFEST}`)),
reactLoadableManifest: {},
ComponentMod,
AppMod,
}
}

Expand Down Expand Up @@ -124,6 +127,7 @@ export async function loadComponents(
reactLoadableManifest,
pageConfig: ComponentMod.config || {},
ComponentMod,
AppMod,
getServerSideProps,
getStaticProps,
getStaticPaths,
Expand Down
43 changes: 24 additions & 19 deletions packages/next/server/render.tsx
Expand Up @@ -183,13 +183,9 @@ function enhanceComponents(
}
}

function renderFlight(
App: AppType,
Component: React.ComponentType,
props: any
) {
const AppServer = (App as any).__next_rsc__
? (App as React.ComponentType)
function renderFlight(AppMod: any, Component: React.ComponentType, props: any) {
const AppServer = AppMod.__next_rsc__
? (AppMod.default as React.ComponentType)
: React.Fragment
return (
<AppServer>
Expand Down Expand Up @@ -355,8 +351,9 @@ const useRSCResponse = createRSCHook()

// Create the wrapper component for a Flight stream.
function createServerComponentRenderer(
App: AppType,
OriginalComponent: React.ComponentType,
AppMod: any,
ComponentMod: any,
{
cachePrefix,
transformStream,
Expand All @@ -374,14 +371,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<Uint8Array> = renderToReadableStream(
renderFlight(App, OriginalComponent, props),
renderFlight(AppMod, OriginalComponent, props),
serverComponentManifest
)

Expand Down Expand Up @@ -463,6 +461,8 @@ export async function renderToHTML(
images,
reactRoot,
runtime,
ComponentMod,
AppMod,
} = renderOpts

const hasConcurrentFeatures = !!runtime
Expand All @@ -473,7 +473,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
Expand All @@ -485,12 +485,17 @@ 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(
OriginalComponent,
AppMod,
ComponentMod,
{
cachePrefix: pathname + (search ? `?${search}` : ''),
transformStream: serverComponentsInlinedTransformStream,
serverComponentManifest,
runtime,
}
)
}

const getFontDefinition = (url: string): string => {
Expand Down Expand Up @@ -1178,7 +1183,7 @@ export async function renderToHTML(

if (renderServerComponentData) {
const stream: ReadableStream<Uint8Array> = renderToReadableStream(
renderFlight(App, OriginalComponent, {
renderFlight(AppMod, OriginalComponent, {
...props.pageProps,
...serverComponentProps,
}),
Expand Down Expand Up @@ -1318,7 +1323,7 @@ export async function renderToHTML(
) : (
<Body>
<AppContainerWithIsomorphicFiberStructure>
{renderOpts.serverComponents && (App as any).__next_rsc__ ? (
{renderOpts.serverComponents && AppMod.__next_rsc__ ? (
<Component {...props.pageProps} router={router} />
) : (
<App {...props} Component={Component} router={router} />
Expand Down

0 comments on commit a518036

Please sign in to comment.