Skip to content

Commit

Permalink
Add handling for static generation in app (#40561)
Browse files Browse the repository at this point in the history
  • Loading branch information
ijjk committed Sep 19, 2022
1 parent 4a53582 commit aed2dc0
Show file tree
Hide file tree
Showing 86 changed files with 1,805 additions and 804 deletions.
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

0 comments on commit aed2dc0

Please sign in to comment.