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 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 allAssets = [...staticFiles, ...publicFiles];
84
- console.log(`next-bun-compile: Found ${staticFiles.length} static + ${publicFiles.length} public = ${allAssets.length} assets`);
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 allAssets) {
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 = allAssets.map((a) => {
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 allAssets = [...staticFiles, ...publicFiles];
83
- console.log(`next-bun-compile: Found ${staticFiles.length} static + ${publicFiles.length} public = ${allAssets.length} assets`);
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 allAssets) {
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 = allAssets.map((a) => {
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 allAssets = [...staticFiles, ...publicFiles];
83
- console.log(`next-bun-compile: Found ${staticFiles.length} static + ${publicFiles.length} public = ${allAssets.length} assets`);
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 allAssets) {
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 = allAssets.map((a) => {
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, { phase }) {
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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-bun-compile",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "description": "Next.js Build Adapter that compiles your app into a Bun single-file executable",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",