Skip to content

Commit

Permalink
feat(deno-server): deno v2 compatibility (#3050)
Browse files Browse the repository at this point in the history
Co-authored-by: Pooya Parsa <[email protected]>
  • Loading branch information
marvinhagemeister and pi0 authored Jan 30, 2025
1 parent 547410d commit 601ade2
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 120 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ jobs:
- uses: denoland/setup-deno@v1
if: ${{ matrix.os != 'windows-latest' }}
with:
deno-version: v1.x
deno-version: v2.x
- run: pnpm install
- run: pnpm test:types
if: ${{ matrix.os != 'windows-latest' }}
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" | "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-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" | "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-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 & {});
140 changes: 140 additions & 0 deletions src/presets/deno/preset-legacy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { builtinModules } from "node:module";
import MagicString from "magic-string";
import { findStaticImports } from "mlly";
import { defineNitroPreset } from "nitropack/kit";
import { writeFile } from "nitropack/kit";
import { isAbsolute, resolve } from "pathe";

// nitro/src/rollup/plugin/import-meta.ts
const ImportMetaRe = /import\.meta|globalThis._importMeta_/;

export const denoServerLegacy = defineNitroPreset(
{
extends: "node-server",
entry: "./runtime/deno-server",
exportConditions: ["deno"],
commands: {
preview: "deno task --config ./deno.json start",
},
unenv: {
inject: {
global: ["unenv/runtime/polyfill/global-this", "default"],
Buffer: ["node:buffer", "Buffer"],
setTimeout: ["node:timers", "setTimeout"],
clearTimeout: ["node:timers", "clearTimeout"],
setInterval: ["node:timers", "setInterval"],
clearInterval: ["node:timers", "clearInterval"],
setImmediate: ["node:timers", "setImmediate"],
clearImmediate: ["node:timers", "clearImmediate"],
// process: ["node:process", "default"],
},
},
rollupConfig: {
output: {
hoistTransitiveImports: false,
},
plugins: [
{
name: "rollup-plugin-node-deno",
resolveId(id) {
id = id.replace("node:", "");
if (builtinModules.includes(id)) {
return {
id: `node:${id}`,
moduleSideEffects: false,
external: true,
};
}
if (isHTTPImport(id)) {
return {
id,
external: true,
};
}
},
renderChunk(code) {
const s = new MagicString(code);
const imports = findStaticImports(code);
for (const i of imports) {
if (
!i.specifier.startsWith(".") &&
!isAbsolute(i.specifier) &&
!isHTTPImport(i.specifier) &&
!i.specifier.startsWith("npm:")
) {
const specifier = i.specifier.replace("node:", "");
s.replace(
i.code,
i.code.replace(
new RegExp(`(?<quote>['"])${i.specifier}\\k<quote>`),
JSON.stringify(
builtinModules.includes(specifier)
? "node:" + specifier
: "npm:" + specifier
)
)
);
}
}
if (s.hasChanged()) {
return {
code: s.toString(),
map: s.generateMap({ includeContent: true }),
};
}
},
},
{
name: "inject-process",
renderChunk: {
order: "post",
handler(code, chunk) {
if (
!chunk.isEntry &&
(!ImportMetaRe.test(code) || code.includes("ROLLUP_NO_REPLACE"))
) {
return;
}

const s = new MagicString(code);
s.prepend(
"import __process__ from 'node:process';globalThis.process=globalThis.process||__process__;"
);

return {
code: s.toString(),
map: s.generateMap({ includeContent: true }),
};
},
},
},
],
},
hooks: {
async compiled(nitro) {
// https://deno.com/manual@v1.34.3/getting_started/configuration_file
const denoJSON = {
tasks: {
start:
"deno run --allow-net --allow-read --allow-write --allow-env --unstable-byonm ./server/index.mjs",
},
};
await writeFile(
resolve(nitro.options.output.dir, "deno.json"),
JSON.stringify(denoJSON, null, 2)
);
},
},
},
{
name: "deno-server-legacy" as const,
aliases: ["deno-server"],
url: import.meta.url,
}
);

const HTTP_IMPORT_RE = /^(https?:)?\/\//;

function isHTTPImport(id: string) {
return HTTP_IMPORT_RE.test(id);
}
110 changes: 7 additions & 103 deletions src/presets/deno/preset.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import { builtinModules } from "node:module";
import MagicString from "magic-string";
import { findStaticImports } from "mlly";
import { defineNitroPreset } from "nitropack/kit";
import { writeFile } from "nitropack/kit";
import { isAbsolute, resolve } from "pathe";
import { resolve } from "pathe";

// nitro/src/rollup/plugin/import-meta.ts
const ImportMetaRe = /import\.meta|globalThis._importMeta_/;
import { denoServerLegacy } from "./preset-legacy";

const denoDeploy = defineNitroPreset(
{
Expand Down Expand Up @@ -45,107 +42,19 @@ const denoServer = defineNitroPreset(
commands: {
preview: "deno task --config ./deno.json start",
},
unenv: {
inject: {
global: ["unenv/runtime/polyfill/global-this", "default"],
Buffer: ["node:buffer", "Buffer"],
setTimeout: ["node:timers", "setTimeout"],
clearTimeout: ["node:timers", "clearTimeout"],
setInterval: ["node:timers", "setInterval"],
clearInterval: ["node:timers", "clearInterval"],
setImmediate: ["node:timers", "setImmediate"],
clearImmediate: ["node:timers", "clearImmediate"],
// process: ["node:process", "default"],
},
},
rollupConfig: {
external: (id) => id.startsWith("https://"),
output: {
hoistTransitiveImports: false,
},
plugins: [
{
name: "rollup-plugin-node-deno",
resolveId(id) {
id = id.replace("node:", "");
if (builtinModules.includes(id)) {
return {
id: `node:${id}`,
moduleSideEffects: false,
external: true,
};
}
if (isHTTPImport(id)) {
return {
id,
external: true,
};
}
},
renderChunk(code) {
const s = new MagicString(code);
const imports = findStaticImports(code);
for (const i of imports) {
if (
!i.specifier.startsWith(".") &&
!isAbsolute(i.specifier) &&
!isHTTPImport(i.specifier) &&
!i.specifier.startsWith("npm:")
) {
const specifier = i.specifier.replace("node:", "");
s.replace(
i.code,
i.code.replace(
new RegExp(`(?<quote>['"])${i.specifier}\\k<quote>`),
JSON.stringify(
builtinModules.includes(specifier)
? "node:" + specifier
: "npm:" + specifier
)
)
);
}
}
if (s.hasChanged()) {
return {
code: s.toString(),
map: s.generateMap({ includeContent: true }),
};
}
},
},
{
name: "inject-process",
renderChunk: {
order: "post",
handler(code, chunk) {
if (
!chunk.isEntry &&
(!ImportMetaRe.test(code) || code.includes("ROLLUP_NO_REPLACE"))
) {
return;
}

const s = new MagicString(code);
s.prepend(
"import __process__ from 'node:process';globalThis.process=globalThis.process||__process__;"
);

return {
code: s.toString(),
map: s.generateMap({ includeContent: true }),
};
},
},
},
],
},
hooks: {
async compiled(nitro) {
// https://deno.com/manual@v1.34.3/getting_started/configuration_file
// https://docs.deno.com/runtime/fundamentals/configuration/
const denoJSON = {
tasks: {
start:
"deno run --allow-net --allow-read --allow-write --allow-env --unstable-byonm ./server/index.mjs",
"deno run --allow-net --allow-read --allow-write --allow-env --unstable-byonm --unstable-node-globals ./server/index.mjs",
},
};
await writeFile(
Expand All @@ -157,14 +66,9 @@ const denoServer = defineNitroPreset(
},
{
name: "deno-server" as const,
compatibilityDate: "2025-01-30",
url: import.meta.url,
}
);

export default [denoDeploy, denoServer] as const;

const HTTP_IMPORT_RE = /^(https?:)?\/\//;

function isHTTPImport(id: string) {
return HTTP_IMPORT_RE.test(id);
}
export default [denoServerLegacy, denoDeploy, denoServer] as const;
2 changes: 1 addition & 1 deletion test/fixture/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ describe("API routes", () => {
describe("defineNitroConfig", () => {
it("should not accept functions to routeRules.cache", () => {
defineNitroConfig({
compatibilityDate: "2024-09-29",
compatibilityDate: "2025-01-30",
routeRules: {
"/**": {
cache: {
Expand Down
22 changes: 9 additions & 13 deletions test/presets/deno-server.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { execa, execaCommandSync } from "execa";
import { getRandomPort, waitForPort } from "get-port-please";
import { resolve } from "pathe";
import { describe } from "vitest";
import { setupTest, testNitro } from "../tests";

Expand All @@ -12,18 +11,15 @@ describe.runIf(hasDeno)("nitro:preset:deno-server", async () => {
const ctx = await setupTest("deno-server");
testNitro(ctx, async () => {
const port = await getRandomPort();
const p = execa(
"deno",
["task", "--config", resolve(ctx.outDir, "deno.json"), "start"],
{
// stdio: "inherit",
stdio: "ignore",
env: {
NITRO_PORT: String(port),
NITRO_HOST: "127.0.0.1",
},
}
);
const p = execa("deno", ["task", "start"], {
cwd: ctx.outDir,
// stdio: "inherit",
stdio: "ignore",
env: {
NITRO_PORT: String(port),
NITRO_HOST: "127.0.0.1",
},
});
ctx.server = {
url: `http://127.0.0.1:${port}`,
close: () => {
Expand Down

0 comments on commit 601ade2

Please sign in to comment.