Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
fa74e70
refactor: remove createServerBundle.ts and integrate its functionalit…
conico974 Jun 27, 2026
33a6eff
feat: implement default overrides for bundle configurations and add t…
conico974 Jun 27, 2026
fe002e1
format
conico974 Jun 27, 2026
9ee9515
fix ts issue
conico974 Jun 27, 2026
6fad0c6
feat: add default overrides for AWS adapter and integrate Cloudflare …
conico974 Jun 27, 2026
84397ab
Revert "feat: add default overrides for AWS adapter and integrate Clo…
conico974 Jun 27, 2026
f22d37f
feat: enhance AWS adapter with default overrides and improve middlewa…
conico974 Jun 27, 2026
a3165f0
refactor(core): return ValidateConfigResult from validateConfig inste…
conico974 Jun 28, 2026
6296e94
refactor(core): extract buildOpenNextOutput, export OpenNextOutput type
conico974 Jun 28, 2026
4e82821
refactor(core): branch on ValidateConfigResult in compileOpenNextConfig
conico974 Jun 28, 2026
0af2e73
feat(core): overridable validateConfig and generic generateOutput on …
conico974 Jun 28, 2026
bab47f1
fix(core): use require.resolve for full-path overrides in resolve plugin
conico974 Jun 28, 2026
51b5e3b
format
conico974 Jun 28, 2026
a437de1
feat(core): enforce mandatory externals in serverBundle and update re…
conico974 Aug 1, 2026
cd8f83e
refactor(core): enhance resolve plugin to support dynamic override re…
conico974 Aug 1, 2026
aa34c72
feat(middleware): implement middleware bundle support and enhance bui…
conico974 Jun 29, 2026
464d340
feat(middleware): enhance middleware support with runtime patches and…
conico974 Jul 14, 2026
65cdda7
feat(opentelemetry): add OpenTelemetry global utils patching and tests
conico974 Jul 14, 2026
c9bf7f8
Merge remote-tracking branch 'origin/main' into vicb/review-pr-38
vicb Aug 28, 2026
354950b
Merge remote-tracking branch 'origin/main' into vicb/review-pr-38
vicb Aug 31, 2026
62258d3
fixup! minor fixes
vicb Aug 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ test("Middleware Rewrite", async ({ page }) => {
await expect(el).toBeVisible();
});

test("Middleware Rewrite handles HEAD requests", async ({ request }) => {
const response = await request.head("/rewrite");

expect(response.status()).toBe(200);
expect(await response.body()).toHaveLength(0);
});

test("Middleware Rewrite External Image", async ({ page }) => {
let responsePromise = new Promise<PwResponse>((resolve) => {
page.on("response", async (resp) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";

export function middleware(request: NextRequest) {
export default function middleware(request: NextRequest) {
const path = request.nextUrl.pathname; //new URL(request.url).pathname;

const host = request.headers.get("host");
Expand Down
37 changes: 36 additions & 1 deletion packages/cloudflare/src/cli/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ import { compileImages } from "./build/open-next/compile-images.js";
import { compileInit } from "./build/open-next/compile-init.js";
import { compileSkewProtection } from "./build/open-next/compile-skew-protection.js";
import { compileDurableObjects } from "./build/open-next/compileDurableObjects.js";
import { patchWebpackMiddlewareRuntime } from "./build/patches/ast/webpack-runtime.js";
import { inlineLoadManifest } from "./build/patches/plugins/load-manifest.js";
import { patchOpenTelemetryGlobalUtils } from "./build/patches/plugins/opentelemetry.js";
import { patchResRevalidate } from "./build/patches/plugins/res-revalidate.js";
import { patchTurbopackRuntime } from "./build/patches/plugins/turbopack.js";
import { patchUseCacheIO } from "./build/patches/plugins/use-cache.js";
Expand Down Expand Up @@ -68,7 +70,40 @@ export default buildAdapter((config: OpenNextConfig, buildOpts: BuildOptions) =>
isInCloudflare: true,
}),
],
additionalCodePatches: [patchResRevalidate, patchUseCacheIO, patchTurbopackRuntime],
additionalCodePatches: [
patchResRevalidate,
patchUseCacheIO,
patchOpenTelemetryGlobalUtils,
patchTurbopackRuntime,
],
},
middlewareBundle: {
useEdgeConfig: true,
banner: (_name: string) => [
`globalThis.monorepoPackagePath = "${normalizePath(packagePath)}";`,
`import { Buffer } from "node:buffer";
globalThis.Buffer = Buffer;

import { AsyncLocalStorage } from "node:async_hooks";
globalThis.AsyncLocalStorage = AsyncLocalStorage;

`,
],
additionalPlugins: (updater: ContentUpdater, outputs: NextAdapterOutputs) => [
inlineRouteHandler(updater, outputs, packagePath),
inlineLoadManifest(updater, buildOpts),
openNextEdgePlugins({
nextDir: path.join(buildOpts.appBuildOutputPath, ".next"),
isInCloudflare: true,
}),
],
additionalCodePatches: [
patchResRevalidate,
patchUseCacheIO,
patchOpenTelemetryGlobalUtils,
patchWebpackMiddlewareRuntime,
patchTurbopackRuntime,
],
},
afterServerBundle: async (buildOpts, _config) => {
compileDurableObjects(buildOpts);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { patchCode } from "@opennextjs/core/build/patch/astCodePatcher.js";
import { describe, expect, test } from "vitest";

import { buildMultipleChunksRule, singleChunkRule } from "./webpack-runtime.js";
import {
buildMultipleChunksRule,
patchWebpackMiddlewareRuntime,
singleChunkRule,
} from "./webpack-runtime.js";

describe("webpack runtime", () => {
describe("multiple chunks", () => {
Expand Down Expand Up @@ -110,4 +114,33 @@ describe("webpack runtime", () => {
`);
});
});

describe("middleware runtime", () => {
const runtimeCode = `
t.f.require=(o,n)=>{e[o]||(658!=o?r(require("./chunks/"+t.u(o))):e[o]=1)}
`;

test("uses the middleware traced chunks", async () => {
const patch = patchWebpackMiddlewareRuntime.patches[0]!;
const result = await patch.patchCode({
code: runtimeCode,
tracedFiles: ["/app/.open-next/middleware/app/.next/server/chunks/123.js"],
} as never);

expect(result).toContain('case 123: r(require("./chunks/123.js")); break;');
expect(result).not.toContain('require("./chunks/" +');
});

test("matches minified runtime code", () => {
expect(patchWebpackMiddlewareRuntime.patches[0]!.contentFilter?.test(runtimeCode)).toBe(true);
});

test("supports middleware with no chunks", async () => {
const patch = patchWebpackMiddlewareRuntime.patches[0]!;
const result = await patch.patchCode({ code: runtimeCode, tracedFiles: [] } as never);

expect(result).toContain("case 658: e[o] = 1; break;");
expect(result).not.toContain('require("./chunks/" +');
});
});
});
60 changes: 50 additions & 10 deletions packages/cloudflare/src/cli/build/patches/ast/webpack-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
*
* For multiple chunks:
* switch (chunkId) {
* case ID1: installChunk(require("./chunks/ID1"); break;
* case ID2: installChunk(require("./chunks/ID2"); break;
* case ID1: installChunk(require("./chunks/ID1")); break;
* case ID2: installChunk(require("./chunks/ID2")); break;
* // ...
* case SELF_ID: installedChunks[chunkId] = 1; break;
* default: throw new Error(`Unknown chunk ${chunkId}`);
Expand All @@ -25,6 +25,8 @@ import { join } from "node:path";

import { type BuildOptions, getPackagePath } from "@opennextjs/core/build/helper.js";
import { patchCode } from "@opennextjs/core/build/patch/astCodePatcher.js";
import type { CodePatcher } from "@opennextjs/core/build/patch/codePatcher.js";
import { getCrossPlatformPathRegex } from "@opennextjs/core/utils/regex.js";

// Inline the code when there are multiple chunks
export function buildMultipleChunksRule(chunks: number[]) {
Expand All @@ -50,7 +52,7 @@ ${chunks.map((chunk) => ` case ${chunk}: $INSTALL(require("./chunks/${ch

// Inline the code when there is a single chunk.
// For example when there is a single Pages API route.
// Note: The chunk does not always exist which explain the need for the try...catch.
// Note: The chunk does not always exist, which explains the need for the try...catch.
export const singleChunkRule = `
rule:
pattern: ($CHUNK_ID, $_PROMISES) => { $$$ }
Expand All @@ -69,6 +71,44 @@ fix: |
}
`;

export function patchWebpackRuntimeCode(code: string, chunks: number[]): string {
let patched = patchCode(code, buildMultipleChunksRule(chunks));
patched = patchCode(patched, singleChunkRule);
return patched;
}

function getWebpackChunks(tracedFiles: string[]): number[] {
const chunks = new Set<number>();
for (const file of tracedFiles) {
const match = file.match(/[\\/]chunks[\\/](\d+)\.js$/);
if (match) {
chunks.add(Number(match[1]));
}
}
return Array.from(chunks);
}

/**
* Rewrites webpack runtime chunk loading in an external middleware bundle.
*
* The middleware trace already contains every required chunk. Using the traced
* paths keeps the generated requires visible to the Worker bundler and also
* supports middleware bundles with no chunks.
*/
export const patchWebpackMiddlewareRuntime: CodePatcher = {
name: "inline-webpack-chunks",
patches: [
{
pathFilter: getCrossPlatformPathRegex(String.raw`webpack(?:-api)?-runtime\.js$`, {
escape: false,
}),
contentFilter: /require\("\.\/chunks\/"\s*\+/,
patchCode: async ({ code, tracedFiles }) =>
patchWebpackRuntimeCode(code, getWebpackChunks(tracedFiles)),
},
],
};

/**
* Fixes the webpack-runtime.js and webpack-api-runtime.js files by inlining
* the webpack dynamic requires.
Expand All @@ -84,11 +124,12 @@ export async function patchWebpackRuntime(buildOpts: BuildOptions) {
);

// Look for all the chunks.
const chunks = readdirSync(join(dotNextServerDir, "chunks"))
.filter((chunk) => /^\d+\.js$/.test(chunk))
.map((chunk) => {
return Number(chunk.replace(/\.js$/, ""));
});
const chunksDir = join(dotNextServerDir, "chunks");
const chunks = existsSync(chunksDir)
? readdirSync(chunksDir)
.filter((chunk) => /^\d+\.js$/.test(chunk))
.map((chunk) => Number(chunk.replace(/\.js$/, "")))
: [];

patchFile(join(dotNextServerDir, "webpack-runtime.js"), chunks);
patchFile(join(dotNextServerDir, "webpack-api-runtime.js"), chunks);
Expand All @@ -103,8 +144,7 @@ export async function patchWebpackRuntime(buildOpts: BuildOptions) {
function patchFile(filename: string, chunks: number[]) {
if (existsSync(filename)) {
let code = readFileSync(filename, "utf-8");
code = patchCode(code, buildMultipleChunksRule(chunks));
code = patchCode(code, singleChunkRule);
code = patchWebpackRuntimeCode(code, chunks);
writeFileSync(filename, code);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { expect, test } from "vitest";

import { patchOpenTelemetryGlobalUtilsCode } from "./opentelemetry.js";

const code = `"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.unregisterGlobal = exports.getGlobal = exports.registerGlobal = void 0;
const platform_1 = require("../platform");
const version_1 = require("../version");
const semver_1 = require("./semver");
const major = version_1.VERSION.split('.')[0];
const GLOBAL_OPENTELEMETRY_API_KEY = Symbol.for(\`opentelemetry.js.api.\${major}\`);
const _global = platform_1._globalThis;
function registerGlobal(type, instance, diag, allowOverride = false) {
const api = (_global[GLOBAL_OPENTELEMETRY_API_KEY] = {
version: version_1.VERSION,
});
return semver_1.isCompatible(api.version);
}
`;

test("patches OpenTelemetry to use the Cloudflare global", () => {
const patched = patchOpenTelemetryGlobalUtilsCode(code);

expect(patched).toContain('// const platform_1 = require("../platform");');
expect(patched).toContain("const _global = globalThis;");
expect(patched).toContain('const version_1 = require("../version");');
expect(patched).toContain("return semver_1.isCompatible(api.version);");
expect(patched).not.toContain("platform_1._globalThis");
});

test("captures the platform and global variable names", () => {
const patched = patchOpenTelemetryGlobalUtilsCode(`
const nodePlatform = require("../platform");
const globalStore = nodePlatform._globalThis;
const unrelatedStore = otherPlatform._globalThis;
`);

expect(patched).toContain('// const nodePlatform = require("../platform");');
expect(patched).toContain("const globalStore = globalThis;");
expect(patched).toContain("const unrelatedStore = otherPlatform._globalThis;");
});
41 changes: 41 additions & 0 deletions packages/cloudflare/src/cli/build/patches/plugins/opentelemetry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { patchCode } from "@opennextjs/core/build/patch/astCodePatcher.js";
import type { CodePatcher } from "@opennextjs/core/build/patch/codePatcher.js";
import { getCrossPlatformPathRegex } from "@opennextjs/core/utils/regex.js";

export const commentPlatformImportRule = `
rule:
pattern: const $PLATFORM = require("../platform");
fix: // const $PLATFORM = require("../platform");
`;

export const replaceGlobalRule = `
rule:
all:
- pattern: const $GLOBAL = $PLATFORM._globalThis;
- inside:
kind: program
stopBy: end
has:
pattern: const $PLATFORM = require("../platform");
fix: const $GLOBAL = globalThis;
`;

export function patchOpenTelemetryGlobalUtilsCode(code: string): string {
let patchedCode = patchCode(code, replaceGlobalRule);
patchedCode = patchCode(patchedCode, commentPlatformImportRule);
return patchedCode;
}

export const patchOpenTelemetryGlobalUtils: CodePatcher = {
name: "patch-opentelemetry-global-utils",
patches: [
{
pathFilter: getCrossPlatformPathRegex(
String.raw`@opentelemetry/api/build/src/internal/global-utils\.js$`,
{ escape: false }
),
contentFilter: /require\(["']\.\.\/platform["']\)/,
patchCode: async ({ code }) => patchOpenTelemetryGlobalUtilsCode(code),
},
],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { describe, expect, test } from "vitest";

import { patchTurbopackRuntime } from "./turbopack.js";

describe("turbopack runtime", () => {
const runtimeCode = `
function loadRuntimeChunkPath(chunkPath) {
const resolved = chunkPath;
return require(resolved);
}
`;

test("uses the middleware traced chunks", async () => {
const patch = patchTurbopackRuntime.patches[0]!;
const result = await patch.patchCode({
code: runtimeCode,
tracedFiles: ["/app/.open-next/middleware/app/.next/server/chunks/ssr/chunk.js"],
} as never);

expect(result).toContain('case "server/chunks/ssr/chunk.js"');
expect(result).toContain(
'return require("/app/.open-next/middleware/app/.next/server/chunks/ssr/chunk.js")'
);
});

test("normalizes Windows paths and excludes the runtime", async () => {
const patch = patchTurbopackRuntime.patches[0]!;
const result = await patch.patchCode({
code: runtimeCode,
tracedFiles: [
String.raw`C:\app\.open-next\middleware\app\.next\server\chunks\ssr\chunk.js`,
String.raw`C:\app\.open-next\middleware\app\.next\server\chunks\[turbopack]_runtime.js`,
],
} as never);

expect(result).toContain('case "server/chunks/ssr/chunk.js"');
expect(result).toContain(
'return require("C:/app/.open-next/middleware/app/.next/server/chunks/ssr/chunk.js")'
);
expect(result).not.toContain('case "server/chunks/[turbopack]_runtime.js"');
});

test("supports middleware with no chunks", async () => {
const patch = patchTurbopackRuntime.patches[0]!;
const result = await patch.patchCode({ code: runtimeCode, tracedFiles: [] } as never);

expect(result).toContain("function requireChunk(chunkPath)");
expect(result).toContain("default:");
});
});
Loading
Loading