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
5 changes: 5 additions & 0 deletions .changeset/red-wasps-kick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cloudflare/unenv-preset": patch
---

Use workerd `node:process` v2 when available
19 changes: 19 additions & 0 deletions fixtures/worker-logs/tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,4 +306,23 @@ describe("'wrangler dev' correctly displays logs", () => {
expect(output).toMatchInlineSnapshot(`[]`);
});
});

describe("nodejs compat process v2", () => {
test("default behavior", async ({ expect }) => {
const output = await getWranglerDevOutput("module", [
"--compatibility-flags=enable_nodejs_process_v2",
"--compatibility-flags=nodejs_compat",
]);
expect(output).toMatchInlineSnapshot(`
[
"<<<<< console.info() message >>>>>",
"<<<<< console.log() message >>>>>",
"X [ERROR] <<<<< console.error() message >>>>>",
"stderr: <<<<< stderr.write() message >>>>>",
"stdout: <<<<< stdout.write() message >>>>>",
"▲ [WARNING] <<<<< console.warning() message >>>>>",
]
`);
});
});
});
2 changes: 1 addition & 1 deletion packages/unenv-preset/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
},
"peerDependencies": {
"unenv": "2.0.0-rc.21",
"workerd": "^1.20250828.1"
"workerd": "^1.20250904.0"
},
"peerDependenciesMeta": {
"workerd": {
Expand Down
188 changes: 112 additions & 76 deletions packages/unenv-preset/src/runtime/node/process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,121 +14,157 @@ const globalProcess: NodeJS.Process = (globalThis as any)["pro" + "cess"];
export const getBuiltinModule: NodeJS.Process["getBuiltinModule"] =
globalProcess.getBuiltinModule;

export const { exit, platform, nextTick } = getBuiltinModule(
"node:process"
) as NodeJS.Process;
const workerdProcess = getBuiltinModule("node:process");

// Workerd has 2 different implementation for `node:process`
//
// See:
// - [workerd `process` v1](https://github.com/cloudflare/workerd/blob/main/src/node/internal/legacy_process.ts)
// - [workerd `process` v2](https://github.com/cloudflare/workerd/blob/main/src/node/internal/public_process.ts)
// - [`enable_nodejs_process_v2` flag](https://github.com/cloudflare/workerd/blob/main/src/workerd/io/compatibility-date.capnp)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const isWorkerdProcessV2 = (globalThis as any).Cloudflare.compatibilityFlags
.enable_nodejs_process_v2;

const unenvProcess = new UnenvProcess({
env: globalProcess.env,
hrtime: UnenvHrTime,
nextTick,
// `hrtime` is only available from workerd process v2
hrtime: isWorkerdProcessV2 ? workerdProcess.hrtime : UnenvHrTime,
// `nextTick` is available from workerd process v1
nextTick: workerdProcess.nextTick,
});

// APIs implemented by workerd module in both v1 and v2
// Note that `env`, `hrtime` and `nextTick` are always retrieved from `unenv`
export const { exit, features, platform } = workerdProcess;

// APIs that can be implemented by either by `unenv` or `workerd`.
// They are always retrieved from `unenv` which might use their `workerd` implementation.
export const {
abort,
addListener,
allowedNodeEnvironmentFlags,
// Always implemented by workerd
env,
// Only implemented in workerd v2
hrtime,
// Always implemented by workerd
nextTick,
} = unenvProcess;

// APIs that are not implemented by `workerd` (whether v1 or v2)
// They are retrieved from `unenv`.
export const {
_channel,
_debugEnd,
_debugProcess,
_disconnect,
_events,
_eventsCount,
_exiting,
_fatalException,
_getActiveHandles,
_getActiveRequests,
_handleQueue,
_kill,
_linkedBinding,
_maxListeners,
_pendingMessage,
_preload_modules,
_rawDebug,
_send,
_startProfilerIdleNotifier,
_stopProfilerIdleNotifier,
_tickCallback,
assert,
availableMemory,
constrainedMemory,
cpuUsage,
disconnect,
domain,
emit, // not exported from workerd but used internally
execPath,
mainModule,
moduleLoadList,
openStdin,
reallyExit,
} = unenvProcess;

// APIs that are not implemented in `workerd` v1 and `undefined` in v2.
// They are retrieved from `unenv`.
export const {
binding,
channel,
connected,
debugPort,
dlopen,
exitCode,
finalization,
getActiveResourcesInfo,
hasUncaughtExceptionCaptureCallback,
kill,
memoryUsage,
permission,
ref,
release,
report,
resourceUsage,
send,
setUncaughtExceptionCaptureCallback,
loadEnvFile,
sourceMapsEnabled,
throwDeprecation,
traceDeprecation,
unref,
} = unenvProcess;

// API that are only implemented starting from v2 of workerd process
export const {
abort,
addListener,
allowedNodeEnvironmentFlags,
arch,
argv,
argv0,
chdir,
config,
connected,
constrainedMemory,
availableMemory,
cpuUsage,
cwd,
debugPort,
dlopen,
disconnect,
emit,
emitWarning,
env,
eventNames,
execArgv,
execPath,
finalization,
features,
getActiveResourcesInfo,
getegid,
geteuid,
getgid,
getgroups,
getMaxListeners,
hrtime,
kill,
listeners,
getuid,
// @ts-expect-error `initgroups` is missing typings
initgroups,
listenerCount,
memoryUsage,
on,
listeners,
loadEnvFile,
off,
on,
once,
pid,
ppid,
prependListener,
prependOnceListener,
rawListeners,
release,
removeAllListeners,
removeListener,
report,
resourceUsage,
setegid,
seteuid,
setgid,
setgroups,
setMaxListeners,
setSourceMapsEnabled,
setuid,
stderr,
stdin,
stdout,
title,
throwDeprecation,
traceDeprecation,
umask,
uptime,
version,
versions,
domain,
initgroups,
moduleLoadList,
reallyExit,
openStdin,
assert,
binding,
send,
exitCode,
channel,
getegid,
geteuid,
getgid,
getgroups,
getuid,
setegid,
seteuid,
setgid,
setgroups,
setuid,
permission,
mainModule,
_events,
_eventsCount,
_exiting,
_maxListeners,
_debugEnd,
_debugProcess,
_fatalException,
_getActiveHandles,
_getActiveRequests,
_kill,
_preload_modules,
_rawDebug,
_startProfilerIdleNotifier,
_stopProfilerIdleNotifier,
_tickCallback,
_disconnect,
_handleQueue,
_pendingMessage,
_channel,
_send,
_linkedBinding,
} = unenvProcess;
} = isWorkerdProcessV2 ? workerdProcess : unenvProcess;

const _process = {
abort,
Expand Down
28 changes: 28 additions & 0 deletions packages/wrangler/e2e/unenv-preset/preset.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,34 @@ const testConfigs: TestConfig[] = [
},
},
],
// node:process v2
[
{
name: "process v1 by date",
compatibilityDate: "2024-09-23",
expectRuntimeFlags: {
enable_nodejs_process_v2: false,
},
},
// TODO: add a config when v2 is enabled by default (>= 2025-09-15)
{
name: "process v2 by flag",
compatibilityDate: "2024-09-23",
compatibilityFlags: ["enable_nodejs_process_v2"],
expectRuntimeFlags: {
enable_nodejs_process_v2: true,
},
},
// TODO: change the date pass the default enabled date (>= 2025-09-15)
{
name: "process v1 by flag",
compatibilityDate: "2024-09-23",
compatibilityFlags: ["disable_nodejs_process_v2"],
expectRuntimeFlags: {
enable_nodejs_process_v2: false,
},
},
],
].flat() as TestConfig[];

describe.each(testConfigs)(
Expand Down
37 changes: 37 additions & 0 deletions packages/wrangler/e2e/unenv-preset/worker/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -505,4 +505,41 @@ export const WorkerdTests: Record<string, () => void> = {
assert.strictEqual(typeof http2.connect, "function");
assert.strictEqual(http2.constants.HTTP2_HEADER_STATUS, ":status");
},

async testProcess() {
const mProcess = await import("node:process");
const gProcess = globalThis.process;

const useV2 = getRuntimeFlagValue("enable_nodejs_process_v2");

for (const p of [mProcess, gProcess]) {
if (useV2) {
// workerd implementation only
assert.equal(p.arch, "x64");
assert.equal(p.title, "workerd");
} else {
// unenv implementation only
assert.equal(p.arch, "");
assert.equal(p.title, "");
}

assert.doesNotThrow(() => p.chdir("/tmp"));
assert.equal(typeof p.cwd(), "string");

assert.equal(typeof p.addListener, "function");
assert.equal(typeof p.eventNames, "function");
assert.equal(typeof p.getMaxListeners, "function");
assert.equal(typeof p.listenerCount, "function");
assert.equal(typeof p.listeners, "function");
assert.equal(typeof p.off, "function");
assert.equal(typeof p.on, "function");
assert.equal(typeof p.once, "function");
assert.equal(typeof p.prependListener, "function");
assert.equal(typeof p.prependOnceListener, "function");
assert.equal(typeof p.rawListeners, "function");
assert.equal(typeof p.removeAllListeners, "function");
assert.equal(typeof p.removeListener, "function");
assert.equal(typeof p.setMaxListeners, "function");
}
},
};
Loading
Loading