Skip to content

Commit

Permalink
Merge branch 'canary' into fix/hmr-ping
Browse files Browse the repository at this point in the history
  • Loading branch information
ijjk authored Aug 23, 2023
2 parents d0bf1ca + fb3f045 commit 97a316c
Show file tree
Hide file tree
Showing 14 changed files with 394 additions and 201 deletions.
21 changes: 21 additions & 0 deletions packages/next/src/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ import { flatReaddir } from '../lib/flat-readdir'
import { eventSwcPlugins } from '../telemetry/events/swc-plugins'
import { normalizeAppPath } from '../shared/lib/router/utils/app-paths'
import {
NEXT_ROUTER_PREFETCH,
RSC,
RSC_CONTENT_TYPE_HEADER,
RSC_VARY_HEADER,
Expand Down Expand Up @@ -223,6 +224,7 @@ export type RoutesManifest = {
rsc: {
header: typeof RSC
varyHeader: typeof RSC_VARY_HEADER
prefetchHeader: typeof NEXT_ROUTER_PREFETCH
}
skipMiddlewareUrlNormalize?: boolean
caseSensitive?: boolean
Expand Down Expand Up @@ -782,6 +784,7 @@ export default async function build(
rsc: {
header: RSC,
varyHeader: RSC_VARY_HEADER,
prefetchHeader: NEXT_ROUTER_PREFETCH,
contentTypeHeader: RSC_CONTENT_TYPE_HEADER,
},
skipMiddlewareUrlNormalize: config.skipMiddlewareUrlNormalize,
Expand Down Expand Up @@ -1135,6 +1138,7 @@ export default async function build(
const additionalSsgPaths = new Map<string, Array<string>>()
const additionalSsgPathsEncoded = new Map<string, Array<string>>()
const appStaticPaths = new Map<string, Array<string>>()
const appPrefetchPaths = new Map<string, string>()
const appStaticPathsEncoded = new Map<string, Array<string>>()
const appNormalizedPaths = new Map<string, string>()
const appDynamicParamPaths = new Set<string>()
Expand Down Expand Up @@ -1653,6 +1657,14 @@ export default async function build(
appDynamicParamPaths.add(originalAppPath)
}
appDefaultConfigs.set(originalAppPath, appConfig)

if (
!isStatic &&
!isAppRouteRoute(originalAppPath) &&
!isDynamicRoute(originalAppPath)
) {
appPrefetchPaths.set(originalAppPath, page)
}
}
} else {
if (isEdgeRuntime(pageRuntime)) {
Expand Down Expand Up @@ -2499,6 +2511,15 @@ export default async function build(
})
})

for (const [originalAppPath, page] of appPrefetchPaths) {
defaultMap[page] = {
page: originalAppPath,
query: {},
_isAppDir: true,
_isAppPrefetch: true,
}
}

if (i18n) {
for (const page of [
...staticPages,
Expand Down
50 changes: 47 additions & 3 deletions packages/next/src/export/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ import {
signalFromNodeResponse,
} from '../server/web/spec-extension/adapters/next-request'
import * as ciEnvironment from '../telemetry/ci-info'
import {
NEXT_ROUTER_PREFETCH,
NEXT_URL,
RSC,
} from '../client/components/app-router-headers'

const envConfig = require('../shared/lib/runtime-config')

Expand Down Expand Up @@ -164,6 +169,7 @@ export default async function exportPage({
const { page } = pathMap
const pathname = normalizeAppPath(page)
const isAppDir = Boolean(pathMap._isAppDir)
const isAppPrefetch = Boolean(pathMap._isAppPrefetch)
const isDynamicError = pathMap._isDynamicError
const filePath = normalizePagePath(path)
const isDynamic = isDynamicRoute(page)
Expand Down Expand Up @@ -387,7 +393,45 @@ export default async function exportPage({
err.digest === NEXT_DYNAMIC_NO_SSR_CODE ||
isRedirectError(err)

if (isRouteHandler) {
const isNotFoundPage = page === '/_not-found'

const generatePrefetchRsc = async () => {
// If we bail for prerendering due to dynamic usage we need to
// generate a static prefetch payload to prevent invoking
// functions during runtime just for prefetching

const { renderToHTMLOrFlight } =
require('../server/app-render/app-render') as typeof import('../server/app-render/app-render')
req.headers[RSC.toLowerCase()] = '1'
req.headers[NEXT_URL.toLowerCase()] = path
req.headers[NEXT_ROUTER_PREFETCH.toLowerCase()] = '1'

curRenderOpts.supportsDynamicHTML = true
delete (curRenderOpts as any).isRevalidate

const prefetchRenderResult = await renderToHTMLOrFlight(
req as any,
res as any,
isNotFoundPage ? '/404' : pathname,
query,
curRenderOpts as any
)
prefetchRenderResult.pipe(res as import('http').ServerResponse)
await res.hasStreamed
const prefetchRscData = Buffer.concat(res.buffers).toString()

await promises.writeFile(
htmlFilepath.replace(/\.html$/, '.prefetch.rsc'),
prefetchRscData
)
}

// for dynamic routes with no generate static params
// we generate strictly the prefetch RSC payload to
// avoid attempting to render with default params e.g. [slug]
if (isAppPrefetch) {
await generatePrefetchRsc()
} else if (isRouteHandler) {
// Ensure that the url for the page is absolute.
req.url = `http://localhost:3000${req.url}`
const request = NextRequestAdapter.fromNodeNextRequest(
Expand Down Expand Up @@ -493,8 +537,6 @@ export default async function exportPage({

try {
curRenderOpts.params ||= {}

const isNotFoundPage = page === '/_not-found'
const result = await renderToHTMLOrFlight(
req as any,
res as any,
Expand Down Expand Up @@ -537,6 +579,8 @@ export default async function exportPage({
throw new Error(
`Page with dynamic = "error" encountered dynamic data method on ${path}.`
)
} else {
await generatePrefetchRsc()
}

const staticBailoutInfo = metadata.staticBailoutInfo || {}
Expand Down
31 changes: 31 additions & 0 deletions packages/next/src/server/base-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ import {
RSC_VARY_HEADER,
FLIGHT_PARAMETERS,
NEXT_RSC_UNION_QUERY,
NEXT_ROUTER_PREFETCH,
RSC_CONTENT_TYPE_HEADER,
} from '../client/components/app-router-headers'
import {
MatchOptions,
Expand Down Expand Up @@ -324,6 +326,10 @@ export default abstract class Server<ServerOptions extends Options = Options> {
renderOpts: RenderOpts
): Promise<RenderResult>

protected async getPrefetchRsc(_pathname: string): Promise<string | null> {
return null
}

protected abstract getIncrementalCache(options: {
requestHeaders: Record<string, undefined | string | string[]>
requestProtocol: 'http' | 'https'
Expand Down Expand Up @@ -1935,6 +1941,31 @@ export default abstract class Server<ServerOptions extends Options = Options> {
} else if (
components.routeModule?.definition.kind === RouteKind.APP_PAGE
) {
const isAppPrefetch = req.headers[NEXT_ROUTER_PREFETCH.toLowerCase()]

if (
isAppPrefetch &&
ssgCacheKey &&
process.env.NODE_ENV === 'production'
) {
try {
const prefetchRsc = await this.getPrefetchRsc(ssgCacheKey)

if (prefetchRsc) {
res.setHeader(
'cache-control',
'private, no-cache, no-store, max-age=0, must-revalidate'
)
res.setHeader('content-type', RSC_CONTENT_TYPE_HEADER)
res.body(prefetchRsc).send()
return null
}
} catch (_) {
// we fallback to invoking the function if prefetch
// data is not available
}
}

const module = components.routeModule as AppPageRouteModule

// Due to the way we pass data by mutating `renderOpts`, we can't extend the
Expand Down
1 change: 1 addition & 0 deletions packages/next/src/server/config-shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ export type ExportPathMap = {
page: string
query?: NextParsedUrlQuery
_isAppDir?: boolean
_isAppPrefetch?: boolean
_isDynamicError?: boolean
}
}
Expand Down
6 changes: 6 additions & 0 deletions packages/next/src/server/next-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1045,6 +1045,12 @@ export default class NextNodeServer extends BaseServer {
return this.runApi(req, res, query, match)
}

protected async getPrefetchRsc(pathname: string) {
return this.getCacheFilesystem()
.readFile(join(this.serverDistDir, 'app', `${pathname}.prefetch.rsc`))
.then((res) => res.toString())
}

protected getCacheFilesystem(): CacheFs {
return nodeFs
}
Expand Down
Loading

0 comments on commit 97a316c

Please sign in to comment.