one
Version:
One is a new React Framework that makes Vite serve both native and web.
168 lines (167 loc) • 5.52 kB
JavaScript
import FSExtra from "fs-extra";
import { setServerGlobals } from "./server/setServerGlobals.mjs";
import { setupBuildInfo } from "./server/setupBuildOptions.mjs";
import { ensureExists } from "./utils/ensureExists.mjs";
process.on("uncaughtException", err => {
console.error(`[one] Uncaught exception`, err?.stack || err);
});
async function serve(args = {}) {
if (args.cluster) {
const {
cpus,
platform
} = await import("node:os");
const numWorkers = typeof args.cluster === "number" ? args.cluster : cpus().length;
const isBun = typeof process.versions.bun !== "undefined";
const canReusePort = !["win32", "darwin"].includes(platform()) && (isBun || (() => {
const [major, minor] = process.versions.node.split(".").map(Number);
return major > 22 || major === 22 && minor >= 12 || major >= 23;
})());
if (canReusePort) {
return await serveWithReusePort(args, numWorkers);
} else if (!isBun) {
return await serveWithCluster(args, numWorkers);
} else {
console.warn(`[one] cluster mode not supported on ${platform()} with bun, running single process`);
return await startWorker(args);
}
}
return await startWorker(args);
}
async function serveWithReusePort(args, numWorkers) {
const {
fork
} = await import("node:child_process");
console.info(`[one] cluster: starting ${numWorkers} workers (SO_REUSEPORT)`);
const workers = [];
let recentCrashes = 0;
let lastCrashTime = 0;
function spawnWorker() {
const child = fork(process.argv[1], process.argv.slice(2).filter(a => !a.startsWith("--cluster")), {
env: {
...process.env,
ONE_CLUSTER_WORKER: "1"
},
stdio: "inherit"
});
workers.push(child);
child.on("exit", (code, signal) => {
const idx = workers.indexOf(child);
if (idx >= 0) workers.splice(idx, 1);
if (code === 0 || signal === "SIGTERM" || signal === "SIGINT") return;
const now = Date.now();
if (now - lastCrashTime < 5e3) {
recentCrashes++;
} else {
recentCrashes = 1;
}
lastCrashTime = now;
if (recentCrashes > numWorkers * 2) {
console.error(`[one] too many worker crashes, stopping`);
process.exit(1);
}
console.error(`[one] worker ${child.pid} died (code ${code}, signal ${signal}), restarting`);
setTimeout(spawnWorker, Math.min(recentCrashes * 500, 5e3));
});
}
for (let i = 0; i < numWorkers; i++) {
spawnWorker();
}
const shutdown = () => {
for (const w of workers) {
w.kill("SIGTERM");
}
setTimeout(() => process.exit(0), 5e3);
};
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
await new Promise(() => {});
}
async function serveWithCluster(args, numWorkers) {
const cluster = await import("node:cluster");
if (cluster.default.isPrimary) {
console.info(`[one] cluster: starting ${numWorkers} workers (IPC)`);
for (let i = 0; i < numWorkers; i++) {
cluster.default.fork();
}
let recentCrashes = 0;
let lastCrashTime = 0;
cluster.default.on("exit", (worker, code, signal) => {
if (code === 0 || signal === "SIGTERM" || signal === "SIGINT") return;
const now = Date.now();
if (now - lastCrashTime < 5e3) {
recentCrashes++;
} else {
recentCrashes = 1;
}
lastCrashTime = now;
if (recentCrashes > numWorkers * 2) {
console.error(`[one] too many worker crashes, stopping`);
process.exit(1);
}
console.error(`[one] worker ${worker.process.pid} died (code ${code}, signal ${signal}), restarting`);
setTimeout(() => cluster.default.fork(), Math.min(recentCrashes * 500, 5e3));
});
const shutdown = () => {
for (const id in cluster.default.workers) {
cluster.default.workers[id]?.process.kill("SIGTERM");
}
setTimeout(() => process.exit(0), 5e3);
};
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
return;
}
return await startWorker(args);
}
async function startWorker(args) {
const outDir = args?.outDir || (FSExtra.existsSync("buildInfo.json") ? "." : null) || "dist";
const buildInfo = await FSExtra.readJSON(`${outDir}/buildInfo.json`);
const {
oneOptions
} = buildInfo;
setServerGlobals();
setupBuildInfo(buildInfo);
ensureExists(oneOptions);
const {
labelProcess
} = await import("./cli/label-process.mjs");
const {
removeUndefined
} = await import("./utils/removeUndefined.mjs");
const {
loadEnv,
serve: vxrnServe,
serveStaticAssets,
compileCacheRules
} = await import("vxrn/serve");
const {
oneServe
} = await import("./server/oneServe.mjs");
labelProcess("serve");
if (args?.loadEnv) {
await loadEnv("production");
}
const cacheRules = oneOptions.server?.cacheControl ? compileCacheRules(oneOptions.server.cacheControl) : void 0;
return await vxrnServe({
outDir: buildInfo.outDir || outDir,
app: args?.app,
...oneOptions.server,
...removeUndefined({
port: args?.port ? +args.port : void 0,
host: args?.host,
compress: args?.compress
}),
async beforeRegisterRoutes(options, app) {
await oneServe(oneOptions, buildInfo, app, {
serveStaticAssets: ctx => serveStaticAssets({
...ctx,
cacheRules
})
});
},
async afterRegisterRoutes(options, app) {}
});
}
export { serve };
//# sourceMappingURL=serve.mjs.map