Skip to content

Commit

Permalink
[next] support middleware-manifest v2 (#8319)
Browse files Browse the repository at this point in the history
### Related Issues

As the title, support a new version of middleware-manifest of next.js that is going to be added in vercel/next.js#39257

### 📋 Checklist

<!--
  Please keep your PR as a Draft until the checklist is complete
-->

#### Tests

- [ ] The code changed/added as part of this PR has been covered with tests
- [ ] All tests pass locally with `yarn test-unit`

#### Code Review

- [ ] This PR has a concise title and thorough description useful to a reviewer
- [ ] Issue from task tracker has a link to this PR
  • Loading branch information
nkzawa committed Aug 25, 2022
1 parent 7c1c089 commit ead1e41
Show file tree
Hide file tree
Showing 3 changed files with 368 additions and 44 deletions.
171 changes: 127 additions & 44 deletions packages/next/src/utils.ts
Expand Up @@ -16,7 +16,13 @@ import {
EdgeFunction,
} from '@vercel/build-utils';
import { NodeFileTraceReasons } from '@vercel/nft';
import { Header, Rewrite, Route, RouteWithSrc } from '@vercel/routing-utils';
import type {
HasField,
Header,
Rewrite,
Route,
RouteWithSrc,
} from '@vercel/routing-utils';
import { Sema } from 'async-sema';
import crc32 from 'buffer-crc32';
import fs, { lstat, stat } from 'fs-extra';
Expand Down Expand Up @@ -2157,23 +2163,44 @@ export {
getSourceFilePathFromPage,
};

interface MiddlewareManifest {
type MiddlewareManifest = MiddlewareManifestV1 | MiddlewareManifestV2;

interface MiddlewareManifestV1 {
version: 1;
sortedMiddleware: string[];
middleware: { [page: string]: EdgeFunctionInfo };
functions?: { [page: string]: EdgeFunctionInfo };
middleware: { [page: string]: EdgeFunctionInfoV1 };
functions?: { [page: string]: EdgeFunctionInfoV1 };
}

interface MiddlewareManifestV2 {
version: 2;
sortedMiddleware: string[];
middleware: { [page: string]: EdgeFunctionInfoV2 };
functions?: { [page: string]: EdgeFunctionInfoV2 };
}

interface EdgeFunctionInfo {
interface BaseEdgeFunctionInfo {
env: string[];
files: string[];
name: string;
page: string;
regexp: string;
wasm?: { filePath: string; name: string }[];
assets?: { filePath: string; name: string }[];
}

interface EdgeFunctionInfoV1 extends BaseEdgeFunctionInfo {
regexp: string;
}

interface EdgeFunctionInfoV2 extends BaseEdgeFunctionInfo {
matchers: EdgeFunctionMatcher[];
}

interface EdgeFunctionMatcher {
regexp: string;
has?: HasField;
}

export async function getMiddlewareBundle({
entryPath,
outputDirectory,
Expand Down Expand Up @@ -2304,7 +2331,7 @@ export async function getMiddlewareBundle({
}),
});
})(),
routeSrc: getRouteSrc(edgeFunction, routesManifest),
routeMatchers: getRouteMatchers(edgeFunction, routesManifest),
};
} catch (e: any) {
e.message = `Can't build edge function ${key}: ${e.message}`;
Expand Down Expand Up @@ -2336,27 +2363,30 @@ export async function getMiddlewareBundle({
continue;
}

const route: Route = {
continue: true,
src: worker.routeSrc,
missing: [
{
type: 'header',
key: 'x-prerender-revalidate',
value: prerenderBypassToken,
},
],
};
for (const matcher of worker.routeMatchers) {
const route: Route = {
continue: true,
src: matcher.regexp,
has: matcher.has,
missing: [
{
type: 'header',
key: 'x-prerender-revalidate',
value: prerenderBypassToken,
},
],
};

route.middlewarePath = shortPath;
if (isCorrectMiddlewareOrder) {
route.override = true;
}
route.middlewarePath = shortPath;
if (isCorrectMiddlewareOrder) {
route.override = true;
}

if (routesManifest.version > 3 && isDynamicRoute(worker.page)) {
source.dynamicRouteMap.set(worker.page, route);
} else {
source.staticRoutes.push(route);
if (routesManifest.version > 3 && isDynamicRoute(worker.page)) {
source.dynamicRouteMap.set(worker.page, route);
} else {
source.staticRoutes.push(route);
}
}
}

Expand All @@ -2378,7 +2408,7 @@ export async function getMiddlewareBundle({
export async function getMiddlewareManifest(
entryPath: string,
outputDirectory: string
): Promise<MiddlewareManifest | undefined> {
): Promise<MiddlewareManifestV2 | undefined> {
const middlewareManifestPath = path.join(
entryPath,
outputDirectory,
Expand All @@ -2394,36 +2424,89 @@ export async function getMiddlewareManifest(
return;
}

return fs.readJSON(middlewareManifestPath);
const manifest = (await fs.readJSON(
middlewareManifestPath
)) as MiddlewareManifest;
return manifest.version === 1
? upgradeMiddlewareManifest(manifest)
: manifest;
}

export function upgradeMiddlewareManifest(
v1: MiddlewareManifestV1
): MiddlewareManifestV2 {
function updateInfo(v1Info: EdgeFunctionInfoV1): EdgeFunctionInfoV2 {
const { regexp, ...rest } = v1Info;
return {
...rest,
matchers: [{ regexp }],
};
}

const middleware = Object.fromEntries(
Object.entries(v1.middleware).map(([p, info]) => [p, updateInfo(info)])
);
const functions = v1.functions
? Object.fromEntries(
Object.entries(v1.functions).map(([p, info]) => [p, updateInfo(info)])
)
: undefined;

return {
...v1,
version: 2,
middleware,
functions,
};
}

/**
* For an object containing middleware info and a routes manifest this will
* generate a string with the route that will activate the middleware on
* Vercel Proxy.
*
* @param param0 The middleware info including regexp and page.
* @param param0 The middleware info including matchers and page.
* @param param1 The routes manifest
* @returns A regexp string for the middleware route.
* @returns matchers for the middleware route.
*/
function getRouteSrc(
{ regexp, page }: EdgeFunctionInfo,
function getRouteMatchers(
info: EdgeFunctionInfoV2,
{ basePath = '', i18n }: RoutesManifest
): string {
if (page === '/') {
return regexp.replace(
'_next',
`${basePath?.substring(1) ? `${basePath?.substring(1)}/` : ''}_next`
);
): EdgeFunctionMatcher[] {
function getRegexp(regexp: string) {
if (info.page === '/') {
return regexp;
}

const locale = i18n?.locales.length
? `(?:/(${i18n.locales
.map(locale => escapeStringRegexp(locale))
.join('|')}))?`
: '';

return `(?:^${basePath}${locale}${regexp.substring(1)})`;
}

const locale = i18n?.locales.length
? `(?:/(${i18n.locales
.map(locale => escapeStringRegexp(locale))
.join('|')}))?`
: '';
function normalizeHas(has: HasField): HasField {
return has.map(v =>
v.type === 'header'
? {
...v,
key: v.key.toLowerCase(),
}
: v
);
}

return `(?:^${basePath}${locale}${regexp.substring(1)})`;
return info.matchers.map(matcher => {
const m: EdgeFunctionMatcher = {
regexp: getRegexp(matcher.regexp),
};
if (matcher.has) {
m.has = normalizeHas(matcher.has);
}
return m;
});
}

/**
Expand Down

0 comments on commit ead1e41

Please sign in to comment.