Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/hungry-geckos-knock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@opennextjs/aws": patch
---

refactor: use utility for cross-platform path regex construction
6 changes: 4 additions & 2 deletions packages/open-next/src/build/createImageOptimizationBundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import path from "node:path";
import logger from "../logger.js";
import { openNextReplacementPlugin } from "../plugins/replacement.js";
import { openNextResolvePlugin } from "../plugins/resolve.js";
import { getCrossPlatformPathRegex } from "../utils/regex.js";
import * as buildHelper from "./helper.js";
import { installDependencies } from "./installDeps.js";

Expand Down Expand Up @@ -39,8 +40,9 @@ export async function createImageOptimizationBundle(
plugins.push(
openNextReplacementPlugin({
name: "opennext-14.1.1-image-optimization",
target:
/plugins(\/|\\)image-optimization(\/|\\)image-optimization\.js/g,
target: getCrossPlatformPathRegex(
"plugins/image-optimization/image-optimization.js",
),
replacements: [
require.resolve(
"../adapters/plugins/image-optimization/image-optimization.replacement.js",
Expand Down
5 changes: 3 additions & 2 deletions packages/open-next/src/build/createServerBundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import logger from "../logger.js";
import { minifyAll } from "../minimize-js.js";
import { openNextReplacementPlugin } from "../plugins/replacement.js";
import { openNextResolvePlugin } from "../plugins/resolve.js";
import { getCrossPlatformPathRegex } from "../utils/regex.js";
import { bundleNextServer } from "./bundleNextServer.js";
import { compileCache } from "./compileCache.js";
import { copyTracedFiles } from "./copyTracedFiles.js";
Expand Down Expand Up @@ -185,15 +186,15 @@ async function generateBundle(
const plugins = [
openNextReplacementPlugin({
name: `requestHandlerOverride ${name}`,
target: /core(\/|\\)requestHandler\.js/g,
target: getCrossPlatformPathRegex("core/requestHandler.js"),
deletes: [
...(disableNextPrebundledReact ? ["applyNextjsPrebundledReact"] : []),
...(disableRouting ? ["withRouting"] : []),
],
}),
openNextReplacementPlugin({
name: `utilOverride ${name}`,
target: /core(\/|\\)util\.js/g,
target: getCrossPlatformPathRegex("core/util.js"),
deletes: [
...(disableNextPrebundledReact ? ["requireHooks"] : []),
...(isBefore13413 ? ["trustHostHeader"] : ["requestHandlerHost"]),
Expand Down
3 changes: 2 additions & 1 deletion packages/open-next/src/build/edge/createEdgeBundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import logger from "../../logger.js";
import { openNextEdgePlugins } from "../../plugins/edge.js";
import { openNextReplacementPlugin } from "../../plugins/replacement.js";
import { openNextResolvePlugin } from "../../plugins/resolve.js";
import { getCrossPlatformPathRegex } from "../../utils/regex.js";
import { type BuildOptions, isEdgeRuntime } from "../helper.js";
import { copyOpenNextConfig, esbuildAsync } from "../helper.js";

Expand Down Expand Up @@ -84,7 +85,7 @@ export async function buildEdgeBundle({
}),
openNextReplacementPlugin({
name: "externalMiddlewareOverrides",
target: /adapters(\/|\\)middleware\.js/g,
target: getCrossPlatformPathRegex("adapters/middleware.js"),
deletes: includeCache ? [] : ["includeCacheInMiddleware"],
}),
openNextEdgePlugins({
Expand Down
18 changes: 11 additions & 7 deletions packages/open-next/src/plugins/edge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
loadRoutesManifest,
} from "../adapters/config/util.js";
import logger from "../logger.js";
import { getCrossPlatformPathRegex } from "../utils/regex.js";

export interface IPluginSettings {
nextDir: string;
Expand Down Expand Up @@ -58,11 +59,14 @@ export function openNextEdgePlugins({
logger.debug(chalk.blue("OpenNext Edge plugin"));
if (edgeFunctionHandlerPath) {
// If we bundle the routing, we need to resolve the middleware
build.onResolve({ filter: /\.(\/|\\)middleware.mjs/g }, () => {
return {
path: edgeFunctionHandlerPath,
};
});
build.onResolve(
{ filter: getCrossPlatformPathRegex("./middleware.mjs") },
() => {
return {
path: edgeFunctionHandlerPath,
};
},
);
}

build.onResolve({ filter: /\.(mjs|wasm)$/g }, () => {
Expand Down Expand Up @@ -94,7 +98,7 @@ export function openNextEdgePlugins({

// We inject the entry files into the edgeFunctionHandler
build.onLoad(
{ filter: /(\/|\\)edgeFunctionHandler.js/g },
{ filter: getCrossPlatformPathRegex("/edgeFunctionHandler.js") },
async (args) => {
let contents = readFileSync(args.path, "utf-8");
contents = `
Expand Down Expand Up @@ -164,7 +168,7 @@ ${contents}
);

build.onLoad(
{ filter: /adapters(\/|\\)config(\/|\\)index/g },
{ filter: getCrossPlatformPathRegex("adapters/config/index") },
async () => {
const NextConfig = loadConfig(nextDir);
const BuildId = loadBuildId(nextDir);
Expand Down
52 changes: 28 additions & 24 deletions packages/open-next/src/plugins/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
import type { ImageLoader, OriginResolver, Warmer } from "types/overrides";

import logger from "../logger.js";
import { getCrossPlatformPathRegex } from "../utils/regex.js";

export interface IPluginSettings {
overrides?: {
Expand Down Expand Up @@ -81,31 +82,34 @@ export function openNextResolvePlugin({
chalk.blue("OpenNext Resolve plugin"),
fnName ? `for ${fnName}` : "",
);
build.onLoad({ filter: /core(\/|\\)resolve\.js/g }, async (args) => {
let contents = readFileSync(args.path, "utf-8");
const overridesEntries = Object.entries(overrides ?? {});
for (let [overrideName, overrideValue] of overridesEntries) {
if (!overrideValue) {
continue;
}
if (overrideName === "wrapper" && overrideValue === "cloudflare") {
// "cloudflare" is deprecated and replaced by "cloudflare-edge".
overrideValue = "cloudflare-edge";
}
const folder =
nameToFolder[overrideName as keyof typeof nameToFolder];
const defaultOverride =
defaultOverrides[overrideName as keyof typeof defaultOverrides];
build.onLoad(
{ filter: getCrossPlatformPathRegex("core/resolve.js") },
async (args) => {
let contents = readFileSync(args.path, "utf-8");
const overridesEntries = Object.entries(overrides ?? {});
for (let [overrideName, overrideValue] of overridesEntries) {
if (!overrideValue) {
continue;
}
if (overrideName === "wrapper" && overrideValue === "cloudflare") {
// "cloudflare" is deprecated and replaced by "cloudflare-edge".
overrideValue = "cloudflare-edge";
}
const folder =
nameToFolder[overrideName as keyof typeof nameToFolder];
const defaultOverride =
defaultOverrides[overrideName as keyof typeof defaultOverrides];

contents = contents.replace(
`../overrides/${folder}/${defaultOverride}.js`,
`../overrides/${folder}/${getOverrideOrDummy(overrideValue)}.js`,
);
}
return {
contents,
};
});
contents = contents.replace(
`../overrides/${folder}/${defaultOverride}.js`,
`../overrides/${folder}/${getOverrideOrDummy(overrideValue)}.js`,
);
}
return {
contents,
};
},
);
},
};
}
10 changes: 10 additions & 0 deletions packages/open-next/src/utils/regex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export function getCrossPlatformPathRegex(
regex: string,
opts: { escape: boolean } = { escape: true },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the escape options ? It's not used anywhere.
My take on this is that we should only escape path related stuff here (i.e. / the starting ./ and file extensions)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the escape options ?

That's something I asked James.

If you only want to escape a path then you would use the default value (true)

But sometimes (i.e. ESBuild filter), you might want to have more control on the pattern and do not escape special chars.

) {
const newExpr = (
opts.escape ? regex.replace(/([[\]().*+?^$|])/g, "\\$1") : regex
).replaceAll("/", "(?:\\/|\\\\)");

return new RegExp(newExpr, "g");
}
22 changes: 22 additions & 0 deletions packages/tests-unit/tests/utils/regex.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { getCrossPlatformPathRegex } from "@opennextjs/aws/utils/regex.js";

const specialChars = "^([123]+|[123]*)?$";

describe("getCrossPlatformPathRegex", () => {
it("should return a regex without escaping characters", () => {
const regexp = getCrossPlatformPathRegex(specialChars, { escape: false });
expect(regexp.source).toEqual(specialChars);
});

it("should always create cross-platform separators", () => {
[true, false].forEach((v) => {
const regexp = getCrossPlatformPathRegex("test/path", { escape: v });
expect(regexp.source).toEqual("test(?:\\/|\\\\)path");
});
});

it("should return a regex with escaped characters", () => {
const regexp = getCrossPlatformPathRegex(specialChars, { escape: true });
expect(regexp.source).toEqual("\\^\\(\\[123\\]\\+\\|\\[123\\]\\*\\)\\?\\$");
});
});
Loading