Skip to content

Commit 7fb6116

Browse files
committed
Add option to minimize server bundle size
1 parent 43d2370 commit 7fb6116

File tree

6 files changed

+309
-2
lines changed

6 files changed

+309
-2
lines changed

.changeset/lemon-trains-fry.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"open-next": patch
3+
---
4+
5+
Add option to minimize server bundle size

README.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,18 @@ AWS link: https://d1gwt3w78t4dm3.cloudfront.net
314314

315315
Vercel link: https://open-next.vercel.app
316316

317+
## Advanced usage
318+
319+
#### OPEN_NEXT_MINIMIZE
320+
321+
Enabling this option will minimize all `.js` and `.json` files in the server function bundle using the [node-minify](https://github.com/srod/node-minify) library. This can reduce the size of the server function bundle by about 40%, depending on the size of your app. To enable it, simply run:
322+
323+
```bash
324+
OPEN_NEXT_MINIMIZE=true open-next build
325+
```
326+
327+
Enabling this option can significantly help to reduce the cold start time of the server function. However, it's an **experimental feature**, and you need to opt-in to use it. Once this option is thoroughly tested and found to be stable, it will be enabled by default.
328+
317329
## Debugging
318330

319331
To find the **server and image optimization log**, go to the AWS CloudWatch console in the **region you deployed to**.
@@ -339,9 +351,9 @@ It is recommended to **turn off debug mode when building for production** becaus
339351
1. Un-minified function code is 2-3X larger than minified code. This will result in longer Lambda cold start times.
340352
1. Logging the event object on each request can result in a lot of logs being written to AWS CloudWatch. This will result in increased AWS costs.
341353

342-
## Opening an issue
354+
#### Opening an issue
343355

344-
To open an issue, create a pull request (PR) and add a new page to the [benchmark app](#example) in `example` folder that demonstrate the issue.
356+
To help diagnose issues, it's always helpful to provide a reproducible setup when opening an issue. One easy way to do this is to create a pull request (PR) and add a new page to the [benchmark app](#example) located in the `example` folder, which reproduces the issue. The PR will automatically deploy the app to AWS.
345357

346358
## Contribute
347359

packages/open-next/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@
2121
"author": "",
2222
"dependencies": {
2323
"@aws-sdk/client-s3": "^3.234.0",
24+
"@node-minify/core": "^8.0.6",
25+
"@node-minify/terser": "^8.0.6",
2426
"@tsconfig/node18": "^1.0.1",
2527
"esbuild": "^0.15.18",
28+
"promise.series": "^0.2.0",
2629
"yargs": "^17.6.2"
2730
},
2831
"devDependencies": {

packages/open-next/src/build.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import fs from "node:fs";
22
import url from "node:url";
33
import path from "node:path";
44
import cp from "node:child_process";
5+
import { minifyAll } from "./minimize-js.js";
56
import { buildSync, BuildOptions } from "esbuild";
67

78
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
@@ -31,6 +32,9 @@ export async function build() {
3132
createServerBundle(monorepoRoot);
3233
createImageOptimizationBundle();
3334
createAssets();
35+
if (process.env.OPEN_NEXT_MINIFY) {
36+
await minifyServerBundle();
37+
}
3438
}
3539

3640
function checkRunningInsideNextjsApp() {
@@ -205,6 +209,14 @@ function createServerBundle(monorepoRoot: string) {
205209
);
206210
}
207211

212+
async function minifyServerBundle() {
213+
console.info(`Minimizing server function...`);
214+
await minifyAll(path.join(outputDir, "server-function"), {
215+
compress_json: true,
216+
mangle: true,
217+
});
218+
}
219+
208220
function createImageOptimizationBundle() {
209221
console.info(`Bundling image optimization function...`);
210222

packages/open-next/src/minimize-js.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Copied and modified from node-minify-all-js by Adones Pitogo
2+
// https://github.com/adonespitogo/node-minify-all-js/blob/master/index.js
3+
4+
// @ts-nocheck
5+
import fs from "node:fs/promises";
6+
import path from "node:path";
7+
import promiseSeries from "promise.series";
8+
import minify from "@node-minify/core";
9+
import terser from "@node-minify/terser";
10+
11+
var failed_files = [];
12+
var total_files = 0;
13+
var options = {};
14+
15+
const minifyJS = async (file) => {
16+
total_files++;
17+
try {
18+
await minify({
19+
compressor: terser,
20+
input: file,
21+
output: file,
22+
options: {
23+
module: options.module,
24+
mangle: options.mangle,
25+
compress: { reduce_vars: false },
26+
},
27+
});
28+
} catch (e) {
29+
failed_files.push(file);
30+
}
31+
//process.stdout.write(".");
32+
};
33+
34+
const minifyJSON = async (file) => {
35+
try {
36+
if (options.compress_json || options.packagejson) {
37+
total_files++;
38+
var is_package_json = file.indexOf("package.json") > -1;
39+
var data = await fs.readFile(file, "utf8");
40+
var json = JSON.parse(data);
41+
var new_json = {};
42+
if (options.packagejson && is_package_json) {
43+
var { name, version, bin, main, binary, engines } = json;
44+
new_json = { name, version };
45+
if (bin) new_json.bin = bin;
46+
if (binary) new_json.binary = binary;
47+
if (main) new_json.main = main;
48+
if (engines) new_json.engines = engines;
49+
} else {
50+
new_json = json;
51+
}
52+
await fs.writeFile(file, JSON.stringify(new_json));
53+
}
54+
} catch (e) {}
55+
//process.stdout.write(".");
56+
};
57+
58+
const walk = async (currentDirPath) => {
59+
var js_files = [];
60+
var json_files = [];
61+
var dirs = [];
62+
var current_dirs = await fs.readdir(currentDirPath);
63+
for (const name of current_dirs) {
64+
var filePath = path.join(currentDirPath, name);
65+
var stat = await fs.stat(filePath);
66+
var is_bin = /\.bin$/;
67+
if (stat.isFile()) {
68+
if (filePath.substr(-5) === ".json") json_files.push(filePath);
69+
else if (filePath.substr(-3) === ".js" || options.all_js)
70+
js_files.push(filePath);
71+
} else if (stat.isDirectory() && !is_bin.test(filePath)) {
72+
dirs.push(filePath);
73+
}
74+
}
75+
var js_promise = Promise.all(js_files.map((f) => minifyJS(f)));
76+
var json_promise = Promise.all(json_files.map((f) => minifyJSON(f)));
77+
await Promise.all([js_promise, json_promise]);
78+
await promiseSeries(dirs.map((dir) => () => walk(dir)));
79+
};
80+
81+
export async function minifyAll(dir, opts) {
82+
Object.assign(options, opts || {});
83+
//console.log("minify-all-js options:\n", JSON.stringify(options, null, 2));
84+
await walk(dir);
85+
//process.stdout.write(".\n");
86+
//console.log("Total found files: " + total_files);
87+
if (failed_files.length) {
88+
console.log(`\n\nFailed to minify files:`);
89+
failed_files.forEach((f) => console.log("\t" + f));
90+
}
91+
}

0 commit comments

Comments
 (0)