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:
parent
b0b5cd8dc0
commit
5306df81c6
2 changed files with 65 additions and 6 deletions
|
@ -140,7 +140,7 @@ async function tryToReadFile(filePath: string, shouldThrow: boolean) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMiddlewareMatchers(
|
export function getMiddlewareMatchers(
|
||||||
matcherOrMatchers: unknown,
|
matcherOrMatchers: unknown,
|
||||||
nextConfig: NextConfig
|
nextConfig: NextConfig
|
||||||
): MiddlewareMatcher[] {
|
): MiddlewareMatcher[] {
|
||||||
|
|
|
@ -5,7 +5,14 @@ import { RouteKind } from '../future/route-kind'
|
||||||
import { DefaultRouteMatcherManager } from '../future/route-matcher-managers/default-route-matcher-manager'
|
import { DefaultRouteMatcherManager } from '../future/route-matcher-managers/default-route-matcher-manager'
|
||||||
import { RouteMatch } from '../future/route-matches/route-match'
|
import { RouteMatch } from '../future/route-matches/route-match'
|
||||||
import type { PageChecker, Route } from '../router'
|
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 RouteResult =
|
||||||
| {
|
| {
|
||||||
type: 'rewrite'
|
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 url = require('url') as typeof import('url')
|
||||||
const { default: Router } = require('../router') as typeof import('../router')
|
const { default: Router } = require('../router') as typeof import('../router')
|
||||||
const { getPathMatch } =
|
const { getPathMatch } =
|
||||||
|
@ -65,14 +76,57 @@ export async function makeResolver(dir: string, nextConfig: NextConfig) {
|
||||||
const devServer = new DevServer({
|
const devServer = new DevServer({
|
||||||
dir,
|
dir,
|
||||||
conf: nextConfig,
|
conf: nextConfig,
|
||||||
|
hostname: 'localhost',
|
||||||
|
port: 3000,
|
||||||
})
|
})
|
||||||
|
|
||||||
await devServer.matchers.reload()
|
await devServer.matchers.reload()
|
||||||
|
|
||||||
// @ts-expect-error
|
if (middleware.files?.length) {
|
||||||
devServer.customRoutes = await loadCustomRoutes(nextConfig)
|
// @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 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(
|
routes.matchers = new DevRouteMatcherManager(
|
||||||
// @ts-expect-error internal method
|
// @ts-expect-error internal method
|
||||||
|
@ -81,6 +135,7 @@ export async function makeResolver(dir: string, nextConfig: NextConfig) {
|
||||||
|
|
||||||
const router = new Router({
|
const router = new Router({
|
||||||
...routes,
|
...routes,
|
||||||
|
catchAllMiddleware,
|
||||||
catchAllRoute: {
|
catchAllRoute: {
|
||||||
match: getPathMatch('/:path*'),
|
match: getPathMatch('/:path*'),
|
||||||
name: 'catchall route',
|
name: 'catchall route',
|
||||||
|
@ -112,6 +167,7 @@ export async function makeResolver(dir: string, nextConfig: NextConfig) {
|
||||||
route.type === 'redirect' ||
|
route.type === 'redirect' ||
|
||||||
route.type === 'header' ||
|
route.type === 'header' ||
|
||||||
route.name === 'catchall route' ||
|
route.name === 'catchall route' ||
|
||||||
|
route.name === 'middleware catchall' ||
|
||||||
route.name?.includes('check')
|
route.name?.includes('check')
|
||||||
return matches
|
return matches
|
||||||
})
|
})
|
||||||
|
@ -122,9 +178,12 @@ export async function makeResolver(dir: string, nextConfig: NextConfig) {
|
||||||
) {
|
) {
|
||||||
const req = new NodeNextRequest(_req)
|
const req = new NodeNextRequest(_req)
|
||||||
const res = new NodeNextResponse(_res)
|
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
|
;(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) {
|
if (!res.originalResponse.headersSent) {
|
||||||
res.setHeader('x-nextjs-route-result', '1')
|
res.setHeader('x-nextjs-route-result', '1')
|
||||||
|
|
Loading…
Reference in a new issue