From c99b22fd0a8958e324baf6eda602d97ef6871059 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Wed, 5 Feb 2025 14:21:50 +0100 Subject: [PATCH] refactor(cloudflare): improve node compat + add tests (#3067) --- src/presets/cloudflare/unenv/preset.ts | 13 +++-- test/fixture/routes/node-compat.ts | 54 +++++++++++++++++++ test/presets/cloudflare-module-legacy.test.ts | 2 +- test/tests.ts | 12 +++++ 4 files changed, 73 insertions(+), 8 deletions(-) create mode 100644 test/fixture/routes/node-compat.ts diff --git a/src/presets/cloudflare/unenv/preset.ts b/src/presets/cloudflare/unenv/preset.ts index 47e93c748e..90cde082d7 100644 --- a/src/presets/cloudflare/unenv/preset.ts +++ b/src/presets/cloudflare/unenv/preset.ts @@ -4,13 +4,6 @@ import type { Plugin } from "rollup"; import { fileURLToPath } from "mlly"; import { join } from "pathe"; -export const cloudflareExternals = [ - "cloudflare:email", - "cloudflare:sockets", - "cloudflare:workers", - "cloudflare:workflows", -] as const; - // Built-in APIs provided by workerd with nodejs compatibility // https://github.com/cloudflare/workers-sdk/blob/main/packages/unenv-preset/src/preset.ts export const nodeCompatModules = [ @@ -65,11 +58,17 @@ export const unenvCfPreset: Preset = { sys: resolvePresetRuntime("util"), "node:sys": resolvePresetRuntime("util"), }, + inject: { + "globalThis.Buffer": ["node:buffer", "Buffer"], + }, }; export const hybridNodePlugin: Plugin = { name: "nitro:cloudflare:hybrid-node-compat", resolveId(id) { + if (id.startsWith("cloudflare:")) { + return { id, external: true }; + } if (id.startsWith("#workerd/node:")) { return { id: id.slice("#workerd/".length), external: true }; } diff --git a/test/fixture/routes/node-compat.ts b/test/fixture/routes/node-compat.ts new file mode 100644 index 0000000000..f8eb029440 --- /dev/null +++ b/test/fixture/routes/node-compat.ts @@ -0,0 +1,54 @@ +import nodeAsyncHooks from "node:async_hooks"; +import nodeCrypto from "node:crypto"; + +const nodeCompatTests = { + buffer: { + Buffer: () => { + const _Buffer = Buffer; + return _Buffer && !("__unenv__" in _Buffer); + }, + "globalThis.Buffer": () => { + const _Buffer = globalThis.Buffer; + return _Buffer && !("__unenv__" in _Buffer); + }, + }, + crypto: { + createHash: () => { + return nodeCrypto + .createHash("sha256") + .update("hello") + .digest("hex") + .startsWith("2cf24"); + }, + }, + async_hooks: { + AsyncLocalStorage: async () => { + if ("__unenv__" in nodeAsyncHooks.AsyncLocalStorage) { + return false; + } + const ctx = new nodeAsyncHooks.AsyncLocalStorage(); + const rand = Math.random(); + return ctx.run(rand, async () => { + await new Promise((r) => r()); + if (ctx.getStore() !== rand) { + return false; + } + return true; + }); + }, + }, +}; + +export default eventHandler(async (event) => { + const results: Record = {}; + for (const [group, groupTests] of Object.entries(nodeCompatTests)) { + for (const [name, test] of Object.entries(groupTests)) { + results[`${group}:${name}`] = !!(await test()); + } + } + return new Response(JSON.stringify(results, null, 2), { + headers: { + "Content-Type": "application/json", + }, + }); +}); diff --git a/test/presets/cloudflare-module-legacy.test.ts b/test/presets/cloudflare-module-legacy.test.ts index 6ac40d3c36..3efd039df6 100644 --- a/test/presets/cloudflare-module-legacy.test.ts +++ b/test/presets/cloudflare-module-legacy.test.ts @@ -5,7 +5,7 @@ import { describe } from "vitest"; import { setupTest, testNitro } from "../tests"; -describe("nitro:preset:cloudflare-module", async () => { +describe("nitro:preset:cloudflare-module-legacy", async () => { const ctx = await setupTest("cloudflare-module-legacy", {}); testNitro(ctx, () => { diff --git a/test/tests.ts b/test/tests.ts index b9c9052b82..7fe4dae579 100644 --- a/test/tests.ts +++ b/test/tests.ts @@ -799,4 +799,16 @@ export function testNitro( sqlts: "--", }); }); + + it.skipIf( + ["cloudflare-worker", "cloudflare-module-legacy", "vercel-edge"].includes( + ctx.preset + ) + )("nodejs compatibility", async () => { + const { data, status } = await callHandler({ url: "/node-compat" }); + expect(status).toBe(200); + for (const key in data) { + expect(data[key], key).toBe(true); + } + }); }