Skip to content

Commit

Permalink
feat(cloudflare): enable native node.js compatibility (#3064)
Browse files Browse the repository at this point in the history
  • Loading branch information
pi0 authored Feb 4, 2025
1 parent 60867fc commit ccf5726
Show file tree
Hide file tree
Showing 9 changed files with 444 additions and 29 deletions.
31 changes: 5 additions & 26 deletions src/presets/cloudflare/preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,10 @@ import {
writeCFPagesHeaders,
writeCFPagesRedirects,
} from "./utils";
import { hybridNodePlugin, unenvCfPreset } from "./unenv/preset";

export type { CloudflareOptions as PresetOptions } from "./types";

const cloudflareExternals = [
// https://developers.cloudflare.com/email-routing/email-workers/reply-email-workers/
"cloudflare:email",
// https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets/
"cloudflare:sockets",
// https://developers.cloudflare.com/durable-objects/get-started/walkthrough/
"cloudflare:workers",
// https://developers.cloudflare.com/workflows/build/workers-api/
"cloudflare:workflows",
] as const;

// TODO: Remove when wrangler -C support landed
// https://github.com/cloudflare/workers-sdk/pull/7994
const isWindows = process.platform === "win32";
Expand All @@ -42,9 +32,7 @@ const cloudflarePages = defineNitroPreset(
publicDir: "{{ output.dir }}/{{ baseURL }}",
serverDir: "{{ output.dir }}/_worker.js",
},
unenv: {
external: [...cloudflareExternals],
},
unenv: unenvCfPreset,
alias: {
// Hotfix: Cloudflare appends /index.html if mime is not found and things like ico are not in standard lite.js!
// https://github.com/nitrojs/nitro/pull/933
Expand All @@ -55,6 +43,7 @@ const cloudflarePages = defineNitroPreset(
esmImport: true,
},
rollupConfig: {
plugins: [hybridNodePlugin],
output: {
entryFileNames: "index.js",
format: "esm",
Expand Down Expand Up @@ -116,9 +105,6 @@ const cloudflare = defineNitroPreset(
wasm: {
lazy: true,
},
unenv: {
external: [...cloudflareExternals],
},
hooks: {
async compiled(nitro: Nitro) {
await writeFile(
Expand Down Expand Up @@ -156,9 +142,6 @@ const cloudflareModuleLegacy = defineNitroPreset(
inlineDynamicImports: false,
},
},
unenv: {
external: [...cloudflareExternals],
},
wasm: {
lazy: false,
esmImport: true,
Expand Down Expand Up @@ -192,10 +175,9 @@ const cloudflareModule = defineNitroPreset(
preview: commandWithDir("npx wrangler dev"),
deploy: commandWithDir("npx wrangler deploy"),
},
unenv: {
external: [...cloudflareExternals],
},
unenv: unenvCfPreset,
rollupConfig: {
plugins: [hybridNodePlugin],
output: {
format: "esm",
exports: "named",
Expand Down Expand Up @@ -231,9 +213,6 @@ const cloudflareDurable = defineNitroPreset(
{
extends: "cloudflare-module",
entry: "./runtime/cloudflare-durable",
unenv: {
external: [...cloudflareExternals],
},
},
{
name: "cloudflare-durable" as const,
Expand Down
77 changes: 77 additions & 0 deletions src/presets/cloudflare/unenv/preset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import type { Preset } from "unenv";
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 = [
"_stream_duplex",
"_stream_passthrough",
"_stream_readable",
"_stream_transform",
"_stream_writable",
"assert",
"assert/strict",
"buffer",
"diagnostics_channel",
"dns",
"dns/promises",
"events",
"net",
"path",
"path/posix",
"path/win32",
"querystring",
"stream",
"stream/consumers",
"stream/promises",
"stream/web",
"string_decoder",
"timers",
"timers/promises",
"url",
"util/types",
"zlib",
];

// Modules implemented via a mix of workerd APIs and polyfills
export const hybridNodeCompatModules = ["async_hooks", "crypto", "util"];

const presetRuntimeDir = fileURLToPath(new URL("runtime/", import.meta.url));
const resolvePresetRuntime = (m: string) => join(presetRuntimeDir, `${m}.mjs`);

export const unenvCfPreset: Preset = {
external: nodeCompatModules.map((m) => `node:${m}`),
alias: {
// <id> => node:<id>
...Object.fromEntries(nodeCompatModules.map((m) => [m, `node:${m}`])),
...Object.fromEntries(hybridNodeCompatModules.map((m) => [m, `node:${m}`])),
// node:<id> => runtime/<id>.mjs (hybrid)
...Object.fromEntries(
hybridNodeCompatModules.map((m) => [
`node:${m}`,
resolvePresetRuntime(m === "sys" ? "util" : m),
])
),
sys: resolvePresetRuntime("util"),
"node:sys": resolvePresetRuntime("util"),
},
};

export const hybridNodePlugin: Plugin = {
name: "nitro:cloudflare:hybrid-node-compat",
resolveId(id) {
if (id.startsWith("#workerd/node:")) {
return { id: id.slice("#workerd/".length), external: true };
}
},
};
30 changes: 30 additions & 0 deletions src/presets/cloudflare/unenv/runtime/async_hooks.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// https://github.com/cloudflare/workers-sdk/blob/main/packages/unenv-preset/src/runtime/node/async_hooks/index.ts

import workerdAsyncHooks from "#workerd/node:async_hooks";

import {
// asyncWrapProviders,
createHook,
executionAsyncId,
executionAsyncResource,
triggerAsyncId,
} from "unenv/runtime/node/async_hooks/index";

export {
// asyncWrapProviders,
createHook,
executionAsyncId,
executionAsyncResource,
triggerAsyncId,
} from "unenv/runtime/node/async_hooks/index";

export const { AsyncLocalStorage, AsyncResource } = workerdAsyncHooks;

export default {
createHook,
executionAsyncId,
executionAsyncResource,
triggerAsyncId,
AsyncLocalStorage,
AsyncResource,
};
190 changes: 190 additions & 0 deletions src/presets/cloudflare/unenv/runtime/crypto.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
// https://github.com/cloudflare/workers-sdk/blob/main/packages/unenv-preset/src/runtime/node/crypto/index.ts

import workerdCrypto from "#workerd/node:crypto";

import {
Cipher,
Cipheriv,
constants,
createCipher,
createCipheriv,
createDecipher,
createDecipheriv,
createECDH,
createSign,
createVerify,
Decipher,
Decipheriv,
diffieHellman,
ECDH,
getCipherInfo,
// hash,
privateDecrypt,
privateEncrypt,
pseudoRandomBytes,
publicDecrypt,
publicEncrypt,
Sign,
sign,
webcrypto as unenvCryptoWebcrypto,
Verify,
verify,
} from "unenv/runtime/node/crypto/index";

export {
Cipher,
Cipheriv,
Decipher,
Decipheriv,
ECDH,
Sign,
Verify,
constants,
createCipheriv,
createDecipheriv,
createECDH,
createSign,
createVerify,
diffieHellman,
getCipherInfo,
// hash,
privateDecrypt,
privateEncrypt,
publicDecrypt,
publicEncrypt,
sign,
verify,
} from "unenv/runtime/node/crypto/index";

export const {
Certificate,
DiffieHellman,
DiffieHellmanGroup,
Hash,
Hmac,
KeyObject,
X509Certificate,
checkPrime,
checkPrimeSync,
createDiffieHellman,
createDiffieHellmanGroup,
createHash,
createHmac,
createPrivateKey,
createPublicKey,
createSecretKey,
generateKey,
generateKeyPair,
generateKeyPairSync,
generateKeySync,
generatePrime,
generatePrimeSync,
getCiphers,
getCurves,
getDiffieHellman,
getFips,
getHashes,
hkdf,
hkdfSync,
pbkdf2,
pbkdf2Sync,
randomBytes,
randomFill,
randomFillSync,
randomInt,
randomUUID,
scrypt,
scryptSync,
secureHeapUsed,
setEngine,
setFips,
subtle,
timingSafeEqual,
} = workerdCrypto;

export const getRandomValues = workerdCrypto.getRandomValues.bind(
workerdCrypto.webcrypto
);

export const webcrypto = {
CryptoKey: unenvCryptoWebcrypto.CryptoKey,
getRandomValues,
randomUUID,
subtle,
};

const fips = workerdCrypto.fips;

export default {
Certificate,
Cipher,
Cipheriv,
Decipher,
Decipheriv,
ECDH,
Sign,
Verify,
X509Certificate,
constants,
createCipheriv,
createDecipheriv,
createECDH,
createSign,
createVerify,
diffieHellman,
getCipherInfo,
// hash,
privateDecrypt,
privateEncrypt,
publicDecrypt,
publicEncrypt,
scrypt,
scryptSync,
sign,
verify,
createCipher,
createDecipher,
pseudoRandomBytes,
DiffieHellman,
DiffieHellmanGroup,
Hash,
Hmac,
KeyObject,
checkPrime,
checkPrimeSync,
createDiffieHellman,
createDiffieHellmanGroup,
createHash,
createHmac,
createPrivateKey,
createPublicKey,
createSecretKey,
generateKey,
generateKeyPair,
generateKeyPairSync,
generateKeySync,
generatePrime,
generatePrimeSync,
getCiphers,
getCurves,
getDiffieHellman,
getFips,
getHashes,
getRandomValues,
hkdf,
hkdfSync,
pbkdf2,
pbkdf2Sync,
randomBytes,
randomFill,
randomFillSync,
randomInt,
randomUUID,
secureHeapUsed,
setEngine,
setFips,
subtle,
timingSafeEqual,
fips,
webcrypto,
};
Loading

0 comments on commit ccf5726

Please sign in to comment.