Skip to content

Commit

Permalink
provide interception rewrites to edge runtime
Browse files Browse the repository at this point in the history
  • Loading branch information
ztanner committed Jan 31, 2024
1 parent 916576c commit b104d0d
Show file tree
Hide file tree
Showing 16 changed files with 120 additions and 39 deletions.
3 changes: 2 additions & 1 deletion packages/next/src/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -708,7 +708,6 @@ export default async function build(
.traceAsyncFn(() => loadCustomRoutes(config))

const { headers, rewrites, redirects } = customRoutes
NextBuildContext.rewrites = rewrites
NextBuildContext.originalRewrites = config._originalRewrites
NextBuildContext.originalRedirects = config._originalRedirects

Expand Down Expand Up @@ -972,6 +971,8 @@ export default async function build(
...generateInterceptionRoutesRewrites(appPaths, config.basePath)
)

NextBuildContext.rewrites = rewrites

const totalAppPagesCount = appPaths.length

const pageKeys = {
Expand Down
4 changes: 4 additions & 0 deletions packages/next/src/build/templates/edge-ssr-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ const subresourceIntegrityManifest = sriEnabled
: undefined
const nextFontManifest = maybeJSONParse(self.__NEXT_FONT_MANIFEST)

const interceptionRouteRewrites =
maybeJSONParse(self.__INTERCEPTION_ROUTE_REWRITE_MANIFEST) ?? []

const render = getRender({
pagesType: PAGE_TYPES.APP,
dev,
Expand All @@ -65,6 +68,7 @@ const render = getRender({
buildId: 'VAR_BUILD_ID',
nextFontManifest,
incrementalCacheHandler,
interceptionRouteRewrites,
})

export const ComponentMod = pageMod
Expand Down
1 change: 1 addition & 0 deletions packages/next/src/build/webpack-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1769,6 +1769,7 @@ export default async function getBaseWebpackConfig(
new MiddlewarePlugin({
dev,
sriEnabled: !dev && !!config.experimental.sri?.algorithm,
rewrites,
}),
isClient &&
new BuildManifestPlugin({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
WebNextResponse,
} from '../../../../server/base-http/web'
import { SERVER_RUNTIME } from '../../../../lib/constants'
import type { PrerenderManifest } from '../../..'
import type { ManifestRewriteRoute, PrerenderManifest } from '../../..'
import { normalizeAppPath } from '../../../../shared/lib/router/utils/app-paths'
import type { SizeLimit } from '../../../../../types'
import { internal_getCurrentFunctionWaitUntil } from '../../../../server/web/internal-edge-wait-until'
Expand All @@ -31,6 +31,7 @@ export function getRender({
buildManifest,
prerenderManifest,
reactLoadableManifest,
interceptionRouteRewrites,
renderToHTML,
clientReferenceManifest,
subresourceIntegrityManifest,
Expand All @@ -54,6 +55,7 @@ export function getRender({
prerenderManifest: PrerenderManifest
reactLoadableManifest: ReactLoadableManifest
subresourceIntegrityManifest?: Record<string, string>
interceptionRouteRewrites?: ManifestRewriteRoute[]
clientReferenceManifest?: ClientReferenceManifest
serverActionsManifest?: any
serverActions?: {
Expand Down Expand Up @@ -85,6 +87,7 @@ export function getRender({
pathname: isAppPath ? normalizeAppPath(page) : page,
pagesType,
prerenderManifest,
interceptionRouteRewrites,
extendRenderOpts: {
buildId,
runtime: SERVER_RUNTIME.experimentalEdge,
Expand Down
40 changes: 31 additions & 9 deletions packages/next/src/build/webpack/plugins/middleware-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@ import {
NEXT_FONT_MANIFEST,
SERVER_REFERENCE_MANIFEST,
PRERENDER_MANIFEST,
INTERCEPTION_ROUTE_REWRITE_MANIFEST,
} from '../../../shared/lib/constants'
import type { MiddlewareConfig } from '../../analysis/get-page-static-info'
import type { Telemetry } from '../../../telemetry/storage'
import { traceGlobals } from '../../../trace/shared'
import { EVENT_BUILD_FEATURE_USAGE } from '../../../telemetry/events'
import { normalizeAppPath } from '../../../shared/lib/router/utils/app-paths'
import { INSTRUMENTATION_HOOK_FILENAME } from '../../../lib/constants'
import type { CustomRoutes } from '../../../lib/load-custom-routes'
import { isInterceptionRouteRewrite } from '../../../lib/generate-interception-routes-rewrites'

const KNOWN_SAFE_DYNAMIC_PACKAGES =
require('../../../lib/known-edge-safe-packages.json') as string[]
Expand Down Expand Up @@ -118,10 +121,10 @@ function getEntryFiles(

files.push(
`server/${MIDDLEWARE_BUILD_MANIFEST}.js`,
`server/${MIDDLEWARE_REACT_LOADABLE_MANIFEST}.js`
`server/${MIDDLEWARE_REACT_LOADABLE_MANIFEST}.js`,
`server/${NEXT_FONT_MANIFEST}.js`,
`server/${INTERCEPTION_ROUTE_REWRITE_MANIFEST}.js`
)

files.push(`server/${NEXT_FONT_MANIFEST}.js`)
}

if (hasInstrumentationHook) {
Expand All @@ -144,9 +147,7 @@ function getEntryFiles(
function getCreateAssets(params: {
compilation: webpack.Compilation
metadataByEntry: Map<string, EntryMetadata>
opts: {
sriEnabled: boolean
}
opts: Omit<Options, 'dev'>
}) {
const { compilation, metadataByEntry, opts } = params
return (assets: any) => {
Expand All @@ -161,6 +162,17 @@ function getCreateAssets(params: {
INSTRUMENTATION_HOOK_FILENAME
)

// we only emit this entry for the edge runtime since it doesn't have access to a routes manifest
// and we don't need to provide the entire route manifest, just the interception routes.
const interceptionRewrites = JSON.stringify(
opts.rewrites.beforeFiles.filter(isInterceptionRouteRewrite)
)
assets[`${INTERCEPTION_ROUTE_REWRITE_MANIFEST}.js`] = new sources.RawSource(
`self.__INTERCEPTION_ROUTE_REWRITE_MANIFEST=${JSON.stringify(
interceptionRewrites
)}`
) as unknown as webpack.sources.RawSource

for (const entrypoint of compilation.entrypoints.values()) {
if (!entrypoint.name) {
continue
Expand Down Expand Up @@ -718,13 +730,22 @@ function getExtractMetadata(params: {
}
}
}

interface Options {
dev: boolean
sriEnabled: boolean
rewrites: CustomRoutes['rewrites']
}

export default class MiddlewarePlugin {
private readonly dev: boolean
private readonly sriEnabled: boolean
private readonly dev: Options['dev']
private readonly sriEnabled: Options['sriEnabled']
private readonly rewrites: Options['rewrites']

constructor({ dev, sriEnabled }: { dev: boolean; sriEnabled: boolean }) {
constructor({ dev, sriEnabled, rewrites }: Options) {
this.dev = dev
this.sriEnabled = sriEnabled
this.rewrites = rewrites
}

public apply(compiler: webpack.Compiler) {
Expand Down Expand Up @@ -769,6 +790,7 @@ export default class MiddlewarePlugin {
metadataByEntry,
opts: {
sriEnabled: this.sriEnabled,
rewrites: this.rewrites,
},
})
)
Expand Down
1 change: 1 addition & 0 deletions packages/next/src/client/route-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ declare global {
__RSC_SERVER_MANIFEST?: any
__NEXT_FONT_MANIFEST?: any
__SUBRESOURCE_INTEGRITY_MANIFEST?: string
__INTERCEPTION_ROUTE_REWRITE_MANIFEST?: string
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
isInterceptionRouteAppPath,
} from '../server/future/helpers/interception-routes'
import type { Rewrite } from './load-custom-routes'
import type { ManifestRewriteRoute } from '../build'

// a function that converts normalised paths (e.g. /foo/[bar]/[baz]) to the format expected by pathToRegexp (e.g. /foo/:bar/:baz)
function toPathToRegexpPath(path: string): string {
Expand Down Expand Up @@ -88,7 +87,7 @@ export function generateInterceptionRoutesRewrites(
return rewrites
}

export function isInterceptionRouteRewrite(route: ManifestRewriteRoute) {
export function isInterceptionRouteRewrite(route: Rewrite) {
// When we generate interception rewrites in the above implementation, we always do so with only a single `has` condition.
return route.has?.[0].key === NEXT_URL
}
2 changes: 1 addition & 1 deletion packages/next/src/server/base-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2001,7 +2001,7 @@ export default abstract class Server<ServerOptions extends Options = Options> {
res.setHeader('vary', RSC_VARY_HEADER)

if (isPrefetchRSCRequest) {
const couldBeRewritten = this.interceptionRouteRewrites?.some(
const couldBeRewritten = this.interceptionRouteRewrites.some(
(rewrite) => {
return new RegExp(rewrite.regex).test(resolvedUrlPathname)
}
Expand Down
1 change: 0 additions & 1 deletion packages/next/src/server/lib/router-utils/filesystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,6 @@ export async function setupFsCheck(opts: {
)
)
const rewrites = {
// TODO: add interception routes generateInterceptionRoutesRewrites()
beforeFiles: customRoutes.rewrites.beforeFiles.map((item) =>
buildCustomRoute('before_files_rewrite', item)
),
Expand Down
11 changes: 10 additions & 1 deletion packages/next/src/server/lib/router-utils/resolve-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
} from '../../../shared/lib/router/utils/prepare-destination'
import { createRequestResponseMocks } from '../mock-request'
import type { TLSSocket } from 'tls'
import { isInterceptionRouteRewrite } from '../../../lib/generate-interception-routes-rewrites'

const debug = setupDebug('next:router-server:resolve-routes')

Expand Down Expand Up @@ -71,7 +72,14 @@ export function getResolveRoutes(
// check middleware (using matchers)
{ match: () => ({}), name: 'middleware' },

...(opts.minimalMode ? [] : fsChecker.rewrites.beforeFiles),
...(opts.minimalMode
? []
: fsChecker.rewrites.beforeFiles.filter(
// TODO: Currently have to filter interception routes out of `beforeFiles` because for some reason
// they are also processed in `fsChecker.interceptionRoutes` despite both being modeled as beforeFiles rewrites
// Need to look into why that's the case
(r) => !isInterceptionRouteRewrite(r)
)),

// check middleware (using matchers)
{ match: () => ({}), name: 'before_files_end' },
Expand Down Expand Up @@ -359,6 +367,7 @@ export function getResolveRoutes(
}

if (params) {
// TODO: Why is this using a special `interceptionRoutes` value and not `fsChecker.beforeFiles.rewrites`?
if (fsChecker.interceptionRoutes && route.name === 'before_files_end') {
for (const interceptionRoute of fsChecker.interceptionRoutes) {
const result = await handleRoute(interceptionRoute)
Expand Down
26 changes: 14 additions & 12 deletions packages/next/src/server/lib/router-utils/setup-dev-bundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2296,18 +2296,20 @@ async function startWatcher(opts: SetupOpts) {
? getMiddlewareRouteMatcher(serverFields.middleware?.matchers)
: undefined

opts.fsChecker.interceptionRoutes =
generateInterceptionRoutesRewrites(
Object.keys(appPaths),
opts.nextConfig.basePath
)?.map((item) =>
buildCustomRoute(
'before_files_rewrite',
item,
opts.nextConfig.basePath,
opts.nextConfig.experimental.caseSensitiveRoutes
)
) || []
const interceptionRoutes = generateInterceptionRoutesRewrites(
Object.keys(appPaths),
opts.nextConfig.basePath
).map((item) =>
buildCustomRoute(
'before_files_rewrite',
item,
opts.nextConfig.basePath,
opts.nextConfig.experimental.caseSensitiveRoutes
)
)

opts.fsChecker.interceptionRoutes = interceptionRoutes
opts.fsChecker.rewrites.beforeFiles.push(...interceptionRoutes)

const exportPathMap =
(typeof nextConfig.exportPathMap === 'function' &&
Expand Down
2 changes: 2 additions & 0 deletions packages/next/src/server/next-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,8 @@ export default class NextNodeServer extends BaseServer {
}

protected getInterceptionRouteRewrites(): ManifestRewriteRoute[] {
if (!this.enabledDirectories.app) return []

const routesManifest = this.getRoutesManifest()
return (
routesManifest?.rewrites.beforeFiles.filter(isInterceptionRouteRewrite) ??
Expand Down
4 changes: 2 additions & 2 deletions packages/next/src/server/web-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ interface WebServerOptions extends Options {
| undefined
incrementalCacheHandler?: any
prerenderManifest: PrerenderManifest | undefined
interceptionRouteRewrites?: ManifestRewriteRoute[]
}
}

Expand Down Expand Up @@ -395,7 +396,6 @@ export default class NextWebServer extends BaseServer<WebServerOptions> {
}

protected getInterceptionRouteRewrites(): ManifestRewriteRoute[] {
// TODO: This needs to be implemented.
return []
return this.serverOptions.webServerConfig.interceptionRouteRewrites ?? []
}
}
3 changes: 3 additions & 0 deletions packages/next/src/shared/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ export const MIDDLEWARE_BUILD_MANIFEST = 'middleware-build-manifest'
// server/middleware-react-loadable-manifest.js
export const MIDDLEWARE_REACT_LOADABLE_MANIFEST =
'middleware-react-loadable-manifest'
// server/interception-route-rewrite-manifest.js
export const INTERCEPTION_ROUTE_REWRITE_MANIFEST =
'interception-route-rewrite-manifest'

// static/runtime/main.js
export const CLIENT_STATIC_FILES_RUNTIME_MAIN = `main`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// this file is swapped in for the normal layout file in the edge runtime test

import Link from 'next/link'

export const runtime = 'edge'

export default function RootLayout({ children }) {
return (
<html>
<head />
<body>
<Link href="/">home</Link>
{children}
</body>
</html>
)
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import { createNextDescribe } from 'e2e-utils'
import { nextTestSetup, FileRef } from 'e2e-utils'
import { check } from 'next-test-utils'
import { join } from 'path'
import { Response } from 'playwright-chromium'

createNextDescribe(
'interception-route-prefetch-cache',
{
files: __dirname,
},
({ next, isNextStart }) => {
describe('interception-route-prefetch-cache', () => {
function runTests({ next }: ReturnType<typeof nextTestSetup>) {
it('should render the correct interception when two distinct layouts share the same path structure', async () => {
const browser = await next.browser('/')

Expand Down Expand Up @@ -42,7 +39,17 @@ createNextDescribe(
/Intercepted on Bar Page/
)
})
}

describe('runtime = nodejs', () => {
const testSetup = nextTestSetup({
files: __dirname,
})
runTests(testSetup)

const { next, isNextStart } = testSetup

// this is a node runtime specific test as edge doesn't support static rendering
if (isNextStart) {
it('should not be a cache HIT when prefetching an interception route', async () => {
const responses: { cacheStatus: string; pathname: string }[] = []
Expand Down Expand Up @@ -78,5 +85,16 @@ createNextDescribe(
expect(interceptionPrefetchResponse.cacheStatus).toBeUndefined()
})
}
}
)
})

describe('runtime = edge', () => {
runTests(
nextTestSetup({
files: {
app: new FileRef(join(__dirname, 'app')),
'app/layout.tsx': new FileRef(join(__dirname, 'app/layout-edge.tsx')),
},
})
)
})
})

0 comments on commit b104d0d

Please sign in to comment.