Skip to content

Commit de687b2

Browse files
authored
perf: drop babel to reduce the server bundle size (#891)
* perf: drop `babel` to reduce the server bundle size * fixup: thanks copilot! * fixup! add .js
1 parent caa57b3 commit de687b2

File tree

5 files changed

+297
-16
lines changed

5 files changed

+297
-16
lines changed

.changeset/new-wombats-crash.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@opennextjs/aws": patch
3+
---
4+
5+
perf: drop `babel` to reduce the server bundle size

packages/open-next/src/build/createServerBundle.ts

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,7 @@ import {
2121
import * as buildHelper from "./helper.js";
2222
import { installDependencies } from "./installDeps.js";
2323
import { type CodePatcher, applyCodePatches } from "./patch/codePatcher.js";
24-
import {
25-
patchBackgroundRevalidation,
26-
patchEnvVars,
27-
patchFetchCacheForISR,
28-
patchFetchCacheSetMissingWaitUntil,
29-
patchNextServer,
30-
patchUnstableCacheForISR,
31-
patchUseCacheForISR,
32-
} from "./patch/patches/index.js";
24+
import * as patches from "./patch/patches/index.js";
3325

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

209201
await applyCodePatches(options, tracedFiles, manifests, [
210-
patchFetchCacheSetMissingWaitUntil,
211-
patchFetchCacheForISR,
212-
patchUnstableCacheForISR,
213-
patchNextServer,
214-
patchEnvVars,
215-
patchBackgroundRevalidation,
216-
patchUseCacheForISR,
202+
patches.patchFetchCacheSetMissingWaitUntil,
203+
patches.patchFetchCacheForISR,
204+
patches.patchUnstableCacheForISR,
205+
patches.patchNextServer,
206+
patches.patchEnvVars,
207+
patches.patchBackgroundRevalidation,
208+
patches.patchUseCacheForISR,
209+
patches.patchDropBabel,
217210
...additionalCodePatches,
218211
]);
219212

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/**
2+
* Patches to avoid pulling babel (~4MB).
3+
*
4+
* Details:
5+
* - empty `NextServer#runMiddleware` and `NextServer#runEdgeFunction` that are not used
6+
* - drop `next/dist/server/node-environment-extensions/error-inspect.js`
7+
*/
8+
9+
import { getCrossPlatformPathRegex } from "utils/regex.js";
10+
import { patchCode } from "../astCodePatcher.js";
11+
import type { CodePatcher } from "../codePatcher.js";
12+
13+
export const patchDropBabel: CodePatcher = {
14+
name: "patch-drop-babel",
15+
patches: [
16+
// Empty the body of `NextServer#runMiddleware`
17+
{
18+
field: {
19+
pathFilter: getCrossPlatformPathRegex(
20+
String.raw`/next/dist/server/next-server\.js$`,
21+
{
22+
escape: false,
23+
},
24+
),
25+
contentFilter: /runMiddleware\(/,
26+
patchCode: async ({ code }) =>
27+
patchCode(code, createEmptyBodyRule("runMiddleware")),
28+
},
29+
},
30+
// Empty the body of `NextServer#runEdgeFunction`
31+
{
32+
field: {
33+
pathFilter: getCrossPlatformPathRegex(
34+
String.raw`/next/dist/server/next-server\.js$`,
35+
{
36+
escape: false,
37+
},
38+
),
39+
contentFilter: /runEdgeFunction\(/,
40+
patchCode: async ({ code }) =>
41+
patchCode(code, createEmptyBodyRule("runEdgeFunction")),
42+
},
43+
},
44+
// Drop `error-inspect` that pulls babel
45+
{
46+
field: {
47+
pathFilter: getCrossPlatformPathRegex(
48+
String.raw`next/dist/server/node-environment\.js$`,
49+
{
50+
escape: false,
51+
},
52+
),
53+
contentFilter: /error-inspect/,
54+
patchCode: async ({ code }) => patchCode(code, errorInspectRule),
55+
},
56+
},
57+
],
58+
};
59+
60+
/**
61+
* Swaps the body for a throwing implementation
62+
*
63+
* @param methodName The name of the method
64+
* @returns A rule to replace the body with a `throw`
65+
*/
66+
export function createEmptyBodyRule(methodName: string) {
67+
return `
68+
rule:
69+
pattern:
70+
selector: method_definition
71+
context: "class { async ${methodName}($$$PARAMS) { $$$_ } }"
72+
fix: |-
73+
async ${methodName}($$$PARAMS) {
74+
throw new Error("${methodName} should not be called with OpenNext");
75+
}
76+
`;
77+
}
78+
79+
/**
80+
* Drops `require("./node-environment-extensions/error-inspect");`
81+
*/
82+
export const errorInspectRule = `
83+
rule:
84+
pattern: require("./node-environment-extensions/error-inspect");
85+
fix: |-
86+
// Removed by OpenNext
87+
// require("./node-environment-extensions/error-inspect");
88+
`;

packages/open-next/src/build/patch/patches/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ export {
77
} from "./patchFetchCacheISR.js";
88
export { patchFetchCacheSetMissingWaitUntil } from "./patchFetchCacheWaitUntil.js";
99
export { patchBackgroundRevalidation } from "./patchBackgroundRevalidation.js";
10+
export { patchDropBabel } from "./dropBabel.js";
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
import { patchCode } from "@opennextjs/aws/build/patch/astCodePatcher.js";
2+
import {
3+
createEmptyBodyRule,
4+
errorInspectRule,
5+
} from "@opennextjs/aws/build/patch/patches/dropBabel.js";
6+
import { describe, expect, test } from "vitest";
7+
8+
describe("babel-drop", () => {
9+
test("Drop body", () => {
10+
const code = `
11+
class NextNodeServer extends _baseserver.default {
12+
constructor(options){
13+
// Initialize super class
14+
super(options);
15+
this.handleNextImageRequest = async (req, res, parsedUrl) => { /* ... */ };
16+
}
17+
async handleUpgrade() {
18+
// The web server does not support web sockets, it's only used for HMR in
19+
// development.
20+
}
21+
getEnabledDirectories(dev) {
22+
const dir = dev ? this.dir : this.serverDistDir;
23+
return {
24+
app: (0, _findpagesdir.findDir)(dir, "app") ? true : false,
25+
pages: (0, _findpagesdir.findDir)(dir, "pages") ? true : false
26+
};
27+
}
28+
/**
29+
* This method gets all middleware matchers and execute them when the request
30+
* matches. It will make sure that each middleware exists and is compiled and
31+
* ready to be invoked. The development server will decorate it to add warns
32+
* and errors with rich traces.
33+
*/ async runMiddleware(params) {
34+
if (process.env.NEXT_MINIMAL) {
35+
throw new Error('invariant: runMiddleware should not be called in minimal mode');
36+
}
37+
// Middleware is skipped for on-demand revalidate requests
38+
if ((0, _apiutils.checkIsOnDemandRevalidate)(params.request, this.renderOpts.previewProps).isOnDemandRevalidate) {
39+
return {
40+
response: new Response(null, {
41+
headers: {
42+
'x-middleware-next': '1'
43+
}
44+
})
45+
};
46+
}
47+
// ...
48+
}
49+
async runEdgeFunction(params) {
50+
if (process.env.NEXT_MINIMAL) {
51+
throw new Error('Middleware is not supported in minimal mode.');
52+
}
53+
let edgeInfo;
54+
const { query, page, match } = params;
55+
if (!match) await this.ensureEdgeFunction({
56+
page,
57+
appPaths: params.appPaths,
58+
url: params.req.url
59+
});
60+
// ...
61+
}
62+
// ...
63+
}`;
64+
65+
expect(
66+
patchCode(code, createEmptyBodyRule("runMiddleware")),
67+
).toMatchInlineSnapshot(`
68+
"class NextNodeServer extends _baseserver.default {
69+
constructor(options){
70+
// Initialize super class
71+
super(options);
72+
this.handleNextImageRequest = async (req, res, parsedUrl) => { /* ... */ };
73+
}
74+
async handleUpgrade() {
75+
// The web server does not support web sockets, it's only used for HMR in
76+
// development.
77+
}
78+
getEnabledDirectories(dev) {
79+
const dir = dev ? this.dir : this.serverDistDir;
80+
return {
81+
app: (0, _findpagesdir.findDir)(dir, "app") ? true : false,
82+
pages: (0, _findpagesdir.findDir)(dir, "pages") ? true : false
83+
};
84+
}
85+
/**
86+
* This method gets all middleware matchers and execute them when the request
87+
* matches. It will make sure that each middleware exists and is compiled and
88+
* ready to be invoked. The development server will decorate it to add warns
89+
* and errors with rich traces.
90+
*/ async runMiddleware(params) {
91+
throw new Error("runMiddleware should not be called with OpenNext");
92+
}
93+
async runEdgeFunction(params) {
94+
if (process.env.NEXT_MINIMAL) {
95+
throw new Error('Middleware is not supported in minimal mode.');
96+
}
97+
let edgeInfo;
98+
const { query, page, match } = params;
99+
if (!match) await this.ensureEdgeFunction({
100+
page,
101+
appPaths: params.appPaths,
102+
url: params.req.url
103+
});
104+
// ...
105+
}
106+
// ...
107+
}"
108+
`);
109+
110+
expect(
111+
patchCode(code, createEmptyBodyRule("runEdgeFunction")),
112+
).toMatchInlineSnapshot(`
113+
"class NextNodeServer extends _baseserver.default {
114+
constructor(options){
115+
// Initialize super class
116+
super(options);
117+
this.handleNextImageRequest = async (req, res, parsedUrl) => { /* ... */ };
118+
}
119+
async handleUpgrade() {
120+
// The web server does not support web sockets, it's only used for HMR in
121+
// development.
122+
}
123+
getEnabledDirectories(dev) {
124+
const dir = dev ? this.dir : this.serverDistDir;
125+
return {
126+
app: (0, _findpagesdir.findDir)(dir, "app") ? true : false,
127+
pages: (0, _findpagesdir.findDir)(dir, "pages") ? true : false
128+
};
129+
}
130+
/**
131+
* This method gets all middleware matchers and execute them when the request
132+
* matches. It will make sure that each middleware exists and is compiled and
133+
* ready to be invoked. The development server will decorate it to add warns
134+
* and errors with rich traces.
135+
*/ async runMiddleware(params) {
136+
if (process.env.NEXT_MINIMAL) {
137+
throw new Error('invariant: runMiddleware should not be called in minimal mode');
138+
}
139+
// Middleware is skipped for on-demand revalidate requests
140+
if ((0, _apiutils.checkIsOnDemandRevalidate)(params.request, this.renderOpts.previewProps).isOnDemandRevalidate) {
141+
return {
142+
response: new Response(null, {
143+
headers: {
144+
'x-middleware-next': '1'
145+
}
146+
})
147+
};
148+
}
149+
// ...
150+
}
151+
async runEdgeFunction(params) {
152+
throw new Error("runEdgeFunction should not be called with OpenNext");
153+
}
154+
// ...
155+
}"
156+
`);
157+
});
158+
159+
test("Error Inspect", () => {
160+
const code = `
161+
// This file should be imported before any others. It sets up the environment
162+
// for later imports to work properly.
163+
"use strict";
164+
Object.defineProperty(exports, "__esModule", {
165+
value: true
166+
});
167+
require("./node-environment-baseline");
168+
require("./node-environment-extensions/error-inspect");
169+
require("./node-environment-extensions/random");
170+
require("./node-environment-extensions/date");
171+
require("./node-environment-extensions/web-crypto");
172+
require("./node-environment-extensions/node-crypto");
173+
//# sourceMappingURL=node-environment.js.map
174+
}`;
175+
176+
expect(patchCode(code, errorInspectRule)).toMatchInlineSnapshot(`
177+
"// This file should be imported before any others. It sets up the environment
178+
// for later imports to work properly.
179+
"use strict";
180+
Object.defineProperty(exports, "__esModule", {
181+
value: true
182+
});
183+
require("./node-environment-baseline");
184+
// Removed by OpenNext
185+
// require("./node-environment-extensions/error-inspect");
186+
require("./node-environment-extensions/random");
187+
require("./node-environment-extensions/date");
188+
require("./node-environment-extensions/web-crypto");
189+
require("./node-environment-extensions/node-crypto");
190+
//# sourceMappingURL=node-environment.js.map
191+
}"
192+
`);
193+
});
194+
});

0 commit comments

Comments
 (0)