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

Implement web server as the request handler for edge SSR #33635

Merged
merged 30 commits into from Jan 26, 2022
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
3a0b602
abstract manifest getters
shuding Jan 21, 2022
f7870fa
Merge branch 'canary' into shu/504f
shuding Jan 21, 2022
1ea2af3
remove unused imports
shuding Jan 21, 2022
ebc348a
create web server
shuding Jan 21, 2022
916544f
merge canary
shuding Jan 21, 2022
a224f25
initial implementation of web-server
shuding Jan 21, 2022
7b38b1b
try importing the web server
shuding Jan 21, 2022
e788b66
load manifest before initializing super
shuding Jan 22, 2022
7eda31b
pass manifests
shuding Jan 24, 2022
0b6f93e
add routes and pages manifests
shuding Jan 24, 2022
2e656b2
Revert "add routes and pages manifests"
shuding Jan 24, 2022
26c6914
Revert "pass manifests"
shuding Jan 24, 2022
e89bcfb
fix chalk and pass config to the loader
shuding Jan 24, 2022
14162d0
move manifest getter
shuding Jan 24, 2022
5e574b6
pipe the server res
shuding Jan 24, 2022
8790427
clean up
shuding Jan 24, 2022
558333e
test dynamic route
shuding Jan 24, 2022
08538b3
current page route
shuding Jan 25, 2022
c0a75b8
fix dynamic route tests
shuding Jan 25, 2022
42b76d4
fix test case
shuding Jan 25, 2022
9701499
fix partial hydration
shuding Jan 25, 2022
9bc2728
fix dev opts
shuding Jan 25, 2022
b5e7949
fix dev flag
shuding Jan 25, 2022
e5658f2
fix build id and ssr error
shuding Jan 25, 2022
5369da5
fix test cases and clean up loader
shuding Jan 25, 2022
f5b4401
clean up
shuding Jan 25, 2022
ce6e1a2
fix type errors
shuding Jan 25, 2022
4445d87
Merge branch 'canary' into shu/504f
kodiakhq[bot] Jan 25, 2022
c0b7c2e
fix stringify
shuding Jan 25, 2022
4df0630
Merge branch 'canary' into shu/504f
shuding Jan 26, 2022
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
3 changes: 1 addition & 2 deletions packages/next/build/entries.ts
Expand Up @@ -157,7 +157,6 @@ export function createEntrypoints(
const isFlight = isFlightPage(config, absolutePagePath)

const webServerRuntime = !!config.experimental.concurrentFeatures
const hasServerComponents = !!config.experimental.serverComponents

if (page.match(MIDDLEWARE_ROUTE)) {
const loaderOpts: MiddlewareLoaderOptions = {
Expand All @@ -177,10 +176,10 @@ export function createEntrypoints(
name: '[name].js',
value: `next-middleware-ssr-loader?${stringify({
page,
stringifiedConfig: JSON.stringify(config),
huozhi marked this conversation as resolved.
Show resolved Hide resolved
absolute500Path: pages['/500'] || '',
absolutePagePath,
isServerComponent: isFlight,
serverComponents: hasServerComponents,
huozhi marked this conversation as resolved.
Show resolved Hide resolved
...defaultServerlessOptions,
} as any)}!`,
isServer: false,
Expand Down
Expand Up @@ -2,13 +2,16 @@ import { stringifyRequest } from '../../stringify-request'

export default async function middlewareSSRLoader(this: any) {
const {
dev,
page,
buildId,
absolutePagePath,
absoluteAppPath,
absoluteDocumentPath,
absolute500Path,
absoluteErrorPath,
isServerComponent,
...restRenderOpts
stringifiedConfig,
} = this.getOptions()

const stringifiedAbsolutePagePath = stringifyRequest(this, absolutePagePath)
Expand Down Expand Up @@ -42,16 +45,37 @@ export default async function middlewareSSRLoader(this: any) {
throw new Error('Your page must export a \`default\` component')
}

const render = getRender({
App,
Document,
pageMod,
errorMod,
// Set server context
self.__current_route = ${JSON.stringify(page)}
self.__server_context = {
Component: pageMod.default,
pageConfig: pageMod.config || {},
buildManifest,
reactLoadableManifest,
rscManifest,
Document,
App,
getStaticProps: pageMod.getStaticProps,
getServerSideProps: pageMod.getServerSideProps,
getStaticPaths: pageMod.getStaticPaths,
ComponentMod: undefined,
serverComponentManifest: ${isServerComponent} ? rscManifest : null,

// components
errorMod,

// renderOpts
buildId: ${JSON.stringify(buildId)},
dev: ${JSON.stringify(dev)},
env: process.env,
supportsDynamicHTML: true,
concurrentFeatures: true,
disableOptimizedLoading: true,
}

const render = getRender({
Document,
isServerComponent: ${isServerComponent},
restRenderOpts: ${JSON.stringify(restRenderOpts)}
config: ${stringifiedConfig},
})

export default function rscMiddleware(opts) {
Expand Down
@@ -1,8 +1,11 @@
import type { NextConfig } from '../../../../server/config-shared'

import { NextRequest } from '../../../../server/web/spec-extension/request'
import { renderToHTML } from '../../../../server/web/render'
import RenderResult from '../../../../server/render-result'
import { toNodeHeaders } from '../../../../server/web/utils'

import WebServer from '../../../../server/web-server'
import { WebNextRequest, WebNextResponse } from '../../../../server/base-http'

const createHeaders = (args?: any) => ({
...args,
'x-middleware-ssr': '1',
Expand All @@ -18,26 +21,22 @@ function sendError(req: any, error: Error) {
}

export function getRender({
App,
Document,
pageMod,
errorMod,
rscManifest,
buildManifest,
reactLoadableManifest,
isServerComponent,
restRenderOpts,
config,
}: {
App: any
Document: any
pageMod: any
errorMod: any
rscManifest: object
buildManifest: any
reactLoadableManifest: any
isServerComponent: boolean
restRenderOpts: any
config: NextConfig
}) {
// Polyfilled for `path-browserify`.
process.cwd = () => ''
const server = new WebServer({
conf: config,
minimalMode: true,
})
const requestHandler = server.getRequestHandler()

return async function render(request: NextRequest) {
const { nextUrl: url, cookies, headers } = request
const { pathname, searchParams } = url
Expand All @@ -56,6 +55,7 @@ export function getRender({
})
}

// @TODO: We should move this into server/render.
if (Document.getInitialProps) {
const err = new Error(
'`getInitialProps` in Document component is not supported with `concurrentFeatures` enabled.'
Expand All @@ -72,92 +72,15 @@ export function getRender({
? JSON.parse(query.__props__)
: undefined

delete query.__flight__
delete query.__props__

const renderOpts = {
...restRenderOpts,
// Locales are not supported yet.
// locales: i18n?.locales,
// locale: detectedLocale,
// defaultLocale,
// domainLocales: i18n?.domains,
dev: process.env.NODE_ENV !== 'production',
App,
Document,
buildManifest,
Component: pageMod.default,
pageConfig: pageMod.config || {},
getStaticProps: pageMod.getStaticProps,
getServerSideProps: pageMod.getServerSideProps,
getStaticPaths: pageMod.getStaticPaths,
reactLoadableManifest,
env: process.env,
supportsDynamicHTML: true,
concurrentFeatures: true,
// When streaming, opt-out the `defer` behavior for script tags.
disableOptimizedLoading: true,
// Extend the context.
Object.assign((self as any).__server_context, {
renderServerComponentData,
serverComponentProps,
serverComponentManifest: isServerComponent ? rscManifest : null,
ComponentMod: null,
}

const transformStream = new TransformStream()
const writer = transformStream.writable.getWriter()
const encoder = new TextEncoder()

let result: RenderResult | null
let renderError: any
try {
result = await renderToHTML(
req as any,
{} as any,
pathname,
query,
renderOpts
)
} catch (err: any) {
console.error(
'An error occurred while rendering the initial result:',
err
)
const errorRes = { statusCode: 500, err }
renderError = err
try {
req.url = '/_error'
result = await renderToHTML(
req as any,
errorRes as any,
'/_error',
query,
{
...renderOpts,
err,
Component: errorMod.default,
getStaticProps: errorMod.getStaticProps,
getServerSideProps: errorMod.getServerSideProps,
getStaticPaths: errorMod.getStaticPaths,
}
)
} catch (err2: any) {
return sendError(req, err2)
}
}

if (!result) {
return sendError(req, new Error('No result returned from render.'))
}

result.pipe({
write: (str: string) => writer.write(encoder.encode(str)),
end: () => writer.close(),
// Not implemented: cork/uncork/on/removeListener
} as any)

return new Response(transformStream.readable, {
headers: createHeaders(),
status: renderError ? 500 : 200,
})

const extendedReq = new WebNextRequest(request)
const extendedRes = new WebNextResponse()
requestHandler(extendedReq, extendedRes)
return await extendedRes.toResponse()
}
}
2 changes: 1 addition & 1 deletion packages/next/lib/chalk.ts
@@ -1,6 +1,6 @@
let chalk: typeof import('next/dist/compiled/chalk')

if (typeof window === 'undefined') {
if (!process.browser) {
huozhi marked this conversation as resolved.
Show resolved Hide resolved
chalk = require('next/dist/compiled/chalk')
} else {
chalk = require('./web/chalk').default
Expand Down
3 changes: 1 addition & 2 deletions packages/next/lib/web/chalk.ts
Expand Up @@ -4,8 +4,7 @@
// - chalk.red('error')
// - chalk.bold.cyan('message')
// - chalk.hex('#fff').underline('hello')
const log = console.log
const chalk: any = new Proxy(log, {
const chalk: any = new Proxy((s: string) => s, {
get(_, prop: string) {
if (
['hex', 'rgb', 'ansi256', 'bgHex', 'bgRgb', 'bgAnsi256'].includes(prop)
Expand Down