next-bun-compile 0.10.0 → 0.11.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 +31 -28
- package/dist/adapter.js +8 -0
- package/dist/{index-d3wb5t4x.js → index-ajd4bx9z.js} +389 -66
- package/dist/index.js +7 -3
- package/dist/runtime/serve.js +700 -0
- package/package.json +16 -8
- package/dist/cli.js +0 -19
package/README.md
CHANGED
|
@@ -23,45 +23,56 @@ bun add -D next-bun-compile
|
|
|
23
23
|
|
|
24
24
|
## Setup
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
next-bun-compile is a [Next.js Build Adapter](https://nextjs.org/docs/app/api-reference/config/next-config-js/adapterPath). Point `adapterPath` at it in `next.config.ts`:
|
|
27
27
|
|
|
28
28
|
```ts
|
|
29
29
|
import type { NextConfig } from "next";
|
|
30
30
|
|
|
31
31
|
const nextConfig: NextConfig = {
|
|
32
|
-
|
|
32
|
+
adapterPath: "next-bun-compile/adapter",
|
|
33
33
|
};
|
|
34
34
|
|
|
35
35
|
export default nextConfig;
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
Or enable it without touching the config at all:
|
|
39
39
|
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
"scripts": {
|
|
43
|
-
"build": "next build && next-bun-compile"
|
|
44
|
-
}
|
|
45
|
-
}
|
|
40
|
+
```bash
|
|
41
|
+
NEXT_ADAPTER_PATH=next-bun-compile/adapter next build
|
|
46
42
|
```
|
|
47
43
|
|
|
44
|
+
No `output: "standalone"` needed — the adapter assembles its own traced
|
|
45
|
+
output tree.
|
|
46
|
+
|
|
47
|
+
> **Monorepos:** Next resolves `adapterPath` from its own package location.
|
|
48
|
+
> If `next-bun-compile` is a nested workspace dependency, resolve it from
|
|
49
|
+
> the app dir instead:
|
|
50
|
+
>
|
|
51
|
+
> ```ts
|
|
52
|
+
> import { createRequire } from "node:module";
|
|
53
|
+
> const req = createRequire(process.cwd() + "/");
|
|
54
|
+
> const nextConfig: NextConfig = {
|
|
55
|
+
> adapterPath: req.resolve("next-bun-compile/adapter"),
|
|
56
|
+
> };
|
|
57
|
+
> ```
|
|
58
|
+
|
|
48
59
|
## Usage
|
|
49
60
|
|
|
50
61
|
```bash
|
|
51
|
-
|
|
52
|
-
./server
|
|
62
|
+
next build # Builds Next.js + compiles to ./server, one command
|
|
63
|
+
./server # Starts on port 3000
|
|
53
64
|
```
|
|
54
65
|
|
|
55
|
-
The binary is fully self-contained — static assets, public files, and the Next.js server are all embedded. Just copy it anywhere and run.
|
|
66
|
+
The binary is fully self-contained — static assets, public files, prerendered pages, and the Next.js server are all embedded. Just copy it anywhere and run. Static assets and fully-static prerendered pages are served straight from memory; ISR and cache-component responses get an in-memory cache that Next's own revalidation invalidates.
|
|
56
67
|
|
|
57
68
|
### Cross-Compilation
|
|
58
69
|
|
|
59
|
-
|
|
70
|
+
Set `NBC_TARGET` to cross-compile for a different platform:
|
|
60
71
|
|
|
61
72
|
```bash
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
73
|
+
NBC_TARGET=bun-linux-x64 next build
|
|
74
|
+
NBC_TARGET=bun-linux-arm64 next build
|
|
75
|
+
NBC_TARGET=bun-windows-x64 next build
|
|
65
76
|
```
|
|
66
77
|
|
|
67
78
|
See the [Bun cross-compilation docs](https://bun.sh/docs/bundler/executables#cross-compile) for all available targets.
|
|
@@ -73,6 +84,8 @@ See the [Bun cross-compilation docs](https://bun.sh/docs/bundler/executables#cro
|
|
|
73
84
|
| `PORT` | `3000` | Server port |
|
|
74
85
|
| `HOSTNAME` | `0.0.0.0` | Server hostname |
|
|
75
86
|
| `KEEP_ALIVE_TIMEOUT` | — | HTTP keep-alive timeout (ms) |
|
|
87
|
+
| `NBC_RUNTIME_DIR` | binary's directory | Where runtime files extract and `.next/cache` lives. Point at tmpfs (e.g. `/tmp/app`) for RAM-backed runtime files and read-only root filesystems |
|
|
88
|
+
| `NBC_TARGET` | host platform | Cross-compile target (build time) |
|
|
76
89
|
|
|
77
90
|
### CDN / `assetPrefix`
|
|
78
91
|
|
|
@@ -80,7 +93,7 @@ If you configure `assetPrefix` in your `next.config.ts`, static assets (`/_next/
|
|
|
80
93
|
|
|
81
94
|
```ts
|
|
82
95
|
const nextConfig: NextConfig = {
|
|
83
|
-
|
|
96
|
+
adapterPath: "next-bun-compile/adapter",
|
|
84
97
|
assetPrefix: "https://cdn.example.com",
|
|
85
98
|
};
|
|
86
99
|
```
|
|
@@ -117,16 +130,6 @@ Some modules can't be resolved at compile time but are never reached in producti
|
|
|
117
130
|
|
|
118
131
|
## Troubleshooting
|
|
119
132
|
|
|
120
|
-
### Stale standalone output after upgrading Next.js
|
|
121
|
-
|
|
122
|
-
Next.js doesn't clean the standalone output directory between builds. If you upgrade Next.js versions, stale files from the old version can cause runtime errors like `Cannot find module 'next/dist/compiled/source-map'`.
|
|
123
|
-
|
|
124
|
-
**Fix:** Clean the standalone output before rebuilding:
|
|
125
|
-
|
|
126
|
-
```bash
|
|
127
|
-
rm -rf .next/standalone && bun next build && next-bun-compile
|
|
128
|
-
```
|
|
129
|
-
|
|
130
133
|
### Packages with dynamic `require()` calls (e.g. pino)
|
|
131
134
|
|
|
132
135
|
Some packages like `pino` use dynamic `require()` calls internally (for worker threads, transports, etc.). Turbopack can't resolve these at build time, so they fail at runtime inside the compiled binary with errors like:
|
|
@@ -139,7 +142,7 @@ Failed to load external module pino-142500b1eb3f4baf: Cannot find package ...
|
|
|
139
142
|
|
|
140
143
|
```ts
|
|
141
144
|
const nextConfig: NextConfig = {
|
|
142
|
-
|
|
145
|
+
adapterPath: "next-bun-compile/adapter",
|
|
143
146
|
transpilePackages: ["pino", "pino-pretty"],
|
|
144
147
|
};
|
|
145
148
|
```
|
package/dist/adapter.js
ADDED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
// src/adapter.ts
|
|
2
|
+
import {
|
|
3
|
+
copyFileSync as copyFileSync3,
|
|
4
|
+
existsSync as existsSync3,
|
|
5
|
+
mkdirSync as mkdirSync3,
|
|
6
|
+
readFileSync as readFileSync2,
|
|
7
|
+
rmSync,
|
|
8
|
+
statSync as statSync3,
|
|
9
|
+
writeFileSync as writeFileSync2
|
|
10
|
+
} from "node:fs";
|
|
11
|
+
import { dirname as dirname2, join as join4, relative as relative2, resolve } from "node:path";
|
|
12
|
+
|
|
13
|
+
// src/build.ts
|
|
14
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, statSync as statSync2 } from "node:fs";
|
|
15
|
+
import { createRequire } from "node:module";
|
|
16
|
+
import { dirname, join as join3 } from "node:path";
|
|
17
|
+
|
|
1
18
|
// src/generate.ts
|
|
2
19
|
import {
|
|
3
20
|
writeFileSync,
|
|
@@ -12,13 +29,6 @@ import {
|
|
|
12
29
|
} from "node:fs";
|
|
13
30
|
import { join, relative, basename } from "node:path";
|
|
14
31
|
import { createHash } from "node:crypto";
|
|
15
|
-
function readAssetPrefix(distDir) {
|
|
16
|
-
const rsfPath = join(distDir, "required-server-files.json");
|
|
17
|
-
if (!existsSync(rsfPath))
|
|
18
|
-
return "";
|
|
19
|
-
const rsf = JSON.parse(readFileSync(rsfPath, "utf-8"));
|
|
20
|
-
return rsf.config?.assetPrefix ?? "";
|
|
21
|
-
}
|
|
22
32
|
function tryStat(p) {
|
|
23
33
|
try {
|
|
24
34
|
return statSync(p);
|
|
@@ -46,10 +56,9 @@ function walkDir(dir, base = dir) {
|
|
|
46
56
|
}
|
|
47
57
|
return results;
|
|
48
58
|
}
|
|
49
|
-
function toVarName(filePath) {
|
|
50
|
-
const hash = createHash("sha256").update(filePath).digest("hex").slice(0, 6);
|
|
59
|
+
function toVarName(filePath, index) {
|
|
51
60
|
const safe = filePath.replace(/[^a-zA-Z0-9]/g, "_").slice(0, 40);
|
|
52
|
-
return `asset_${
|
|
61
|
+
return `asset_${index}_${safe}`;
|
|
53
62
|
}
|
|
54
63
|
function findPackageDirs(standaloneDir, pkg) {
|
|
55
64
|
const dirs = [];
|
|
@@ -336,36 +345,6 @@ function rewriteTurbopackAliases(standaloneNextDir, aliases, resolutions) {
|
|
|
336
345
|
}
|
|
337
346
|
return rewrittenPaths;
|
|
338
347
|
}
|
|
339
|
-
function findServerDir(standaloneDir) {
|
|
340
|
-
if (existsSync(join(standaloneDir, "server.js"))) {
|
|
341
|
-
return standaloneDir;
|
|
342
|
-
}
|
|
343
|
-
function search(dir) {
|
|
344
|
-
if (!existsSync(dir))
|
|
345
|
-
return null;
|
|
346
|
-
for (const entry of readdirSync(dir)) {
|
|
347
|
-
if (entry === "node_modules")
|
|
348
|
-
continue;
|
|
349
|
-
const full = join(dir, entry);
|
|
350
|
-
const stat = tryStat(full);
|
|
351
|
-
if (!stat || !stat.isDirectory())
|
|
352
|
-
continue;
|
|
353
|
-
if (existsSync(join(full, "server.js")))
|
|
354
|
-
return full;
|
|
355
|
-
const found2 = search(full);
|
|
356
|
-
if (found2)
|
|
357
|
-
return found2;
|
|
358
|
-
}
|
|
359
|
-
return null;
|
|
360
|
-
}
|
|
361
|
-
const found = search(standaloneDir);
|
|
362
|
-
if (!found) {
|
|
363
|
-
throw new Error("next-bun-compile: Could not find server.js in standalone output");
|
|
364
|
-
}
|
|
365
|
-
const rel = relative(standaloneDir, found);
|
|
366
|
-
console.log(`next-bun-compile: Monorepo layout detected — server.js found at ${rel}/`);
|
|
367
|
-
return found;
|
|
368
|
-
}
|
|
369
348
|
function patchRequireHook(standaloneDir) {
|
|
370
349
|
const nextDirs = findPackageDirs(standaloneDir, "next");
|
|
371
350
|
const target = "let resolve = process.env.NEXT_MINIMAL ? __non_webpack_require__.resolve : require.resolve;";
|
|
@@ -390,8 +369,12 @@ let resolve = (id) => { try { return _resolve(id); } catch { return ''; } };`;
|
|
|
390
369
|
function collectExternalModules(standaloneDir) {
|
|
391
370
|
const pkgRoots = new Map;
|
|
392
371
|
function addPkg(name, path) {
|
|
393
|
-
|
|
394
|
-
|
|
372
|
+
let set = pkgRoots.get(name);
|
|
373
|
+
if (!set) {
|
|
374
|
+
set = new Set;
|
|
375
|
+
pkgRoots.set(name, set);
|
|
376
|
+
}
|
|
377
|
+
set.add(path);
|
|
395
378
|
}
|
|
396
379
|
function scanDir(dir) {
|
|
397
380
|
if (!existsSync(dir))
|
|
@@ -447,12 +430,16 @@ function collectExternalModules(standaloneDir) {
|
|
|
447
430
|
if (pkgRoots.size === 0)
|
|
448
431
|
return [];
|
|
449
432
|
const results = [];
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
433
|
+
const seenMods = new Set;
|
|
434
|
+
for (const [name, paths] of pkgRoots) {
|
|
435
|
+
for (const pkgPath of paths) {
|
|
436
|
+
for (const f of walkDir(pkgPath)) {
|
|
437
|
+
const mod = `${name}/${f.relativePath.replace(/\\/g, "/")}`;
|
|
438
|
+
if (seenMods.has(mod))
|
|
439
|
+
continue;
|
|
440
|
+
seenMods.add(mod);
|
|
441
|
+
results.push({ mod, src: f.absolutePath });
|
|
442
|
+
}
|
|
456
443
|
}
|
|
457
444
|
}
|
|
458
445
|
return results;
|
|
@@ -499,9 +486,108 @@ function fixModuleResolution(standaloneDir) {
|
|
|
499
486
|
}
|
|
500
487
|
}
|
|
501
488
|
}
|
|
489
|
+
function readAdapterSnapshot(distDir) {
|
|
490
|
+
try {
|
|
491
|
+
const raw = JSON.parse(readFileSync(join(distDir, "nbc-adapter-outputs.json"), "utf-8"));
|
|
492
|
+
if (raw?.version !== 1 || !Array.isArray(raw.prerenders))
|
|
493
|
+
return null;
|
|
494
|
+
const buildId = readFileSync(join(distDir, "BUILD_ID"), "utf-8").trim();
|
|
495
|
+
if (raw.buildId !== buildId)
|
|
496
|
+
return null;
|
|
497
|
+
return raw;
|
|
498
|
+
} catch {
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
function computeStaticTiersFromSnapshot(snapshot, args) {
|
|
503
|
+
const none = (why) => ({
|
|
504
|
+
tier1: [],
|
|
505
|
+
staticPages: [],
|
|
506
|
+
disabled: why,
|
|
507
|
+
customCacheHandler: snapshot.hasCustomCacheHandler
|
|
508
|
+
});
|
|
509
|
+
if (snapshot.basePath)
|
|
510
|
+
return none("basePath is set");
|
|
511
|
+
if (snapshot.i18n)
|
|
512
|
+
return none("i18n is configured");
|
|
513
|
+
const matchers = [];
|
|
514
|
+
for (const source of [
|
|
515
|
+
...snapshot.middlewareMatchers,
|
|
516
|
+
...snapshot.routingRules
|
|
517
|
+
]) {
|
|
518
|
+
try {
|
|
519
|
+
matchers.push(new RegExp(source));
|
|
520
|
+
} catch {
|
|
521
|
+
matchers.push(/.*/);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
const covered = (p) => matchers.some((re) => re.test(p));
|
|
525
|
+
const tier1 = [];
|
|
526
|
+
if (!args.assetPrefix) {
|
|
527
|
+
for (const f of args.staticFiles) {
|
|
528
|
+
if (!covered(f.urlPath)) {
|
|
529
|
+
tier1.push({ urlPath: f.urlPath, key: f.urlPath, kind: "static" });
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
for (const f of args.publicFiles) {
|
|
534
|
+
if (!covered(f.urlPath)) {
|
|
535
|
+
tier1.push({ urlPath: f.urlPath, key: f.urlPath, kind: "public" });
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
const byPathname = new Map(snapshot.prerenders.map((p) => [p.pathname, p]));
|
|
539
|
+
const staticPages = [];
|
|
540
|
+
for (const p of snapshot.prerenders) {
|
|
541
|
+
if (!p.file || !p.file.endsWith(".html"))
|
|
542
|
+
continue;
|
|
543
|
+
if (p.pathname.startsWith("/_"))
|
|
544
|
+
continue;
|
|
545
|
+
if (p.revalidate !== false || p.postponed)
|
|
546
|
+
continue;
|
|
547
|
+
if (covered(p.pathname))
|
|
548
|
+
continue;
|
|
549
|
+
const rscPathname = p.pathname === "/" ? "/index.rsc" : `${p.pathname}.rsc`;
|
|
550
|
+
const rscFile = byPathname.get(rscPathname)?.file ?? null;
|
|
551
|
+
const headers = {};
|
|
552
|
+
let tags = [];
|
|
553
|
+
for (const [k, v] of Object.entries(p.headers)) {
|
|
554
|
+
const lc = k.toLowerCase();
|
|
555
|
+
if (lc === "x-next-cache-tags") {
|
|
556
|
+
tags = v.split(",").map((t) => t.trim()).filter(Boolean);
|
|
557
|
+
} else if (lc !== "vary" && lc !== "content-type") {
|
|
558
|
+
headers[k] = v;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
staticPages.push({
|
|
562
|
+
path: p.pathname,
|
|
563
|
+
htmlKey: `__runtime/.next/${p.file.replace(/\\/g, "/")}`,
|
|
564
|
+
rscKey: rscFile ? `__runtime/.next/${rscFile.replace(/\\/g, "/")}` : null,
|
|
565
|
+
headers,
|
|
566
|
+
status: p.status,
|
|
567
|
+
tags
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
return {
|
|
571
|
+
tier1,
|
|
572
|
+
staticPages,
|
|
573
|
+
disabled: null,
|
|
574
|
+
customCacheHandler: snapshot.hasCustomCacheHandler
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
function computeStaticTiers(args) {
|
|
578
|
+
const { distDir, staticFiles, publicFiles, assetPrefix } = args;
|
|
579
|
+
const snapshot = readAdapterSnapshot(distDir);
|
|
580
|
+
if (!snapshot) {
|
|
581
|
+
throw new Error('next-bun-compile: adapter outputs not found for this build. Build through the adapter: set `adapterPath: "next-bun-compile/adapter"` in next.config (or NEXT_ADAPTER_PATH=next-bun-compile/adapter) and run `next build`.');
|
|
582
|
+
}
|
|
583
|
+
return computeStaticTiersFromSnapshot(snapshot, {
|
|
584
|
+
staticFiles,
|
|
585
|
+
publicFiles,
|
|
586
|
+
assetPrefix
|
|
587
|
+
});
|
|
588
|
+
}
|
|
502
589
|
function generateEntryPoint(options) {
|
|
503
|
-
const { standaloneDir, distDir, projectDir } = options;
|
|
504
|
-
const serverDir = findServerDir(standaloneDir);
|
|
590
|
+
const { standaloneDir, serverDir, distDir, projectDir } = options;
|
|
505
591
|
generateStubs(standaloneDir);
|
|
506
592
|
patchRequireHook(standaloneDir);
|
|
507
593
|
fixModuleResolution(standaloneDir);
|
|
@@ -515,7 +601,30 @@ function generateEntryPoint(options) {
|
|
|
515
601
|
...f,
|
|
516
602
|
urlPath: `/${f.relativePath.replace(/\\/g, "/")}`
|
|
517
603
|
}));
|
|
604
|
+
const rsfConfig = JSON.parse(readFileSync(join(distDir, "required-server-files.json"), "utf-8")).config ?? {};
|
|
605
|
+
const assetPrefix = rsfConfig.assetPrefix ?? "";
|
|
606
|
+
const {
|
|
607
|
+
tier1,
|
|
608
|
+
staticPages,
|
|
609
|
+
disabled: tiersDisabled,
|
|
610
|
+
customCacheHandler
|
|
611
|
+
} = computeStaticTiers({
|
|
612
|
+
distDir,
|
|
613
|
+
staticFiles,
|
|
614
|
+
publicFiles,
|
|
615
|
+
assetPrefix
|
|
616
|
+
});
|
|
617
|
+
if (tiersDisabled) {
|
|
618
|
+
console.log(`next-bun-compile: memory tiers disabled (${tiersDisabled}) — all requests go through Next`);
|
|
619
|
+
} else {
|
|
620
|
+
console.log(`next-bun-compile: Serving ${tier1.length} assets + ${staticPages.length} prerendered pages from memory`);
|
|
621
|
+
}
|
|
518
622
|
const standaloneNextDir = join(serverDir, ".next");
|
|
623
|
+
const hasCustomCacheHandler = customCacheHandler;
|
|
624
|
+
if (staticPages.length > 0 && hasCustomCacheHandler) {
|
|
625
|
+
console.log("next-bun-compile: custom cacheHandler detected — prerendered pages stay with Next (Tier 2 off)");
|
|
626
|
+
staticPages.length = 0;
|
|
627
|
+
}
|
|
519
628
|
const turbopackAliases = findTurbopackAliases(standaloneNextDir);
|
|
520
629
|
const aliasNames = new Set(turbopackAliases.map((a) => a.alias));
|
|
521
630
|
const runtimeFiles = walkDir(standaloneNextDir).filter((f) => {
|
|
@@ -545,7 +654,6 @@ function generateEntryPoint(options) {
|
|
|
545
654
|
const canonicalResolutions = buildCanonicalResolutions(externalDir, turbopackAliases);
|
|
546
655
|
validateAliasResolutions(turbopackAliases, canonicalResolutions);
|
|
547
656
|
const rewrittenChunks = rewriteTurbopackAliases(standaloneNextDir, turbopackAliases, canonicalResolutions);
|
|
548
|
-
const assetPrefix = readAssetPrefix(distDir);
|
|
549
657
|
const assetsToEmbed = assetPrefix ? [...publicFiles, ...runtimeFiles] : [...staticFiles, ...publicFiles, ...runtimeFiles];
|
|
550
658
|
if (assetPrefix) {
|
|
551
659
|
console.log(`next-bun-compile: assetPrefix detected — skipping ${staticFiles.length} static assets (served from CDN)`);
|
|
@@ -560,8 +668,8 @@ function generateEntryPoint(options) {
|
|
|
560
668
|
const buildHash = hasher.digest("hex");
|
|
561
669
|
const imports = [];
|
|
562
670
|
const mapEntries = [];
|
|
563
|
-
for (const asset of assetsToEmbed) {
|
|
564
|
-
const varName = toVarName(asset.urlPath);
|
|
671
|
+
for (const [i, asset] of assetsToEmbed.entries()) {
|
|
672
|
+
const varName = toVarName(asset.urlPath, i);
|
|
565
673
|
const importPath = relative(serverDir, asset.absolutePath).replace(/\\/g, "/");
|
|
566
674
|
imports.push(`import ${varName} from "./${importPath}" with { type: "file" };`);
|
|
567
675
|
mapEntries.push(` ["${asset.urlPath}", ${varName}],`);
|
|
@@ -573,11 +681,8 @@ ${mapEntries.join(`
|
|
|
573
681
|
`)}
|
|
574
682
|
]);
|
|
575
683
|
`);
|
|
576
|
-
const
|
|
577
|
-
|
|
578
|
-
if (!configMatch) {
|
|
579
|
-
throw new Error("next-bun-compile: Could not extract nextConfig from standalone server.js");
|
|
580
|
-
}
|
|
684
|
+
const serveRuntimeSrc = join(import.meta.dirname, "runtime/serve.js");
|
|
685
|
+
copyFileSync(existsSync(serveRuntimeSrc) ? serveRuntimeSrc : join(import.meta.dirname, "../src/runtime/serve.js"), join(serverDir, "nbc-serve.js"));
|
|
581
686
|
const assetExtractions = assetsToEmbed.map((a) => {
|
|
582
687
|
let diskPath;
|
|
583
688
|
if (a.urlPath.startsWith("__runtime/")) {
|
|
@@ -594,7 +699,13 @@ const path = require("path");
|
|
|
594
699
|
const fs = require("fs");
|
|
595
700
|
const Module = require("module");
|
|
596
701
|
|
|
597
|
-
|
|
702
|
+
// NBC_RUNTIME_DIR relocates extraction + Next's working dir — point it at
|
|
703
|
+
// tmpfs (e.g. /tmp/app) for RAM-backed runtime files and compatibility
|
|
704
|
+
// with read-only root filesystems. Default: next to the binary.
|
|
705
|
+
const baseDir = process.env.NBC_RUNTIME_DIR
|
|
706
|
+
? path.resolve(process.env.NBC_RUNTIME_DIR)
|
|
707
|
+
: path.dirname(process.execPath);
|
|
708
|
+
fs.mkdirSync(baseDir, { recursive: true });
|
|
598
709
|
process.chdir(baseDir);
|
|
599
710
|
process.env.NODE_ENV = "production";
|
|
600
711
|
|
|
@@ -758,7 +869,7 @@ Module._resolveFilename = function(request, parent, isMain, options) {
|
|
|
758
869
|
}
|
|
759
870
|
};
|
|
760
871
|
|
|
761
|
-
const nextConfig = ${
|
|
872
|
+
const nextConfig = ${JSON.stringify(rsfConfig)};
|
|
762
873
|
process.env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(nextConfig);
|
|
763
874
|
|
|
764
875
|
const currentPort = parseInt(process.env.PORT, 10) || 3000;
|
|
@@ -819,12 +930,24 @@ async function extractAssets() {
|
|
|
819
930
|
console.log(\`Extracted \${extractions.length} assets\`);
|
|
820
931
|
}
|
|
821
932
|
|
|
933
|
+
const __NBC_TIER1 = ${JSON.stringify(tier1)};
|
|
934
|
+
const __NBC_STATIC_PAGES = ${JSON.stringify(staticPages)};
|
|
935
|
+
|
|
822
936
|
extractAssets().then(() => {
|
|
823
|
-
require("
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
937
|
+
const { start } = require("./nbc-serve.js");
|
|
938
|
+
return start({
|
|
939
|
+
assetMap,
|
|
940
|
+
nextConfig,
|
|
941
|
+
port: currentPort,
|
|
942
|
+
hostname,
|
|
943
|
+
keepAliveTimeout,
|
|
944
|
+
tier1: __NBC_TIER1,
|
|
945
|
+
staticPages: __NBC_STATIC_PAGES,
|
|
946
|
+
baseDir,
|
|
947
|
+
// Revalidation events are observed on the default filesystem cache
|
|
948
|
+
// handler; with a custom handler they never fire, so response
|
|
949
|
+
// caching would serve stale pages.
|
|
950
|
+
enableL1: ${JSON.stringify(!hasCustomCacheHandler)},
|
|
828
951
|
});
|
|
829
952
|
}).catch((err) => { console.error(err); process.exit(1); });
|
|
830
953
|
`;
|
|
@@ -845,6 +968,16 @@ function compile(options) {
|
|
|
845
968
|
"--compile",
|
|
846
969
|
"--minify",
|
|
847
970
|
"--sourcemap",
|
|
971
|
+
"--external",
|
|
972
|
+
"webpack",
|
|
973
|
+
"--external",
|
|
974
|
+
"webpack/*",
|
|
975
|
+
"--external",
|
|
976
|
+
"next/dist/build/webpack/*",
|
|
977
|
+
"--external",
|
|
978
|
+
"sass",
|
|
979
|
+
"--external",
|
|
980
|
+
"critters",
|
|
848
981
|
"--define",
|
|
849
982
|
"process.env.TURBOPACK=1",
|
|
850
983
|
"--define",
|
|
@@ -853,6 +986,7 @@ function compile(options) {
|
|
|
853
986
|
'process.env.NEXT_RUNTIME="nodejs"',
|
|
854
987
|
"--outfile",
|
|
855
988
|
outfile,
|
|
989
|
+
...process.env.NBC_TARGET ? [`--target=${process.env.NBC_TARGET}`] : [],
|
|
856
990
|
...extraArgs
|
|
857
991
|
];
|
|
858
992
|
console.log(`next-bun-compile: Compiling to ${outfile}...`);
|
|
@@ -868,4 +1002,193 @@ function compile(options) {
|
|
|
868
1002
|
console.log(`next-bun-compile: Done → ${outfile}`);
|
|
869
1003
|
}
|
|
870
1004
|
|
|
871
|
-
|
|
1005
|
+
// src/build.ts
|
|
1006
|
+
async function ensureServerRuntime(projectDir, standaloneDir) {
|
|
1007
|
+
const req = createRequire(join3(projectDir, "package.json"));
|
|
1008
|
+
const { nodeFileTrace } = req("next/dist/compiled/@vercel/nft");
|
|
1009
|
+
const entries = [
|
|
1010
|
+
req.resolve("next/dist/server/lib/router-server.js"),
|
|
1011
|
+
req.resolve("next/dist/server/lib/incremental-cache/file-system-cache.js")
|
|
1012
|
+
];
|
|
1013
|
+
const { fileList } = await nodeFileTrace(entries, {
|
|
1014
|
+
base: "/",
|
|
1015
|
+
ignore: [
|
|
1016
|
+
"**/*.d.ts",
|
|
1017
|
+
"**/*.map",
|
|
1018
|
+
"**/next/dist/compiled/next-server/**/*.dev.js",
|
|
1019
|
+
"**/next/dist/compiled/webpack/*",
|
|
1020
|
+
"**/node_modules/webpack5/**/*",
|
|
1021
|
+
"**/next/dist/server/lib/route-resolver*",
|
|
1022
|
+
"**/next/dist/compiled/semver/semver/**/*.js",
|
|
1023
|
+
"**/next/dist/compiled/jest-worker/**/*",
|
|
1024
|
+
"**/node_modules/react/**/*.development.js",
|
|
1025
|
+
"**/node_modules/react-dom/**/*.development.js"
|
|
1026
|
+
]
|
|
1027
|
+
});
|
|
1028
|
+
let copied = 0;
|
|
1029
|
+
for (const f of fileList) {
|
|
1030
|
+
const src = "/" + f;
|
|
1031
|
+
const idx = src.indexOf("/node_modules/");
|
|
1032
|
+
if (idx === -1)
|
|
1033
|
+
continue;
|
|
1034
|
+
const dest = join3(standaloneDir, src.slice(idx + 1));
|
|
1035
|
+
try {
|
|
1036
|
+
if (statSync2(src).isDirectory())
|
|
1037
|
+
continue;
|
|
1038
|
+
} catch {
|
|
1039
|
+
continue;
|
|
1040
|
+
}
|
|
1041
|
+
if (existsSync2(dest))
|
|
1042
|
+
continue;
|
|
1043
|
+
mkdirSync2(dirname(dest), { recursive: true });
|
|
1044
|
+
copyFileSync2(src, dest);
|
|
1045
|
+
copied++;
|
|
1046
|
+
}
|
|
1047
|
+
if (copied > 0) {
|
|
1048
|
+
console.log(`next-bun-compile: added ${copied} server-runtime files to the traced tree`);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
async function runBuild(options) {
|
|
1052
|
+
const { projectDir, standaloneDir, serverDir, extraArgs = [] } = options;
|
|
1053
|
+
const distDir = join3(projectDir, ".next");
|
|
1054
|
+
await ensureServerRuntime(projectDir, standaloneDir);
|
|
1055
|
+
generateEntryPoint({ standaloneDir, serverDir, distDir, projectDir });
|
|
1056
|
+
const outfile = join3(projectDir, "server");
|
|
1057
|
+
compile({ serverDir, outfile, extraArgs });
|
|
1058
|
+
return outfile;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
// src/adapter.ts
|
|
1062
|
+
var ADAPTER_OUTPUTS_FILE = "nbc-adapter-outputs.json";
|
|
1063
|
+
function flattenHeaders(headers) {
|
|
1064
|
+
const out = {};
|
|
1065
|
+
for (const [k, v] of Object.entries(headers ?? {})) {
|
|
1066
|
+
out[k] = Array.isArray(v) ? v.join(", ") : v;
|
|
1067
|
+
}
|
|
1068
|
+
return out;
|
|
1069
|
+
}
|
|
1070
|
+
async function assembleStandalone(ctx) {
|
|
1071
|
+
const staging = join4(ctx.distDir, "nbc-standalone");
|
|
1072
|
+
rmSync(staging, { recursive: true, force: true });
|
|
1073
|
+
const copied = new Set;
|
|
1074
|
+
let escaped = 0;
|
|
1075
|
+
const copyTo = (destRel, src) => {
|
|
1076
|
+
if (destRel.startsWith("..")) {
|
|
1077
|
+
escaped++;
|
|
1078
|
+
return;
|
|
1079
|
+
}
|
|
1080
|
+
if (copied.has(destRel))
|
|
1081
|
+
return;
|
|
1082
|
+
try {
|
|
1083
|
+
if (!statSync3(src).isFile())
|
|
1084
|
+
return;
|
|
1085
|
+
} catch {
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
copied.add(destRel);
|
|
1089
|
+
const dest = join4(staging, destRel);
|
|
1090
|
+
mkdirSync3(dirname2(dest), { recursive: true });
|
|
1091
|
+
copyFileSync3(src, dest);
|
|
1092
|
+
};
|
|
1093
|
+
const outputs = ctx.outputs ?? {};
|
|
1094
|
+
const routeOutputs = [
|
|
1095
|
+
...outputs.pages ?? [],
|
|
1096
|
+
...outputs.pagesApi ?? [],
|
|
1097
|
+
...outputs.appPages ?? [],
|
|
1098
|
+
...outputs.appRoutes ?? [],
|
|
1099
|
+
...outputs.middleware ? [outputs.middleware] : []
|
|
1100
|
+
];
|
|
1101
|
+
for (const o of routeOutputs) {
|
|
1102
|
+
if (o.filePath)
|
|
1103
|
+
copyTo(relative2(ctx.repoRoot, o.filePath), o.filePath);
|
|
1104
|
+
for (const [key, src] of Object.entries(o.assets ?? {}))
|
|
1105
|
+
copyTo(key, src);
|
|
1106
|
+
}
|
|
1107
|
+
for (const nft of ["next-server.js.nft.json", "next-minimal-server.js.nft.json"]) {
|
|
1108
|
+
const nftPath = join4(ctx.distDir, nft);
|
|
1109
|
+
if (!existsSync3(nftPath))
|
|
1110
|
+
continue;
|
|
1111
|
+
try {
|
|
1112
|
+
const { files } = JSON.parse(readFileSync2(nftPath, "utf-8"));
|
|
1113
|
+
for (const f of files) {
|
|
1114
|
+
const src = resolve(ctx.distDir, f);
|
|
1115
|
+
copyTo(relative2(ctx.repoRoot, src), src);
|
|
1116
|
+
}
|
|
1117
|
+
} catch {}
|
|
1118
|
+
}
|
|
1119
|
+
for (const p of outputs.prerenders ?? []) {
|
|
1120
|
+
const file = p.fallback?.filePath;
|
|
1121
|
+
if (!file)
|
|
1122
|
+
continue;
|
|
1123
|
+
copyTo(relative2(ctx.repoRoot, file), file);
|
|
1124
|
+
if (file.endsWith(".html")) {
|
|
1125
|
+
const meta = file.slice(0, -".html".length) + ".meta";
|
|
1126
|
+
copyTo(relative2(ctx.repoRoot, meta), meta);
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
for (const extra of ["BUILD_ID", "required-server-files.json"]) {
|
|
1130
|
+
const src = join4(ctx.distDir, extra);
|
|
1131
|
+
copyTo(relative2(ctx.repoRoot, src), src);
|
|
1132
|
+
}
|
|
1133
|
+
const appStaging = join4(staging, relative2(ctx.repoRoot, ctx.projectDir));
|
|
1134
|
+
mkdirSync3(appStaging, { recursive: true });
|
|
1135
|
+
if (escaped > 0) {
|
|
1136
|
+
console.warn(`next-bun-compile: ${escaped} traced file(s) outside the repo root were skipped`);
|
|
1137
|
+
}
|
|
1138
|
+
console.log(`next-bun-compile: assembled ${copied.size} traced files (no standalone output needed)`);
|
|
1139
|
+
return { standaloneDir: staging, serverDir: appStaging };
|
|
1140
|
+
}
|
|
1141
|
+
var adapter = {
|
|
1142
|
+
name: "next-bun-compile",
|
|
1143
|
+
async onBuildComplete(ctx) {
|
|
1144
|
+
const routingRules = [];
|
|
1145
|
+
for (const key of [
|
|
1146
|
+
"beforeMiddleware",
|
|
1147
|
+
"beforeFiles",
|
|
1148
|
+
"afterFiles",
|
|
1149
|
+
"onMatch",
|
|
1150
|
+
"fallback"
|
|
1151
|
+
]) {
|
|
1152
|
+
const list = ctx.routing?.[key];
|
|
1153
|
+
if (!Array.isArray(list))
|
|
1154
|
+
continue;
|
|
1155
|
+
for (const route of list) {
|
|
1156
|
+
if (route.sourceRegex && typeof route.source === "string" && !route.priority && (route.headers || route.destination || route.status)) {
|
|
1157
|
+
routingRules.push(route.sourceRegex);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
const prerenders = (ctx.outputs?.prerenders ?? []).flatMap((p) => {
|
|
1162
|
+
if (!p.pathname)
|
|
1163
|
+
return [];
|
|
1164
|
+
return [
|
|
1165
|
+
{
|
|
1166
|
+
pathname: p.pathname,
|
|
1167
|
+
file: p.fallback?.filePath ? relative2(ctx.distDir, p.fallback.filePath) : null,
|
|
1168
|
+
status: p.fallback?.initialStatus ?? 200,
|
|
1169
|
+
revalidate: p.fallback?.initialRevalidate ?? false,
|
|
1170
|
+
postponed: !!p.fallback?.postponedState,
|
|
1171
|
+
headers: flattenHeaders(p.fallback?.initialHeaders)
|
|
1172
|
+
}
|
|
1173
|
+
];
|
|
1174
|
+
});
|
|
1175
|
+
const snapshot = {
|
|
1176
|
+
version: 1,
|
|
1177
|
+
buildId: ctx.buildId,
|
|
1178
|
+
nextVersion: ctx.nextVersion,
|
|
1179
|
+
basePath: ctx.config.basePath ?? "",
|
|
1180
|
+
i18n: !!ctx.config.i18n,
|
|
1181
|
+
hasCustomCacheHandler: !!ctx.config.cacheHandler,
|
|
1182
|
+
middlewareMatchers: (ctx.outputs?.middleware?.config?.matchers ?? []).map((m) => m.sourceRegex).filter((r) => !!r),
|
|
1183
|
+
routingRules,
|
|
1184
|
+
prerenders
|
|
1185
|
+
};
|
|
1186
|
+
writeFileSync2(join4(ctx.distDir, ADAPTER_OUTPUTS_FILE), JSON.stringify(snapshot, null, 2));
|
|
1187
|
+
console.log(`next-bun-compile: adapter outputs written (${prerenders.length} prerender entries)`);
|
|
1188
|
+
const { standaloneDir, serverDir } = await assembleStandalone(ctx);
|
|
1189
|
+
await runBuild({ projectDir: ctx.projectDir, standaloneDir, serverDir });
|
|
1190
|
+
}
|
|
1191
|
+
};
|
|
1192
|
+
var adapter_default = adapter;
|
|
1193
|
+
|
|
1194
|
+
export { generateEntryPoint, compile, runBuild, ADAPTER_OUTPUTS_FILE, adapter_default };
|