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