Skip to content

Instantly share code, notes, and snippets.

@easrng
Last active June 2, 2024 05:42
Show Gist options
  • Save easrng/9604bb71f6a49420c62c14218e76cdb3 to your computer and use it in GitHub Desktop.
Save easrng/9604bb71f6a49420c62c14218e76cdb3 to your computer and use it in GitHub Desktop.
cross-runtime http `serve` function for js
/// <reference lib="webworker" />
// @ts-ignore
type _makeTSPlaygroundLoadTheBunTypes = import("bun-types");
type ServeOptions = {
fetch: (req: Request) => PromiseLike<Response> | Response;
port?: number;
hostname?: string;
signal?: AbortSignal;
};
type ServeAddress = { hostname: string; port: number; url: string };
type ServeFunction = (options: ServeOptions) => Promise<ServeAddress>;
function getAddress(options: ServeOptions): ServeAddress {
const address: ServeAddress = {
port: options.port ?? 3000,
hostname: options.hostname ?? "::",
url: "",
};
const url = new URL("http://x");
url.hostname = address.hostname.includes(":")
? `[${address.hostname}]`
: address.hostname;
url.port = address.port.toString();
if (url.hostname === "[::]") {
url.hostname = "localhost";
address.hostname = "localhost";
}
address.url = url.href;
return address;
}
let nodeServerPromise: Promise<typeof import("@hono/node-server")>;
type DenoType = {
serve(
options: {
port?: number;
hostname?: string;
signal?: AbortSignal;
reusePort?: boolean;
onError?: (error: unknown) => Response | Promise<Response>;
onListen?: (localAddr: unknown) => void;
},
handler: (request: Request) => Response | Promise<Response>,
): unknown;
};
const serveWeb: ServeFunction = async ({ fetch, signal }) => {
if (!signal || !signal.aborted) {
const handler = (event: FetchEvent) => {
event.respondWith(fetch(event.request));
};
globalThis.addEventListener("fetch", handler as any);
signal?.addEventListener(
"abort",
() => {
globalThis.removeEventListener("fetch", handler as any);
},
{ once: true },
);
}
return Promise.resolve(
typeof location !== "undefined"
? {
hostname: location.host,
port: parseInt(location.port),
url: location.origin,
}
: {
hostname: "unknown.invalid",
port: 0,
url: "http://unknown.invalid",
},
);
};
const serveBun: ServeFunction = async (options) => {
let Bun;
if ("Bun" in globalThis) {
Bun = (globalThis as any).Bun as typeof import("bun");
} else {
throw new Error("not running in Bun");
}
const address = getAddress(options);
if (!options.signal || !options.signal.aborted) {
const server = Bun.serve({
fetch: async (req) => options.fetch(req),
hostname: address.hostname,
port: address.port,
});
if (options.signal) {
options.signal.addEventListener("abort", () => server.stop(), {
once: true,
});
}
}
return address;
};
const serveNode: ServeFunction = async (options) => {
const address = getAddress(options);
if (!options.signal || !options.signal.aborted) {
const nodeServe = await (nodeServerPromise ||
(nodeServerPromise = import("@hono/node-server")));
if (!options.signal || !options.signal.aborted) {
let readyResolve: () => void;
const readyPromise = new Promise<void>(
(resolve) => (readyResolve = resolve),
);
const server = nodeServe.serve(
{
fetch: options.fetch,
port: address.port,
hostname: address.hostname,
},
() => {
readyResolve();
},
);
if (options.signal) {
options.signal.addEventListener("abort", () => server.close(), {
once: true,
});
}
await readyPromise;
}
}
return address;
};
const serveDeno: ServeFunction = async (options) => {
let Deno;
if ("Deno" in globalThis) {
Deno = (globalThis as any).Deno as DenoType;
} else {
throw new Error("not running in Deno");
}
const address = getAddress(options);
if (!options.signal || !options.signal.aborted) {
let readyResolve: () => void;
const readyPromise = new Promise<void>(
(resolve) => (readyResolve = resolve),
);
Deno.serve(
{
hostname: address.hostname,
port: address.port,
signal: options.signal,
onListen() {
readyResolve();
},
},
async (req) => options.fetch(req),
);
await readyPromise;
}
return address;
};
const serveAuto: ServeFunction = (options) => {
if ("Bun" in globalThis) {
return serveBun(options);
} else if ("Deno" in globalThis) {
return serveDeno(options);
} else if (typeof process !== "undefined") {
return serveNode(options);
} else if (
typeof ServiceWorkerGlobalScope !== "undefined" &&
globalThis instanceof ServiceWorkerGlobalScope
) {
return serveWeb(options);
} else {
throw new Error("Unsupported runtime.");
}
};
export {
serveWeb,
serveNode,
serveBun,
serveDeno,
serveAuto,
serveAuto as default,
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment