Skip to content

Commit

Permalink
Handle edge runtime for app (#39910)
Browse files Browse the repository at this point in the history
Continuation of #38817 this adds handling to allow leveraging the `experimental-edge` runtime for `app`. 

## Bug

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

Co-authored-by: Jiachi Liu <inbox@huozhi.im>
  • Loading branch information
ijjk and huozhi committed Aug 24, 2022
1 parent b757db4 commit 49b4dae
Show file tree
Hide file tree
Showing 19 changed files with 215 additions and 65 deletions.
2 changes: 1 addition & 1 deletion packages/next/build/analysis/get-page-static-info.ts
Expand Up @@ -256,5 +256,5 @@ export async function getPageStaticInfo(params: {
}
}

return { ssr: false, ssg: false }
return { ssr: false, ssg: false, runtime: nextConfig.experimental?.runtime }
}
16 changes: 16 additions & 0 deletions packages/next/build/entries.ts
Expand Up @@ -164,6 +164,8 @@ export function getEdgeServerEntry(opts: {
page: string
pages: { [page: string]: string }
middleware?: { pathMatcher?: RegExp }
pagesType?: 'app' | 'pages' | 'root'
appDirLoader?: string
}) {
if (isMiddlewareFile(opts.page)) {
const loaderParams: MiddlewareLoaderOptions = {
Expand Down Expand Up @@ -203,6 +205,8 @@ export function getEdgeServerEntry(opts: {
),
page: opts.page,
stringifiedConfig: JSON.stringify(opts.config),
pagesType: opts.pagesType,
appDirLoader: Buffer.from(opts.appDirLoader || '').toString('base64'),
}

return {
Expand Down Expand Up @@ -445,6 +449,16 @@ export async function createEntrypoints(params: CreateEntrypointsParams) {
}
},
onEdgeServer: () => {
const appDirLoader =
pagesType === 'app'
? getAppEntry({
name: serverBundlePath,
pagePath: mappings[page],
appDir: appDir!,
pageExtensions,
}).import
: ''

edgeServer[serverBundlePath] = getEdgeServerEntry({
...params,
absolutePagePath: mappings[page],
Expand All @@ -453,6 +467,8 @@ export async function createEntrypoints(params: CreateEntrypointsParams) {
isServerComponent,
page,
middleware: staticInfo?.middleware,
pagesType,
appDirLoader,
})
},
})
Expand Down
4 changes: 4 additions & 0 deletions packages/next/build/index.ts
Expand Up @@ -808,6 +808,10 @@ export default async function build(
? [
path.join(SERVER_DIRECTORY, FLIGHT_MANIFEST + '.js'),
path.join(SERVER_DIRECTORY, FLIGHT_MANIFEST + '.json'),
path.join(
SERVER_DIRECTORY,
FLIGHT_SERVER_CSS_MANIFEST + '.js'
),
path.join(
SERVER_DIRECTORY,
FLIGHT_SERVER_CSS_MANIFEST + '.json'
Expand Down
Expand Up @@ -30,6 +30,7 @@ export interface EdgeMiddlewareMeta {

export interface EdgeSSRMeta {
isServerComponent: boolean
isAppDir?: boolean
page: string
}

Expand Down
Expand Up @@ -12,6 +12,8 @@ export type EdgeSSRLoaderQuery = {
isServerComponent: boolean
page: string
stringifiedConfig: string
appDirLoader?: string
pagesType?: 'app' | 'pages' | 'root'
}

export default async function edgeSSRLoader(this: any) {
Expand All @@ -26,12 +28,21 @@ export default async function edgeSSRLoader(this: any) {
absoluteErrorPath,
isServerComponent,
stringifiedConfig,
appDirLoader: appDirLoaderBase64,
pagesType,
} = this.getOptions()

const appDirLoader = Buffer.from(
appDirLoaderBase64 || '',
'base64'
).toString()
const isAppDir = pagesType === 'app'

const buildInfo = getModuleBuildInfo(this._module)
buildInfo.nextEdgeSSR = {
isServerComponent: isServerComponent === 'true',
page: page,
isAppDir,
}
buildInfo.route = {
page,
Expand All @@ -46,6 +57,11 @@ export default async function edgeSSRLoader(this: any) {
? stringifyRequest(this, absolute500Path)
: null

const pageModPath = `${appDirLoader}${stringifiedPagePath.substring(
1,
stringifiedPagePath.length - 1
)}`

const transformed = `
import { adapter, enhanceGlobals } from 'next/dist/server/web/adapter'
import { getRender } from 'next/dist/build/webpack/loaders/next-edge-ssr-loader/render'
Expand All @@ -54,8 +70,21 @@ export default async function edgeSSRLoader(this: any) {
enhanceGlobals()
${
isAppDir
? `
const appRenderToHTML = require('next/dist/server/app-render').renderToHTMLOrFlight
const pagesRenderToHTML = null
const pageMod = require(${JSON.stringify(pageModPath)})
`
: `
const appRenderToHTML = null
const pagesRenderToHTML = require('next/dist/server/render').renderToHTML
const pageMod = require(${stringifiedPagePath})
`
}
const appMod = require(${stringifiedAppPath})
const pageMod = require(${stringifiedPagePath})
const errorMod = require(${stringifiedErrorPath})
const error500Mod = ${
stringified500Path ? `require(${stringified500Path})` : 'null'
Expand All @@ -64,6 +93,7 @@ export default async function edgeSSRLoader(this: any) {
const buildManifest = self.__BUILD_MANIFEST
const reactLoadableManifest = self.__REACT_LOADABLE_MANIFEST
const rscManifest = self.__RSC_MANIFEST
const rscCssManifest = self.__RSC_CSS_MANIFEST
const render = getRender({
dev: ${dev},
Expand All @@ -74,8 +104,11 @@ export default async function edgeSSRLoader(this: any) {
error500Mod,
Document,
buildManifest,
appRenderToHTML,
pagesRenderToHTML,
reactLoadableManifest,
serverComponentManifest: ${isServerComponent} ? rscManifest : null,
serverCSSManifest: ${isServerComponent} ? rscCssManifest : null,
config: ${stringifiedConfig},
buildId: ${JSON.stringify(buildId)},
})
Expand Down
Expand Up @@ -20,7 +20,10 @@ export function getRender({
Document,
buildManifest,
reactLoadableManifest,
appRenderToHTML,
pagesRenderToHTML,
serverComponentManifest,
serverCSSManifest,
config,
buildId,
}: {
Expand All @@ -30,10 +33,13 @@ export function getRender({
pageMod: any
errorMod: any
error500Mod: any
appRenderToHTML: any
pagesRenderToHTML: any
Document: DocumentType
buildManifest: BuildManifest
reactLoadableManifest: ReactLoadableManifest
serverComponentManifest: any
serverCSSManifest: any
appServerMod: any
config: NextConfig
buildId: string
Expand All @@ -58,7 +64,10 @@ export function getRender({
supportsDynamicHTML: true,
disableOptimizedLoading: true,
serverComponentManifest,
serverCSSManifest,
},
appRenderToHTML,
pagesRenderToHTML,
loadComponent: async (pathname) => {
if (pathname === page) {
return {
Expand Down

This file was deleted.

33 changes: 24 additions & 9 deletions packages/next/build/webpack/plugins/flight-client-entry-plugin.ts
Expand Up @@ -72,19 +72,31 @@ export class FlightClientEntryPlugin {
// client component entry.
for (const [name, entry] of compilation.entries.entries()) {
// Check if the page entry is a server component or not.
const entryDependency = entry.dependencies?.[0]
const entryDependency: webpack.NormalModule | undefined =
entry.dependencies?.[0]
// Ensure only next-app-loader entries are handled.
if (!entryDependency || !entryDependency.request) continue

const request = entryDependency.request

if (
!entryDependency ||
!entryDependency.request ||
!entryDependency.request.startsWith('next-app-loader?')
) {
!request.startsWith('next-edge-ssr-loader?') &&
!request.startsWith('next-app-loader?')
)
continue
}

const entryModule: webpack.NormalModule =
let entryModule: webpack.NormalModule =
compilation.moduleGraph.getResolvedModule(entryDependency)

if (request.startsWith('next-edge-ssr-loader?')) {
entryModule.dependencies.forEach((dependency) => {
const modRequest: string | undefined = (dependency as any).request
if (modRequest?.includes('next-app-loader')) {
entryModule = compilation.moduleGraph.getResolvedModule(dependency)
}
})
}

const internalClientComponentEntryImports = new Set<
ClientComponentImports[0]
>()
Expand Down Expand Up @@ -152,12 +164,15 @@ export class FlightClientEntryPlugin {
name: PLUGIN_NAME,
// Have to be in the optimize stage to run after updating the CSS
// asset hash via extract mini css plugin.
// @ts-ignore TODO: Remove ignore when webpack 5 is stable
stage: webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_HASH,
},
(assets: webpack.Compilation['assets']) => {
const manifest = JSON.stringify(flightCSSManifest)
assets[FLIGHT_SERVER_CSS_MANIFEST + '.json'] = new sources.RawSource(
JSON.stringify(flightCSSManifest)
manifest
) as unknown as webpack.sources.RawSource
assets[FLIGHT_SERVER_CSS_MANIFEST + '.js'] = new sources.RawSource(
'self.__RSC_CSS_MANIFEST=' + manifest
) as unknown as webpack.sources.RawSource
}
)
Expand Down
9 changes: 6 additions & 3 deletions packages/next/build/webpack/plugins/flight-manifest-plugin.ts
Expand Up @@ -94,7 +94,6 @@ export class FlightManifestPlugin {
name: PLUGIN_NAME,
// Have to be in the optimize stage to run after updating the CSS
// asset hash via extract mini css plugin.
// @ts-ignore TODO: Remove ignore when webpack 5 is stable
stage: webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_HASH,
},
(assets) => this.createAsset(assets, compilation, compiler.context)
Expand Down Expand Up @@ -233,10 +232,14 @@ export class FlightManifestPlugin {
moduleExportedKeys.forEach((name) => {
let requiredChunks: ManifestChunks = []
if (!moduleExports[name]) {
const isRelatedChunk = (c: webpack.Chunk) =>
const isRelatedChunk = (c: webpack.Chunk) => {
// If current chunk is a page, it should require the related page chunk;
// If current chunk is a component, it should filter out the related page chunk;
chunk.name?.startsWith('pages/') || !c.name?.startsWith('pages/')
return (
chunk.name?.startsWith('pages/') ||
!c.name?.startsWith('pages/')
)
}

if (appDir) {
requiredChunks = chunkGroup.chunks
Expand Down
2 changes: 2 additions & 0 deletions packages/next/build/webpack/plugins/middleware-plugin.ts
Expand Up @@ -15,6 +15,7 @@ import {
MIDDLEWARE_MANIFEST,
MIDDLEWARE_REACT_LOADABLE_MANIFEST,
NEXT_CLIENT_SSR_ENTRY_SUFFIX,
FLIGHT_SERVER_CSS_MANIFEST,
} from '../../../shared/lib/constants'

export interface EdgeFunctionDefinition {
Expand Down Expand Up @@ -83,6 +84,7 @@ function getEntryFiles(entryFiles: string[], meta: EntryMetadata) {
if (meta.edgeSSR) {
if (meta.edgeSSR.isServerComponent) {
files.push(`server/${FLIGHT_MANIFEST}.js`)
files.push(`server/${FLIGHT_SERVER_CSS_MANIFEST}.js`)
files.push(
...entryFiles
.filter(
Expand Down
8 changes: 6 additions & 2 deletions packages/next/server/app-render.tsx
Expand Up @@ -17,7 +17,6 @@ import {
continueFromInitialStream,
} from './node-web-streams-helper'
import { isDynamicRoute } from '../shared/lib/router/utils'
import { tryGetPreviewData } from './api-utils/node'
import { htmlEscapeJsonString } from './htmlescape'
import { shouldUseReactRoot, stripInternalQueries } from './utils'
import { NextApiRequestCookies } from './api-utils'
Expand Down Expand Up @@ -423,7 +422,7 @@ export async function renderToHTMLOrFlight(
const {
buildManifest,
serverComponentManifest,
serverCSSManifest,
serverCSSManifest = {},
supportsDynamicHTML,
ComponentMod,
} = renderOpts
Expand Down Expand Up @@ -475,6 +474,11 @@ export async function renderToHTMLOrFlight(
*/
const loaderTree: LoaderTree = ComponentMod.tree

const tryGetPreviewData =
process.env.NEXT_RUNTIME === 'edge'
? () => false
: require('./api-utils/node').tryGetPreviewData

// Reads of this are cached on the `req` object, so this should resolve
// instantly. There's no need to pass this data down from a previous
// invoke, where we'd have to consider server & serverless.
Expand Down
31 changes: 16 additions & 15 deletions packages/next/server/base-server.ts
Expand Up @@ -208,6 +208,7 @@ export default abstract class Server<ServerOptions extends Options = Options> {
crossOrigin?: string
supportsDynamicHTML?: boolean
serverComponentManifest?: any
serverCSSManifest?: any
renderServerComponentData?: boolean
serverComponentProps?: any
largePageDataBytes?: number
Expand Down Expand Up @@ -1493,26 +1494,26 @@ export default abstract class Server<ServerOptions extends Options = Options> {
return path
}

protected async renderPageComponent(
ctx: RequestContext,
bubbleNoFallback: boolean
) {
// map the route to the actual bundle name
const getOriginalAppPath = (appPath: string) => {
if (this.nextConfig.experimental.appDir) {
const originalAppPath = this.appPathRoutes?.[appPath]
// map the route to the actual bundle name
protected getOriginalAppPath(route: string) {
if (this.nextConfig.experimental.appDir) {
const originalAppPath = this.appPathRoutes?.[route]

if (!originalAppPath) {
return null
}

return originalAppPath
if (!originalAppPath) {
return null
}
return null

return originalAppPath
}
return null
}

protected async renderPageComponent(
ctx: RequestContext,
bubbleNoFallback: boolean
) {
const { query, pathname } = ctx
const appPath = getOriginalAppPath(pathname)
const appPath = this.getOriginalAppPath(pathname)

let page = pathname
if (typeof appPath === 'string') {
Expand Down

0 comments on commit 49b4dae

Please sign in to comment.