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 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
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
89 changes: 67 additions & 22 deletions packages/next/build/utils.ts
Expand Up @@ -34,7 +34,10 @@ import { removeTrailingSlash } from '../shared/lib/router/utils/remove-trailing-
import { UnwrapPromise } from '../lib/coalesced-function'
import { normalizeLocalePath } from '../shared/lib/i18n/normalize-locale-path'
import * as Log from './output/log'
import { loadComponents } from '../server/load-components'
import {
loadComponents,
LoadComponentsReturnType,
} from '../server/load-components'
import { trace } from '../trace'
import { setHttpAgentOptions } from '../server/config'
import { recursiveDelete } from '../lib/recursive-delete'
Expand All @@ -43,6 +46,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 +1012,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,24 +1055,51 @@ export async function isPageStatic(
require('../shared/lib/runtime-config').setConfig(runtimeEnvConfig)
setHttpAgentOptions(httpAgentOptions)

const mod = await loadComponents(distDir, page, serverless)
const Comp = mod.Component
let componentsResult: LoadComponentsReturnType

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,
})
const mod =
runtime.context._ENTRIES[`middleware_${edgeInfo.name}`].ComponentMod

componentsResult = {
Component: mod.default,
ComponentMod: mod,
pageConfig: mod.config || {},
// @ts-expect-error this is not needed during require
buildManifest: {},
reactLoadableManifest: {},
getServerSideProps: mod.getServerSideProps,
getStaticPaths: mod.getStaticPaths,
getStaticProps: mod.getStaticProps,
}
} else {
componentsResult = await loadComponents(distDir, page, serverless)
}
const Comp = componentsResult.Component

if (!Comp || !isValidElementType(Comp) || typeof Comp === 'string') {
throw new Error('INVALID_DEFAULT_EXPORT')
}

const hasGetInitialProps = !!(Comp as any).getInitialProps
const hasStaticProps = !!mod.getStaticProps
const hasStaticPaths = !!mod.getStaticPaths
const hasServerProps = !!mod.getServerSideProps
const hasLegacyServerProps = !!(await mod.ComponentMod
const hasStaticProps = !!componentsResult.getStaticProps
const hasStaticPaths = !!componentsResult.getStaticPaths
const hasServerProps = !!componentsResult.getServerSideProps
const hasLegacyServerProps = !!(await componentsResult.ComponentMod
.unstable_getServerProps)
const hasLegacyStaticProps = !!(await mod.ComponentMod
const hasLegacyStaticProps = !!(await componentsResult.ComponentMod
.unstable_getStaticProps)
const hasLegacyStaticPaths = !!(await mod.ComponentMod
const hasLegacyStaticPaths = !!(await componentsResult.ComponentMod
.unstable_getStaticPaths)
const hasLegacyStaticParams = !!(await mod.ComponentMod
const hasLegacyStaticParams = !!(await componentsResult.ComponentMod
.unstable_getStaticParams)

if (hasLegacyStaticParams) {
Expand Down Expand Up @@ -1121,15 +1166,15 @@ export async function isPageStatic(
encodedPaths: encodedPrerenderRoutes,
} = await buildStaticPaths(
page,
mod.getStaticPaths!,
componentsResult.getStaticPaths!,
configFileName,
locales,
defaultLocale
))
}

const isNextImageImported = (global as any).__NEXT_IMAGE_IMPORTED
const config: PageConfig = mod.pageConfig
const config: PageConfig = componentsResult.pageConfig
return {
isStatic: !hasStaticProps && !hasGetInitialProps && !hasServerProps,
isHybridAmp: config.amp === 'hybrid',
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