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

Update to detect GSSP with edge runtime during build #40076

Merged
merged 8 commits into from Aug 30, 2022
Merged
Show file tree
Hide file tree
Changes from 4 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
59 changes: 42 additions & 17 deletions packages/next/build/index.ts
Expand Up @@ -1162,16 +1162,17 @@ export default async function build(
const errorPageStaticResult = nonStaticErrorPageSpan.traceAsyncFn(
async () =>
hasCustomErrorPage &&
staticWorkers.isPageStatic(
'/_error',
staticWorkers.isPageStatic({
page: '/_error',
distDir,
isLikeServerless,
serverless: isLikeServerless,
configFileName,
runtimeEnvConfig,
config.httpAgentOptions,
config.i18n?.locales,
config.i18n?.defaultLocale
)
httpAgentOptions: config.httpAgentOptions,
locales: config.i18n?.locales,
defaultLocale: config.i18n?.defaultLocale,
pageRuntime: config.experimental.runtime,
})
)

// we don't output _app in serverless mode so use _app export
Expand Down Expand Up @@ -1274,29 +1275,53 @@ export default async function build(
// Only calculate page static information if the page is not an
// app page.
pageType !== 'app' &&
!isReservedPage(page) &&
// We currently don't support static optimization in the Edge runtime.
pageRuntime !== SERVER_RUNTIME.edge
!isReservedPage(page)
) {
try {
let edgeInfo: any

if (pageRuntime === SERVER_RUNTIME.edge) {
const manifest = require(join(
distDir,
serverDir,
MIDDLEWARE_MANIFEST
))

edgeInfo = manifest.functions[page]
}

let isPageStaticSpan =
checkPageSpan.traceChild('is-page-static')
let workerResult = await isPageStaticSpan.traceAsyncFn(
() => {
return staticWorkers.isPageStatic(
return staticWorkers.isPageStatic({
page,
distDir,
isLikeServerless,
serverless: isLikeServerless,
configFileName,
runtimeEnvConfig,
config.httpAgentOptions,
config.i18n?.locales,
config.i18n?.defaultLocale,
isPageStaticSpan.id
)
httpAgentOptions: config.httpAgentOptions,
locales: config.i18n?.locales,
defaultLocale: config.i18n?.defaultLocale,
parentId: isPageStaticSpan.id,
pageRuntime,
edgeInfo,
})
}
)

if (pageRuntime === SERVER_RUNTIME.edge) {
if (workerResult.hasStaticProps) {
console.warn(
`"getStaticProps" is not yet supported fully with "experimental-edge", detected on ${page}`
)
}
// TODO: add handling for statically rendering edge
// pages and allow edge with Prerender outputs
workerResult.isStatic = false
workerResult.hasStaticProps = false
}

if (config.outputFileTracing) {
pageTraceIncludes.set(
page,
Expand Down
56 changes: 45 additions & 11 deletions packages/next/build/utils.ts
Expand Up @@ -43,6 +43,7 @@ import { MiddlewareManifest } from './webpack/plugins/middleware-plugin'
import { denormalizePagePath } from '../shared/lib/page-path/denormalize-page-path'
import { normalizePagePath } from '../shared/lib/page-path/normalize-page-path'
import { AppBuildManifest } from './webpack/plugins/app-build-manifest-plugin'
import { getRuntimeContext } from '../server/web/sandbox'

export type ROUTER_TYPE = 'pages' | 'app'

Expand Down Expand Up @@ -1008,17 +1009,31 @@ export async function buildStaticPaths(
}
}

export async function isPageStatic(
page: string,
distDir: string,
serverless: boolean,
configFileName: string,
runtimeEnvConfig: any,
httpAgentOptions: NextConfigComplete['httpAgentOptions'],
locales?: string[],
defaultLocale?: string,
export async function isPageStatic({
page,
distDir,
serverless,
configFileName,
runtimeEnvConfig,
httpAgentOptions,
locales,
defaultLocale,
parentId,
pageRuntime,
edgeInfo,
}: {
page: string
distDir: string
serverless: boolean
configFileName: string
runtimeEnvConfig: any
httpAgentOptions: NextConfigComplete['httpAgentOptions']
locales?: string[]
defaultLocale?: string
parentId?: any
): Promise<{
edgeInfo?: any
pageRuntime: ServerRuntime
}): Promise<{
isStatic?: boolean
isAmpOnly?: boolean
isHybridAmp?: boolean
Expand All @@ -1037,7 +1052,26 @@ export async function isPageStatic(
require('../shared/lib/runtime-config').setConfig(runtimeEnvConfig)
setHttpAgentOptions(httpAgentOptions)

const mod = await loadComponents(distDir, page, serverless)
let mod: any

if (pageRuntime === SERVER_RUNTIME.edge) {
const runtime = await getRuntimeContext({
paths: edgeInfo.files.map((file: string) => path.join(distDir, file)),
env: edgeInfo.env,
edgeFunctionEntry: edgeInfo,
name: edgeInfo.name,
useCache: true,
distDir,
})
mod =
runtime.context._ENTRIES[`middleware_${edgeInfo.name}`].ComponentMod

mod.Component = mod.default
mod.ComponentMod = mod
ijjk marked this conversation as resolved.
Show resolved Hide resolved
mod.pageConfig = mod.config || {}
} else {
mod = await loadComponents(distDir, page, serverless)
}
const Comp = mod.Component

if (!Comp || !isValidElementType(Comp) || typeof Comp === 'string') {
Expand Down
Expand Up @@ -112,6 +112,8 @@ export default async function edgeSSRLoader(this: any) {
config: ${stringifiedConfig},
buildId: ${JSON.stringify(buildId)},
})

export const ComponentMod = pageMod

export default function(opts) {
return adapter({
Expand Down
Expand Up @@ -68,6 +68,45 @@ export type ServerlessHandlerCtx = {
i18n?: NextConfig['i18n']
}

export function interpolateDynamicPath(
pathname: string,
params: ParsedUrlQuery,
defaultRouteRegex?: ReturnType<typeof getNamedRouteRegex> | undefined
) {
if (!defaultRouteRegex) return pathname

for (const param of Object.keys(defaultRouteRegex.groups)) {
const { optional, repeat } = defaultRouteRegex.groups[param]
let builtParam = `[${repeat ? '...' : ''}${param}]`

if (optional) {
builtParam = `[${builtParam}]`
}

const paramIdx = pathname!.indexOf(builtParam)

if (paramIdx > -1) {
let paramValue: string

if (Array.isArray(params[param])) {
paramValue = (params[param] as string[])
.map((v) => v && encodeURIComponent(v))
.join('/')
} else {
paramValue =
params[param] && encodeURIComponent(params[param] as string)
}

pathname =
pathname.slice(0, paramIdx) +
(paramValue || '') +
pathname.slice(paramIdx + builtParam.length)
}
}

return pathname
}

export function getUtils({
page,
i18n,
Expand Down Expand Up @@ -297,41 +336,6 @@ export function getUtils({
)(req.headers['x-now-route-matches'] as string) as ParsedUrlQuery
}

function interpolateDynamicPath(pathname: string, params: ParsedUrlQuery) {
if (!defaultRouteRegex) return pathname

for (const param of Object.keys(defaultRouteRegex.groups)) {
const { optional, repeat } = defaultRouteRegex.groups[param]
let builtParam = `[${repeat ? '...' : ''}${param}]`

if (optional) {
builtParam = `[${builtParam}]`
}

const paramIdx = pathname!.indexOf(builtParam)

if (paramIdx > -1) {
let paramValue: string

if (Array.isArray(params[param])) {
paramValue = (params[param] as string[])
.map((v) => v && encodeURIComponent(v))
.join('/')
} else {
paramValue =
params[param] && encodeURIComponent(params[param] as string)
}

pathname =
pathname.slice(0, paramIdx) +
(paramValue || '') +
pathname.slice(paramIdx + builtParam.length)
}
}

return pathname
}

function normalizeVercelUrl(
req: BaseNextRequest | IncomingMessage,
trustQuery: boolean,
Expand Down Expand Up @@ -570,8 +574,11 @@ export function getUtils({
normalizeVercelUrl,
dynamicRouteMatcher,
defaultRouteMatches,
interpolateDynamicPath,
getParamsFromRouteMatches,
normalizeDynamicRouteParams,
interpolateDynamicPath: (
pathname: string,
params: Record<string, string | string[]>
) => interpolateDynamicPath(pathname, params, defaultRouteRegex),
}
}
3 changes: 2 additions & 1 deletion packages/next/server/base-server.ts
Expand Up @@ -946,7 +946,8 @@ export default abstract class Server<ServerOptions extends Options = Options> {

// Toggle whether or not this is a Data request
const isDataReq =
!!query.__nextDataReq && (isSSG || hasServerProps || isServerComponent)
!!(query.__nextDataReq || req.headers['x-nextjs-data']) &&
(isSSG || hasServerProps || isServerComponent)

delete query.__nextDataReq

Expand Down
29 changes: 28 additions & 1 deletion packages/next/server/next-server.ts
Expand Up @@ -96,6 +96,8 @@ import { checkIsManualRevalidate } from './api-utils'
import { shouldUseReactRoot, isTargetLikeServerless } from './utils'
import ResponseCache from './response-cache'
import { IncrementalCache } from './lib/incremental-cache'
import { interpolateDynamicPath } from '../build/webpack/loaders/next-serverless-loader/utils'
import { getNamedRouteRegex } from '../shared/lib/router/utils/route-regex'

if (shouldUseReactRoot) {
;(process.env as any).__NEXT_REACT_ROOT = 'true'
Expand Down Expand Up @@ -1951,7 +1953,32 @@ export default class NextNodeServer extends BaseServer {
}

// For middleware to "fetch" we must always provide an absolute URL
const url = getRequestMeta(params.req, '__NEXT_INIT_URL')!
const isDataReq = !!params.query.__nextDataReq
const query = urlQueryToSearchParams(
Object.assign({}, getRequestMeta(params.req, '__NEXT_INIT_QUERY') || {})
).toString()
const locale = params.query.__nextLocale
let normalizedPathname = params.page

if (isDataReq) {
params.req.headers['x-nextjs-data'] = '1'
}

if (isDynamicRoute(normalizedPathname)) {
const routeRegex = getNamedRouteRegex(params.page)
normalizedPathname = interpolateDynamicPath(
params.page,
Object.assign({}, params.params, params.query),
routeRegex
)
}

const url = `${getRequestMeta(params.req, '_protocol')}://${
this.hostname
}:${this.port}${locale ? `/${locale}` : ''}${normalizedPathname}${
query ? `?${query}` : ''
}`

if (!url.startsWith('http')) {
throw new Error(
'To use middleware you must provide a `hostname` and `port` to the Next.js Server'
Expand Down
6 changes: 2 additions & 4 deletions packages/next/server/web-server.ts
Expand Up @@ -66,7 +66,6 @@ export default class NextWebServer extends BaseServer<WebServerOptions> {
res: BaseNextResponse,
parsedUrl: UrlWithParsedQuery
): Promise<void> {
parsedUrl.pathname = this.serverOptions.webServerConfig.page
super.run(req, res, parsedUrl)
}
protected async hasPage(page: string) {
Expand Down Expand Up @@ -343,11 +342,10 @@ export default class NextWebServer extends BaseServer<WebServerOptions> {
{} as any,
pathname,
query,
{
...renderOpts,
Object.assign(renderOpts, {
disableOptimizedLoading: true,
runtime: 'experimental-edge',
},
}),
!!pagesRenderToHTML
)
} else {
Expand Down
15 changes: 14 additions & 1 deletion packages/next/server/web/sandbox/sandbox.ts
Expand Up @@ -3,6 +3,7 @@ import { getServerError } from 'next/dist/compiled/@next/react-dev-overlay/dist/
import { getModuleContext } from './context'
import { EdgeFunctionDefinition } from '../../../build/webpack/plugins/middleware-plugin'
import { requestToBodyStream } from '../../body-streams'
import type { EdgeRuntime } from 'next/dist/compiled/edge-runtime'

export const ErrorSource = Symbol('SandboxError')

Expand Down Expand Up @@ -43,7 +44,15 @@ function withTaggedErrors(fn: RunnerFn): RunnerFn {
})
}

export const run = withTaggedErrors(async (params) => {
export const getRuntimeContext = async (params: {
name: string
onWarning?: any
useCache: boolean
env: string[]
edgeFunctionEntry: any
distDir: string
paths: string[]
}): Promise<EdgeRuntime<any>> => {
const { runtime, evaluateInContext } = await getModuleContext({
moduleName: params.name,
onWarning: params.onWarning ?? (() => {}),
Expand All @@ -56,7 +65,11 @@ export const run = withTaggedErrors(async (params) => {
for (const paramPath of params.paths) {
evaluateInContext(paramPath)
}
return runtime
}

export const run = withTaggedErrors(async (params) => {
const runtime = await getRuntimeContext(params)
const subreq = params.request.headers[`x-middleware-subrequest`]
const subrequests = typeof subreq === 'string' ? subreq.split(':') : []
if (subrequests.includes(params.name)) {
Expand Down