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

Add handling for static generation in app #40561

Merged
merged 9 commits into from Sep 19, 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
230 changes: 186 additions & 44 deletions packages/next/build/index.ts
Expand Up @@ -97,6 +97,7 @@ import {
printTreeView,
copyTracedFiles,
isReservedPage,
AppConfig,
} from './utils'
import getBaseWebpackConfig from './webpack-config'
import { PagesManifest } from './webpack/plugins/pages-manifest-plugin'
Expand Down Expand Up @@ -1065,6 +1066,11 @@ export default async function build(
const serverPropsPages = new Set<string>()
const additionalSsgPaths = new Map<string, Array<string>>()
const additionalSsgPathsEncoded = new Map<string, Array<string>>()
const appStaticPaths = new Map<string, Array<string>>()
const appStaticPathsEncoded = new Map<string, Array<string>>()
const appNormalizedPaths = new Map<string, string>()
const appDynamicParamPaths = new Set<string>()
const appDefaultConfigs = new Map<string, AppConfig>()
const pageTraceIncludes = new Map<string, Array<string>>()
const pageTraceExcludes = new Map<string, Array<string>>()
const pageInfos = new Map<string, PageInfo>()
Expand All @@ -1087,6 +1093,26 @@ export default async function build(
: require.resolve('./utils')
let infoPrinted = false

let appPathsManifest: Record<string, string> = {}
const appPathRoutes: Record<string, string> = {}

if (appDir) {
appPathsManifest = JSON.parse(
await promises.readFile(
path.join(distDir, serverDir, APP_PATHS_MANIFEST),
'utf8'
)
)

Object.keys(appPathsManifest).forEach((entry) => {
appPathRoutes[entry] = normalizeAppPath(entry) || '/'
})
await promises.writeFile(
path.join(distDir, APP_PATH_ROUTES_MANIFEST),
JSON.stringify(appPathRoutes, null, 2)
)
}

process.env.NEXT_PHASE = PHASE_PRODUCTION_BUILD

const staticWorkers = new Worker(staticWorker, {
Expand Down Expand Up @@ -1255,33 +1281,49 @@ export default async function build(
let isHybridAmp = false
let ssgPageRoutes: string[] | null = null

const pagePath =
pageType === 'pages'
? pagesPaths.find(
(p) =>
p.startsWith(actualPage + '.') ||
p.startsWith(actualPage + '/index.')
let pagePath = ''

if (pageType === 'pages') {
pagePath =
pagesPaths.find(
(p) =>
p.startsWith(actualPage + '.') ||
p.startsWith(actualPage + '/index.')
) || ''
}
let originalAppPath: string | undefined

if (pageType === 'app' && mappedAppPages) {
for (const [originalPath, normalizedPath] of Object.entries(
appPathRoutes
)) {
if (normalizedPath === page) {
pagePath = mappedAppPages[originalPath].replace(
/^private-next-app-dir/,
''
)
: appPaths?.find((p) => p.startsWith(actualPage + '/page.'))
originalAppPath = originalPath
break
}
}
}

const staticInfo =
pagesDir && pageType === 'pages' && pagePath
? await getPageStaticInfo({
pageFilePath: join(pagesDir, pagePath),
nextConfig: config,
})
: {}
const pageRuntime = staticInfo.runtime
const staticInfo = pagePath
? await getPageStaticInfo({
pageFilePath: join(
(pageType === 'pages' ? pagesDir : appDir) || '',
pagePath
),
nextConfig: config,
})
: undefined

const pageRuntime = staticInfo?.runtime
isServerComponent =
pageType === 'app' &&
staticInfo.rsc !== RSC_MODULE_TYPES.client
staticInfo?.rsc !== RSC_MODULE_TYPES.client

if (
// Only calculate page static information if the page is not an
// app page.
pageType !== 'app' &&
!isReservedPage(page)
) {
if (!isReservedPage(page)) {
try {
let edgeInfo: any

Expand All @@ -1291,8 +1333,10 @@ export default async function build(
serverDir,
MIDDLEWARE_MANIFEST
))
const manifestKey =
pageType === 'pages' ? page : join(page, 'page')

edgeInfo = manifest.functions[page]
edgeInfo = manifest.functions[manifestKey]
}

let isPageStaticSpan =
Expand All @@ -1301,6 +1345,7 @@ export default async function build(
() => {
return staticWorkers.isPageStatic({
page,
originalAppPath,
distDir,
serverless: isLikeServerless,
configFileName,
Expand All @@ -1311,10 +1356,50 @@ export default async function build(
parentId: isPageStaticSpan.id,
pageRuntime,
edgeInfo,
pageType,
hasServerComponents,
})
}
)

if (pageType === 'app' && originalAppPath) {
appNormalizedPaths.set(originalAppPath, page)

// TODO-APP: handle prerendering with edge
// runtime
if (pageRuntime === 'experimental-edge') {
return
}

if (
workerResult.encodedPrerenderRoutes &&
workerResult.prerenderRoutes
) {
appStaticPaths.set(
originalAppPath,
workerResult.prerenderRoutes
)
appStaticPathsEncoded.set(
originalAppPath,
workerResult.encodedPrerenderRoutes
)
}
if (!isDynamicRoute(page)) {
appStaticPaths.set(originalAppPath, [page])
appStaticPathsEncoded.set(originalAppPath, [page])
}
if (workerResult.prerenderFallback) {
// whether or not to allow requests for paths not
// returned from generateStaticParams
appDynamicParamPaths.add(originalAppPath)
}
appDefaultConfigs.set(
originalAppPath,
workerResult.appConfig || {}
)
return
}

if (pageRuntime === SERVER_RUNTIME.edge) {
if (workerResult.hasStaticProps) {
console.warn(
Expand Down Expand Up @@ -1821,24 +1906,6 @@ export default async function build(
'utf8'
)

if (appDir) {
const appPathsManifest = JSON.parse(
await promises.readFile(
path.join(distDir, serverDir, APP_PATHS_MANIFEST),
'utf8'
)
)
const appPathRoutes: Record<string, string> = {}

Object.keys(appPathsManifest).forEach((entry) => {
appPathRoutes[entry] = normalizeAppPath(entry) || '/'
})
await promises.writeFile(
path.join(distDir, APP_PATH_ROUTES_MANIFEST),
JSON.stringify(appPathRoutes, null, 2)
)
}

const middlewareManifest: MiddlewareManifest = JSON.parse(
await promises.readFile(
path.join(distDir, serverDir, MIDDLEWARE_MANIFEST),
Expand All @@ -1865,6 +1932,7 @@ export default async function build(
}

const finalPrerenderRoutes: { [route: string]: SsgRoute } = {}
const finalDynamicRoutes: PrerenderManifest['dynamicRoutes'] = {}
const tbdPrerenderRoutes: string[] = []
let ssgNotFoundPaths: string[] = []

Expand All @@ -1889,7 +1957,16 @@ export default async function build(

const combinedPages = [...staticPages, ...ssgPages]

if (combinedPages.length > 0 || useStatic404 || useDefaultStatic500) {
// we need to trigger automatic exporting when we have
// - static 404/500
// - getStaticProps paths
// - experimental app is enabled
if (
combinedPages.length > 0 ||
useStatic404 ||
useDefaultStatic500 ||
config.experimental.appDir
) {
const staticGenerationSpan =
nextBuildSpan.traceChild('static-generation')
await staticGenerationSpan.traceAsyncFn(async () => {
Expand Down Expand Up @@ -1986,6 +2063,20 @@ export default async function build(
}
}

// TODO: output manifest specific to app paths and their
// revalidate periods and dynamicParams settings
appStaticPaths.forEach((routes, originalAppPath) => {
const encodedRoutes = appStaticPathsEncoded.get(originalAppPath)

routes.forEach((route, routeIdx) => {
defaultMap[route] = {
page: originalAppPath,
query: { __nextSsgPath: encodedRoutes?.[routeIdx] },
_isAppDir: true,
}
})
})

if (i18n) {
for (const page of [
...staticPages,
Expand Down Expand Up @@ -2035,6 +2126,58 @@ export default async function build(
await promises.unlink(serverBundle)
}

for (const [originalAppPath, routes] of appStaticPaths) {
const page = appNormalizedPaths.get(originalAppPath) || ''
const appConfig = appDefaultConfigs.get(originalAppPath) || {}
let hasDynamicData = appConfig.revalidate === 0

routes.forEach((route) => {
let revalidate = exportConfig.initialPageRevalidationMap[route]

if (typeof revalidate === 'undefined') {
revalidate =
typeof appConfig.revalidate !== 'undefined'
? appConfig.revalidate
: false
}
if (revalidate !== 0) {
const normalizedRoute = normalizePagePath(route)
const dataRoute = path.posix.join(`${normalizedRoute}.rsc`)
finalPrerenderRoutes[route] = {
initialRevalidateSeconds: revalidate,
srcRoute: page,
dataRoute,
}
} else {
hasDynamicData = true
}
})

if (!hasDynamicData && isDynamicRoute(originalAppPath)) {
const normalizedRoute = normalizePagePath(page)
const dataRoute = path.posix.join(`${normalizedRoute}.rsc`)

// TODO: create a separate manifest to allow enforcing
// dynamicParams for non-static paths?
finalDynamicRoutes[page] = {
routeRegex: normalizeRouteRegex(
getNamedRouteRegex(page).re.source
),
dataRoute,
// if dynamicParams are enabled treat as fallback:
// 'blocking' if not it's fallback: false
fallback: appDynamicParamPaths.has(originalAppPath)
? null
: false,
dataRouteRegex: normalizeRouteRegex(
getNamedRouteRegex(
dataRoute.replace(/\.rsc$/, '')
).re.source.replace(/\(\?:\\\/\)\?\$$/, '\\.rsc$')
),
}
}
}

const moveExportedPage = async (
originPage: string,
page: string,
Expand Down Expand Up @@ -2347,8 +2490,7 @@ export default async function build(
telemetry.record(eventPackageUsedInGetServerSideProps(telemetryPlugin))
}

if (ssgPages.size > 0) {
const finalDynamicRoutes: PrerenderManifest['dynamicRoutes'] = {}
if (ssgPages.size > 0 || appDir) {
tbdPrerenderRoutes.forEach((tbdRoute) => {
const normalizedRoute = normalizePagePath(tbdRoute)
const dataRoute = path.posix.join(
Expand Down