Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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/new-wombats-crash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@opennextjs/aws": patch
Copy link

Copilot AI Jun 5, 2025

Choose a reason for hiding this comment

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

The changeset only lists @opennextjs/aws but the patchDropBabel changes live in the Open Next package; the affected package should be included here so the release picks up your updates.

Suggested change
"@opennextjs/aws": patch
"@opennextjs/aws": patch
"open-next": patch

Copilot uses AI. Check for mistakes.
---

perf: drop `babel` to reduce the server bundle size
25 changes: 9 additions & 16 deletions packages/open-next/src/build/createServerBundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,7 @@ import {
import * as buildHelper from "./helper.js";
import { installDependencies } from "./installDeps.js";
import { type CodePatcher, applyCodePatches } from "./patch/codePatcher.js";
import {
patchBackgroundRevalidation,
patchEnvVars,
patchFetchCacheForISR,
patchFetchCacheSetMissingWaitUntil,
patchNextServer,
patchUnstableCacheForISR,
patchUseCacheForISR,
} from "./patch/patches/index.js";
import * as patches from "./patch/patches/index.js";

interface CodeCustomization {
// These patches are meant to apply on user and next generated code
Expand Down Expand Up @@ -207,13 +199,14 @@ async function generateBundle(
const additionalCodePatches = codeCustomization?.additionalCodePatches ?? [];

await applyCodePatches(options, tracedFiles, manifests, [
patchFetchCacheSetMissingWaitUntil,
patchFetchCacheForISR,
patchUnstableCacheForISR,
patchNextServer,
patchEnvVars,
patchBackgroundRevalidation,
patchUseCacheForISR,
patches.patchFetchCacheSetMissingWaitUntil,
patches.patchFetchCacheForISR,
patches.patchUnstableCacheForISR,
patches.patchNextServer,
patches.patchEnvVars,
patches.patchBackgroundRevalidation,
patches.patchUseCacheForISR,
patches.patchDropBabel,
...additionalCodePatches,
]);

Expand Down
88 changes: 88 additions & 0 deletions packages/open-next/src/build/patch/patches/dropBabel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/**
* Patches to avoid pulling babel (~4MB).
*
* Details:
* - empty `NextServer#runMiddleware` and `NextServer#runEdgeFunction` that are not used
* - drop `next/dist/server/node-environment-extensions/error-inspect.js`
*/

import { getCrossPlatformPathRegex } from "utils/regex";
import { patchCode } from "../astCodePatcher";
import type { CodePatcher } from "../codePatcher";

export const patchDropBabel: CodePatcher = {
name: "patch-drop-babel",
patches: [
// Empty the body of `NextServer#runMiddleware`
{
field: {
pathFilter: getCrossPlatformPathRegex(
String.raw`/next/dist/server/next-server\.js$`,
{
escape: false,
},
),
contentFilter: /runMiddleware\(/,
patchCode: async ({ code }) =>
patchCode(code, createEmptyBodyRule("runMiddleware")),
},
},
// Empty the body of `NextServer#runEdgeFunction`
{
field: {
pathFilter: getCrossPlatformPathRegex(
String.raw`/next/dist/server/next-server\.js$`,
{
escape: false,
},
),
contentFilter: /runMiddleware\(/,
Copy link

Copilot AI Jun 5, 2025

Choose a reason for hiding this comment

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

The second patch’s contentFilter still matches runMiddleware(; it should match runEdgeFunction( so the edge function body is emptied correctly.

Suggested change
contentFilter: /runMiddleware\(/,
contentFilter: /runEdgeFunction\(/,

Copilot uses AI. Check for mistakes.
patchCode: async ({ code }) =>
patchCode(code, createEmptyBodyRule("runEdgeFunction")),
},
},
// Drop `error-inspect` that pulls babel
{
field: {
pathFilter: getCrossPlatformPathRegex(
String.raw`next/dist/server/node-environment\.js$`,
{
escape: false,
},
),
contentFilter: /error-inspect/,
patchCode: async ({ code }) => patchCode(code, "errorInspectRule"),
Copy link

Copilot AI Jun 5, 2025

Choose a reason for hiding this comment

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

Passing the rule name as a string prevents the actual errorInspectRule from being applied. Use the variable errorInspectRule instead of the literal string.

Suggested change
patchCode: async ({ code }) => patchCode(code, "errorInspectRule"),
patchCode: async ({ code }) => patchCode(code, errorInspectRule),

Copilot uses AI. Check for mistakes.
},
},
],
};

/**
* Swaps the body for a throwing implementation
*
* @param methodName The name of the method
* @returns A rule to replace the body with a `throw`
*/
export function createEmptyBodyRule(methodName: string) {
return `
rule:
pattern:
selector: method_definition
context: "class { async ${methodName}($$$PARAMS) { $$$_ } }"
fix: |-
async ${methodName}($$$PARAMS) {
throw new Error("${methodName} should not be called with OpenNext");
}
`;
}

/**
* Drops `require("./node-environment-extensions/error-inspect");`
*/
export const errorInspectRule = `
rule:
pattern: require("./node-environment-extensions/error-inspect");
fix: |-
// Removed by OpenNext
// require("./node-environment-extensions/error-inspect");
`;
1 change: 1 addition & 0 deletions packages/open-next/src/build/patch/patches/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export {
} from "./patchFetchCacheISR.js";
export { patchFetchCacheSetMissingWaitUntil } from "./patchFetchCacheWaitUntil.js";
export { patchBackgroundRevalidation } from "./patchBackgroundRevalidation.js";
export { patchDropBabel } from "./dropBabel.js";
194 changes: 194 additions & 0 deletions packages/tests-unit/tests/build/patch/patches/dropBabel.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import { patchCode } from "@opennextjs/aws/build/patch/astCodePatcher.js";
import {
createEmptyBodyRule,
errorInspectRule,
} from "@opennextjs/aws/build/patch/patches/dropBabel.js";
import { describe, expect, test } from "vitest";

describe("babel-drop", () => {
test("Drop body", () => {
const code = `
class NextNodeServer extends _baseserver.default {
constructor(options){
// Initialize super class
super(options);
this.handleNextImageRequest = async (req, res, parsedUrl) => { /* ... */ };
}
async handleUpgrade() {
// The web server does not support web sockets, it's only used for HMR in
// development.
}
getEnabledDirectories(dev) {
const dir = dev ? this.dir : this.serverDistDir;
return {
app: (0, _findpagesdir.findDir)(dir, "app") ? true : false,
pages: (0, _findpagesdir.findDir)(dir, "pages") ? true : false
};
}
/**
* This method gets all middleware matchers and execute them when the request
* matches. It will make sure that each middleware exists and is compiled and
* ready to be invoked. The development server will decorate it to add warns
* and errors with rich traces.
*/ async runMiddleware(params) {
if (process.env.NEXT_MINIMAL) {
throw new Error('invariant: runMiddleware should not be called in minimal mode');
}
// Middleware is skipped for on-demand revalidate requests
if ((0, _apiutils.checkIsOnDemandRevalidate)(params.request, this.renderOpts.previewProps).isOnDemandRevalidate) {
return {
response: new Response(null, {
headers: {
'x-middleware-next': '1'
}
})
};
}
// ...
}
async runEdgeFunction(params) {
if (process.env.NEXT_MINIMAL) {
throw new Error('Middleware is not supported in minimal mode.');
}
let edgeInfo;
const { query, page, match } = params;
if (!match) await this.ensureEdgeFunction({
page,
appPaths: params.appPaths,
url: params.req.url
});
// ...
}
// ...
}`;

expect(
patchCode(code, createEmptyBodyRule("runMiddleware")),
).toMatchInlineSnapshot(`
"class NextNodeServer extends _baseserver.default {
constructor(options){
// Initialize super class
super(options);
this.handleNextImageRequest = async (req, res, parsedUrl) => { /* ... */ };
}
async handleUpgrade() {
// The web server does not support web sockets, it's only used for HMR in
// development.
}
getEnabledDirectories(dev) {
const dir = dev ? this.dir : this.serverDistDir;
return {
app: (0, _findpagesdir.findDir)(dir, "app") ? true : false,
pages: (0, _findpagesdir.findDir)(dir, "pages") ? true : false
};
}
/**
* This method gets all middleware matchers and execute them when the request
* matches. It will make sure that each middleware exists and is compiled and
* ready to be invoked. The development server will decorate it to add warns
* and errors with rich traces.
*/ async runMiddleware(params) {
throw new Error("runMiddleware should not be called with OpenNext");
}
async runEdgeFunction(params) {
if (process.env.NEXT_MINIMAL) {
throw new Error('Middleware is not supported in minimal mode.');
}
let edgeInfo;
const { query, page, match } = params;
if (!match) await this.ensureEdgeFunction({
page,
appPaths: params.appPaths,
url: params.req.url
});
// ...
}
// ...
}"
`);

expect(
patchCode(code, createEmptyBodyRule("runEdgeFunction")),
).toMatchInlineSnapshot(`
"class NextNodeServer extends _baseserver.default {
constructor(options){
// Initialize super class
super(options);
this.handleNextImageRequest = async (req, res, parsedUrl) => { /* ... */ };
}
async handleUpgrade() {
// The web server does not support web sockets, it's only used for HMR in
// development.
}
getEnabledDirectories(dev) {
const dir = dev ? this.dir : this.serverDistDir;
return {
app: (0, _findpagesdir.findDir)(dir, "app") ? true : false,
pages: (0, _findpagesdir.findDir)(dir, "pages") ? true : false
};
}
/**
* This method gets all middleware matchers and execute them when the request
* matches. It will make sure that each middleware exists and is compiled and
* ready to be invoked. The development server will decorate it to add warns
* and errors with rich traces.
*/ async runMiddleware(params) {
if (process.env.NEXT_MINIMAL) {
throw new Error('invariant: runMiddleware should not be called in minimal mode');
}
// Middleware is skipped for on-demand revalidate requests
if ((0, _apiutils.checkIsOnDemandRevalidate)(params.request, this.renderOpts.previewProps).isOnDemandRevalidate) {
return {
response: new Response(null, {
headers: {
'x-middleware-next': '1'
}
})
};
}
// ...
}
async runEdgeFunction(params) {
throw new Error("runEdgeFunction should not be called with OpenNext");
}
// ...
}"
`);
});

test("Error Inspect", () => {
const code = `
// This file should be imported before any others. It sets up the environment
// for later imports to work properly.
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
require("./node-environment-baseline");
require("./node-environment-extensions/error-inspect");
require("./node-environment-extensions/random");
require("./node-environment-extensions/date");
require("./node-environment-extensions/web-crypto");
require("./node-environment-extensions/node-crypto");
//# sourceMappingURL=node-environment.js.map
}`;

expect(patchCode(code, errorInspectRule)).toMatchInlineSnapshot(`
"// This file should be imported before any others. It sets up the environment
// for later imports to work properly.
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
require("./node-environment-baseline");
// Removed by OpenNext
// require("./node-environment-extensions/error-inspect");
require("./node-environment-extensions/random");
require("./node-environment-extensions/date");
require("./node-environment-extensions/web-crypto");
require("./node-environment-extensions/node-crypto");
//# sourceMappingURL=node-environment.js.map
}"
`);
});
});