Skip to content

Commit

Permalink
Refactor page component getter in web server (vercel#33759)
Browse files Browse the repository at this point in the history
Cleans up the code of the middleware SSR loader and the web server. Currently the page components and render options are provided to the server via `globalThis` which is not ideal.

Instead we can inject `extendRenderOpts` and `loadComponent` to the web server. Since this is the the minimal mode and we'll need to handle `?flight` requests, we update the server render opts upon `updateRenderOpts` (In the future this should be changed to be passed to `requestHandler` to avoid race conditions).

Currently, we can't fully get rid of the `__server_context` global as we call `getBuildId` in the base server constructor.

## Bug

- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Errors have helpful link attached, see `contributing.md`

## Feature

- [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`

## Documentation / Examples

- [ ] Make sure the linting passes by running `yarn lint`
  • Loading branch information
shuding authored and natew committed Feb 16, 2022
1 parent 607cabe commit 6059799
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 76 deletions.
Expand Up @@ -44,37 +44,25 @@ export default async function middlewareSSRLoader(this: any) {
}
// Set server context
self.__current_route = ${JSON.stringify(page)}
self.__server_context = {
Component: pageMod.default,
pageConfig: pageMod.config || {},
buildManifest,
reactLoadableManifest,
Document,
App,
getStaticProps: pageMod.getStaticProps,
getServerSideProps: pageMod.getServerSideProps,
getStaticPaths: pageMod.getStaticPaths,
ComponentMod: undefined,
serverComponentManifest: ${isServerComponent} ? rscManifest : null,
// components
errorMod,
error500Mod,
// renderOpts
page: ${JSON.stringify(page)},
buildId: ${JSON.stringify(buildId)},
dev: ${dev},
env: process.env,
supportsDynamicHTML: true,
concurrentFeatures: true,
disableOptimizedLoading: true,
}
const render = getRender({
dev: ${dev},
page: ${JSON.stringify(page)},
pageMod,
errorMod,
error500Mod,
App,
Document,
buildManifest,
reactLoadableManifest,
serverComponentManifest: ${isServerComponent} ? rscManifest : null,
isServerComponent: ${isServerComponent},
config: ${stringifiedConfig},
buildId: ${JSON.stringify(buildId)},
})
export default function rscMiddleware(opts) {
Expand Down
@@ -1,4 +1,7 @@
import type { NextConfig } from '../../../../server/config-shared'
import type { DocumentType, AppType } from '../../../../shared/lib/utils'
import type { BuildManifest } from '../../../../server/get-page-files'
import type { ReactLoadableManifest } from '../../../../server/load-components'

import { NextRequest } from '../../../../server/web/spec-extension/request'
import { toNodeHeaders } from '../../../../server/web/utils'
Expand All @@ -20,20 +23,98 @@ function sendError(req: any, error: Error) {
})
}

// Polyfilled for `path-browserify` inside the Web Server.
process.cwd = () => ''

export function getRender({
dev,
page,
pageMod,
errorMod,
error500Mod,
Document,
App,
buildManifest,
reactLoadableManifest,
serverComponentManifest,
isServerComponent,
config,
buildId,
}: {
Document: any
dev: boolean
page: string
pageMod: any
errorMod: any
error500Mod: any
Document: DocumentType
App: AppType
buildManifest: BuildManifest
reactLoadableManifest: ReactLoadableManifest
serverComponentManifest: any | null
isServerComponent: boolean
config: NextConfig
buildId: string
}) {
// Polyfilled for `path-browserify`.
process.cwd = () => ''
const baseLoadComponentResult = {
dev,
buildManifest,
reactLoadableManifest,
Document,
App,
}

const server = new WebServer({
conf: config,
minimalMode: true,
webServerConfig: {
extendRenderOpts: {
buildId,
supportsDynamicHTML: true,
concurrentFeatures: true,
disableOptimizedLoading: true,
serverComponentManifest,
},
loadComponent: async (pathname) => {
if (pathname === page) {
return {
...baseLoadComponentResult,
Component: pageMod.default,
pageConfig: pageMod.config || {},
getStaticProps: pageMod.getStaticProps,
getServerSideProps: pageMod.getServerSideProps,
getStaticPaths: pageMod.getStaticPaths,
ComponentMod: pageMod,
}
}

// If there is a custom 500 page, we need to handle it separately.
if (pathname === '/500' && error500Mod) {
return {
...baseLoadComponentResult,
Component: error500Mod.default,
pageConfig: error500Mod.config || {},
getStaticProps: error500Mod.getStaticProps,
getServerSideProps: error500Mod.getServerSideProps,
getStaticPaths: error500Mod.getStaticPaths,
ComponentMod: error500Mod,
}
}

if (pathname === '/_error') {
return {
...baseLoadComponentResult,
Component: errorMod.default,
pageConfig: errorMod.config || {},
getStaticProps: errorMod.getStaticProps,
getServerSideProps: errorMod.getServerSideProps,
getStaticPaths: errorMod.getStaticPaths,
ComponentMod: errorMod,
}
}

return null
},
},
})
const requestHandler = server.getRequestHandler()

Expand Down Expand Up @@ -72,8 +153,8 @@ export function getRender({
? JSON.parse(query.__props__)
: undefined

// Extend the context.
Object.assign((self as any).__server_context, {
// Extend the render options.
server.updateRenderOpts({
renderServerComponentData,
serverComponentProps,
})
Expand Down
4 changes: 4 additions & 0 deletions packages/next/server/base-server.ts
Expand Up @@ -158,6 +158,10 @@ export default abstract class Server {
concurrentFeatures?: boolean
serverComponents?: boolean
crossOrigin?: string
supportsDynamicHTML?: boolean
serverComponentManifest?: any
renderServerComponentData?: boolean
serverComponentProps?: any
}
private incrementalCache: IncrementalCache
private responseCache: ResponseCache
Expand Down
2 changes: 1 addition & 1 deletion packages/next/server/load-components.ts
Expand Up @@ -19,7 +19,7 @@ export type ManifestItem = {
files: string[]
}

type ReactLoadableManifest = { [moduleId: string]: ManifestItem }
export type ReactLoadableManifest = { [moduleId: string]: ManifestItem }

export type LoadComponentsReturnType = {
Component: React.ComponentType
Expand Down
73 changes: 26 additions & 47 deletions packages/next/server/web-server.ts
Expand Up @@ -4,12 +4,24 @@ import type RenderResult from './render-result'
import type { NextParsedUrlQuery } from './request-meta'
import type { Params } from './router'
import type { PayloadOptions } from './send-payload'
import type { LoadComponentsReturnType } from './load-components'

import BaseServer from './base-server'
import BaseServer, { Options } from './base-server'
import { renderToHTML } from './render'
import { LoadComponentsReturnType } from './load-components'

interface WebServerConfig {
loadComponent: (pathname: string) => Promise<LoadComponentsReturnType | null>
extendRenderOpts?: Partial<BaseServer['renderOpts']>
}

export default class NextWebServer extends BaseServer {
webServerConfig: WebServerConfig

constructor(options: Options & { webServerConfig: WebServerConfig }) {
super(options)
this.webServerConfig = options.webServerConfig
Object.assign(this.renderOpts, options.webServerConfig.extendRenderOpts)
}
protected generateRewrites() {
// @TODO: assuming minimal mode right now
return {
Expand Down Expand Up @@ -78,7 +90,7 @@ export default class NextWebServer extends BaseServer {
}
protected getPagesManifest() {
return {
[(globalThis as any).__current_route]: '',
[(globalThis as any).__server_context.page]: '',
}
}
protected getFilesystemPaths() {
Expand Down Expand Up @@ -158,52 +170,19 @@ export default class NextWebServer extends BaseServer {
query?: NextParsedUrlQuery,
params?: Params | null
) {
if (pathname === (globalThis as any).__current_route) {
return {
query: {
...(query || {}),
...(params || {}),
},
components: (globalThis as any)
.__server_context as LoadComponentsReturnType,
}
}

const { errorMod, error500Mod } = (globalThis as any).__server_context
const result = await this.webServerConfig.loadComponent(pathname)
if (!result) return null

// If there is a custom 500 page.
if (pathname === '/500' && error500Mod) {
return {
query: {
...(query || {}),
...(params || {}),
},
components: {
...(globalThis as any).__server_context,
Component: error500Mod.default,
getStaticProps: error500Mod.getStaticProps,
getServerSideProps: error500Mod.getServerSideProps,
getStaticPaths: error500Mod.getStaticPaths,
} as LoadComponentsReturnType,
}
}

if (pathname === '/_error') {
return {
query: {
...(query || {}),
...(params || {}),
},
components: {
...(globalThis as any).__server_context,
Component: errorMod.default,
getStaticProps: errorMod.getStaticProps,
getServerSideProps: errorMod.getServerSideProps,
getStaticPaths: errorMod.getStaticPaths,
} as LoadComponentsReturnType,
}
return {
query: {
...(query || {}),
...(params || {}),
},
components: result,
}
}

return null
public updateRenderOpts(renderOpts: Partial<BaseServer['renderOpts']>) {
Object.assign(this.renderOpts, renderOpts)
}
}

0 comments on commit 6059799

Please sign in to comment.