Skip to content

perf: drop babel to reduce the server bundle size #702

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
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/fast-spiders-hammer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@opennextjs/cloudflare": patch
---

perf: drop `babel` to reduce the server bundle size
2 changes: 2 additions & 0 deletions packages/cloudflare/src/cli/build/bundle-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { getOpenNextConfig } from "../../api/config.js";
import { patchVercelOgLibrary } from "./patches/ast/patch-vercel-og-library.js";
import { patchWebpackRuntime } from "./patches/ast/webpack-runtime.js";
import * as patches from "./patches/index.js";
import { patchDropBabel } from "./patches/plugins/babel.js";
import { inlineBuildId } from "./patches/plugins/build-id.js";
import { inlineDynamicRequires } from "./patches/plugins/dynamic-requires.js";
import { inlineEvalManifest } from "./patches/plugins/eval-manifest.js";
Expand Down Expand Up @@ -101,6 +102,7 @@ export async function bundleServer(buildOpts: BuildOptions): Promise<void> {
inlineLoadManifest(updater, buildOpts),
inlineBuildId(updater),
patchDepdDeprecations(updater),
patchDropBabel(updater),
// Apply updater updates, must be the last plugin
updater.plugin,
] as Plugin[],
Expand Down
193 changes: 193 additions & 0 deletions packages/cloudflare/src/cli/build/patches/plugins/babel.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import { patchCode } from "@opennextjs/aws/build/patch/astCodePatcher.js";
import { describe, expect, test } from "vitest";

import { createEmptyBodyRule, errorInspectRule } from "./babel.js";

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 by 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 by 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
}"
`);
});
});
76 changes: 76 additions & 0 deletions packages/cloudflare/src/cli/build/patches/plugins/babel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* 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 { patchCode } from "@opennextjs/aws/build/patch/astCodePatcher.js";
import type { ContentUpdater, Plugin } from "@opennextjs/aws/plugins/content-updater";
import { getCrossPlatformPathRegex } from "@opennextjs/aws/utils/regex.js";

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

export function patchDropBabel(updater: ContentUpdater): Plugin {
updater.updateContent("drop-babel-next-server", [
{
field: {
filter: getCrossPlatformPathRegex(String.raw`/next/dist/server/next-server\.js$`, {
escape: false,
}),
contentFilter: /runMiddleware\(/,
callback: ({ contents }) => {
contents = patchCode(contents, createEmptyBodyRule("runMiddleware"));
contents = patchCode(contents, createEmptyBodyRule("runEdgeFunction"));
return contents;
},
},
},
]);

updater.updateContent("drop-babel-error-inspect", [
{
field: {
filter: getCrossPlatformPathRegex(String.raw`next/dist/server/node-environment\.js$`, {
escape: false,
}),
contentFilter: /node-environment-extensions\/error-inspect/,
callback: ({ contents }) => patchCode(contents, errorInspectRule),
},
},
]);

return {
name: "drop-babel",
setup() {},
};
}