next-bun-compile 0.3.0 → 0.4.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
@@ -67,9 +67,73 @@ function generateStubs(standaloneDir) {
67
67
  console.log(`next-bun-compile: Created ${count} module stubs`);
68
68
  }
69
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/ssr");
85
+ if (!existsSync(chunksDir))
86
+ return [];
87
+ const seeds = new Set;
88
+ for (const entry of readdirSync(chunksDir)) {
89
+ if (!entry.endsWith(".js"))
90
+ continue;
91
+ const content = readFileSync(join(chunksDir, entry), "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
+ }
70
133
  function generateEntryPoint(options) {
71
134
  const { standaloneDir, distDir, projectDir } = options;
72
135
  generateStubs(standaloneDir);
136
+ patchRequireHook(standaloneDir);
73
137
  const staticDir = join(distDir, "static");
74
138
  const staticFiles = walkDir(staticDir).map((f) => ({
75
139
  ...f,
@@ -80,13 +144,37 @@ function generateEntryPoint(options) {
80
144
  ...f,
81
145
  urlPath: `/${f.relativePath.replace(/\\/g, "/")}`
82
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
+ }
83
171
  const ctx = JSON.parse(readFileSync(join(distDir, "bun-compile-ctx.json"), "utf-8"));
84
172
  const { assetPrefix } = ctx;
85
- const assetsToEmbed = assetPrefix ? publicFiles : [...staticFiles, ...publicFiles];
173
+ const assetsToEmbed = assetPrefix ? [...publicFiles, ...runtimeFiles] : [...staticFiles, ...publicFiles, ...runtimeFiles];
86
174
  if (assetPrefix) {
87
175
  console.log(`next-bun-compile: assetPrefix detected — skipping ${staticFiles.length} static assets (served from CDN)`);
88
176
  }
89
- console.log(`next-bun-compile: Embedding ${assetsToEmbed.length} assets (${assetPrefix ? "public only" : `${staticFiles.length} static + ${publicFiles.length} public`})`);
177
+ console.log(`next-bun-compile: Embedding ${assetsToEmbed.length} assets (${staticFiles.length} static + ${publicFiles.length} public + ${runtimeFiles.length} runtime)`);
90
178
  const imports = [];
91
179
  const mapEntries = [];
92
180
  for (const asset of assetsToEmbed) {
@@ -108,7 +196,14 @@ ${mapEntries.join(`
108
196
  throw new Error("next-bun-compile: Could not extract nextConfig from standalone server.js");
109
197
  }
110
198
  const assetExtractions = assetsToEmbed.map((a) => {
111
- const diskPath = a.urlPath.startsWith("/_next/static/") ? ".next/static/" + a.relativePath : "public/" + a.relativePath;
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
+ }
112
207
  return [a.urlPath, diskPath];
113
208
  });
114
209
  const serverEntry = `import { assetMap } from "./assets.generated.js";
package/dist/generate.js CHANGED
@@ -66,9 +66,73 @@ function generateStubs(standaloneDir) {
66
66
  console.log(`next-bun-compile: Created ${count} module stubs`);
67
67
  }
68
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/ssr");
84
+ if (!existsSync(chunksDir))
85
+ return [];
86
+ const seeds = new Set;
87
+ for (const entry of readdirSync(chunksDir)) {
88
+ if (!entry.endsWith(".js"))
89
+ continue;
90
+ const content = readFileSync(join(chunksDir, entry), "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
+ }
69
132
  function generateEntryPoint(options) {
70
133
  const { standaloneDir, distDir, projectDir } = options;
71
134
  generateStubs(standaloneDir);
135
+ patchRequireHook(standaloneDir);
72
136
  const staticDir = join(distDir, "static");
73
137
  const staticFiles = walkDir(staticDir).map((f) => ({
74
138
  ...f,
@@ -79,13 +143,37 @@ function generateEntryPoint(options) {
79
143
  ...f,
80
144
  urlPath: `/${f.relativePath.replace(/\\/g, "/")}`
81
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
+ }
82
170
  const ctx = JSON.parse(readFileSync(join(distDir, "bun-compile-ctx.json"), "utf-8"));
83
171
  const { assetPrefix } = ctx;
84
- const assetsToEmbed = assetPrefix ? publicFiles : [...staticFiles, ...publicFiles];
172
+ const assetsToEmbed = assetPrefix ? [...publicFiles, ...runtimeFiles] : [...staticFiles, ...publicFiles, ...runtimeFiles];
85
173
  if (assetPrefix) {
86
174
  console.log(`next-bun-compile: assetPrefix detected — skipping ${staticFiles.length} static assets (served from CDN)`);
87
175
  }
88
- console.log(`next-bun-compile: Embedding ${assetsToEmbed.length} assets (${assetPrefix ? "public only" : `${staticFiles.length} static + ${publicFiles.length} public`})`);
176
+ console.log(`next-bun-compile: Embedding ${assetsToEmbed.length} assets (${staticFiles.length} static + ${publicFiles.length} public + ${runtimeFiles.length} runtime)`);
89
177
  const imports = [];
90
178
  const mapEntries = [];
91
179
  for (const asset of assetsToEmbed) {
@@ -107,7 +195,14 @@ ${mapEntries.join(`
107
195
  throw new Error("next-bun-compile: Could not extract nextConfig from standalone server.js");
108
196
  }
109
197
  const assetExtractions = assetsToEmbed.map((a) => {
110
- const diskPath = a.urlPath.startsWith("/_next/static/") ? ".next/static/" + a.relativePath : "public/" + a.relativePath;
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
+ }
111
206
  return [a.urlPath, diskPath];
112
207
  });
113
208
  const serverEntry = `import { assetMap } from "./assets.generated.js";
package/dist/index.js CHANGED
@@ -66,9 +66,73 @@ function generateStubs(standaloneDir) {
66
66
  console.log(`next-bun-compile: Created ${count} module stubs`);
67
67
  }
68
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/ssr");
84
+ if (!existsSync(chunksDir))
85
+ return [];
86
+ const seeds = new Set;
87
+ for (const entry of readdirSync(chunksDir)) {
88
+ if (!entry.endsWith(".js"))
89
+ continue;
90
+ const content = readFileSync(join(chunksDir, entry), "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
+ }
69
132
  function generateEntryPoint(options) {
70
133
  const { standaloneDir, distDir, projectDir } = options;
71
134
  generateStubs(standaloneDir);
135
+ patchRequireHook(standaloneDir);
72
136
  const staticDir = join(distDir, "static");
73
137
  const staticFiles = walkDir(staticDir).map((f) => ({
74
138
  ...f,
@@ -79,13 +143,37 @@ function generateEntryPoint(options) {
79
143
  ...f,
80
144
  urlPath: `/${f.relativePath.replace(/\\/g, "/")}`
81
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
+ }
82
170
  const ctx = JSON.parse(readFileSync(join(distDir, "bun-compile-ctx.json"), "utf-8"));
83
171
  const { assetPrefix } = ctx;
84
- const assetsToEmbed = assetPrefix ? publicFiles : [...staticFiles, ...publicFiles];
172
+ const assetsToEmbed = assetPrefix ? [...publicFiles, ...runtimeFiles] : [...staticFiles, ...publicFiles, ...runtimeFiles];
85
173
  if (assetPrefix) {
86
174
  console.log(`next-bun-compile: assetPrefix detected — skipping ${staticFiles.length} static assets (served from CDN)`);
87
175
  }
88
- console.log(`next-bun-compile: Embedding ${assetsToEmbed.length} assets (${assetPrefix ? "public only" : `${staticFiles.length} static + ${publicFiles.length} public`})`);
176
+ console.log(`next-bun-compile: Embedding ${assetsToEmbed.length} assets (${staticFiles.length} static + ${publicFiles.length} public + ${runtimeFiles.length} runtime)`);
89
177
  const imports = [];
90
178
  const mapEntries = [];
91
179
  for (const asset of assetsToEmbed) {
@@ -107,7 +195,14 @@ ${mapEntries.join(`
107
195
  throw new Error("next-bun-compile: Could not extract nextConfig from standalone server.js");
108
196
  }
109
197
  const assetExtractions = assetsToEmbed.map((a) => {
110
- const diskPath = a.urlPath.startsWith("/_next/static/") ? ".next/static/" + a.relativePath : "public/" + a.relativePath;
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
+ }
111
206
  return [a.urlPath, diskPath];
112
207
  });
113
208
  const serverEntry = `import { assetMap } from "./assets.generated.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-bun-compile",
3
- "version": "0.3.0",
3
+ "version": "0.4.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",