Skip to content

Commit

Permalink
refactor: use utility for cross-platform path regex construction (#701)
Browse files Browse the repository at this point in the history
* create utility

* replace usage with the utility

* changeset

* fix formatting

* add comment

* add some additional special chars and use string.raw

* add extra tests

* add another string.raw

* use string.raw in comment example too

* Update packages/open-next/src/utils/regex.ts

* change options
  • Loading branch information
james-elicx authored Jan 17, 2025
1 parent 25d317c commit 00ce837
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 36 deletions.
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,
};
},
);
},
};
}
27 changes: 27 additions & 0 deletions packages/open-next/src/utils/regex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
type Options = {
escape?: boolean;
flags?: string;
};

/**
* 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,
{ escape: shouldEscape = true, flags = "g" }: Options = {},
) {
const newExpr = (
shouldEscape ? regex.replace(/([[\]().*+?^$|{}\\])/g, "\\$1") : regex
).replaceAll("/", String.raw`(?:\/|\\)`);

return new RegExp(newExpr, flags);
}
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)`);
});
});

0 comments on commit 00ce837

Please sign in to comment.