next-bun-compile 0.7.0 → 0.8.0
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 +10 -23
- package/dist/cli.js +2 -6
- package/dist/{index-np1qb2b3.js → index-34djf48r.js} +31 -13
- package/dist/index.js +1 -39
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# next-bun-compile
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Compile your Next.js app into a single-file [Bun](https://bun.sh) executable.
|
|
4
4
|
|
|
5
5
|
One command. One binary. No runtime dependencies.
|
|
6
6
|
|
|
@@ -23,30 +23,18 @@ bun add -D next-bun-compile
|
|
|
23
23
|
|
|
24
24
|
## Setup
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
Enable Next.js standalone output in your `next.config.ts`:
|
|
27
27
|
|
|
28
28
|
```ts
|
|
29
29
|
import type { NextConfig } from "next";
|
|
30
30
|
|
|
31
31
|
const nextConfig: NextConfig = {
|
|
32
|
-
|
|
32
|
+
output: "standalone",
|
|
33
33
|
};
|
|
34
34
|
|
|
35
35
|
export default nextConfig;
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
<details>
|
|
39
|
-
<summary>Using Next.js 16.1? Use the experimental config instead</summary>
|
|
40
|
-
|
|
41
|
-
```ts
|
|
42
|
-
const nextConfig: NextConfig = {
|
|
43
|
-
experimental: {
|
|
44
|
-
adapterPath: import.meta.resolve("next-bun-compile"),
|
|
45
|
-
},
|
|
46
|
-
};
|
|
47
|
-
```
|
|
48
|
-
</details>
|
|
49
|
-
|
|
50
38
|
Update your build script in `package.json`:
|
|
51
39
|
|
|
52
40
|
```json
|
|
@@ -88,12 +76,12 @@ See the [Bun cross-compilation docs](https://bun.sh/docs/bundler/executables#cro
|
|
|
88
76
|
|
|
89
77
|
### CDN / `assetPrefix`
|
|
90
78
|
|
|
91
|
-
If you configure `assetPrefix` in your `next.config.ts`, static assets (`/_next/static/`) are served from your CDN instead of the origin server.
|
|
79
|
+
If you configure `assetPrefix` in your `next.config.ts`, static assets (`/_next/static/`) are served from your CDN instead of the origin server. `next-bun-compile` detects this from the build output and skips embedding static assets in the binary — only public files are embedded. This results in a smaller binary.
|
|
92
80
|
|
|
93
81
|
```ts
|
|
94
82
|
const nextConfig: NextConfig = {
|
|
83
|
+
output: "standalone",
|
|
95
84
|
assetPrefix: "https://cdn.example.com",
|
|
96
|
-
adapterPath: import.meta.resolve("next-bun-compile"),
|
|
97
85
|
};
|
|
98
86
|
```
|
|
99
87
|
|
|
@@ -115,18 +103,17 @@ Startup improvements scale with codebase size — larger applications benefit mo
|
|
|
115
103
|
|
|
116
104
|
## How It Works
|
|
117
105
|
|
|
118
|
-
1. **
|
|
119
|
-
2. **
|
|
120
|
-
3. **Code generation** — Creates a `server-entry.js` that:
|
|
106
|
+
1. **Asset discovery** — Scans `.next/static/` and `public/` for all static files
|
|
107
|
+
2. **Code generation** — Creates a `server-entry.js` that:
|
|
121
108
|
- Embeds all assets into the binary via Bun's `import ... with { type: "file" }`
|
|
122
109
|
- Extracts them to disk on first run
|
|
123
110
|
- Fixes `__dirname` for compiled binary context
|
|
124
111
|
- Starts the Next.js server
|
|
125
|
-
|
|
112
|
+
3. **Compilation** — Runs `bun build --compile` with `--define` flags to eliminate dead code branches (dev-only modules, non-turbo runtimes)
|
|
126
113
|
|
|
127
114
|
### Module Stubs
|
|
128
115
|
|
|
129
|
-
Some modules can't be resolved at compile time but are never reached in production (dev servers, optional dependencies).
|
|
116
|
+
Some modules can't be resolved at compile time but are never reached in production (dev servers, optional dependencies). `next-bun-compile` creates no-op stubs for these **only if** the real module isn't installed. If you actually use `@opentelemetry/api` or `critters`, the real package gets bundled instead.
|
|
130
117
|
|
|
131
118
|
## Troubleshooting
|
|
132
119
|
|
|
@@ -152,8 +139,8 @@ Failed to load external module pino-142500b1eb3f4baf: Cannot find package ...
|
|
|
152
139
|
|
|
153
140
|
```ts
|
|
154
141
|
const nextConfig: NextConfig = {
|
|
142
|
+
output: "standalone",
|
|
155
143
|
transpilePackages: ["pino", "pino-pretty"],
|
|
156
|
-
adapterPath: import.meta.resolve("next-bun-compile"),
|
|
157
144
|
};
|
|
158
145
|
```
|
|
159
146
|
|
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
compile,
|
|
4
4
|
generateEntryPoint
|
|
5
|
-
} from "./index-
|
|
5
|
+
} from "./index-34djf48r.js";
|
|
6
6
|
|
|
7
7
|
// src/cli.ts
|
|
8
8
|
import { existsSync } from "node:fs";
|
|
@@ -12,12 +12,8 @@ var projectDir = resolve(".");
|
|
|
12
12
|
var distDir = join(projectDir, ".next");
|
|
13
13
|
var standaloneDir = join(distDir, "standalone");
|
|
14
14
|
if (!existsSync(standaloneDir)) {
|
|
15
|
-
console.error('next-bun-compile: No standalone output found.
|
|
15
|
+
console.error('next-bun-compile: No standalone output found. Add `output: "standalone"` to next.config.ts and re-run `next build`.');
|
|
16
16
|
process.exit(1);
|
|
17
17
|
}
|
|
18
|
-
var ctxPath = join(distDir, "bun-compile-ctx.json");
|
|
19
|
-
if (existsSync(ctxPath)) {
|
|
20
|
-
console.log("next-bun-compile: Using build context from adapter");
|
|
21
|
-
}
|
|
22
18
|
var serverDir = generateEntryPoint({ standaloneDir, distDir, projectDir });
|
|
23
19
|
compile({ serverDir, outfile: join(projectDir, "server"), extraArgs });
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
3
|
-
|
|
4
1
|
// src/generate.ts
|
|
5
2
|
import {
|
|
6
3
|
writeFileSync,
|
|
@@ -14,6 +11,18 @@ import {
|
|
|
14
11
|
} from "node:fs";
|
|
15
12
|
import { join, relative, basename } from "node:path";
|
|
16
13
|
import { createHash } from "node:crypto";
|
|
14
|
+
function readAssetPrefix(distDir) {
|
|
15
|
+
const ctxPath = join(distDir, "bun-compile-ctx.json");
|
|
16
|
+
if (existsSync(ctxPath)) {
|
|
17
|
+
return JSON.parse(readFileSync(ctxPath, "utf-8")).assetPrefix ?? "";
|
|
18
|
+
}
|
|
19
|
+
const rsfPath = join(distDir, "required-server-files.json");
|
|
20
|
+
if (existsSync(rsfPath)) {
|
|
21
|
+
const rsf = JSON.parse(readFileSync(rsfPath, "utf-8"));
|
|
22
|
+
return rsf.config?.assetPrefix ?? "";
|
|
23
|
+
}
|
|
24
|
+
return "";
|
|
25
|
+
}
|
|
17
26
|
function tryStat(p) {
|
|
18
27
|
try {
|
|
19
28
|
return statSync(p);
|
|
@@ -534,8 +543,7 @@ function generateEntryPoint(options) {
|
|
|
534
543
|
const canonicalResolutions = buildCanonicalResolutions(externalDir, turbopackAliases);
|
|
535
544
|
validateAliasResolutions(turbopackAliases, canonicalResolutions);
|
|
536
545
|
rewriteTurbopackAliases(standaloneNextDir, turbopackAliases, canonicalResolutions);
|
|
537
|
-
const
|
|
538
|
-
const { assetPrefix } = ctx;
|
|
546
|
+
const assetPrefix = readAssetPrefix(distDir);
|
|
539
547
|
const assetsToEmbed = assetPrefix ? [...publicFiles, ...runtimeFiles] : [...staticFiles, ...publicFiles, ...runtimeFiles];
|
|
540
548
|
if (assetPrefix) {
|
|
541
549
|
console.log(`next-bun-compile: assetPrefix detected — skipping ${staticFiles.length} static assets (served from CDN)`);
|
|
@@ -753,13 +761,25 @@ if (Number.isNaN(keepAliveTimeout) || !Number.isFinite(keepAliveTimeout) || keep
|
|
|
753
761
|
|
|
754
762
|
const extractions = ${JSON.stringify(assetExtractions)};
|
|
755
763
|
async function extractAssets() {
|
|
756
|
-
|
|
764
|
+
// Collect everything that isn't already on disk, dedupe parent dirs
|
|
765
|
+
// for a single mkdir pass, then issue all writes concurrently.
|
|
766
|
+
// ~45% faster than serial extraction on a typical Next.js app with
|
|
767
|
+
// sharp; trivially correct because each write is to a distinct path.
|
|
768
|
+
const todo = [];
|
|
757
769
|
for (const [urlPath, diskPath] of extractions) {
|
|
758
770
|
const fullPath = path.join(baseDir, diskPath);
|
|
759
771
|
if (fs.existsSync(fullPath)) continue;
|
|
760
|
-
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
761
772
|
const embedded = assetMap.get(urlPath);
|
|
762
773
|
if (!embedded) continue;
|
|
774
|
+
todo.push({ diskPath, fullPath, embedded });
|
|
775
|
+
}
|
|
776
|
+
if (todo.length === 0) return;
|
|
777
|
+
|
|
778
|
+
const dirs = new Set();
|
|
779
|
+
for (const t of todo) dirs.add(path.dirname(t.fullPath));
|
|
780
|
+
for (const d of dirs) fs.mkdirSync(d, { recursive: true });
|
|
781
|
+
|
|
782
|
+
await Promise.all(todo.map(async ({ diskPath, fullPath, embedded }) => {
|
|
763
783
|
// Server chunks may contain __NBC_BASE__ placeholders injected at
|
|
764
784
|
// build time by rewriteTurbopackAliases. Substitute the real baseDir
|
|
765
785
|
// before writing so bun resolves the absolute paths at chunk load
|
|
@@ -768,14 +788,12 @@ async function extractAssets() {
|
|
|
768
788
|
const text = await Bun.file(embedded).text();
|
|
769
789
|
if (text.indexOf("__NBC_BASE__") !== -1) {
|
|
770
790
|
await Bun.write(fullPath, text.split("__NBC_BASE__").join(baseDir));
|
|
771
|
-
|
|
772
|
-
continue;
|
|
791
|
+
return;
|
|
773
792
|
}
|
|
774
793
|
}
|
|
775
794
|
await Bun.write(fullPath, Bun.file(embedded));
|
|
776
|
-
|
|
777
|
-
}
|
|
778
|
-
if (n > 0) console.log(\`Extracted \${n} assets\`);
|
|
795
|
+
}));
|
|
796
|
+
console.log(\`Extracted \${todo.length} assets\`);
|
|
779
797
|
}
|
|
780
798
|
|
|
781
799
|
extractAssets().then(() => {
|
|
@@ -820,4 +838,4 @@ function compile(options) {
|
|
|
820
838
|
console.log(`next-bun-compile: Done → ${outfile}`);
|
|
821
839
|
}
|
|
822
840
|
|
|
823
|
-
export {
|
|
841
|
+
export { generateEntryPoint, compile };
|
package/dist/index.js
CHANGED
|
@@ -1,46 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
-
__require,
|
|
3
2
|
compile,
|
|
4
3
|
generateEntryPoint
|
|
5
|
-
} from "./index-
|
|
6
|
-
|
|
7
|
-
// src/index.ts
|
|
8
|
-
import { join } from "node:path";
|
|
9
|
-
var knownTranspilePackages = ["pino", "pino-pretty"];
|
|
10
|
-
var adapter = {
|
|
11
|
-
name: "next-bun-compile",
|
|
12
|
-
modifyConfig(config, ctx) {
|
|
13
|
-
if (!ctx) {
|
|
14
|
-
throw new Error("next-bun-compile: Next.js 16+ is required. Please upgrade your Next.js version.");
|
|
15
|
-
}
|
|
16
|
-
if (ctx.phase !== "phase-production-build")
|
|
17
|
-
return config;
|
|
18
|
-
if (process.argv.includes("--webpack")) {
|
|
19
|
-
throw new Error("next-bun-compile: Webpack builds are not supported. Remove --webpack to use Turbopack (default).");
|
|
20
|
-
}
|
|
21
|
-
if (config.output !== "standalone") {
|
|
22
|
-
console.warn('next-bun-compile: Setting output to "standalone" (required for compilation)');
|
|
23
|
-
config.output = "standalone";
|
|
24
|
-
}
|
|
25
|
-
const existing = config.transpilePackages ?? [];
|
|
26
|
-
const toAdd = knownTranspilePackages.filter((p) => !existing.includes(p));
|
|
27
|
-
if (toAdd.length > 0) {
|
|
28
|
-
config.transpilePackages = [...existing, ...toAdd];
|
|
29
|
-
}
|
|
30
|
-
return config;
|
|
31
|
-
},
|
|
32
|
-
async onBuildComplete(ctx) {
|
|
33
|
-
const { writeFileSync } = await import("node:fs");
|
|
34
|
-
writeFileSync(join(ctx.distDir, "bun-compile-ctx.json"), JSON.stringify({
|
|
35
|
-
distDir: ctx.distDir,
|
|
36
|
-
projectDir: ctx.projectDir,
|
|
37
|
-
assetPrefix: ctx.config.assetPrefix || ""
|
|
38
|
-
}));
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
var src_default = adapter;
|
|
4
|
+
} from "./index-34djf48r.js";
|
|
42
5
|
export {
|
|
43
6
|
generateEntryPoint,
|
|
44
|
-
src_default as default,
|
|
45
7
|
compile
|
|
46
8
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "next-bun-compile",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Next.js
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"description": "Compile your Next.js app into a Bun single-file executable",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|