next-bun-compile 0.4.2 → 0.5.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/dist/cli.js CHANGED
@@ -1,297 +1,23 @@
1
1
  #!/usr/bin/env node
2
- import { createRequire } from "node:module";
3
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
4
-
5
- // src/generate.ts
6
2
  import {
7
- writeFileSync,
8
- readFileSync,
9
- existsSync,
10
- readdirSync,
11
- statSync,
12
- mkdirSync
13
- } from "node:fs";
14
- import { join, relative } from "node:path";
15
- import { createHash } from "node:crypto";
16
- function walkDir(dir, base = dir) {
17
- const results = [];
18
- if (!existsSync(dir))
19
- return results;
20
- for (const entry of readdirSync(dir)) {
21
- const full = join(dir, entry);
22
- if (statSync(full).isDirectory()) {
23
- results.push(...walkDir(full, base));
24
- } else {
25
- results.push({ absolutePath: full, relativePath: relative(base, full) });
26
- }
27
- }
28
- return results;
29
- }
30
- function toVarName(filePath) {
31
- const hash = createHash("md5").update(filePath).digest("hex").slice(0, 6);
32
- const safe = filePath.replace(/[^a-zA-Z0-9]/g, "_").slice(0, 40);
33
- return `asset_${safe}_${hash}`;
34
- }
35
- function generateStubs(standaloneDir) {
36
- const stubs = [
37
- {
38
- path: "node_modules/next/dist/server/dev/next-dev-server.js",
39
- content: "module.exports = { default: null };"
40
- },
41
- {
42
- path: "node_modules/next/dist/server/lib/router-utils/setup-dev-bundler.js",
43
- content: "module.exports = {};"
44
- },
45
- {
46
- path: "node_modules/@opentelemetry/api/index.js",
47
- content: "throw new Error('not installed');"
48
- },
49
- {
50
- path: "node_modules/critters/index.js",
51
- content: "module.exports = {};"
52
- }
53
- ];
54
- let count = 0;
55
- for (const stub of stubs) {
56
- const fullPath = join(standaloneDir, stub.path);
57
- if (!existsSync(fullPath)) {
58
- const dir = join(fullPath, "..");
59
- if (!existsSync(dir)) {
60
- mkdirSync(dir, { recursive: true });
61
- }
62
- writeFileSync(fullPath, stub.content);
63
- count++;
64
- }
65
- }
66
- if (count > 0) {
67
- console.log(`next-bun-compile: Created ${count} module stubs`);
68
- }
69
- }
70
- function patchRequireHook(standaloneDir) {
71
- const hookPath = join(standaloneDir, "node_modules/next/dist/server/require-hook.js");
72
- if (!existsSync(hookPath))
73
- return;
74
- let content = readFileSync(hookPath, "utf-8");
75
- const target = "let resolve = process.env.NEXT_MINIMAL ? __non_webpack_require__.resolve : require.resolve;";
76
- if (!content.includes(target))
77
- return;
78
- content = content.replace(target, `let _resolve = process.env.NEXT_MINIMAL ? __non_webpack_require__.resolve : require.resolve;
79
- let resolve = (id) => { try { return _resolve(id); } catch { return ''; } };`);
80
- writeFileSync(hookPath, content);
81
- console.log("next-bun-compile: Patched require-hook.js for compiled binary compatibility");
82
- }
83
- function collectExternalModules(standaloneDir) {
84
- const chunksDir = join(standaloneDir, ".next/server/chunks");
85
- if (!existsSync(chunksDir))
86
- return [];
87
- const seeds = new Set;
88
- for (const { absolutePath } of walkDir(chunksDir)) {
89
- if (!absolutePath.endsWith(".js"))
90
- continue;
91
- const content = readFileSync(absolutePath, "utf-8");
92
- for (const match of content.matchAll(/require\("(next\/dist\/[^"]+)"\)/g)) {
93
- seeds.add(match[1]);
94
- }
95
- }
96
- const deps = new Set;
97
- function trace(file) {
98
- if (deps.has(file))
99
- return;
100
- let fullPath = join(standaloneDir, "node_modules", file);
101
- if (existsSync(fullPath) && statSync(fullPath).isDirectory()) {
102
- const pkgJson = join(fullPath, "package.json");
103
- if (existsSync(pkgJson)) {
104
- deps.add(file + "/package.json");
105
- }
106
- file = file + "/index.js";
107
- fullPath = join(standaloneDir, "node_modules", file);
108
- }
109
- if (!existsSync(fullPath))
110
- return;
111
- deps.add(file);
112
- const content = readFileSync(fullPath, "utf-8");
113
- for (const match of content.matchAll(/require\("([^"]+)"\)/g)) {
114
- const req = match[1];
115
- let resolved;
116
- if (req.startsWith(".")) {
117
- resolved = join(file, "..", req).replace(/\\/g, "/");
118
- if (!resolved.endsWith(".js"))
119
- resolved += ".js";
120
- } else if (req.startsWith("next/")) {
121
- resolved = req;
122
- if (!resolved.endsWith(".js"))
123
- resolved += ".js";
124
- }
125
- if (resolved)
126
- trace(resolved);
127
- }
128
- }
129
- for (const seed of seeds)
130
- trace(seed);
131
- return [...deps];
132
- }
133
- function generateEntryPoint(options) {
134
- const { standaloneDir, distDir, projectDir } = options;
135
- generateStubs(standaloneDir);
136
- patchRequireHook(standaloneDir);
137
- const staticDir = join(distDir, "static");
138
- const staticFiles = walkDir(staticDir).map((f) => ({
139
- ...f,
140
- urlPath: `/_next/static/${f.relativePath.replace(/\\/g, "/")}`
141
- }));
142
- const publicDir = join(projectDir, "public");
143
- const publicFiles = walkDir(publicDir).map((f) => ({
144
- ...f,
145
- urlPath: `/${f.relativePath.replace(/\\/g, "/")}`
146
- }));
147
- const standaloneNextDir = join(standaloneDir, ".next");
148
- const runtimeFiles = walkDir(standaloneNextDir).map((f) => ({
149
- ...f,
150
- urlPath: `__runtime/.next/${f.relativePath.replace(/\\/g, "/")}`
151
- }));
152
- const externalModules = collectExternalModules(standaloneDir);
153
- const externalPaths = ["next/package.json", ...externalModules];
154
- const externalDir = join(standaloneDir, ".next/__external");
155
- for (const mod of externalPaths) {
156
- const src = join(standaloneDir, "node_modules", mod);
157
- if (!existsSync(src))
158
- continue;
159
- const dest = join(externalDir, mod);
160
- mkdirSync(join(dest, ".."), { recursive: true });
161
- writeFileSync(dest, readFileSync(src));
162
- runtimeFiles.push({
163
- absolutePath: dest,
164
- relativePath: `__external/${mod}`,
165
- urlPath: `__runtime/.next/node_modules/${mod.replace(/\\/g, "/")}`
166
- });
167
- }
168
- if (externalModules.length > 0) {
169
- console.log(`next-bun-compile: Embedding ${externalModules.length} external modules for SSR`);
170
- }
171
- const ctx = JSON.parse(readFileSync(join(distDir, "bun-compile-ctx.json"), "utf-8"));
172
- const { assetPrefix } = ctx;
173
- const assetsToEmbed = assetPrefix ? [...publicFiles, ...runtimeFiles] : [...staticFiles, ...publicFiles, ...runtimeFiles];
174
- if (assetPrefix) {
175
- console.log(`next-bun-compile: assetPrefix detected — skipping ${staticFiles.length} static assets (served from CDN)`);
176
- }
177
- console.log(`next-bun-compile: Embedding ${assetsToEmbed.length} assets (${staticFiles.length} static + ${publicFiles.length} public + ${runtimeFiles.length} runtime)`);
178
- const imports = [];
179
- const mapEntries = [];
180
- for (const asset of assetsToEmbed) {
181
- const varName = toVarName(asset.urlPath);
182
- const importPath = relative(standaloneDir, asset.absolutePath).replace(/\\/g, "/");
183
- imports.push(`import ${varName} from "./${importPath}" with { type: "file" };`);
184
- mapEntries.push(` ["${asset.urlPath}", ${varName}],`);
185
- }
186
- writeFileSync(join(standaloneDir, "assets.generated.js"), `${imports.join(`
187
- `)}
188
- export const assetMap = new Map([
189
- ${mapEntries.join(`
190
- `)}
191
- ]);
192
- `);
193
- const standaloneServerSrc = readFileSync(join(standaloneDir, "server.js"), "utf-8");
194
- const configMatch = standaloneServerSrc.match(/const nextConfig = ({[\s\S]*?})\n/);
195
- if (!configMatch) {
196
- throw new Error("next-bun-compile: Could not extract nextConfig from standalone server.js");
197
- }
198
- const assetExtractions = assetsToEmbed.map((a) => {
199
- let diskPath;
200
- if (a.urlPath.startsWith("__runtime/")) {
201
- diskPath = a.urlPath.slice("__runtime/".length);
202
- } else if (a.urlPath.startsWith("/_next/static/")) {
203
- diskPath = ".next/static/" + a.relativePath;
204
- } else {
205
- diskPath = "public/" + a.relativePath;
206
- }
207
- return [a.urlPath, diskPath];
208
- });
209
- const serverEntry = `import { assetMap } from "./assets.generated.js";
210
- const path = require("path");
211
- const fs = require("fs");
212
-
213
- const baseDir = path.dirname(process.execPath);
214
- process.chdir(baseDir);
215
- process.env.NODE_ENV = "production";
216
-
217
- const nextConfig = ${configMatch[1]};
218
- process.env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(nextConfig);
219
-
220
- const currentPort = parseInt(process.env.PORT, 10) || 3000;
221
- const hostname = process.env.HOSTNAME || "0.0.0.0";
222
- let keepAliveTimeout = parseInt(process.env.KEEP_ALIVE_TIMEOUT, 10);
223
- if (Number.isNaN(keepAliveTimeout) || !Number.isFinite(keepAliveTimeout) || keepAliveTimeout < 0) {
224
- keepAliveTimeout = undefined;
225
- }
226
-
227
- const extractions = ${JSON.stringify(assetExtractions)};
228
- async function extractAssets() {
229
- let n = 0;
230
- for (const [urlPath, diskPath] of extractions) {
231
- const fullPath = path.join(baseDir, diskPath);
232
- if (fs.existsSync(fullPath)) continue;
233
- fs.mkdirSync(path.dirname(fullPath), { recursive: true });
234
- const embedded = assetMap.get(urlPath);
235
- if (embedded) { await Bun.write(fullPath, Bun.file(embedded)); n++; }
236
- }
237
- if (n > 0) console.log(\`Extracted \${n} assets\`);
238
- }
239
-
240
- extractAssets().then(() => {
241
- require("next");
242
- const { startServer } = require("next/dist/server/lib/start-server");
243
- return startServer({
244
- dir: baseDir, isDev: false, config: nextConfig,
245
- hostname, port: currentPort, allowRetry: false, keepAliveTimeout,
246
- });
247
- }).catch((err) => { console.error(err); process.exit(1); });
248
- `;
249
- writeFileSync(join(standaloneDir, "server-entry.js"), serverEntry);
250
- }
251
-
252
- // src/compile.ts
253
- import { execFileSync } from "node:child_process";
254
- import { join as join2 } from "node:path";
255
- function compile(options) {
256
- const { standaloneDir, outfile, extraArgs = [] } = options;
257
- const entryPoint = join2(standaloneDir, "server-entry.js");
258
- const args = [
259
- "build",
260
- entryPoint,
261
- "--production",
262
- "--compile",
263
- "--minify",
264
- "--bytecode",
265
- "--sourcemap",
266
- "--define",
267
- "process.env.TURBOPACK=1",
268
- "--define",
269
- "process.env.__NEXT_EXPERIMENTAL_REACT=",
270
- "--define",
271
- 'process.env.NEXT_RUNTIME="nodejs"',
272
- "--outfile",
273
- outfile,
274
- ...extraArgs
275
- ];
276
- console.log(`next-bun-compile: Compiling to ${outfile}...`);
277
- execFileSync("bun", args, { stdio: "inherit" });
278
- console.log(`next-bun-compile: Done → ${outfile}`);
279
- }
3
+ compile,
4
+ generateEntryPoint
5
+ } from "./index-81394x5f.js";
280
6
 
281
7
  // src/cli.ts
282
- import { existsSync as existsSync2 } from "node:fs";
283
- import { join as join3, resolve } from "node:path";
8
+ import { existsSync } from "node:fs";
9
+ import { join, resolve } from "node:path";
284
10
  var extraArgs = process.argv.slice(2);
285
11
  var projectDir = resolve(".");
286
- var distDir = join3(projectDir, ".next");
287
- var standaloneDir = join3(distDir, "standalone");
288
- if (!existsSync2(standaloneDir)) {
12
+ var distDir = join(projectDir, ".next");
13
+ var standaloneDir = join(distDir, "standalone");
14
+ if (!existsSync(standaloneDir)) {
289
15
  console.error('next-bun-compile: No standalone output found. Run "next build" first with output: "standalone" in next.config.ts.');
290
16
  process.exit(1);
291
17
  }
292
- var ctxPath = join3(distDir, "bun-compile-ctx.json");
293
- if (existsSync2(ctxPath)) {
18
+ var ctxPath = join(distDir, "bun-compile-ctx.json");
19
+ if (existsSync(ctxPath)) {
294
20
  console.log("next-bun-compile: Using build context from adapter");
295
21
  }
296
- generateEntryPoint({ standaloneDir, distDir, projectDir });
297
- compile({ standaloneDir, outfile: join3(projectDir, "server"), extraArgs });
22
+ var serverDir = generateEntryPoint({ standaloneDir, distDir, projectDir });
23
+ compile({ serverDir, outfile: join(projectDir, "server"), extraArgs });
@@ -31,56 +31,122 @@ function toVarName(filePath) {
31
31
  const safe = filePath.replace(/[^a-zA-Z0-9]/g, "_").slice(0, 40);
32
32
  return `asset_${safe}_${hash}`;
33
33
  }
34
+ function findPackageDirs(nodeModulesDir, pkg) {
35
+ const dirs = [];
36
+ const direct = join(nodeModulesDir, pkg);
37
+ if (existsSync(direct))
38
+ dirs.push(direct);
39
+ const bunDir = join(nodeModulesDir, ".bun");
40
+ if (existsSync(bunDir)) {
41
+ const scope = pkg.startsWith("@") ? pkg.split("/")[0] + "+" + pkg.split("/")[1] : pkg;
42
+ for (const entry of readdirSync(bunDir)) {
43
+ if (!entry.startsWith(scope + "@"))
44
+ continue;
45
+ const hoisted = join(bunDir, entry, "node_modules", pkg);
46
+ if (existsSync(hoisted))
47
+ dirs.push(hoisted);
48
+ }
49
+ }
50
+ return dirs;
51
+ }
34
52
  function generateStubs(standaloneDir) {
35
53
  const stubs = [
36
54
  {
37
- path: "node_modules/next/dist/server/dev/next-dev-server.js",
55
+ pkg: "next",
56
+ subpath: "dist/server/dev/next-dev-server.js",
38
57
  content: "module.exports = { default: null };"
39
58
  },
40
59
  {
41
- path: "node_modules/next/dist/server/lib/router-utils/setup-dev-bundler.js",
60
+ pkg: "next",
61
+ subpath: "dist/server/lib/router-utils/setup-dev-bundler.js",
42
62
  content: "module.exports = {};"
43
63
  },
44
64
  {
45
- path: "node_modules/@opentelemetry/api/index.js",
65
+ pkg: "@opentelemetry/api",
66
+ subpath: "index.js",
46
67
  content: "throw new Error('not installed');"
47
68
  },
48
69
  {
49
- path: "node_modules/critters/index.js",
70
+ pkg: "critters",
71
+ subpath: "index.js",
50
72
  content: "module.exports = {};"
51
73
  }
52
74
  ];
75
+ const nodeModulesDir = join(standaloneDir, "node_modules");
53
76
  let count = 0;
54
77
  for (const stub of stubs) {
55
- const fullPath = join(standaloneDir, stub.path);
56
- if (!existsSync(fullPath)) {
57
- const dir = join(fullPath, "..");
58
- if (!existsSync(dir)) {
59
- mkdirSync(dir, { recursive: true });
78
+ const pkgDirs = findPackageDirs(nodeModulesDir, stub.pkg);
79
+ if (pkgDirs.length === 0)
80
+ pkgDirs.push(join(nodeModulesDir, stub.pkg));
81
+ for (const pkgDir of pkgDirs) {
82
+ const fullPath = join(pkgDir, stub.subpath);
83
+ if (!existsSync(fullPath)) {
84
+ const dir = join(fullPath, "..");
85
+ if (!existsSync(dir)) {
86
+ mkdirSync(dir, { recursive: true });
87
+ }
88
+ writeFileSync(fullPath, stub.content);
89
+ count++;
60
90
  }
61
- writeFileSync(fullPath, stub.content);
62
- count++;
63
91
  }
64
92
  }
65
93
  if (count > 0) {
66
94
  console.log(`next-bun-compile: Created ${count} module stubs`);
67
95
  }
68
96
  }
97
+ function findServerDir(standaloneDir) {
98
+ if (existsSync(join(standaloneDir, "server.js"))) {
99
+ return standaloneDir;
100
+ }
101
+ function search(dir) {
102
+ if (!existsSync(dir))
103
+ return null;
104
+ for (const entry of readdirSync(dir)) {
105
+ if (entry === "node_modules")
106
+ continue;
107
+ const full = join(dir, entry);
108
+ if (!statSync(full).isDirectory())
109
+ continue;
110
+ if (existsSync(join(full, "server.js")))
111
+ return full;
112
+ const found2 = search(full);
113
+ if (found2)
114
+ return found2;
115
+ }
116
+ return null;
117
+ }
118
+ const found = search(standaloneDir);
119
+ if (!found) {
120
+ throw new Error("next-bun-compile: Could not find server.js in standalone output");
121
+ }
122
+ const rel = relative(standaloneDir, found);
123
+ console.log(`next-bun-compile: Monorepo layout detected — server.js found at ${rel}/`);
124
+ return found;
125
+ }
69
126
  function patchRequireHook(standaloneDir) {
70
- const hookPath = join(standaloneDir, "node_modules/next/dist/server/require-hook.js");
71
- if (!existsSync(hookPath))
72
- return;
73
- let content = readFileSync(hookPath, "utf-8");
127
+ const nodeModulesDir = join(standaloneDir, "node_modules");
128
+ const nextDirs = findPackageDirs(nodeModulesDir, "next");
74
129
  const target = "let resolve = process.env.NEXT_MINIMAL ? __non_webpack_require__.resolve : require.resolve;";
75
- if (!content.includes(target))
76
- return;
77
- content = content.replace(target, `let _resolve = process.env.NEXT_MINIMAL ? __non_webpack_require__.resolve : require.resolve;
78
- let resolve = (id) => { try { return _resolve(id); } catch { return ''; } };`);
79
- writeFileSync(hookPath, content);
80
- console.log("next-bun-compile: Patched require-hook.js for compiled binary compatibility");
130
+ const replacement = `let _resolve = process.env.NEXT_MINIMAL ? __non_webpack_require__.resolve : require.resolve;
131
+ let resolve = (id) => { try { return _resolve(id); } catch { return ''; } };`;
132
+ let patched = 0;
133
+ for (const nextDir of nextDirs) {
134
+ const hookPath = join(nextDir, "dist/server/require-hook.js");
135
+ if (!existsSync(hookPath))
136
+ continue;
137
+ let content = readFileSync(hookPath, "utf-8");
138
+ if (!content.includes(target))
139
+ continue;
140
+ content = content.replace(target, replacement);
141
+ writeFileSync(hookPath, content);
142
+ patched++;
143
+ }
144
+ if (patched > 0) {
145
+ console.log("next-bun-compile: Patched require-hook.js for compiled binary compatibility");
146
+ }
81
147
  }
82
- function collectExternalModules(standaloneDir) {
83
- const chunksDir = join(standaloneDir, ".next/server/chunks");
148
+ function collectExternalModules(standaloneDir, serverDir) {
149
+ const chunksDir = join(serverDir, ".next/server/chunks");
84
150
  if (!existsSync(chunksDir))
85
151
  return [];
86
152
  const seeds = new Set;
@@ -131,6 +197,7 @@ function collectExternalModules(standaloneDir) {
131
197
  }
132
198
  function generateEntryPoint(options) {
133
199
  const { standaloneDir, distDir, projectDir } = options;
200
+ const serverDir = findServerDir(standaloneDir);
134
201
  generateStubs(standaloneDir);
135
202
  patchRequireHook(standaloneDir);
136
203
  const staticDir = join(distDir, "static");
@@ -143,14 +210,14 @@ function generateEntryPoint(options) {
143
210
  ...f,
144
211
  urlPath: `/${f.relativePath.replace(/\\/g, "/")}`
145
212
  }));
146
- const standaloneNextDir = join(standaloneDir, ".next");
213
+ const standaloneNextDir = join(serverDir, ".next");
147
214
  const runtimeFiles = walkDir(standaloneNextDir).map((f) => ({
148
215
  ...f,
149
216
  urlPath: `__runtime/.next/${f.relativePath.replace(/\\/g, "/")}`
150
217
  }));
151
- const externalModules = collectExternalModules(standaloneDir);
218
+ const externalModules = collectExternalModules(standaloneDir, serverDir);
152
219
  const externalPaths = ["next/package.json", ...externalModules];
153
- const externalDir = join(standaloneDir, ".next/__external");
220
+ const externalDir = join(serverDir, ".next/__external");
154
221
  for (const mod of externalPaths) {
155
222
  const src = join(standaloneDir, "node_modules", mod);
156
223
  if (!existsSync(src))
@@ -178,18 +245,18 @@ function generateEntryPoint(options) {
178
245
  const mapEntries = [];
179
246
  for (const asset of assetsToEmbed) {
180
247
  const varName = toVarName(asset.urlPath);
181
- const importPath = relative(standaloneDir, asset.absolutePath).replace(/\\/g, "/");
248
+ const importPath = relative(serverDir, asset.absolutePath).replace(/\\/g, "/");
182
249
  imports.push(`import ${varName} from "./${importPath}" with { type: "file" };`);
183
250
  mapEntries.push(` ["${asset.urlPath}", ${varName}],`);
184
251
  }
185
- writeFileSync(join(standaloneDir, "assets.generated.js"), `${imports.join(`
252
+ writeFileSync(join(serverDir, "assets.generated.js"), `${imports.join(`
186
253
  `)}
187
254
  export const assetMap = new Map([
188
255
  ${mapEntries.join(`
189
256
  `)}
190
257
  ]);
191
258
  `);
192
- const standaloneServerSrc = readFileSync(join(standaloneDir, "server.js"), "utf-8");
259
+ const standaloneServerSrc = readFileSync(join(serverDir, "server.js"), "utf-8");
193
260
  const configMatch = standaloneServerSrc.match(/const nextConfig = ({[\s\S]*?})\n/);
194
261
  if (!configMatch) {
195
262
  throw new Error("next-bun-compile: Could not extract nextConfig from standalone server.js");
@@ -245,8 +312,37 @@ extractAssets().then(() => {
245
312
  });
246
313
  }).catch((err) => { console.error(err); process.exit(1); });
247
314
  `;
248
- writeFileSync(join(standaloneDir, "server-entry.js"), serverEntry);
315
+ writeFileSync(join(serverDir, "server-entry.js"), serverEntry);
316
+ return serverDir;
249
317
  }
250
- export {
251
- generateEntryPoint
252
- };
318
+
319
+ // src/compile.ts
320
+ import { execFileSync } from "node:child_process";
321
+ import { join as join2 } from "node:path";
322
+ function compile(options) {
323
+ const { serverDir, outfile, extraArgs = [] } = options;
324
+ const entryPoint = join2(serverDir, "server-entry.js");
325
+ const args = [
326
+ "build",
327
+ entryPoint,
328
+ "--production",
329
+ "--compile",
330
+ "--minify",
331
+ "--bytecode",
332
+ "--sourcemap",
333
+ "--define",
334
+ "process.env.TURBOPACK=1",
335
+ "--define",
336
+ "process.env.__NEXT_EXPERIMENTAL_REACT=",
337
+ "--define",
338
+ 'process.env.NEXT_RUNTIME="nodejs"',
339
+ "--outfile",
340
+ outfile,
341
+ ...extraArgs
342
+ ];
343
+ console.log(`next-bun-compile: Compiling to ${outfile}...`);
344
+ execFileSync("bun", args, { stdio: "inherit" });
345
+ console.log(`next-bun-compile: Done → ${outfile}`);
346
+ }
347
+
348
+ export { __require, generateEntryPoint, compile };
package/dist/index.js CHANGED
@@ -1,284 +1,11 @@
1
- import { createRequire } from "node:module";
2
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
-
4
- // src/generate.ts
5
1
  import {
6
- writeFileSync,
7
- readFileSync,
8
- existsSync,
9
- readdirSync,
10
- statSync,
11
- mkdirSync
12
- } from "node:fs";
13
- import { join, relative } from "node:path";
14
- import { createHash } from "node:crypto";
15
- function walkDir(dir, base = dir) {
16
- const results = [];
17
- if (!existsSync(dir))
18
- return results;
19
- for (const entry of readdirSync(dir)) {
20
- const full = join(dir, entry);
21
- if (statSync(full).isDirectory()) {
22
- results.push(...walkDir(full, base));
23
- } else {
24
- results.push({ absolutePath: full, relativePath: relative(base, full) });
25
- }
26
- }
27
- return results;
28
- }
29
- function toVarName(filePath) {
30
- const hash = createHash("md5").update(filePath).digest("hex").slice(0, 6);
31
- const safe = filePath.replace(/[^a-zA-Z0-9]/g, "_").slice(0, 40);
32
- return `asset_${safe}_${hash}`;
33
- }
34
- function generateStubs(standaloneDir) {
35
- const stubs = [
36
- {
37
- path: "node_modules/next/dist/server/dev/next-dev-server.js",
38
- content: "module.exports = { default: null };"
39
- },
40
- {
41
- path: "node_modules/next/dist/server/lib/router-utils/setup-dev-bundler.js",
42
- content: "module.exports = {};"
43
- },
44
- {
45
- path: "node_modules/@opentelemetry/api/index.js",
46
- content: "throw new Error('not installed');"
47
- },
48
- {
49
- path: "node_modules/critters/index.js",
50
- content: "module.exports = {};"
51
- }
52
- ];
53
- let count = 0;
54
- for (const stub of stubs) {
55
- const fullPath = join(standaloneDir, stub.path);
56
- if (!existsSync(fullPath)) {
57
- const dir = join(fullPath, "..");
58
- if (!existsSync(dir)) {
59
- mkdirSync(dir, { recursive: true });
60
- }
61
- writeFileSync(fullPath, stub.content);
62
- count++;
63
- }
64
- }
65
- if (count > 0) {
66
- console.log(`next-bun-compile: Created ${count} module stubs`);
67
- }
68
- }
69
- function patchRequireHook(standaloneDir) {
70
- const hookPath = join(standaloneDir, "node_modules/next/dist/server/require-hook.js");
71
- if (!existsSync(hookPath))
72
- return;
73
- let content = readFileSync(hookPath, "utf-8");
74
- const target = "let resolve = process.env.NEXT_MINIMAL ? __non_webpack_require__.resolve : require.resolve;";
75
- if (!content.includes(target))
76
- return;
77
- content = content.replace(target, `let _resolve = process.env.NEXT_MINIMAL ? __non_webpack_require__.resolve : require.resolve;
78
- let resolve = (id) => { try { return _resolve(id); } catch { return ''; } };`);
79
- writeFileSync(hookPath, content);
80
- console.log("next-bun-compile: Patched require-hook.js for compiled binary compatibility");
81
- }
82
- function collectExternalModules(standaloneDir) {
83
- const chunksDir = join(standaloneDir, ".next/server/chunks");
84
- if (!existsSync(chunksDir))
85
- return [];
86
- const seeds = new Set;
87
- for (const { absolutePath } of walkDir(chunksDir)) {
88
- if (!absolutePath.endsWith(".js"))
89
- continue;
90
- const content = readFileSync(absolutePath, "utf-8");
91
- for (const match of content.matchAll(/require\("(next\/dist\/[^"]+)"\)/g)) {
92
- seeds.add(match[1]);
93
- }
94
- }
95
- const deps = new Set;
96
- function trace(file) {
97
- if (deps.has(file))
98
- return;
99
- let fullPath = join(standaloneDir, "node_modules", file);
100
- if (existsSync(fullPath) && statSync(fullPath).isDirectory()) {
101
- const pkgJson = join(fullPath, "package.json");
102
- if (existsSync(pkgJson)) {
103
- deps.add(file + "/package.json");
104
- }
105
- file = file + "/index.js";
106
- fullPath = join(standaloneDir, "node_modules", file);
107
- }
108
- if (!existsSync(fullPath))
109
- return;
110
- deps.add(file);
111
- const content = readFileSync(fullPath, "utf-8");
112
- for (const match of content.matchAll(/require\("([^"]+)"\)/g)) {
113
- const req = match[1];
114
- let resolved;
115
- if (req.startsWith(".")) {
116
- resolved = join(file, "..", req).replace(/\\/g, "/");
117
- if (!resolved.endsWith(".js"))
118
- resolved += ".js";
119
- } else if (req.startsWith("next/")) {
120
- resolved = req;
121
- if (!resolved.endsWith(".js"))
122
- resolved += ".js";
123
- }
124
- if (resolved)
125
- trace(resolved);
126
- }
127
- }
128
- for (const seed of seeds)
129
- trace(seed);
130
- return [...deps];
131
- }
132
- function generateEntryPoint(options) {
133
- const { standaloneDir, distDir, projectDir } = options;
134
- generateStubs(standaloneDir);
135
- patchRequireHook(standaloneDir);
136
- const staticDir = join(distDir, "static");
137
- const staticFiles = walkDir(staticDir).map((f) => ({
138
- ...f,
139
- urlPath: `/_next/static/${f.relativePath.replace(/\\/g, "/")}`
140
- }));
141
- const publicDir = join(projectDir, "public");
142
- const publicFiles = walkDir(publicDir).map((f) => ({
143
- ...f,
144
- urlPath: `/${f.relativePath.replace(/\\/g, "/")}`
145
- }));
146
- const standaloneNextDir = join(standaloneDir, ".next");
147
- const runtimeFiles = walkDir(standaloneNextDir).map((f) => ({
148
- ...f,
149
- urlPath: `__runtime/.next/${f.relativePath.replace(/\\/g, "/")}`
150
- }));
151
- const externalModules = collectExternalModules(standaloneDir);
152
- const externalPaths = ["next/package.json", ...externalModules];
153
- const externalDir = join(standaloneDir, ".next/__external");
154
- for (const mod of externalPaths) {
155
- const src = join(standaloneDir, "node_modules", mod);
156
- if (!existsSync(src))
157
- continue;
158
- const dest = join(externalDir, mod);
159
- mkdirSync(join(dest, ".."), { recursive: true });
160
- writeFileSync(dest, readFileSync(src));
161
- runtimeFiles.push({
162
- absolutePath: dest,
163
- relativePath: `__external/${mod}`,
164
- urlPath: `__runtime/.next/node_modules/${mod.replace(/\\/g, "/")}`
165
- });
166
- }
167
- if (externalModules.length > 0) {
168
- console.log(`next-bun-compile: Embedding ${externalModules.length} external modules for SSR`);
169
- }
170
- const ctx = JSON.parse(readFileSync(join(distDir, "bun-compile-ctx.json"), "utf-8"));
171
- const { assetPrefix } = ctx;
172
- const assetsToEmbed = assetPrefix ? [...publicFiles, ...runtimeFiles] : [...staticFiles, ...publicFiles, ...runtimeFiles];
173
- if (assetPrefix) {
174
- console.log(`next-bun-compile: assetPrefix detected — skipping ${staticFiles.length} static assets (served from CDN)`);
175
- }
176
- console.log(`next-bun-compile: Embedding ${assetsToEmbed.length} assets (${staticFiles.length} static + ${publicFiles.length} public + ${runtimeFiles.length} runtime)`);
177
- const imports = [];
178
- const mapEntries = [];
179
- for (const asset of assetsToEmbed) {
180
- const varName = toVarName(asset.urlPath);
181
- const importPath = relative(standaloneDir, asset.absolutePath).replace(/\\/g, "/");
182
- imports.push(`import ${varName} from "./${importPath}" with { type: "file" };`);
183
- mapEntries.push(` ["${asset.urlPath}", ${varName}],`);
184
- }
185
- writeFileSync(join(standaloneDir, "assets.generated.js"), `${imports.join(`
186
- `)}
187
- export const assetMap = new Map([
188
- ${mapEntries.join(`
189
- `)}
190
- ]);
191
- `);
192
- const standaloneServerSrc = readFileSync(join(standaloneDir, "server.js"), "utf-8");
193
- const configMatch = standaloneServerSrc.match(/const nextConfig = ({[\s\S]*?})\n/);
194
- if (!configMatch) {
195
- throw new Error("next-bun-compile: Could not extract nextConfig from standalone server.js");
196
- }
197
- const assetExtractions = assetsToEmbed.map((a) => {
198
- let diskPath;
199
- if (a.urlPath.startsWith("__runtime/")) {
200
- diskPath = a.urlPath.slice("__runtime/".length);
201
- } else if (a.urlPath.startsWith("/_next/static/")) {
202
- diskPath = ".next/static/" + a.relativePath;
203
- } else {
204
- diskPath = "public/" + a.relativePath;
205
- }
206
- return [a.urlPath, diskPath];
207
- });
208
- const serverEntry = `import { assetMap } from "./assets.generated.js";
209
- const path = require("path");
210
- const fs = require("fs");
211
-
212
- const baseDir = path.dirname(process.execPath);
213
- process.chdir(baseDir);
214
- process.env.NODE_ENV = "production";
215
-
216
- const nextConfig = ${configMatch[1]};
217
- process.env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(nextConfig);
218
-
219
- const currentPort = parseInt(process.env.PORT, 10) || 3000;
220
- const hostname = process.env.HOSTNAME || "0.0.0.0";
221
- let keepAliveTimeout = parseInt(process.env.KEEP_ALIVE_TIMEOUT, 10);
222
- if (Number.isNaN(keepAliveTimeout) || !Number.isFinite(keepAliveTimeout) || keepAliveTimeout < 0) {
223
- keepAliveTimeout = undefined;
224
- }
225
-
226
- const extractions = ${JSON.stringify(assetExtractions)};
227
- async function extractAssets() {
228
- let n = 0;
229
- for (const [urlPath, diskPath] of extractions) {
230
- const fullPath = path.join(baseDir, diskPath);
231
- if (fs.existsSync(fullPath)) continue;
232
- fs.mkdirSync(path.dirname(fullPath), { recursive: true });
233
- const embedded = assetMap.get(urlPath);
234
- if (embedded) { await Bun.write(fullPath, Bun.file(embedded)); n++; }
235
- }
236
- if (n > 0) console.log(\`Extracted \${n} assets\`);
237
- }
238
-
239
- extractAssets().then(() => {
240
- require("next");
241
- const { startServer } = require("next/dist/server/lib/start-server");
242
- return startServer({
243
- dir: baseDir, isDev: false, config: nextConfig,
244
- hostname, port: currentPort, allowRetry: false, keepAliveTimeout,
245
- });
246
- }).catch((err) => { console.error(err); process.exit(1); });
247
- `;
248
- writeFileSync(join(standaloneDir, "server-entry.js"), serverEntry);
249
- }
250
-
251
- // src/compile.ts
252
- import { execFileSync } from "node:child_process";
253
- import { join as join2 } from "node:path";
254
- function compile(options) {
255
- const { standaloneDir, outfile, extraArgs = [] } = options;
256
- const entryPoint = join2(standaloneDir, "server-entry.js");
257
- const args = [
258
- "build",
259
- entryPoint,
260
- "--production",
261
- "--compile",
262
- "--minify",
263
- "--bytecode",
264
- "--sourcemap",
265
- "--define",
266
- "process.env.TURBOPACK=1",
267
- "--define",
268
- "process.env.__NEXT_EXPERIMENTAL_REACT=",
269
- "--define",
270
- 'process.env.NEXT_RUNTIME="nodejs"',
271
- "--outfile",
272
- outfile,
273
- ...extraArgs
274
- ];
275
- console.log(`next-bun-compile: Compiling to ${outfile}...`);
276
- execFileSync("bun", args, { stdio: "inherit" });
277
- console.log(`next-bun-compile: Done → ${outfile}`);
278
- }
2
+ __require,
3
+ compile,
4
+ generateEntryPoint
5
+ } from "./index-81394x5f.js";
279
6
 
280
7
  // src/index.ts
281
- import { join as join3 } from "node:path";
8
+ import { join } from "node:path";
282
9
  var knownTranspilePackages = ["pino", "pino-pretty"];
283
10
  var adapter = {
284
11
  name: "next-bun-compile",
@@ -303,8 +30,8 @@ var adapter = {
303
30
  return config;
304
31
  },
305
32
  async onBuildComplete(ctx) {
306
- const { writeFileSync: writeFileSync2 } = await import("node:fs");
307
- writeFileSync2(join3(ctx.distDir, "bun-compile-ctx.json"), JSON.stringify({
33
+ const { writeFileSync } = await import("node:fs");
34
+ writeFileSync(join(ctx.distDir, "bun-compile-ctx.json"), JSON.stringify({
308
35
  distDir: ctx.distDir,
309
36
  projectDir: ctx.projectDir,
310
37
  assetPrefix: ctx.config.assetPrefix || ""
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-bun-compile",
3
- "version": "0.4.2",
3
+ "version": "0.5.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",
@@ -18,7 +18,7 @@
18
18
  "dist"
19
19
  ],
20
20
  "scripts": {
21
- "build": "bun build src/index.ts src/generate.ts src/compile.ts src/cli.ts --outdir dist --target node",
21
+ "build": "bun build src/index.ts src/cli.ts --outdir dist --target node --splitting",
22
22
  "typecheck": "tsc --noEmit"
23
23
  },
24
24
  "peerDependencies": {
package/dist/compile.js DELETED
@@ -1,34 +0,0 @@
1
- import { createRequire } from "node:module";
2
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
-
4
- // src/compile.ts
5
- import { execFileSync } from "node:child_process";
6
- import { join } from "node:path";
7
- function compile(options) {
8
- const { standaloneDir, outfile, extraArgs = [] } = options;
9
- const entryPoint = join(standaloneDir, "server-entry.js");
10
- const args = [
11
- "build",
12
- entryPoint,
13
- "--production",
14
- "--compile",
15
- "--minify",
16
- "--bytecode",
17
- "--sourcemap",
18
- "--define",
19
- "process.env.TURBOPACK=1",
20
- "--define",
21
- "process.env.__NEXT_EXPERIMENTAL_REACT=",
22
- "--define",
23
- 'process.env.NEXT_RUNTIME="nodejs"',
24
- "--outfile",
25
- outfile,
26
- ...extraArgs
27
- ];
28
- console.log(`next-bun-compile: Compiling to ${outfile}...`);
29
- execFileSync("bun", args, { stdio: "inherit" });
30
- console.log(`next-bun-compile: Done → ${outfile}`);
31
- }
32
- export {
33
- compile
34
- };