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

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.js";
import { patchCode } from "../astCodePatcher.js";
import type { CodePatcher } from "../codePatcher.js";

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: /runEdgeFunction\(/,
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),
},
},
],
};

/**
* 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
}"
`);
});
});