Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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,
};
},
);
},
};
}
22 changes: 22 additions & 0 deletions packages/open-next/src/utils/regex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Constructs a regular expression for a path that supports separators for multiple platforms
* - Uses posix separators (`/`) as the input that should be made cross-platform.
* - Special characters are escaped by default but can be controlled through opts.escape.
* - Posix separators are always escaped.
*
* @example
* ```ts
* getCrossPlatformPathRegex("./middleware.mjs")
* getCrossPlatformPathRegex(String.raw`\./middleware\.(mjs|cjs)`, { escape: false })
* ```
*/
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("/", String.raw`(?:\/|\\)`);

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

const specialChars = "^([123]+|[123]{1,3})*\\?$";

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(String.raw`test(?:\/|\\)path`);
});
});

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

it("should return cross-platform paths with escaped special characters", () => {
[
["core/resolve.js", String.raw`core(?:\/|\\)resolve\.js`],
["./middleware.mjs", String.raw`\.(?:\/|\\)middleware\.mjs`],
].forEach(([input, output]) =>
expect(getCrossPlatformPathRegex(input).source).toEqual(output),
);
});

it("should return cross-platform paths without escaping special characters", () => {
const regex = getCrossPlatformPathRegex(
String.raw`\./middleware\.(mjs|cjs)`,
{ escape: false },
);
expect(regex.source).toEqual(String.raw`\.(?:\/|\\)middleware\.(mjs|cjs)`);
});
});
Loading