Skip to content

Commit

Permalink
Separate routing code from render servers (vercel#52492)
Browse files Browse the repository at this point in the history
This breaks out routing handling from `next-server`, `next-dev-server`,
and `base-server` so that these are only handling the "render" work and
eventually these will be deleted completely in favor of the bundling
work being done.

The `router` process and separate `render` processes are still
maintained here although will be investigated further in follow-up to
see if we can reduce the need for these.

We are also changing the `require-cache` IPC to a single call instead of
call per entry to reduce overhead and also de-dupes handling for
starting the server between the standalone server and normal server.

To maintain support for existing turbopack route resolving this
implements the new route resolving in place of the existing
`route-resolver` until the new nextturbo API is fully landed.

After these initial changes we should continue to eliminate non-render
related code from `next-server`, `base-server`, and `next-dev-server`.
  • Loading branch information
ijjk authored Jul 21, 2023
1 parent 04ae5be commit f57eecd
Show file tree
Hide file tree
Showing 59 changed files with 5,218 additions and 4,693 deletions.
238 changes: 110 additions & 128 deletions packages/next/src/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import os from 'os'
import { Worker } from '../lib/worker'
import { defaultConfig } from '../server/config-shared'
import devalue from 'next/dist/compiled/devalue'
import { escapeStringRegexp } from '../shared/lib/escape-regexp'
import findUp from 'next/dist/compiled/find-up'
import { nanoid } from 'next/dist/compiled/nanoid/index.cjs'
import { pathToRegexp } from 'next/dist/compiled/path-to-regexp'
Expand Down Expand Up @@ -141,7 +140,12 @@ import { createClientRouterFilter } from '../lib/create-client-router-filter'
import { createValidFileMatcher } from '../server/lib/find-page-file'
import { startTypeChecking } from './type-check'
import { generateInterceptionRoutesRewrites } from '../lib/generate-interception-routes-rewrites'
import { baseOverrides, experimentalOverrides } from '../server/require-hook'
import { buildDataRoute } from '../server/lib/router-utils/build-data-route'
import {
baseOverrides,
defaultOverrides,
experimentalOverrides,
} from '../server/require-hook'

export type SsgRoute = {
initialRevalidateSeconds: number | false
Expand All @@ -166,6 +170,66 @@ export type PrerenderManifest = {
preview: __ApiPreviewProps
}

type CustomRoute = {
regex: string
statusCode?: number | undefined
permanent?: undefined
source: string
locale?: false | undefined
basePath?: false | undefined
destination?: string | undefined
}

export type RoutesManifest = {
version: number
pages404: boolean
basePath: string
redirects: Array<CustomRoute>
rewrites?:
| Array<CustomRoute>
| {
beforeFiles: Array<CustomRoute>
afterFiles: Array<CustomRoute>
fallback: Array<CustomRoute>
}
headers: Array<CustomRoute>
staticRoutes: Array<{
page: string
regex: string
namedRegex?: string
routeKeys?: { [key: string]: string }
}>
dynamicRoutes: Array<{
page: string
regex: string
namedRegex?: string
routeKeys?: { [key: string]: string }
}>
dataRoutes: Array<{
page: string
routeKeys?: { [key: string]: string }
dataRouteRegex: string
namedDataRouteRegex?: string
}>
i18n?: {
domains?: Array<{
http?: true
domain: string
locales?: string[]
defaultLocale: string
}>
locales: string[]
defaultLocale: string
localeDetection?: false
}
rsc: {
header: typeof RSC
varyHeader: typeof RSC_VARY_HEADER
}
skipMiddlewareUrlNormalize?: boolean
caseSensitive?: boolean
}

async function generateClientSsgManifest(
prerenderManifest: PrerenderManifest,
{
Expand Down Expand Up @@ -684,89 +748,45 @@ export default async function build(
}

const routesManifestPath = path.join(distDir, ROUTES_MANIFEST)
const routesManifest: {
version: number
pages404: boolean
basePath: string
redirects: Array<ReturnType<typeof buildCustomRoute>>
rewrites?:
| Array<ReturnType<typeof buildCustomRoute>>
| {
beforeFiles: Array<ReturnType<typeof buildCustomRoute>>
afterFiles: Array<ReturnType<typeof buildCustomRoute>>
fallback: Array<ReturnType<typeof buildCustomRoute>>
const routesManifest: RoutesManifest = nextBuildSpan
.traceChild('generate-routes-manifest')
.traceFn(() => {
const sortedRoutes = getSortedRoutes([
...pageKeys.pages,
...(pageKeys.app ?? []),
])
const dynamicRoutes: Array<ReturnType<typeof pageToRoute>> = []
const staticRoutes: typeof dynamicRoutes = []

for (const route of sortedRoutes) {
if (isDynamicRoute(route)) {
dynamicRoutes.push(pageToRoute(route))
} else if (!isReservedPage(route)) {
staticRoutes.push(pageToRoute(route))
}
headers: Array<ReturnType<typeof buildCustomRoute>>
staticRoutes: Array<{
page: string
regex: string
namedRegex?: string
routeKeys?: { [key: string]: string }
}>
dynamicRoutes: Array<{
page: string
regex: string
namedRegex?: string
routeKeys?: { [key: string]: string }
}>
dataRoutes: Array<{
page: string
routeKeys?: { [key: string]: string }
dataRouteRegex: string
namedDataRouteRegex?: string
}>
i18n?: {
domains?: Array<{
http?: true
domain: string
locales?: string[]
defaultLocale: string
}>
locales: string[]
defaultLocale: string
localeDetection?: false
}
rsc: {
header: typeof RSC
varyHeader: typeof RSC_VARY_HEADER
}
skipMiddlewareUrlNormalize?: boolean
caseSensitive?: boolean
} = nextBuildSpan.traceChild('generate-routes-manifest').traceFn(() => {
const sortedRoutes = getSortedRoutes([
...pageKeys.pages,
...(pageKeys.app ?? []),
])
const dynamicRoutes: Array<ReturnType<typeof pageToRoute>> = []
const staticRoutes: typeof dynamicRoutes = []

for (const route of sortedRoutes) {
if (isDynamicRoute(route)) {
dynamicRoutes.push(pageToRoute(route))
} else if (!isReservedPage(route)) {
staticRoutes.push(pageToRoute(route))
}
}

return {
version: 3,
pages404: true,
caseSensitive: !!config.experimental.caseSensitiveRoutes,
basePath: config.basePath,
redirects: redirects.map((r: any) => buildCustomRoute(r, 'redirect')),
headers: headers.map((r: any) => buildCustomRoute(r, 'header')),
dynamicRoutes,
staticRoutes,
dataRoutes: [],
i18n: config.i18n || undefined,
rsc: {
header: RSC,
varyHeader: RSC_VARY_HEADER,
contentTypeHeader: RSC_CONTENT_TYPE_HEADER,
},
skipMiddlewareUrlNormalize: config.skipMiddlewareUrlNormalize,
}
})
return {
version: 3,
pages404: true,
caseSensitive: !!config.experimental.caseSensitiveRoutes,
basePath: config.basePath,
redirects: redirects.map((r: any) =>
buildCustomRoute(r, 'redirect')
),
headers: headers.map((r: any) => buildCustomRoute(r, 'header')),
dynamicRoutes,
staticRoutes,
dataRoutes: [],
i18n: config.i18n || undefined,
rsc: {
header: RSC,
varyHeader: RSC_VARY_HEADER,
contentTypeHeader: RSC_CONTENT_TYPE_HEADER,
},
skipMiddlewareUrlNormalize: config.skipMiddlewareUrlNormalize,
}
})

if (rewrites.beforeFiles.length === 0 && rewrites.fallback.length === 0) {
routesManifest.rewrites = rewrites.afterFiles.map((r: any) =>
Expand Down Expand Up @@ -903,6 +923,7 @@ export default async function build(
]
: []),
path.join(SERVER_DIRECTORY, APP_PATHS_MANIFEST),
path.join(APP_PATH_ROUTES_MANIFEST),
APP_BUILD_MANIFEST,
path.join(
SERVER_DIRECTORY,
Expand Down Expand Up @@ -1962,6 +1983,11 @@ export default async function build(
...Object.values(experimentalOverrides).map((override) =>
require.resolve(override)
),
...(config.experimental.turbotrace
? []
: Object.values(defaultOverrides).map((value) =>
require.resolve(value)
)),
]

// ensure we trace any dependencies needed for custom
Expand All @@ -1979,9 +2005,7 @@ export default async function build(
const vanillaServerEntries = [
...sharedEntriesSet,
isStandalone
? require.resolve(
'next/dist/server/lib/render-server-standalone'
)
? require.resolve('next/dist/server/lib/start-server')
: null,
require.resolve('next/dist/server/next-server'),
].filter(Boolean) as string[]
Expand Down Expand Up @@ -2158,49 +2182,7 @@ export default async function build(
...serverPropsPages,
...ssgPages,
]).map((page) => {
const pagePath = normalizePagePath(page)
const dataRoute = path.posix.join(
'/_next/data',
buildId,
`${pagePath}.json`
)

let dataRouteRegex: string
let namedDataRouteRegex: string | undefined
let routeKeys: { [named: string]: string } | undefined

if (isDynamicRoute(page)) {
const routeRegex = getNamedRouteRegex(
dataRoute.replace(/\.json$/, ''),
true
)

dataRouteRegex = normalizeRouteRegex(
routeRegex.re.source.replace(/\(\?:\\\/\)\?\$$/, `\\.json$`)
)
namedDataRouteRegex = routeRegex.namedRegex!.replace(
/\(\?:\/\)\?\$$/,
`\\.json$`
)
routeKeys = routeRegex.routeKeys
} else {
dataRouteRegex = normalizeRouteRegex(
new RegExp(
`^${path.posix.join(
'/_next/data',
escapeStringRegexp(buildId),
`${pagePath}.json`
)}$`
).source
)
}

return {
page,
routeKeys,
dataRouteRegex,
namedDataRouteRegex,
}
return buildDataRoute(page, buildId)
})

await fs.writeFile(
Expand Down
67 changes: 26 additions & 41 deletions packages/next/src/build/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1928,12 +1928,12 @@ export async function copyTracedFiles(
import path from 'path'
import { fileURLToPath } from 'url'
const __dirname = fileURLToPath(new URL('.', import.meta.url))
import { createServerHandler } from 'next/dist/server/lib/render-server-standalone.js'
import { startServer } from 'next/dist/server/lib/start-server.js'
`
: `
const http = require('http')
const path = require('path')
const { createServerHandler } = require('next/dist/server/lib/render-server-standalone')`
const { startServer } = require('next/dist/server/lib/start-server')`
}
const dir = path.join(__dirname)
Expand All @@ -1950,53 +1950,38 @@ if (!process.env.NEXT_MANUAL_SIG_HANDLE) {
const currentPort = parseInt(process.env.PORT, 10) || 3000
const hostname = process.env.HOSTNAME || 'localhost'
const keepAliveTimeout = parseInt(process.env.KEEP_ALIVE_TIMEOUT, 10);
const isValidKeepAliveTimeout =
!Number.isNaN(keepAliveTimeout) &&
Number.isFinite(keepAliveTimeout) &&
keepAliveTimeout >= 0;
let keepAliveTimeout = parseInt(process.env.KEEP_ALIVE_TIMEOUT, 10);
const nextConfig = ${JSON.stringify({
...serverConfig,
distDir: `./${path.relative(dir, distDir)}`,
})}
process.env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(nextConfig)
createServerHandler({
port: currentPort,
hostname,
dir,
conf: nextConfig,
keepAliveTimeout: isValidKeepAliveTimeout ? keepAliveTimeout : undefined,
}).then((nextHandler) => {
const server = http.createServer(async (req, res) => {
try {
await nextHandler(req, res)
} catch (err) {
console.error(err);
res.statusCode = 500
res.end('Internal Server Error')
}
})
if (isValidKeepAliveTimeout) {
server.keepAliveTimeout = keepAliveTimeout
}
server.listen(currentPort, async (err) => {
if (err) {
console.error("Failed to start server", err)
process.exit(1)
}
console.log(
'Listening on port',
currentPort,
'url: http://' + hostname + ':' + currentPort
)
});
if (
Number.isNaN(keepAliveTimeout) ||
!Number.isFinite(keepAliveTimeout) ||
keepAliveTimeout < 0
) {
keepAliveTimeout = undefined
}
}).catch(err => {
startServer({
dir,
isDev: false,
config: nextConfig,
hostname: hostname === 'localhost' ? '0.0.0.0' : hostname,
port: currentPort,
allowRetry: false,
keepAliveTimeout,
useWorkers: !!nextConfig.experimental?.appDir,
}).then(() => {
console.log(
'Listening on port',
currentPort,
'url: http://' + hostname + ':' + currentPort
)
}).catch((err) => {
console.error(err);
process.exit(1);
});`
Expand Down
Loading

0 comments on commit f57eecd

Please sign in to comment.