Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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-dev" | "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" | "cleavr" | "cli" | "cloudflare" | "cloudflare-dev" | "cloudflare-durable" | "cloudflare-module" | "cloudflare-module-legacy" | "cloudflare-pages" | "cloudflare-pages-static" | "cloudflare-worker" | "deno" | "deno-deploy" | "deno-handler" | "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-dev" | "cloudflareDev" | "cloudflare_dev" | "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" | "cleavr" | "cli" | "cloudflare" | "cloudflare-dev" | "cloudflareDev" | "cloudflare_dev" | "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-handler" | "denoHandler" | "deno_handler" | "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 & {});
21 changes: 20 additions & 1 deletion src/presets/deno/preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,23 @@ const denoServer = defineNitroPreset(
}
);

export default [denoServerLegacy, denoDeploy, denoServer] as const;
const denoHandler = defineNitroPreset(
{
extends: "node-server",
entry: "./runtime/deno-handler",
exportConditions: ["deno"],
rollupConfig: {
external: (id) => id.startsWith("https://"),
output: {
hoistTransitiveImports: false,
},
},
},
{
name: "deno-handler" as const,
compatibilityDate: "2025-05-20",
url: import.meta.url,
}
);

export default [denoServerLegacy, denoDeploy, denoServer, denoHandler] as const;
58 changes: 58 additions & 0 deletions src/presets/deno/runtime/deno-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import "#nitro-internal-pollyfills";
import "./_deno-env-polyfill";
import { useNitroApp } from "nitropack/runtime";

import type { Deno as _Deno } from "@deno/types";
import wsAdapter from "crossws/adapters/deno";

// TODO: Declare conflict with crossws
declare global {
const Deno: typeof import("@deno/types").Deno;
}

const nitroApp = useNitroApp();

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

export async function fetch(
request: Request,
info?: _Deno.ServeHandlerInfo
): Promise<Response> {
// https://crossws.unjs.io/adapters/deno
if (
import.meta._websocket &&
request.headers.get("upgrade") === "websocket"
) {
if (!info) {
throw new Error(
"deno-handler: websocket upgrade requires the second `info` arg from Deno.serve"
);
}
return ws!.handleUpgrade(request, info);
}

const url = new URL(request.url);

// https://deno.land/api?s=Body
let body;
if (request.body) {
body = await request.arrayBuffer();
}

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

// Library mode: caller invokes `fetch(request, info?)` directly. No port is
// bound; the embedding process owns the listen lifecycle (e.g. a multi-site
// dispatcher routing several Nitro builds in the same Deno runtime).
export default { fetch };
40 changes: 40 additions & 0 deletions test/presets/deno-handler.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { execa, execaCommandSync } from "execa";
import { getRandomPort, waitForPort } from "get-port-please";
import { promises as fsp } from "node:fs";
import { resolve } from "pathe";
import { describe } from "vitest";
import { setupTest, testNitro } from "../tests.ts";

const hasDeno =
execaCommandSync("deno --version", { stdio: "ignore", reject: false })
.exitCode === 0;

describe.runIf(hasDeno)("nitro:preset:deno-handler", async () => {
const ctx = await setupTest("deno-handler");
testNitro(ctx, async () => {
const port = await getRandomPort();
// The preset emits a handler-only build; spin up a minimal Deno wrapper
// that imports the exported `fetch` and serves it. Library users would
// do the same in their own dispatcher or test harness.
const wrapperPath = resolve(ctx.outDir, "server/wrapper.mjs");
await fsp.writeFile(
wrapperPath,
`import { fetch } from "./index.mjs";\nDeno.serve({ port: ${port}, hostname: "127.0.0.1" }, fetch);\n`
);
execa("deno", ["run", "-A", wrapperPath], {
cwd: ctx.outDir,
stdio: "ignore",
});
ctx.server = {
url: `http://127.0.0.1:${port}`,
close: () => {
// p.kill()
},
} as any;
await waitForPort(port, { delay: 1000, retries: 20, host: "127.0.0.1" });
return async ({ url, ...opts }) => {
const res = await ctx.fetch(url, opts);
return res;
};
});
});
10 changes: 8 additions & 2 deletions test/tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ export function testNitro(
});
});

it.skipIf(ctx.preset === "deno-server")(
it.skipIf(["deno-server", "deno-handler"].includes(ctx.preset))(
"resolve module version conflicts",
async () => {
const { data } = await callHandler({ url: "/modules" });
Expand Down Expand Up @@ -690,6 +690,7 @@ export function testNitro(
"nitro-dev",
"vercel",
(nodeMajorVersion || 0) < 18 && "deno-server",
(nodeMajorVersion || 0) < 18 && "deno-handler",
(nodeMajorVersion || 0) < 18 && "bun",
].filter(Boolean);
if (notSplittingPresets.includes(ctx.preset)) {
Expand Down Expand Up @@ -724,6 +725,7 @@ export function testNitro(
// TODO: Investigate
ctx.preset === "bun" ||
ctx.preset === "deno-server" ||
ctx.preset === "deno-handler" ||
ctx.preset === "nitro-dev"
)("sourcemap works", async () => {
const { data } = await callHandler({ url: "/error-stack" });
Expand Down Expand Up @@ -806,6 +808,7 @@ export function testNitro(
[
"bun",
"deno-server",
"deno-handler",
"deno-deploy",
"netlify",
"netlify-legacy",
Expand Down Expand Up @@ -853,7 +856,10 @@ export function testNitro(
) {
continue;
}
if (ctx.preset === "deno-server" && key === "globals:BroadcastChannel") {
if (
["deno-server", "deno-handler"].includes(ctx.preset) &&
key === "globals:BroadcastChannel"
) {
continue; // unstable API
}
expect(data[key], key).toBe(true);
Expand Down