Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor export marks of server components #34945

Merged
merged 3 commits into from Mar 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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