Skip to content

Commit

Permalink
feat(preset): adding bun-cluster preset
Browse files Browse the repository at this point in the history
  • Loading branch information
Saeid-Za committed Feb 2, 2025
1 parent 3b31e9d commit 2a808f7
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 5 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@
"@scalar/api-reference": "^1.25.109",
"@types/archiver": "^6.0.3",
"@types/aws-lambda": "^8.10.147",
"@types/bun": "^1.2.2",
"@types/estree": "^1.0.6",
"@types/etag": "^1.8.3",
"@types/fs-extra": "^11.0.4",
Expand Down
4 changes: 2 additions & 2 deletions src/presets/_types.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ export interface PresetOptions {

export const presetsWithConfig = ["awsAmplify","awsLambda","azure","cloudflare","firebase","netlify","vercel"] as const;

export type PresetName = "alwaysdata" | "aws-amplify" | "aws-lambda" | "azure" | "azure-functions" | "azure-swa" | "base-worker" | "bun" | "cleavr" | "cli" | "cloudflare" | "cloudflare-durable" | "cloudflare-module" | "cloudflare-module-legacy" | "cloudflare-pages" | "cloudflare-pages-static" | "cloudflare-worker" | "deno" | "deno-deploy" | "deno-server" | "deno-server-legacy" | "digital-ocean" | "edgio" | "firebase" | "firebase-app-hosting" | "flight-control" | "genezio" | "github-pages" | "gitlab-pages" | "heroku" | "iis" | "iis-handler" | "iis-node" | "koyeb" | "layer0" | "netlify" | "netlify-builder" | "netlify-edge" | "netlify-legacy" | "netlify-static" | "nitro-dev" | "nitro-prerender" | "node" | "node-cluster" | "node-listener" | "node-server" | "platform-sh" | "render-com" | "service-worker" | "static" | "stormkit" | "vercel" | "vercel-edge" | "vercel-static" | "winterjs" | "zeabur" | "zeabur-static" | "zerops" | "zerops-static";
export type PresetName = "alwaysdata" | "aws-amplify" | "aws-lambda" | "azure" | "azure-functions" | "azure-swa" | "base-worker" | "bun" | "bun-cluster" | "cleavr" | "cli" | "cloudflare" | "cloudflare-durable" | "cloudflare-module" | "cloudflare-module-legacy" | "cloudflare-pages" | "cloudflare-pages-static" | "cloudflare-worker" | "deno" | "deno-deploy" | "deno-server" | "deno-server-legacy" | "digital-ocean" | "edgio" | "firebase" | "firebase-app-hosting" | "flight-control" | "genezio" | "github-pages" | "gitlab-pages" | "heroku" | "iis" | "iis-handler" | "iis-node" | "koyeb" | "layer0" | "netlify" | "netlify-builder" | "netlify-edge" | "netlify-legacy" | "netlify-static" | "nitro-dev" | "nitro-prerender" | "node" | "node-cluster" | "node-listener" | "node-server" | "platform-sh" | "render-com" | "service-worker" | "static" | "stormkit" | "vercel" | "vercel-edge" | "vercel-static" | "winterjs" | "zeabur" | "zeabur-static" | "zerops" | "zerops-static";

export type PresetNameInput = "alwaysdata" | "aws-amplify" | "awsAmplify" | "aws_amplify" | "aws-lambda" | "awsLambda" | "aws_lambda" | "azure" | "azure-functions" | "azureFunctions" | "azure_functions" | "azure-swa" | "azureSwa" | "azure_swa" | "base-worker" | "baseWorker" | "base_worker" | "bun" | "cleavr" | "cli" | "cloudflare" | "cloudflare-durable" | "cloudflareDurable" | "cloudflare_durable" | "cloudflare-module" | "cloudflareModule" | "cloudflare_module" | "cloudflare-module-legacy" | "cloudflareModuleLegacy" | "cloudflare_module_legacy" | "cloudflare-pages" | "cloudflarePages" | "cloudflare_pages" | "cloudflare-pages-static" | "cloudflarePagesStatic" | "cloudflare_pages_static" | "cloudflare-worker" | "cloudflareWorker" | "cloudflare_worker" | "deno" | "deno-deploy" | "denoDeploy" | "deno_deploy" | "deno-server" | "denoServer" | "deno_server" | "deno-server-legacy" | "denoServerLegacy" | "deno_server_legacy" | "digital-ocean" | "digitalOcean" | "digital_ocean" | "edgio" | "firebase" | "firebase-app-hosting" | "firebaseAppHosting" | "firebase_app_hosting" | "flight-control" | "flightControl" | "flight_control" | "genezio" | "github-pages" | "githubPages" | "github_pages" | "gitlab-pages" | "gitlabPages" | "gitlab_pages" | "heroku" | "iis" | "iis-handler" | "iisHandler" | "iis_handler" | "iis-node" | "iisNode" | "iis_node" | "koyeb" | "layer0" | "netlify" | "netlify-builder" | "netlifyBuilder" | "netlify_builder" | "netlify-edge" | "netlifyEdge" | "netlify_edge" | "netlify-legacy" | "netlifyLegacy" | "netlify_legacy" | "netlify-static" | "netlifyStatic" | "netlify_static" | "nitro-dev" | "nitroDev" | "nitro_dev" | "nitro-prerender" | "nitroPrerender" | "nitro_prerender" | "node" | "node-cluster" | "nodeCluster" | "node_cluster" | "node-listener" | "nodeListener" | "node_listener" | "node-server" | "nodeServer" | "node_server" | "platform-sh" | "platformSh" | "platform_sh" | "render-com" | "renderCom" | "render_com" | "service-worker" | "serviceWorker" | "service_worker" | "static" | "stormkit" | "vercel" | "vercel-edge" | "vercelEdge" | "vercel_edge" | "vercel-static" | "vercelStatic" | "vercel_static" | "winterjs" | "zeabur" | "zeabur-static" | "zeaburStatic" | "zeabur_static" | "zerops" | "zerops-static" | "zeropsStatic" | "zerops_static" | (string & {});
export type PresetNameInput = "alwaysdata" | "aws-amplify" | "awsAmplify" | "aws_amplify" | "aws-lambda" | "awsLambda" | "aws_lambda" | "azure" | "azure-functions" | "azureFunctions" | "azure_functions" | "azure-swa" | "azureSwa" | "azure_swa" | "base-worker" | "baseWorker" | "base_worker" | "bun" | "bun-cluster" | "bunCluster" | "bun_cluster" | "cleavr" | "cli" | "cloudflare" | "cloudflare-durable" | "cloudflareDurable" | "cloudflare_durable" | "cloudflare-module" | "cloudflareModule" | "cloudflare_module" | "cloudflare-module-legacy" | "cloudflareModuleLegacy" | "cloudflare_module_legacy" | "cloudflare-pages" | "cloudflarePages" | "cloudflare_pages" | "cloudflare-pages-static" | "cloudflarePagesStatic" | "cloudflare_pages_static" | "cloudflare-worker" | "cloudflareWorker" | "cloudflare_worker" | "deno" | "deno-deploy" | "denoDeploy" | "deno_deploy" | "deno-server" | "denoServer" | "deno_server" | "deno-server-legacy" | "denoServerLegacy" | "deno_server_legacy" | "digital-ocean" | "digitalOcean" | "digital_ocean" | "edgio" | "firebase" | "firebase-app-hosting" | "firebaseAppHosting" | "firebase_app_hosting" | "flight-control" | "flightControl" | "flight_control" | "genezio" | "github-pages" | "githubPages" | "github_pages" | "gitlab-pages" | "gitlabPages" | "gitlab_pages" | "heroku" | "iis" | "iis-handler" | "iisHandler" | "iis_handler" | "iis-node" | "iisNode" | "iis_node" | "koyeb" | "layer0" | "netlify" | "netlify-builder" | "netlifyBuilder" | "netlify_builder" | "netlify-edge" | "netlifyEdge" | "netlify_edge" | "netlify-legacy" | "netlifyLegacy" | "netlify_legacy" | "netlify-static" | "netlifyStatic" | "netlify_static" | "nitro-dev" | "nitroDev" | "nitro_dev" | "nitro-prerender" | "nitroPrerender" | "nitro_prerender" | "node" | "node-cluster" | "nodeCluster" | "node_cluster" | "node-listener" | "nodeListener" | "node_listener" | "node-server" | "nodeServer" | "node_server" | "platform-sh" | "platformSh" | "platform_sh" | "render-com" | "renderCom" | "render_com" | "service-worker" | "serviceWorker" | "service_worker" | "static" | "stormkit" | "vercel" | "vercel-edge" | "vercelEdge" | "vercel_edge" | "vercel-static" | "vercelStatic" | "vercel_static" | "winterjs" | "zeabur" | "zeabur-static" | "zeaburStatic" | "zeabur_static" | "zerops" | "zerops-static" | "zeropsStatic" | "zerops_static" | (string & {});
63 changes: 61 additions & 2 deletions src/presets/bun/preset.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { fileURLToPath, resolvePathSync } from "mlly";
import { defineNitroPreset } from "nitropack/kit";
import { dirname, join, normalize, parse } from "pathe";

const dirName = dirname(fileURLToPath(import.meta.url))

const bun = defineNitroPreset(
{
Expand All @@ -7,7 +11,7 @@ const bun = defineNitroPreset(
// https://bun.sh/docs/runtime/modules#resolution
exportConditions: ["bun", "worker", "node", "import", "default"],
commands: {
preview: "bun run ./server/index.mjs",
preview: "bun --bun ./server/index.mjs",
},
},
{
Expand All @@ -16,4 +20,59 @@ const bun = defineNitroPreset(
}
);

export default [bun] as const;
const bunCluster = defineNitroPreset(
{
extends: "node-server",
entry: "./runtime/bun-cluster",
commands: {
preview: "bun --bun ./server/index.mjs",
},
rollupConfig: {
external: ["bun"],
input: [join(dirName + "/runtime/bun-cluster"), join(dirName + "/runtime/bun-worker")],
},
hooks: {
"rollup:before"(_nitro, rollupConfig) {
// ensure worker and master code chunk is seperated and isolated into it's own entry-file
// this prevents worker file importing from master file
const manualChunks = rollupConfig.output?.manualChunks;
if (manualChunks && typeof manualChunks === "function") {
const workerFile = resolvePathSync("./runtime/bun-worker", {
url: import.meta.url,
});

const masterFile = resolvePathSync("./runtime/bun-cluster", {
url: import.meta.url,
});

rollupConfig.output.manualChunks = (id, meta) => {
if (id.includes("bun-worker") && normalize(id) === workerFile) {
return "nitro/bun-worker";
}

if (id.includes("bun-cluster") && normalize(id) === masterFile) {
return "nitro/bun-cluster";
}

return manualChunks(id, meta)
};
}

// unique name for master to find worker file.
rollupConfig.output.entryFileNames = (chunkInfo) => {
if (chunkInfo.name === 'bun-worker') {
return 'bun-worker.mjs';
}
return "index.mjs"
}
},
},
exportConditions: ["bun", "worker", "node", "import", "default"],
},
{
name: "bun-cluster" as const,
url: import.meta.url,
}
);

export default [bun, bunCluster] as const;
81 changes: 81 additions & 0 deletions src/presets/bun/runtime/bun-cluster.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { spawn, type Subprocess } from 'bun';
import { fileURLToPath } from 'mlly';
import {
getGracefulShutdownConfig,
trapUnhandledNodeErrors,
} from 'nitropack/runtime/internal';
import { join, dirname } from 'pathe';

const numberOfWorkers =
Number.parseInt(process.env.NITRO_CLUSTER_WORKERS || "") ||
navigator.hardwareConcurrency;

const workers: Subprocess[] = Array.from({ length: numberOfWorkers })
const dirName = dirname(fileURLToPath(import.meta.url))
const workerFile = join(dirName, "./bun-worker")

for (let i = 0; i < numberOfWorkers; i++) {
workers[i] = createWorker(i)
}

let isShuttingDown = false;
let isTimedOut = false

const shutdownConfig = getGracefulShutdownConfig();

if (!shutdownConfig.disabled) {
for (const signal of shutdownConfig.signals) {
process.once(signal, onShutdown);
}
}

trapUnhandledNodeErrors();

function createWorker(workerIndex: number): Subprocess {
return spawn({
cmd: ["bun", "--bun", workerFile],
stdout: "inherit",
stderr: "inherit",
stdin: "inherit",
onExit: () => {
if (!isShuttingDown)
workers[workerIndex] = createWorker(workerIndex)
}
});
}

async function onShutdown() {
if (isShuttingDown) {
return;
}
isShuttingDown = true;

killWorkers()

const workersKilled = Promise.all(workers.map(i => i.exited))

await Promise.any([setTimeout(), workersKilled])

if (isTimedOut) {
console.warn(
"[nitro] [cluster] Timeout reached for graceful shutdown. Forcing exit."
);
}

if (shutdownConfig.forceExit) {
process.exit(0);
}
}

async function setTimeout() {
isTimedOut = false
await Bun.sleep(shutdownConfig.timeout)
isTimedOut = true
}

function killWorkers() {
for (const bun of workers) {
bun.kill();
}
}

46 changes: 46 additions & 0 deletions src/presets/bun/runtime/bun-worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import "#nitro-internal-pollyfills";
import { useNitroApp } from "nitropack/runtime";
import { startScheduleRunner } from "nitropack/runtime/internal";

import wsAdapter from "crossws/adapters/bun";

const nitroApp = useNitroApp();

const ws = import.meta._websocket
? wsAdapter(nitroApp.h3App.websocket)
: undefined;

const server = Bun.serve({
reusePort: true,
port: process.env.NITRO_PORT || process.env.PORT || 3000,
websocket: import.meta._websocket ? ws!.websocket : (undefined as any),
async fetch(req: Request, server: any) {
// https://crossws.unjs.io/adapters/bun
if (import.meta._websocket && req.headers.get("upgrade") === "websocket") {
return ws!.handleUpgrade(req, server);
}

const url = new URL(req.url);

let body;
if (req.body) {
body = await req.arrayBuffer();
}

return nitroApp.localFetch(url.pathname + url.search, {
host: url.hostname,
protocol: url.protocol,
headers: req.headers,
method: req.method,
redirect: req.redirect,
body,
});
},
});

console.log(`Listening on http://localhost:${server.port}...`);

// Scheduled tasks
if (import.meta._tasks) {
startScheduleRunner();
}
1 change: 0 additions & 1 deletion src/presets/bun/runtime/bun.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ const ws = import.meta._websocket
? wsAdapter(nitroApp.h3App.websocket)
: undefined;

// @ts-expect-error
const server = Bun.serve({
port: process.env.NITRO_PORT || process.env.PORT || 3000,
websocket: import.meta._websocket ? ws!.websocket : (undefined as any),
Expand Down

0 comments on commit 2a808f7

Please sign in to comment.