next-bun-compile 0.6.13 → 0.7.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
@@ -8,6 +8,8 @@ One command. One binary. No runtime dependencies.
8
8
  next build # → ./server (single executable with embedded assets)
9
9
  ```
10
10
 
11
+ **📖 Docs: [ramonmalcolm10.github.io/next-bun-compile](https://ramonmalcolm10.github.io/next-bun-compile/)**
12
+
11
13
  ## Requirements
12
14
 
13
15
  - [Bun](https://bun.sh) >= 1.3
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  compile,
4
4
  generateEntryPoint
5
- } from "./index-3xvctb1p.js";
5
+ } from "./index-jevfyh6f.js";
6
6
 
7
7
  // src/cli.ts
8
8
  import { existsSync } from "node:fs";
@@ -46,24 +46,56 @@ function toVarName(filePath) {
46
46
  const safe = filePath.replace(/[^a-zA-Z0-9]/g, "_").slice(0, 40);
47
47
  return `asset_${safe}_${hash}`;
48
48
  }
49
- function findPackageDirs(nodeModulesDir, pkg) {
49
+ function findPackageDirs(standaloneDir, pkg) {
50
50
  const dirs = [];
51
- const direct = join(nodeModulesDir, pkg);
52
- if (existsSync(direct))
53
- dirs.push(direct);
54
51
  const prefix = pkg.startsWith("@") ? pkg.split("/")[0] + "+" + pkg.split("/")[1] : pkg;
55
- for (const store of [".bun", ".pnpm"]) {
56
- const storeDir = join(nodeModulesDir, store);
57
- if (!existsSync(storeDir))
58
- continue;
59
- for (const entry of readdirSync(storeDir)) {
60
- if (!entry.startsWith(prefix + "@"))
52
+ const seen = new Set;
53
+ const checkNodeModules = (nodeModulesDir) => {
54
+ const direct = join(nodeModulesDir, pkg);
55
+ if (existsSync(direct) && !seen.has(direct)) {
56
+ seen.add(direct);
57
+ dirs.push(direct);
58
+ }
59
+ for (const store of [".bun", ".pnpm"]) {
60
+ const storeDir = join(nodeModulesDir, store);
61
+ if (!existsSync(storeDir))
62
+ continue;
63
+ let entries;
64
+ try {
65
+ entries = readdirSync(storeDir);
66
+ } catch {
61
67
  continue;
62
- const hoisted = join(storeDir, entry, "node_modules", pkg);
63
- if (existsSync(hoisted))
64
- dirs.push(hoisted);
68
+ }
69
+ for (const entry of entries) {
70
+ if (!entry.startsWith(prefix + "@"))
71
+ continue;
72
+ const hoisted = join(storeDir, entry, "node_modules", pkg);
73
+ if (existsSync(hoisted) && !seen.has(hoisted)) {
74
+ seen.add(hoisted);
75
+ dirs.push(hoisted);
76
+ }
77
+ }
65
78
  }
66
- }
79
+ };
80
+ const walk = (dir) => {
81
+ let entries;
82
+ try {
83
+ entries = readdirSync(dir);
84
+ } catch {
85
+ return;
86
+ }
87
+ for (const entry of entries) {
88
+ const full = join(dir, entry);
89
+ if (entry === "node_modules") {
90
+ checkNodeModules(full);
91
+ continue;
92
+ }
93
+ const stat = tryStat(full);
94
+ if (stat && stat.isDirectory())
95
+ walk(full);
96
+ }
97
+ };
98
+ walk(standaloneDir);
67
99
  return dirs;
68
100
  }
69
101
  function generateStubs(standaloneDir) {
@@ -92,7 +124,7 @@ function generateStubs(standaloneDir) {
92
124
  const nodeModulesDir = join(standaloneDir, "node_modules");
93
125
  let count = 0;
94
126
  for (const stub of stubs) {
95
- const pkgDirs = findPackageDirs(nodeModulesDir, stub.pkg);
127
+ const pkgDirs = findPackageDirs(standaloneDir, stub.pkg);
96
128
  if (pkgDirs.length === 0)
97
129
  pkgDirs.push(join(nodeModulesDir, stub.pkg));
98
130
  for (const pkgDir of pkgDirs) {
@@ -230,6 +262,41 @@ function buildCanonicalResolutions(externalRoot, aliases) {
230
262
  }
231
263
  return out;
232
264
  }
265
+ function validateAliasResolutions(aliases, resolutions) {
266
+ const verbose = process.env.NEXT_BUN_COMPILE_VERBOSE === "1";
267
+ const all = [];
268
+ for (const { alias, target, subpaths } of aliases) {
269
+ all.push({ ref: alias, canonical: target, file: resolutions.get(alias) ?? null });
270
+ for (const sub of subpaths) {
271
+ const ref = `${alias}/${sub}`;
272
+ all.push({
273
+ ref,
274
+ canonical: `${target}/${sub}`,
275
+ file: resolutions.get(ref) ?? null
276
+ });
277
+ }
278
+ }
279
+ const unresolved = all.filter((e) => !e.file).map(({ ref, canonical }) => ({ ref, canonical }));
280
+ if (verbose && all.length > 0) {
281
+ console.log(`next-bun-compile: Validating ${all.length} turbopack alias reference(s):`);
282
+ for (const { ref, canonical, file } of all) {
283
+ if (file) {
284
+ const display = file.replace(/^\.next\/node_modules\//, "");
285
+ console.log(` ✓ ${ref} → ${canonical} (${display})`);
286
+ } else {
287
+ console.log(` ✗ ${ref} → ${canonical} (NOT FOUND)`);
288
+ }
289
+ }
290
+ }
291
+ if (unresolved.length > 0) {
292
+ console.warn(`next-bun-compile: ⚠ ${unresolved.length} of ${all.length} turbopack alias reference(s) won't resolve at runtime:`);
293
+ for (const { ref, canonical } of unresolved) {
294
+ console.warn(` ✗ ${ref} → ${canonical}`);
295
+ }
296
+ console.warn(`next-bun-compile: These chunks will throw at runtime when the reference is hit. ` + `Either the package is missing from dependencies, or it's hidden behind a path the ` + `standalone trace didn't include. Try adding to transpilePackages in next.config.`);
297
+ }
298
+ return unresolved;
299
+ }
233
300
  function rewriteTurbopackAliases(standaloneNextDir, aliases, resolutions) {
234
301
  if (aliases.length === 0 || resolutions.size === 0)
235
302
  return;
@@ -294,8 +361,7 @@ function findServerDir(standaloneDir) {
294
361
  return found;
295
362
  }
296
363
  function patchRequireHook(standaloneDir) {
297
- const nodeModulesDir = join(standaloneDir, "node_modules");
298
- const nextDirs = findPackageDirs(nodeModulesDir, "next");
364
+ const nextDirs = findPackageDirs(standaloneDir, "next");
299
365
  const target = "let resolve = process.env.NEXT_MINIMAL ? __non_webpack_require__.resolve : require.resolve;";
300
366
  const replacement = `let _resolve = process.env.NEXT_MINIMAL ? __non_webpack_require__.resolve : require.resolve;
301
367
  let resolve = (id) => { try { return _resolve(id); } catch { return ''; } };`;
@@ -316,9 +382,6 @@ let resolve = (id) => { try { return _resolve(id); } catch { return ''; } };`;
316
382
  }
317
383
  }
318
384
  function collectExternalModules(standaloneDir) {
319
- const nodeModulesDir = join(standaloneDir, "node_modules");
320
- if (!existsSync(nodeModulesDir))
321
- return [];
322
385
  const pkgRoots = new Map;
323
386
  function addPkg(name, path) {
324
387
  if (!pkgRoots.has(name))
@@ -346,17 +409,37 @@ function collectExternalModules(standaloneDir) {
346
409
  }
347
410
  }
348
411
  }
349
- scanDir(nodeModulesDir);
350
- for (const store of [".bun", ".pnpm"]) {
351
- const storeDir = join(nodeModulesDir, store);
352
- if (!existsSync(storeDir))
353
- continue;
354
- for (const storeEntry of readdirSync(storeDir)) {
355
- const nested = join(storeDir, storeEntry, "node_modules");
356
- if (existsSync(nested))
357
- scanDir(nested);
412
+ function walkForNodeModules(dir) {
413
+ let entries;
414
+ try {
415
+ entries = readdirSync(dir);
416
+ } catch {
417
+ return;
418
+ }
419
+ for (const entry of entries) {
420
+ const full = join(dir, entry);
421
+ if (entry === "node_modules") {
422
+ scanDir(full);
423
+ for (const store of [".bun", ".pnpm"]) {
424
+ const storeDir = join(full, store);
425
+ if (!existsSync(storeDir))
426
+ continue;
427
+ for (const storeEntry of readdirSync(storeDir)) {
428
+ const nested = join(storeDir, storeEntry, "node_modules");
429
+ if (existsSync(nested))
430
+ scanDir(nested);
431
+ }
432
+ }
433
+ continue;
434
+ }
435
+ const stat = tryStat(full);
436
+ if (stat && stat.isDirectory())
437
+ walkForNodeModules(full);
358
438
  }
359
439
  }
440
+ walkForNodeModules(standaloneDir);
441
+ if (pkgRoots.size === 0)
442
+ return [];
360
443
  const results = [];
361
444
  for (const [name, pkgPath] of pkgRoots) {
362
445
  for (const f of walkDir(pkgPath)) {
@@ -369,8 +452,7 @@ function collectExternalModules(standaloneDir) {
369
452
  return results;
370
453
  }
371
454
  function fixModuleResolution(standaloneDir) {
372
- const nodeModulesDir = join(standaloneDir, "node_modules");
373
- for (const pkgDir of findPackageDirs(nodeModulesDir, "next")) {
455
+ for (const pkgDir of findPackageDirs(standaloneDir, "next")) {
374
456
  const compiledDir = join(pkgDir, "dist/compiled");
375
457
  if (!existsSync(compiledDir))
376
458
  continue;
@@ -389,7 +471,7 @@ function fixModuleResolution(standaloneDir) {
389
471
  }
390
472
  }
391
473
  }
392
- for (const helpersDir of findPackageDirs(nodeModulesDir, "@swc/helpers")) {
474
+ for (const helpersDir of findPackageDirs(standaloneDir, "@swc/helpers")) {
393
475
  const cjsDir = join(helpersDir, "cjs");
394
476
  if (!existsSync(cjsDir))
395
477
  continue;
@@ -450,6 +532,7 @@ function generateEntryPoint(options) {
450
532
  console.log(`next-bun-compile: Embedding ${externalModules.length} external modules for SSR`);
451
533
  }
452
534
  const canonicalResolutions = buildCanonicalResolutions(externalDir, turbopackAliases);
535
+ validateAliasResolutions(turbopackAliases, canonicalResolutions);
453
536
  rewriteTurbopackAliases(standaloneNextDir, turbopackAliases, canonicalResolutions);
454
537
  const ctx = JSON.parse(readFileSync(join(distDir, "bun-compile-ctx.json"), "utf-8"));
455
538
  const { assetPrefix } = ctx;
@@ -511,6 +594,20 @@ process.env.NODE_ENV = "production";
511
594
  // honor exports maps. Generic: every externalized package's internal deps
512
595
  // get resolved without per-package patching.
513
596
  const __nbcAliases = ${JSON.stringify(Object.fromEntries(turbopackAliases.map((a) => [a.alias, a.target])))};
597
+ // Debug mode: set NEXT_BUN_COMPILE_DEBUG=1 to log every resolver-hook
598
+ // decision (alias redirects, fallback walks, fallback failures). Off by
599
+ // default so production logs stay clean; turn it on when reproducing a
600
+ // resolution bug — that one log line is usually enough to know which
601
+ // package was missing and where the walk gave up.
602
+ const __nbcDebug = process.env.NEXT_BUN_COMPILE_DEBUG === "1";
603
+ function __nbcLog(msg) { console.log("next-bun-compile [debug]:", msg); }
604
+ if (__nbcDebug) {
605
+ const n = Object.keys(__nbcAliases).length;
606
+ __nbcLog(\`resolver hook installed; \${n} alias mapping(s):\`);
607
+ for (const [k, v] of Object.entries(__nbcAliases)) {
608
+ __nbcLog(\` \${k} → \${v}\`);
609
+ }
610
+ }
514
611
  const __nbcOrigResolveFilename = Module._resolveFilename;
515
612
  function __nbcStatFile(p) {
516
613
  try { return fs.statSync(p).isFile() ? p : null; } catch { return null; }
@@ -540,12 +637,34 @@ function __nbcResolveSubpath(pkgDir, pkgJson, sub) {
540
637
  }
541
638
  }
542
639
  }
543
- return __nbcStatFile(path.join(pkgDir, sub))
640
+ // Direct file forms
641
+ const direct = __nbcStatFile(path.join(pkgDir, sub))
544
642
  || __nbcStatFile(path.join(pkgDir, sub + ".js"))
545
643
  || __nbcStatFile(path.join(pkgDir, sub + ".cjs"))
546
644
  || __nbcStatFile(path.join(pkgDir, sub + ".mjs"))
547
- || __nbcStatFile(path.join(pkgDir, sub + ".json"))
548
- || __nbcStatFile(path.join(pkgDir, sub, "index.js"))
645
+ || __nbcStatFile(path.join(pkgDir, sub + ".json"));
646
+ if (direct) return direct;
647
+ // Subpath is a directory: read its own package.json + main (e.g.
648
+ // next/dist/compiled/source-map has main: "source-map.js"). Falls
649
+ // back to index.js / index.cjs lookup if no package.json.
650
+ const subDir = path.join(pkgDir, sub);
651
+ try {
652
+ if (fs.statSync(subDir).isDirectory()) {
653
+ const subPkgPath = path.join(subDir, "package.json");
654
+ if (fs.existsSync(subPkgPath)) {
655
+ try {
656
+ const subPkg = JSON.parse(fs.readFileSync(subPkgPath, "utf-8"));
657
+ const main = typeof subPkg.main === "string" ? subPkg.main : null;
658
+ if (main) {
659
+ const f = __nbcStatFile(path.join(subDir, main))
660
+ || __nbcStatFile(path.join(subDir, main + ".js"));
661
+ if (f) return f;
662
+ }
663
+ } catch {}
664
+ }
665
+ }
666
+ } catch {}
667
+ return __nbcStatFile(path.join(pkgDir, sub, "index.js"))
549
668
  || __nbcStatFile(path.join(pkgDir, sub, "index.cjs"));
550
669
  }
551
670
  function __nbcResolvePackage(request, fromDir) {
@@ -596,6 +715,10 @@ function __nbcRedirectAlias(request) {
596
715
  }
597
716
  Module._resolveFilename = function(request, parent, isMain, options) {
598
717
  const redirected = __nbcRedirectAlias(request);
718
+ if (__nbcDebug && redirected !== request) {
719
+ const from = parent && parent.filename ? parent.filename : "<unknown>";
720
+ __nbcLog(\`redirected "\${request}" → "\${redirected}" (from \${from})\`);
721
+ }
599
722
  try {
600
723
  return __nbcOrigResolveFilename.call(this, redirected, parent, isMain, options);
601
724
  } catch (err) {
@@ -605,7 +728,15 @@ Module._resolveFilename = function(request, parent, isMain, options) {
605
728
  }
606
729
  const fromDir = parent && parent.filename ? path.dirname(parent.filename) : process.cwd();
607
730
  const resolved = __nbcResolvePackage(redirected, fromDir);
608
- if (resolved) return resolved;
731
+ if (resolved) {
732
+ if (__nbcDebug) {
733
+ __nbcLog(\`fallback resolved "\${redirected}" → \${resolved} (from \${fromDir})\`);
734
+ }
735
+ return resolved;
736
+ }
737
+ if (__nbcDebug) {
738
+ __nbcLog(\`fallback FAILED for "\${redirected}" (from \${fromDir}); throwing original ResolveMessage\`);
739
+ }
609
740
  throw err;
610
741
  }
611
742
  };
@@ -622,13 +753,25 @@ if (Number.isNaN(keepAliveTimeout) || !Number.isFinite(keepAliveTimeout) || keep
622
753
 
623
754
  const extractions = ${JSON.stringify(assetExtractions)};
624
755
  async function extractAssets() {
625
- let n = 0;
756
+ // Collect everything that isn't already on disk, dedupe parent dirs
757
+ // for a single mkdir pass, then issue all writes concurrently.
758
+ // ~45% faster than serial extraction on a typical Next.js app with
759
+ // sharp; trivially correct because each write is to a distinct path.
760
+ const todo = [];
626
761
  for (const [urlPath, diskPath] of extractions) {
627
762
  const fullPath = path.join(baseDir, diskPath);
628
763
  if (fs.existsSync(fullPath)) continue;
629
- fs.mkdirSync(path.dirname(fullPath), { recursive: true });
630
764
  const embedded = assetMap.get(urlPath);
631
765
  if (!embedded) continue;
766
+ todo.push({ diskPath, fullPath, embedded });
767
+ }
768
+ if (todo.length === 0) return;
769
+
770
+ const dirs = new Set();
771
+ for (const t of todo) dirs.add(path.dirname(t.fullPath));
772
+ for (const d of dirs) fs.mkdirSync(d, { recursive: true });
773
+
774
+ await Promise.all(todo.map(async ({ diskPath, fullPath, embedded }) => {
632
775
  // Server chunks may contain __NBC_BASE__ placeholders injected at
633
776
  // build time by rewriteTurbopackAliases. Substitute the real baseDir
634
777
  // before writing so bun resolves the absolute paths at chunk load
@@ -637,14 +780,12 @@ async function extractAssets() {
637
780
  const text = await Bun.file(embedded).text();
638
781
  if (text.indexOf("__NBC_BASE__") !== -1) {
639
782
  await Bun.write(fullPath, text.split("__NBC_BASE__").join(baseDir));
640
- n++;
641
- continue;
783
+ return;
642
784
  }
643
785
  }
644
786
  await Bun.write(fullPath, Bun.file(embedded));
645
- n++;
646
- }
647
- if (n > 0) console.log(\`Extracted \${n} assets\`);
787
+ }));
788
+ console.log(\`Extracted \${todo.length} assets\`);
648
789
  }
649
790
 
650
791
  extractAssets().then(() => {
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  __require,
3
3
  compile,
4
4
  generateEntryPoint
5
- } from "./index-3xvctb1p.js";
5
+ } from "./index-jevfyh6f.js";
6
6
 
7
7
  // src/index.ts
8
8
  import { join } from "node:path";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-bun-compile",
3
- "version": "0.6.13",
3
+ "version": "0.7.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",