Skip to content

Commit b3a9115

Browse files
committed
initial implementation
1 parent 94d6eca commit b3a9115

File tree

9 files changed

+156
-4
lines changed

9 files changed

+156
-4
lines changed

packages/open-next/src/adapters/config/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
loadMiddlewareManifest,
1313
loadPrerenderManifest,
1414
loadRoutesManifest,
15+
loadFunctionsConfigManifest,
1516
} from "./util.js";
1617

1718
export const NEXT_DIR = path.join(__dirname, ".next");
@@ -35,3 +36,6 @@ export const MiddlewareManifest =
3536
export const AppPathsManifest = /* @__PURE__ */ loadAppPathsManifest(NEXT_DIR);
3637
export const AppPathRoutesManifest =
3738
/* @__PURE__ */ loadAppPathRoutesManifest(NEXT_DIR);
39+
40+
export const FunctionsConfigManifest =
41+
/* @__PURE__ */ loadFunctionsConfigManifest(NEXT_DIR);

packages/open-next/src/adapters/config/util.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import fs from "node:fs";
22
import path from "node:path";
33
import type {
4+
FunctionsConfigManifest,
45
MiddlewareManifest,
56
NextConfig,
67
PrerenderManifest,
@@ -123,3 +124,17 @@ export function loadMiddlewareManifest(nextDir: string) {
123124
const json = fs.readFileSync(filePath, "utf-8");
124125
return JSON.parse(json) as MiddlewareManifest;
125126
}
127+
128+
export function loadFunctionsConfigManifest(nextDir: string) {
129+
const filePath = path.join(
130+
nextDir,
131+
"server",
132+
"functions-config-manifest.json",
133+
);
134+
try {
135+
const json = fs.readFileSync(filePath, "utf-8");
136+
return JSON.parse(json) as FunctionsConfigManifest;
137+
} catch (e) {
138+
return;
139+
}
140+
}

packages/open-next/src/build/copyTracedFiles.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,15 @@ File ${fullFilePath} does not exist
141141
}
142142
};
143143

144+
if (existsSync(path.join(dotNextDir, "server/middleware.js.nft.json"))) {
145+
//TODO: For now we copy the middleware.js file not from the standalone dir, this will probably be removed in the future
146+
filesToCopy.set(
147+
path.join(dotNextDir, "server/middleware.js"),
148+
path.join(outputNextDir, "server/middleware.js"),
149+
);
150+
safeComputeCopyFilesForPage("middleware");
151+
}
152+
144153
const hasPageDir = routes.some((route) => route.startsWith("pages/"));
145154
const hasAppDir = routes.some((route) => route.startsWith("app/"));
146155

packages/open-next/src/build/createMiddleware.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import fs from "node:fs";
2+
import fsAsync from "node:fs/promises";
23
import path from "node:path";
34

45
import logger from "../logger.js";
56
import type {
7+
FunctionsConfigManifest,
68
MiddlewareInfo,
79
MiddlewareManifest,
810
} from "../types/next-types.js";
@@ -36,6 +38,42 @@ export async function createMiddleware(
3638
| MiddlewareInfo
3739
| undefined;
3840

41+
if (!middlewareInfo) {
42+
// If there is no middleware info, it might be a node middleware
43+
const functionsConfigManifestPath = path.join(
44+
appBuildOutputPath,
45+
".next/server/functions-config-manifest.json",
46+
);
47+
const functionsConfigManifest = JSON.parse(
48+
await fsAsync
49+
.readFile(functionsConfigManifestPath, "utf8")
50+
.catch(() => "{}"),
51+
) as FunctionsConfigManifest;
52+
53+
// TODO: Handle external node middleware in the future
54+
if (functionsConfigManifest?.functions["/_middleware"]) {
55+
// If we are here, it means that we are using a node middleware
56+
await buildHelper.esbuildAsync(
57+
{
58+
entryPoints: [
59+
path.join(
60+
options.openNextDistDir,
61+
"core",
62+
"nodeMiddlewareHandler.js",
63+
),
64+
],
65+
external: ["./.next/*"],
66+
outfile: path.join(options.buildDir, "middleware.mjs"),
67+
bundle: true,
68+
platform: "node",
69+
},
70+
options,
71+
);
72+
// We return early to not build the edge middleware
73+
return;
74+
}
75+
}
76+
3977
if (config.middleware?.external) {
4078
const outputPath = path.join(outputDir, "middleware");
4179
fs.mkdirSync(outputPath, { recursive: true });

packages/open-next/src/core/edgeFunctionHandler.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export default async function edgeFunctionHandler(
2828
},
2929
},
3030
});
31+
// TODO: use the global waitUntil
3132
await result.waitUntil;
3233
const response = result.response;
3334
return response;
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import type { RequestData } from "types/global";
2+
3+
type EdgeRequest = Omit<RequestData, "page">;
4+
5+
// Do we need Buffer here?
6+
import { Buffer } from "node:buffer";
7+
globalThis.Buffer = Buffer;
8+
9+
// AsyncLocalStorage is needed to be defined globally
10+
import { AsyncLocalStorage } from "node:async_hooks";
11+
globalThis.AsyncLocalStorage = AsyncLocalStorage;
12+
13+
interface NodeMiddleware {
14+
default: (req: {
15+
handler: any;
16+
request: EdgeRequest;
17+
page: "middleware";
18+
}) => Promise<{
19+
response: Response;
20+
waitUntil: Promise<void>;
21+
}>;
22+
middleware: any;
23+
}
24+
25+
let _module: NodeMiddleware | undefined;
26+
27+
export default async function middlewareHandler(
28+
request: EdgeRequest,
29+
): Promise<Response> {
30+
if (!_module) {
31+
// We use await import here so that we are sure that it is loaded after AsyncLocalStorage is defined on globalThis
32+
// TODO: We will probably need to change this at build time when used in a monorepo (or if the location changes)
33+
//@ts-expect-error - This file should be bundled with esbuild
34+
_module = (await import("./.next/server/middleware.js")).default;
35+
}
36+
const adapterFn = _module!.default || _module;
37+
const result = await adapterFn({
38+
handler: _module!.middleware || _module,
39+
request: request,
40+
page: "middleware",
41+
});
42+
// Not sure if we should await it here or defer to the global als
43+
globalThis.__openNextAls
44+
.getStore()
45+
?.pendingPromiseRunner.add(result.waitUntil);
46+
return result.response;
47+
}

packages/open-next/src/core/routing/middleware.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import type { ReadableStream } from "node:stream/web";
22

3-
import { MiddlewareManifest, NextConfig } from "config/index.js";
3+
import {
4+
FunctionsConfigManifest,
5+
MiddlewareManifest,
6+
NextConfig,
7+
} from "config/index.js";
48
import type { InternalEvent, InternalResult } from "types/open-next.js";
59
import { emptyReadableStream } from "utils/stream.js";
610

@@ -20,8 +24,12 @@ import {
2024
} from "./util.js";
2125

2226
const middlewareManifest = MiddlewareManifest;
27+
const functionsConfigManifest = FunctionsConfigManifest;
2328

24-
const middleMatch = getMiddlewareMatch(middlewareManifest);
29+
const middleMatch = getMiddlewareMatch(
30+
middlewareManifest,
31+
functionsConfigManifest,
32+
);
2533

2634
type MiddlewareEvent = InternalEvent & {
2735
responseHeaders?: Record<string, string | string[]>;

packages/open-next/src/core/routing/util.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ import { BuildId, HtmlPages, NextConfig } from "config/index.js";
66
import type { IncomingMessage } from "http/index.js";
77
import { OpenNextNodeResponse } from "http/openNextResponse.js";
88
import { parseHeaders } from "http/util.js";
9-
import type { MiddlewareManifest } from "types/next-types";
9+
import type {
10+
FunctionsConfigManifest,
11+
MiddlewareManifest,
12+
} from "types/next-types";
1013
import type {
1114
InternalEvent,
1215
InternalResult,
@@ -142,7 +145,17 @@ export function convertToQuery(querystring: string) {
142145
*
143146
* @__PURE__
144147
*/
145-
export function getMiddlewareMatch(middlewareManifest: MiddlewareManifest) {
148+
export function getMiddlewareMatch(
149+
middlewareManifest: MiddlewareManifest,
150+
functionsManifest?: FunctionsConfigManifest,
151+
) {
152+
if (functionsManifest) {
153+
return (
154+
functionsManifest.functions["/_middleware"].matchers?.map(
155+
({ regexp }) => new RegExp(regexp),
156+
) ?? [/.*/]
157+
);
158+
}
146159
const rootMiddleware = middlewareManifest.middleware["/"];
147160
if (!rootMiddleware?.matchers) return [];
148161
return rootMiddleware.matchers.map(({ regexp }) => new RegExp(regexp));

packages/open-next/src/types/next-types.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,3 +178,20 @@ export type PluginHandler = (
178178
res: OpenNextNodeResponse,
179179
options: Options,
180180
) => Promise<OpenNextNodeResponse | undefined>;
181+
182+
export interface FunctionsConfigManifest {
183+
version: number;
184+
functions: Record<
185+
string,
186+
{
187+
maxDuration?: number | undefined;
188+
runtime?: "nodejs";
189+
matchers?: Array<{
190+
regexp: string;
191+
originalSource: string;
192+
has?: Rewrite["has"];
193+
missing?: Rewrite["has"];
194+
}>;
195+
}
196+
>;
197+
}

0 commit comments

Comments
 (0)