Skip to content

Commit d0801b1

Browse files
authored
Drop node:process polyfill when v2 is available (#10805)
1 parent 0a554f9 commit d0801b1

File tree

6 files changed

+105
-140
lines changed

6 files changed

+105
-140
lines changed

.changeset/mean-numbers-unite.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
"@cloudflare/unenv-preset": patch
3+
---
4+
5+
Drop `node:process` polyfill when v2 is available
6+
7+
Note that EventEmitters (`on`, `off`, `addListener`, `removeListener`, ...) used to be available on the import while they should not have been. They are now only available on the global process:
8+
9+
```
10+
import p from "node:process";
11+
12+
// Working before this PR, not working after this PR
13+
p.on("exit", exitHandler);
14+
15+
// Use the global process instead (works before and after the PR)
16+
process.on("exit", exitHandler);
17+
```

packages/unenv-preset/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
},
5050
"peerDependencies": {
5151
"unenv": "2.0.0-rc.21",
52-
"workerd": "^1.20250912.0"
52+
"workerd": "^1.20250924.0"
5353
},
5454
"peerDependenciesMeta": {
5555
"workerd": {

packages/unenv-preset/src/preset.ts

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ const nativeModules = [
4747
];
4848

4949
// Modules implemented via a mix of workerd APIs and polyfills.
50-
const hybridModules = ["console", "process"];
50+
const hybridModules = ["console"];
5151

5252
/**
5353
* Creates the Cloudflare preset for the given compatibility date and compatibility flags
@@ -72,6 +72,7 @@ export function getCloudflarePreset({
7272
const http2Overrides = getHttp2Overrides(compat);
7373
const osOverrides = getOsOverrides(compat);
7474
const fsOverrides = getFsOverrides(compat);
75+
const processOverrides = getProcessOverrides(compat);
7576

7677
// "dynamic" as they depend on the compatibility date and flags
7778
const dynamicNativeModules = [
@@ -80,6 +81,7 @@ export function getCloudflarePreset({
8081
...http2Overrides.nativeModules,
8182
...osOverrides.nativeModules,
8283
...fsOverrides.nativeModules,
84+
...processOverrides.nativeModules,
8385
];
8486

8587
// "dynamic" as they depend on the compatibility date and flags
@@ -89,6 +91,7 @@ export function getCloudflarePreset({
8991
...http2Overrides.hybridModules,
9092
...osOverrides.hybridModules,
9193
...fsOverrides.hybridModules,
94+
...processOverrides.hybridModules,
9295
];
9396

9497
return {
@@ -122,7 +125,7 @@ export function getCloudflarePreset({
122125
clearImmediate: false,
123126
setImmediate: false,
124127
console: "@cloudflare/unenv-preset/node/console",
125-
process: "@cloudflare/unenv-preset/node/process",
128+
...processOverrides.inject,
126129
},
127130
polyfill: ["@cloudflare/unenv-preset/polyfill/performance"],
128131
external: dynamicNativeModules.flatMap((p) => [p, `node:${p}`]),
@@ -305,3 +308,48 @@ function getFsOverrides({
305308
hybridModules: [],
306309
};
307310
}
311+
312+
/**
313+
* Returns the overrides for `node:process` and `node:fs/promises`
314+
*
315+
* The native process v2 implementation:
316+
* - is enabled starting from 2025-09-15
317+
* - can be enabled with the "enable_nodejs_process_v2" flag
318+
* - can be disabled with the "disable_nodejs_process_v2" flag
319+
*/
320+
function getProcessOverrides({
321+
compatibilityDate,
322+
compatibilityFlags,
323+
}: {
324+
compatibilityDate: string;
325+
compatibilityFlags: string[];
326+
}): {
327+
nativeModules: string[];
328+
hybridModules: string[];
329+
inject: { process: string | false };
330+
} {
331+
const disabledV2ByFlag = compatibilityFlags.includes(
332+
"disable_nodejs_process_v2"
333+
);
334+
335+
const enabledV2ByFlag = compatibilityFlags.includes(
336+
"enable_nodejs_process_v2"
337+
);
338+
const enabledV2ByDate = compatibilityDate >= "2025-09-15";
339+
340+
const isV2 = (enabledV2ByFlag || enabledV2ByDate) && !disabledV2ByFlag;
341+
342+
return isV2
343+
? {
344+
nativeModules: ["process"],
345+
hybridModules: [],
346+
// We can use the native global, return `false` to drop the unenv default
347+
inject: { process: false },
348+
}
349+
: {
350+
nativeModules: [],
351+
hybridModules: ["process"],
352+
// Use the module default export as the global `process`
353+
inject: { process: "@cloudflare/unenv-preset/node/process" },
354+
};
355+
}

packages/unenv-preset/src/runtime/node/process.ts

Lines changed: 18 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// The polyfill is only used with the process v1 native implementation
2+
// process v2 implements all the APIs from workerd v1.20250924.0
3+
14
import { hrtime as UnenvHrTime } from "unenv/node/internal/process/hrtime";
25
import { Process as UnenvProcess } from "unenv/node/internal/process/process";
36

@@ -16,92 +19,47 @@ export const getBuiltinModule: NodeJS.Process["getBuiltinModule"] =
1619

1720
const workerdProcess = getBuiltinModule("node:process");
1821

19-
// Workerd has 2 different implementation for `node:process`
20-
//
21-
// See:
22-
// - [workerd `process` v1](https://github.com/cloudflare/workerd/blob/main/src/node/internal/legacy_process.ts)
23-
// - [workerd `process` v2](https://github.com/cloudflare/workerd/blob/main/src/node/internal/public_process.ts)
24-
// - [`enable_nodejs_process_v2` flag](https://github.com/cloudflare/workerd/blob/main/src/workerd/io/compatibility-date.capnp)
25-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
26-
const isWorkerdProcessV2 = (globalThis as any).Cloudflare.compatibilityFlags
27-
.enable_nodejs_process_v2;
28-
2922
const unenvProcess = new UnenvProcess({
3023
env: globalProcess.env,
31-
// `hrtime` is only available from workerd process v2
32-
hrtime: isWorkerdProcessV2 ? workerdProcess.hrtime : UnenvHrTime,
24+
hrtime: UnenvHrTime,
3325
// `nextTick` is available from workerd process v1
3426
nextTick: workerdProcess.nextTick,
3527
});
3628

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

41-
// APIs that can be implemented by either `unenv` or `workerd`.
42-
// They are always retrieved from `unenv` which might use their `workerd` implementation.
43-
export const {
44-
// Always implemented by workerd
45-
env,
46-
// Only implemented in workerd v2
47-
hrtime,
48-
// Always implemented by workerd
49-
nextTick,
50-
} = unenvProcess;
51-
52-
// APIs that are not implemented by `workerd` (whether v1 or v2)
53-
// They are retrieved from `unenv`.
5433
export const {
5534
_channel,
35+
_debugEnd,
36+
_debugProcess,
5637
_disconnect,
5738
_events,
5839
_eventsCount,
59-
_handleQueue,
60-
_maxListeners,
61-
_pendingMessage,
62-
_send,
63-
assert,
64-
disconnect,
65-
mainModule,
66-
} = unenvProcess;
67-
68-
// API that are only implemented starting from v2 of workerd process
69-
// They are retrieved from unenv when process v1 is used
70-
export const {
71-
// @ts-expect-error `_debugEnd` is missing typings
72-
_debugEnd,
73-
// @ts-expect-error `_debugProcess` is missing typings
74-
_debugProcess,
75-
// @ts-expect-error `_exiting` is missing typings
7640
_exiting,
77-
// @ts-expect-error `_fatalException` is missing typings
7841
_fatalException,
79-
// @ts-expect-error `_getActiveHandles` is missing typings
8042
_getActiveHandles,
81-
// @ts-expect-error `_getActiveRequests` is missing typings
8243
_getActiveRequests,
83-
// @ts-expect-error `_kill` is missing typings
44+
_handleQueue,
8445
_kill,
85-
// @ts-expect-error `_linkedBinding` is missing typings
8646
_linkedBinding,
87-
// @ts-expect-error `_preload_modules` is missing typings
47+
_maxListeners,
48+
_pendingMessage,
8849
_preload_modules,
89-
// @ts-expect-error `_rawDebug` is missing typings
9050
_rawDebug,
91-
// @ts-expect-error `_startProfilerIdleNotifier` is missing typings
51+
_send,
9252
_startProfilerIdleNotifier,
93-
// @ts-expect-error `_stopProfilerIdleNotifier` is missing typings
9453
_stopProfilerIdleNotifier,
95-
// @ts-expect-error `_tickCallback` is missing typings
9654
_tickCallback,
9755
abort,
9856
addListener,
9957
allowedNodeEnvironmentFlags,
10058
arch,
10159
argv,
10260
argv0,
61+
assert,
10362
availableMemory,
104-
// @ts-expect-error `binding` is missing typings
10563
binding,
10664
channel,
10765
chdir,
@@ -111,11 +69,12 @@ export const {
11169
cpuUsage,
11270
cwd,
11371
debugPort,
72+
disconnect,
11473
dlopen,
115-
// @ts-expect-error `domain` is missing typings
11674
domain,
11775
emit,
11876
emitWarning,
77+
env,
11978
eventNames,
12079
execArgv,
12180
execPath,
@@ -129,27 +88,26 @@ export const {
12988
getMaxListeners,
13089
getuid,
13190
hasUncaughtExceptionCaptureCallback,
132-
// @ts-expect-error `initgroups` is missing typings
91+
hrtime,
13392
initgroups,
13493
kill,
13594
listenerCount,
13695
listeners,
13796
loadEnvFile,
97+
mainModule,
13898
memoryUsage,
139-
// @ts-expect-error `moduleLoadList` is missing typings
14099
moduleLoadList,
100+
nextTick,
141101
off,
142102
on,
143103
once,
144-
// @ts-expect-error `openStdin` is missing typings
145104
openStdin,
146105
permission,
147106
pid,
148107
ppid,
149108
prependListener,
150109
prependOnceListener,
151110
rawListeners,
152-
// @ts-expect-error `reallyExit` is missing typings
153111
reallyExit,
154112
ref,
155113
release,
@@ -178,7 +136,7 @@ export const {
178136
uptime,
179137
version,
180138
versions,
181-
} = isWorkerdProcessV2 ? workerdProcess : unenvProcess;
139+
} = unenvProcess;
182140

183141
const _process = {
184142
abort,

packages/wrangler/e2e/unenv-preset/worker/index.ts

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,7 @@ export const WorkerdTests: Record<string, () => void> = {
513513
const useV2 = getRuntimeFlagValue("enable_nodejs_process_v2");
514514

515515
for (const p of [mProcess, gProcess]) {
516+
assert.equal(typeof (p as any).binding, "function");
516517
if (useV2) {
517518
// workerd implementation only
518519
assert.equal(p.arch, "x64");
@@ -525,23 +526,22 @@ export const WorkerdTests: Record<string, () => void> = {
525526

526527
assert.doesNotThrow(() => p.chdir("/tmp"));
527528
assert.equal(typeof p.cwd(), "string");
528-
529-
assert.equal(typeof p.addListener, "function");
530-
assert.equal(typeof p.eventNames, "function");
531-
assert.equal(typeof p.getMaxListeners, "function");
532-
assert.equal(typeof p.listenerCount, "function");
533-
assert.equal(typeof p.listeners, "function");
534-
assert.equal(typeof p.off, "function");
535-
assert.equal(typeof p.on, "function");
536-
assert.equal(typeof p.once, "function");
537-
assert.equal(typeof p.prependListener, "function");
538-
assert.equal(typeof p.prependOnceListener, "function");
539-
assert.equal(typeof p.rawListeners, "function");
540-
assert.equal(typeof p.removeAllListeners, "function");
541-
assert.equal(typeof p.removeListener, "function");
542-
assert.equal(typeof p.setMaxListeners, "function");
543-
assert.equal(typeof (p as any).binding, "function");
544-
assert.equal(typeof p.permission, "object");
545529
}
530+
531+
// Event APIs are only available on global process
532+
assert.equal(typeof gProcess.addListener, "function");
533+
assert.equal(typeof gProcess.eventNames, "function");
534+
assert.equal(typeof gProcess.getMaxListeners, "function");
535+
assert.equal(typeof gProcess.listenerCount, "function");
536+
assert.equal(typeof gProcess.listeners, "function");
537+
assert.equal(typeof gProcess.off, "function");
538+
assert.equal(typeof gProcess.on, "function");
539+
assert.equal(typeof gProcess.once, "function");
540+
assert.equal(typeof gProcess.prependListener, "function");
541+
assert.equal(typeof gProcess.prependOnceListener, "function");
542+
assert.equal(typeof gProcess.rawListeners, "function");
543+
assert.equal(typeof gProcess.removeAllListeners, "function");
544+
assert.equal(typeof gProcess.removeListener, "function");
545+
assert.equal(typeof gProcess.setMaxListeners, "function");
546546
},
547547
};

0 commit comments

Comments
 (0)