Skip to content

Commit bc05ce9

Browse files
authoredJun 23, 2023
Move Pages render out of server (#51678)
This removes calls for rendering files in `pages/` via the server code and relies instead relies on the bundled `routeModule` to perform the rendering. Future cases that try to call the legacy render will hit a new: ``` Invariant: render should have used routeModule ``` Error. These cases should be reported.
1 parent 0a4b6ef commit bc05ce9

File tree

14 files changed

+227
-52
lines changed

14 files changed

+227
-52
lines changed
 

‎packages/next/src/build/entries.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ import { isAppRouteRoute } from '../lib/is-app-route-route'
5353
import { normalizeMetadataRoute } from '../lib/metadata/get-metadata-route'
5454
import { fileExists } from '../lib/file-exists'
5555
import { getRouteLoaderEntry } from './webpack/loaders/next-route-loader'
56-
import { isInternalPathname } from '../lib/is-internal-pathname'
56+
import { isInternalComponent } from '../lib/is-internal-component'
5757
import { isStaticMetadataRouteFile } from '../lib/metadata/is-metadata-route'
5858

5959
export async function getStaticInfoIncludingLayouts({
@@ -596,16 +596,15 @@ export async function createEntrypoints(
596596
} else if (
597597
!isAPIRoute(page) &&
598598
!isMiddlewareFile(page) &&
599-
!isInternalPathname(absolutePagePath)
599+
!isInternalComponent(absolutePagePath)
600600
) {
601601
server[serverBundlePath] = [
602602
getRouteLoaderEntry({
603603
page,
604+
pages,
604605
absolutePagePath,
605606
preferredRegion: staticInfo.preferredRegion,
606-
middlewareConfig: Buffer.from(
607-
JSON.stringify(staticInfo.middleware || {})
608-
).toString('base64'),
607+
middlewareConfig: staticInfo.middleware ?? {},
609608
}),
610609
]
611610
} else {

‎packages/next/src/build/webpack/loaders/next-route-loader/index.ts

+52-9
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ import { PagesRouteModuleOptions } from '../../../../server/future/route-modules
66
import { RouteKind } from '../../../../server/future/route-kind'
77
import { normalizePagePath } from '../../../../shared/lib/page-path/normalize-page-path'
88
import { MiddlewareConfig } from '../../../analysis/get-page-static-info'
9+
import { decodeFromBase64, encodeToBase64 } from '../utils'
10+
import { isInstrumentationHookFile } from '../../../worker'
11+
12+
type RouteLoaderOptionsInput = {
13+
page: string
14+
pages: { [page: string]: string }
15+
preferredRegion: string | string[] | undefined
16+
absolutePagePath: string
17+
middlewareConfig: MiddlewareConfig
18+
}
919

1020
/**
1121
* The options for the route loader.
@@ -25,17 +35,29 @@ type RouteLoaderOptions = {
2535
* The absolute path to the userland page file.
2636
*/
2737
absolutePagePath: string
28-
29-
middlewareConfig: string
38+
absoluteAppPath: string
39+
absoluteDocumentPath: string
40+
middlewareConfigBase64: string
3041
}
3142

3243
/**
3344
* Returns the loader entry for a given page.
3445
*
35-
* @param query the options to create the loader entry
46+
* @param options the options to create the loader entry
3647
* @returns the encoded loader entry
3748
*/
38-
export function getRouteLoaderEntry(query: RouteLoaderOptions): string {
49+
export function getRouteLoaderEntry(options: RouteLoaderOptionsInput): string {
50+
const query: RouteLoaderOptions = {
51+
page: options.page,
52+
preferredRegion: options.preferredRegion,
53+
absolutePagePath: options.absolutePagePath,
54+
// These are the path references to the internal components that may be
55+
// overridden by userland components.
56+
absoluteAppPath: options.pages['/_app'],
57+
absoluteDocumentPath: options.pages['/_document'],
58+
middlewareConfigBase64: encodeToBase64(options.middlewareConfig),
59+
}
60+
3961
return `next-route-loader?${stringify(query)}!`
4062
}
4163

@@ -49,16 +71,18 @@ const loader: webpack.LoaderDefinitionFunction<RouteLoaderOptions> =
4971
page,
5072
preferredRegion,
5173
absolutePagePath,
52-
middlewareConfig: middlewareConfigBase64,
74+
absoluteAppPath,
75+
absoluteDocumentPath,
76+
middlewareConfigBase64,
5377
} = this.getOptions()
5478

5579
// Ensure we only run this loader for as a module.
5680
if (!this._module) {
5781
throw new Error('Invariant: expected this to reference a module')
5882
}
5983

60-
const middlewareConfig: MiddlewareConfig = JSON.parse(
61-
Buffer.from(middlewareConfigBase64, 'base64').toString()
84+
const middlewareConfig: MiddlewareConfig = decodeFromBase64(
85+
middlewareConfigBase64
6286
)
6387

6488
// Attach build info to the module.
@@ -70,7 +94,7 @@ const loader: webpack.LoaderDefinitionFunction<RouteLoaderOptions> =
7094
middlewareConfig,
7195
}
7296

73-
const options: Omit<PagesRouteModuleOptions, 'userland'> = {
97+
const options: Omit<PagesRouteModuleOptions, 'userland' | 'components'> = {
7498
definition: {
7599
kind: RouteKind.PAGES,
76100
page: normalizePagePath(page),
@@ -86,6 +110,10 @@ const loader: webpack.LoaderDefinitionFunction<RouteLoaderOptions> =
86110
import RouteModule from "next/dist/server/future/route-modules/pages/module"
87111
import { hoist } from "next/dist/build/webpack/loaders/next-route-loader/helpers"
88112
113+
// Import the app and document modules.
114+
import * as moduleDocument from ${JSON.stringify(absoluteDocumentPath)}
115+
import * as moduleApp from ${JSON.stringify(absoluteAppPath)}
116+
89117
// Import the userland code.
90118
import * as userland from ${JSON.stringify(absolutePagePath)}
91119
@@ -98,6 +126,14 @@ const loader: webpack.LoaderDefinitionFunction<RouteLoaderOptions> =
98126
export const getServerSideProps = hoist(userland, "getServerSideProps")
99127
export const config = hoist(userland, "config")
100128
export const reportWebVitals = hoist(userland, "reportWebVitals")
129+
${
130+
// When we're building the instrumentation page (only when the
131+
// instrumentation file conflicts with a page also labeled
132+
// /instrumentation) hoist the `register` method.
133+
isInstrumentationHookFile(page)
134+
? 'export const register = hoist(userland, "register")'
135+
: ''
136+
}
101137
102138
// Re-export legacy methods.
103139
export const unstable_getStaticProps = hoist(userland, "unstable_getStaticProps")
@@ -108,7 +144,14 @@ const loader: webpack.LoaderDefinitionFunction<RouteLoaderOptions> =
108144
109145
// Create and export the route module that will be consumed.
110146
const options = ${JSON.stringify(options)}
111-
const routeModule = new RouteModule({ ...options, userland })
147+
const routeModule = new RouteModule({
148+
...options,
149+
components: {
150+
App: moduleApp.default,
151+
Document: moduleDocument.default,
152+
},
153+
userland,
154+
})
112155
113156
export { routeModule }
114157
`

‎packages/next/src/build/webpack/loaders/utils.ts

+8
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,11 @@ export function generateActionId(filePath: string, exportName: string) {
5050
.update(filePath + ':' + exportName)
5151
.digest('hex')
5252
}
53+
54+
export function encodeToBase64<T extends {}>(obj: T): string {
55+
return Buffer.from(JSON.stringify(obj)).toString('base64')
56+
}
57+
58+
export function decodeFromBase64<T extends {}>(str: string): T {
59+
return JSON.parse(Buffer.from(str, 'base64').toString('utf8'))
60+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export function isInternalComponent(pathname: string): boolean {
2+
switch (pathname) {
3+
case 'next/dist/pages/_app':
4+
case 'next/dist/pages/_document':
5+
return true
6+
default:
7+
return false
8+
}
9+
}

‎packages/next/src/lib/is-internal-pathname.ts

-3
This file was deleted.

‎packages/next/src/server/base-server.ts

+25-2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import type { NextFontManifest } from '../build/webpack/plugins/next-font-manife
3030
import type { PagesRouteModule } from './future/route-modules/pages/module'
3131
import type { NodeNextRequest, NodeNextResponse } from './base-http/node'
3232
import type { AppRouteRouteMatch } from './future/route-matches/app-route-route-match'
33+
import type { RouteDefinition } from './future/route-definitions/route-definition'
3334

3435
import { format as formatUrl, parse as parseUrl } from 'url'
3536
import { getRedirectStatus } from '../lib/redirect-status'
@@ -109,7 +110,11 @@ import {
109110
toNodeOutgoingHttpHeaders,
110111
} from './web/utils'
111112
import { NEXT_QUERY_PARAM_PREFIX } from '../lib/constants'
112-
import { isRouteMatch } from './future/route-matches/route-match'
113+
import {
114+
isRouteMatch,
115+
parsedUrlQueryToParams,
116+
type RouteMatch,
117+
} from './future/route-matches/route-match'
113118

114119
export type FindComponentsResult = {
115120
components: LoadComponentsReturnType
@@ -1675,7 +1680,18 @@ export default abstract class Server<ServerOptions extends Options = Options> {
16751680
let result: RenderResult
16761681

16771682
// Get the match for the page if it exists.
1678-
const match = getRequestMeta(req, '_nextMatch')
1683+
const match: RouteMatch<RouteDefinition<RouteKind>> | undefined =
1684+
getRequestMeta(req, '_nextMatch') ??
1685+
// If the match can't be found, rely on the loaded route module. This
1686+
// should only be required during development when we add FS routes.
1687+
(this.renderOpts.dev && components.ComponentMod?.routeModule
1688+
? {
1689+
definition: components.ComponentMod.routeModule.definition,
1690+
params: opts.params
1691+
? parsedUrlQueryToParams(opts.params)
1692+
: undefined,
1693+
}
1694+
: undefined)
16791695

16801696
if (
16811697
match &&
@@ -2578,6 +2594,13 @@ export default abstract class Server<ServerOptions extends Options = Options> {
25782594
const fallbackComponents = await this.getFallbackErrorComponents()
25792595

25802596
if (fallbackComponents) {
2597+
// There was an error, so use it's definition from the route module
2598+
// to add the match to the request.
2599+
addRequestMeta(ctx.req, '_nextMatch', {
2600+
definition: fallbackComponents.ComponentMod.routeModule.definition,
2601+
params: undefined,
2602+
})
2603+
25812604
return this.renderToResponseWithComponents(
25822605
{
25832606
...ctx,

‎packages/next/src/server/dev/hot-reloader.ts

+4-7
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ import getRouteFromEntrypoint from '../get-route-from-entrypoint'
4747
import { fileExists } from '../../lib/file-exists'
4848
import {
4949
difference,
50-
isInstrumentationHookFile,
5150
isMiddlewareFile,
5251
isMiddlewareFilename,
5352
} from '../../build/utils'
@@ -62,7 +61,7 @@ import { RouteMatch } from '../future/route-matches/route-match'
6261
import { parseVersionInfo, VersionInfo } from './parse-version-info'
6362
import { isAPIRoute } from '../../lib/is-api-route'
6463
import { getRouteLoaderEntry } from '../../build/webpack/loaders/next-route-loader'
65-
import { isInternalPathname } from '../../lib/is-internal-pathname'
64+
import { isInternalComponent } from '../../lib/is-internal-component'
6665

6766
function diff(a: Set<any>, b: Set<any>) {
6867
return new Set([...a].filter((v) => !b.has(v)))
@@ -899,16 +898,14 @@ export default class HotReloader {
899898
} else if (
900899
!isAPIRoute(page) &&
901900
!isMiddlewareFile(page) &&
902-
!isInternalPathname(relativeRequest) &&
903-
!isInstrumentationHookFile(page)
901+
!isInternalComponent(relativeRequest)
904902
) {
905903
value = getRouteLoaderEntry({
906904
page,
905+
pages: this.pagesMapping,
907906
absolutePagePath: relativeRequest,
908907
preferredRegion: staticInfo.preferredRegion,
909-
middlewareConfig: Buffer.from(
910-
JSON.stringify(staticInfo.middleware || {})
911-
).toString('base64'),
908+
middlewareConfig: staticInfo.middleware ?? {},
912909
})
913910
} else {
914911
value = relativeRequest

‎packages/next/src/server/future/route-matches/route-match.ts

+20
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { ParsedUrlQuery } from 'querystring'
12
import type { RouteDefinition } from '../route-definitions/route-definition'
23

34
/**
@@ -31,3 +32,22 @@ export function isRouteMatch<M extends RouteMatch>(
3132
): match is M {
3233
return match.definition.kind === kind
3334
}
35+
36+
/**
37+
* Converts the query into params.
38+
*
39+
* @param query the query to convert to params
40+
* @returns the params
41+
*/
42+
export function parsedUrlQueryToParams(
43+
query: ParsedUrlQuery
44+
): Record<string, string | string[]> {
45+
const params: Record<string, string | string[]> = {}
46+
47+
for (const [key, value] of Object.entries(query)) {
48+
if (typeof value === 'undefined') continue
49+
params[key] = value
50+
}
51+
52+
return params
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import Document from '../../../../../pages/_document'
2+
import App from '../../../../../pages/_app'
3+
import { RouteKind } from '../../../route-kind'
4+
5+
import * as moduleError from '../../../../../pages/_error'
6+
7+
import PagesRouteModule from '../module'
8+
9+
export const routeModule = new PagesRouteModule({
10+
// TODO: add descriptor for internal error page
11+
definition: {
12+
kind: RouteKind.PAGES,
13+
page: '/_error',
14+
pathname: '/_error',
15+
filename: '',
16+
bundlePath: '',
17+
},
18+
components: {
19+
App,
20+
Document,
21+
},
22+
userland: moduleError,
23+
})

0 commit comments

Comments
 (0)
Please sign in to comment.