next-bun-compile 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -0
- package/dist/cli.js +11 -7
- package/dist/generate.js +11 -7
- package/dist/index.js +15 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -62,6 +62,35 @@ The binary is fully self-contained — static assets, public files, and the Next
|
|
|
62
62
|
| `HOSTNAME` | `0.0.0.0` | Server hostname |
|
|
63
63
|
| `KEEP_ALIVE_TIMEOUT` | — | HTTP keep-alive timeout (ms) |
|
|
64
64
|
|
|
65
|
+
### CDN / `assetPrefix`
|
|
66
|
+
|
|
67
|
+
If you configure `assetPrefix` in your `next.config.ts`, static assets (`/_next/static/`) are served from your CDN instead of the origin server. The adapter detects this automatically and skips embedding static assets in the binary — only public files are embedded. This results in a smaller binary.
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
const nextConfig: NextConfig = {
|
|
71
|
+
assetPrefix: "https://cdn.example.com",
|
|
72
|
+
experimental: {
|
|
73
|
+
adapterPath: require.resolve("next-bun-compile"),
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
You'll need to upload `.next/static/` to your CDN separately.
|
|
79
|
+
|
|
80
|
+
## Performance
|
|
81
|
+
|
|
82
|
+
The compiled binary uses Bun's `--bytecode` flag to pre-compile JavaScript to bytecode at build time, skipping the parsing step at startup. Code is also minified and dead code paths (dev-only modules, non-turbo runtimes) are eliminated via `--define` flags.
|
|
83
|
+
|
|
84
|
+
Benchmarks on a real Next.js app (both running on Bun's runtime):
|
|
85
|
+
|
|
86
|
+
| | Standalone | Compiled Binary |
|
|
87
|
+
|---|---|---|
|
|
88
|
+
| **Startup** | 84ms | **45ms (1.9x faster)** |
|
|
89
|
+
| **Memory (RSS)** | 60 MB | 72 MB |
|
|
90
|
+
| **Size** | 91 MB (directory) | 99 MB (single file) |
|
|
91
|
+
|
|
92
|
+
Startup improvements scale with codebase size — larger applications benefit more since there's more code to parse.
|
|
93
|
+
|
|
65
94
|
## How It Works
|
|
66
95
|
|
|
67
96
|
1. **Adapter hook** — `modifyConfig()` sets `output: "standalone"` automatically so you don't need to configure it
|
package/dist/cli.js
CHANGED
|
@@ -73,18 +73,23 @@ function generateEntryPoint(options) {
|
|
|
73
73
|
const staticDir = join(distDir, "static");
|
|
74
74
|
const staticFiles = walkDir(staticDir).map((f) => ({
|
|
75
75
|
...f,
|
|
76
|
-
urlPath: `/_next/static/${f.relativePath}`
|
|
76
|
+
urlPath: `/_next/static/${f.relativePath.replace(/\\/g, "/")}`
|
|
77
77
|
}));
|
|
78
78
|
const publicDir = join(projectDir, "public");
|
|
79
79
|
const publicFiles = walkDir(publicDir).map((f) => ({
|
|
80
80
|
...f,
|
|
81
|
-
urlPath: `/${f.relativePath}`
|
|
81
|
+
urlPath: `/${f.relativePath.replace(/\\/g, "/")}`
|
|
82
82
|
}));
|
|
83
|
-
const
|
|
84
|
-
|
|
83
|
+
const ctx = JSON.parse(readFileSync(join(distDir, "bun-compile-ctx.json"), "utf-8"));
|
|
84
|
+
const { assetPrefix } = ctx;
|
|
85
|
+
const assetsToEmbed = assetPrefix ? publicFiles : [...staticFiles, ...publicFiles];
|
|
86
|
+
if (assetPrefix) {
|
|
87
|
+
console.log(`next-bun-compile: assetPrefix detected — skipping ${staticFiles.length} static assets (served from CDN)`);
|
|
88
|
+
}
|
|
89
|
+
console.log(`next-bun-compile: Embedding ${assetsToEmbed.length} assets (${assetPrefix ? "public only" : `${staticFiles.length} static + ${publicFiles.length} public`})`);
|
|
85
90
|
const imports = [];
|
|
86
91
|
const mapEntries = [];
|
|
87
|
-
for (const asset of
|
|
92
|
+
for (const asset of assetsToEmbed) {
|
|
88
93
|
const varName = toVarName(asset.urlPath);
|
|
89
94
|
const importPath = relative(standaloneDir, asset.absolutePath).replace(/\\/g, "/");
|
|
90
95
|
imports.push(`import ${varName} from "./${importPath}" with { type: "file" };`);
|
|
@@ -102,7 +107,7 @@ ${mapEntries.join(`
|
|
|
102
107
|
if (!configMatch) {
|
|
103
108
|
throw new Error("next-bun-compile: Could not extract nextConfig from standalone server.js");
|
|
104
109
|
}
|
|
105
|
-
const assetExtractions =
|
|
110
|
+
const assetExtractions = assetsToEmbed.map((a) => {
|
|
106
111
|
const diskPath = a.urlPath.startsWith("/_next/static/") ? ".next/static/" + a.relativePath : "public/" + a.relativePath;
|
|
107
112
|
return [a.urlPath, diskPath];
|
|
108
113
|
});
|
|
@@ -147,7 +152,6 @@ extractAssets().then(() => {
|
|
|
147
152
|
}).catch((err) => { console.error(err); process.exit(1); });
|
|
148
153
|
`;
|
|
149
154
|
writeFileSync(join(standaloneDir, "server-entry.js"), serverEntry);
|
|
150
|
-
console.log("next-bun-compile: Generated server-entry.js + assets.generated.js");
|
|
151
155
|
}
|
|
152
156
|
|
|
153
157
|
// src/compile.ts
|
package/dist/generate.js
CHANGED
|
@@ -72,18 +72,23 @@ function generateEntryPoint(options) {
|
|
|
72
72
|
const staticDir = join(distDir, "static");
|
|
73
73
|
const staticFiles = walkDir(staticDir).map((f) => ({
|
|
74
74
|
...f,
|
|
75
|
-
urlPath: `/_next/static/${f.relativePath}`
|
|
75
|
+
urlPath: `/_next/static/${f.relativePath.replace(/\\/g, "/")}`
|
|
76
76
|
}));
|
|
77
77
|
const publicDir = join(projectDir, "public");
|
|
78
78
|
const publicFiles = walkDir(publicDir).map((f) => ({
|
|
79
79
|
...f,
|
|
80
|
-
urlPath: `/${f.relativePath}`
|
|
80
|
+
urlPath: `/${f.relativePath.replace(/\\/g, "/")}`
|
|
81
81
|
}));
|
|
82
|
-
const
|
|
83
|
-
|
|
82
|
+
const ctx = JSON.parse(readFileSync(join(distDir, "bun-compile-ctx.json"), "utf-8"));
|
|
83
|
+
const { assetPrefix } = ctx;
|
|
84
|
+
const assetsToEmbed = assetPrefix ? publicFiles : [...staticFiles, ...publicFiles];
|
|
85
|
+
if (assetPrefix) {
|
|
86
|
+
console.log(`next-bun-compile: assetPrefix detected — skipping ${staticFiles.length} static assets (served from CDN)`);
|
|
87
|
+
}
|
|
88
|
+
console.log(`next-bun-compile: Embedding ${assetsToEmbed.length} assets (${assetPrefix ? "public only" : `${staticFiles.length} static + ${publicFiles.length} public`})`);
|
|
84
89
|
const imports = [];
|
|
85
90
|
const mapEntries = [];
|
|
86
|
-
for (const asset of
|
|
91
|
+
for (const asset of assetsToEmbed) {
|
|
87
92
|
const varName = toVarName(asset.urlPath);
|
|
88
93
|
const importPath = relative(standaloneDir, asset.absolutePath).replace(/\\/g, "/");
|
|
89
94
|
imports.push(`import ${varName} from "./${importPath}" with { type: "file" };`);
|
|
@@ -101,7 +106,7 @@ ${mapEntries.join(`
|
|
|
101
106
|
if (!configMatch) {
|
|
102
107
|
throw new Error("next-bun-compile: Could not extract nextConfig from standalone server.js");
|
|
103
108
|
}
|
|
104
|
-
const assetExtractions =
|
|
109
|
+
const assetExtractions = assetsToEmbed.map((a) => {
|
|
105
110
|
const diskPath = a.urlPath.startsWith("/_next/static/") ? ".next/static/" + a.relativePath : "public/" + a.relativePath;
|
|
106
111
|
return [a.urlPath, diskPath];
|
|
107
112
|
});
|
|
@@ -146,7 +151,6 @@ extractAssets().then(() => {
|
|
|
146
151
|
}).catch((err) => { console.error(err); process.exit(1); });
|
|
147
152
|
`;
|
|
148
153
|
writeFileSync(join(standaloneDir, "server-entry.js"), serverEntry);
|
|
149
|
-
console.log("next-bun-compile: Generated server-entry.js + assets.generated.js");
|
|
150
154
|
}
|
|
151
155
|
export {
|
|
152
156
|
generateEntryPoint
|
package/dist/index.js
CHANGED
|
@@ -72,18 +72,23 @@ function generateEntryPoint(options) {
|
|
|
72
72
|
const staticDir = join(distDir, "static");
|
|
73
73
|
const staticFiles = walkDir(staticDir).map((f) => ({
|
|
74
74
|
...f,
|
|
75
|
-
urlPath: `/_next/static/${f.relativePath}`
|
|
75
|
+
urlPath: `/_next/static/${f.relativePath.replace(/\\/g, "/")}`
|
|
76
76
|
}));
|
|
77
77
|
const publicDir = join(projectDir, "public");
|
|
78
78
|
const publicFiles = walkDir(publicDir).map((f) => ({
|
|
79
79
|
...f,
|
|
80
|
-
urlPath: `/${f.relativePath}`
|
|
80
|
+
urlPath: `/${f.relativePath.replace(/\\/g, "/")}`
|
|
81
81
|
}));
|
|
82
|
-
const
|
|
83
|
-
|
|
82
|
+
const ctx = JSON.parse(readFileSync(join(distDir, "bun-compile-ctx.json"), "utf-8"));
|
|
83
|
+
const { assetPrefix } = ctx;
|
|
84
|
+
const assetsToEmbed = assetPrefix ? publicFiles : [...staticFiles, ...publicFiles];
|
|
85
|
+
if (assetPrefix) {
|
|
86
|
+
console.log(`next-bun-compile: assetPrefix detected — skipping ${staticFiles.length} static assets (served from CDN)`);
|
|
87
|
+
}
|
|
88
|
+
console.log(`next-bun-compile: Embedding ${assetsToEmbed.length} assets (${assetPrefix ? "public only" : `${staticFiles.length} static + ${publicFiles.length} public`})`);
|
|
84
89
|
const imports = [];
|
|
85
90
|
const mapEntries = [];
|
|
86
|
-
for (const asset of
|
|
91
|
+
for (const asset of assetsToEmbed) {
|
|
87
92
|
const varName = toVarName(asset.urlPath);
|
|
88
93
|
const importPath = relative(standaloneDir, asset.absolutePath).replace(/\\/g, "/");
|
|
89
94
|
imports.push(`import ${varName} from "./${importPath}" with { type: "file" };`);
|
|
@@ -101,7 +106,7 @@ ${mapEntries.join(`
|
|
|
101
106
|
if (!configMatch) {
|
|
102
107
|
throw new Error("next-bun-compile: Could not extract nextConfig from standalone server.js");
|
|
103
108
|
}
|
|
104
|
-
const assetExtractions =
|
|
109
|
+
const assetExtractions = assetsToEmbed.map((a) => {
|
|
105
110
|
const diskPath = a.urlPath.startsWith("/_next/static/") ? ".next/static/" + a.relativePath : "public/" + a.relativePath;
|
|
106
111
|
return [a.urlPath, diskPath];
|
|
107
112
|
});
|
|
@@ -146,7 +151,6 @@ extractAssets().then(() => {
|
|
|
146
151
|
}).catch((err) => { console.error(err); process.exit(1); });
|
|
147
152
|
`;
|
|
148
153
|
writeFileSync(join(standaloneDir, "server-entry.js"), serverEntry);
|
|
149
|
-
console.log("next-bun-compile: Generated server-entry.js + assets.generated.js");
|
|
150
154
|
}
|
|
151
155
|
|
|
152
156
|
// src/compile.ts
|
|
@@ -182,8 +186,8 @@ import { join as join3 } from "node:path";
|
|
|
182
186
|
var knownTranspilePackages = ["pino", "pino-pretty"];
|
|
183
187
|
var adapter = {
|
|
184
188
|
name: "next-bun-compile",
|
|
185
|
-
modifyConfig(config,
|
|
186
|
-
if (phase !== "phase-production-build")
|
|
189
|
+
modifyConfig(config, ctx) {
|
|
190
|
+
if (ctx?.phase && ctx.phase !== "phase-production-build")
|
|
187
191
|
return config;
|
|
188
192
|
if (config.output !== "standalone") {
|
|
189
193
|
console.warn('next-bun-compile: Setting output to "standalone" (required for compilation)');
|
|
@@ -200,7 +204,8 @@ var adapter = {
|
|
|
200
204
|
const { writeFileSync: writeFileSync2 } = await import("node:fs");
|
|
201
205
|
writeFileSync2(join3(ctx.distDir, "bun-compile-ctx.json"), JSON.stringify({
|
|
202
206
|
distDir: ctx.distDir,
|
|
203
|
-
projectDir: ctx.projectDir
|
|
207
|
+
projectDir: ctx.projectDir,
|
|
208
|
+
assetPrefix: ctx.config.assetPrefix || ""
|
|
204
209
|
}));
|
|
205
210
|
}
|
|
206
211
|
};
|