Implement middleware support for Turbopack (#46397)

This implements middleware support for Turbopack's route resolver. In
https://github.com/vercel/turbo/pull/3930, I'm updating the data that we
pass to include a new `MiddlewareConfig`, which includes the files
needed for invoking the edge function and the matchers extracted from
the middleware's static `export config = {}`.

~~This needs to wait for https://github.com/vercel/turbo/pull/3930 to
land first~~ Merged.
Fixes https://linear.app/vercel/issue/WEB-624

---------

Co-authored-by: JJ Kasper <jj@jjsweb.site>
This commit is contained in:
Justin Ridgewell 2023-03-01 03:16:04 -05:00 committed by GitHub
parent b0b5cd8dc0
commit 5306df81c6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 65 additions and 6 deletions

View file

@ -140,7 +140,7 @@ async function tryToReadFile(filePath: string, shouldThrow: boolean) {
}
}
function getMiddlewareMatchers(
export function getMiddlewareMatchers(
matcherOrMatchers: unknown,
nextConfig: NextConfig
): MiddlewareMatcher[] {

View file

@ -5,7 +5,14 @@ import { RouteKind } from '../future/route-kind'
import { DefaultRouteMatcherManager } from '../future/route-matcher-managers/default-route-matcher-manager'
import { RouteMatch } from '../future/route-matches/route-match'
import type { PageChecker, Route } from '../router'
import { getMiddlewareMatchers } from '../../build/analysis/get-page-static-info'
import { getMiddlewareRouteMatcher } from '../../shared/lib/router/utils/middleware-route-matcher'
import { join } from 'path'
type MiddlewareConfig = {
matcher: string[]
files: string[]
}
type RouteResult =
| {
type: 'rewrite'
@ -48,7 +55,11 @@ class DevRouteMatcherManager extends DefaultRouteMatcherManager {
}
}
export async function makeResolver(dir: string, nextConfig: NextConfig) {
export async function makeResolver(
dir: string,
nextConfig: NextConfig,
middleware: MiddlewareConfig
) {
const url = require('url') as typeof import('url')
const { default: Router } = require('../router') as typeof import('../router')
const { getPathMatch } =
@ -65,14 +76,57 @@ export async function makeResolver(dir: string, nextConfig: NextConfig) {
const devServer = new DevServer({
dir,
conf: nextConfig,
hostname: 'localhost',
port: 3000,
})
await devServer.matchers.reload()
if (middleware.files?.length) {
// @ts-expect-error
devServer.customRoutes = await loadCustomRoutes(nextConfig)
const matchers = middleware.matcher
? getMiddlewareMatchers(middleware.matcher, nextConfig)
: [{ regexp: '.*' }]
// @ts-expect-error
devServer.middleware = {
page: '/',
match: getMiddlewareRouteMatcher(matchers),
matchers,
}
type GetEdgeFunctionInfo =
typeof DevServer['prototype']['getEdgeFunctionInfo']
const getEdgeFunctionInfo = (
original: GetEdgeFunctionInfo
): GetEdgeFunctionInfo => {
return (params: { page: string; middleware: boolean }) => {
if (params.middleware) {
return {
name: 'middleware',
paths: middleware.files.map((file) => join(process.cwd(), file)),
env: [],
wasm: [],
assets: [],
}
}
return original(params)
}
}
// @ts-expect-error protected
devServer.getEdgeFunctionInfo = getEdgeFunctionInfo(
// @ts-expect-error protected
devServer.getEdgeFunctionInfo.bind(devServer)
)
// @ts-expect-error protected
devServer.hasMiddleware = () => true
}
const routeResults = new WeakMap<any, string>()
const routes = devServer.generateRoutes.bind(devServer)()
const routes = devServer.generateRoutes()
// @ts-expect-error protected
const catchAllMiddleware = devServer.generateCatchAllMiddlewareRoute(true)
routes.matchers = new DevRouteMatcherManager(
// @ts-expect-error internal method
@ -81,6 +135,7 @@ export async function makeResolver(dir: string, nextConfig: NextConfig) {
const router = new Router({
...routes,
catchAllMiddleware,
catchAllRoute: {
match: getPathMatch('/:path*'),
name: 'catchall route',
@ -112,6 +167,7 @@ export async function makeResolver(dir: string, nextConfig: NextConfig) {
route.type === 'redirect' ||
route.type === 'header' ||
route.name === 'catchall route' ||
route.name === 'middleware catchall' ||
route.name?.includes('check')
return matches
})
@ -122,9 +178,12 @@ export async function makeResolver(dir: string, nextConfig: NextConfig) {
) {
const req = new NodeNextRequest(_req)
const res = new NodeNextResponse(_res)
const parsedUrl = url.parse(req.url!, true)
// @ts-expect-error protected
devServer.attachRequestMeta(req, parsedUrl)
;(req as any)._initUrl = req.url
await router.execute.bind(router)(req, res, url.parse(req.url!, true))
await router.execute(req, res, parsedUrl)
if (!res.originalResponse.headersSent) {
res.setHeader('x-nextjs-route-result', '1')