Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit e285955

Browse files
authored
Add node:{assert,buffer,events,util} behind nodejs_compat flag (#498)
* Add `node:{assert,buffer,events,util}` behind `nodejs_compat` flag * fixup! Add `node:{assert,buffer,events,util}` behind `nodejs_compat` flag * fixup! Add `node:{assert,buffer,events,util}` behind `nodejs_compat` flag * fixup! Add `node:{assert,buffer,events,util}` behind `nodejs_compat` flag
1 parent 7174947 commit e285955

File tree

10 files changed

+200
-24
lines changed

10 files changed

+200
-24
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,6 @@
5757
"node": ">=16.13"
5858
},
5959
"volta": {
60-
"node": "19.3.0"
60+
"node": "16.13.0"
6161
}
6262
}

packages/core/src/plugins/core.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ import {
7171
} from "../standards";
7272
import { assertsInRequest } from "../standards/helpers";
7373
import type { BindingsOptions } from "./bindings";
74-
import { additionalModules } from "./node";
74+
import { additionalNodeModules } from "./node";
7575

7676
const DEFAULT_MODULE_RULES: ModuleRule[] = [
7777
{ type: "ESModule", include: ["**/*.mjs"] },
@@ -426,7 +426,8 @@ export class CorePlugin extends Plugin<CoreOptions> implements CoreOptions {
426426

427427
const nodejsCompat = ctx.compat.isEnabled("nodejs_compat");
428428
if (nodejsCompat) {
429-
this.#additionalModules = additionalModules;
429+
const experimental = ctx.compat.isEnabled("experimental");
430+
this.#additionalModules = additionalNodeModules(experimental);
430431
}
431432

432433
const extraGlobals: Context = {};
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import assert from "node:assert";
2+
3+
export default assert;
4+
5+
export const AssertionError = assert.AssertionError;
6+
export const deepEqual = assert.deepEqual;
7+
export const deepStrictEqual = assert.deepStrictEqual;
8+
export const doesNotMatch = assert.doesNotMatch;
9+
export const doesNotReject = assert.doesNotReject;
10+
export const doesNotThrow = assert.doesNotThrow;
11+
export const equal = assert.equal;
12+
export const fail = assert.fail;
13+
export const ifError = assert.ifError;
14+
export const match = assert.match;
15+
export const notDeepEqual = assert.notDeepEqual;
16+
export const notDeepStrictEqual = assert.notDeepStrictEqual;
17+
export const notEqual = assert.notEqual;
18+
export const notStrictEqual = assert.notStrictEqual;
19+
export const ok = assert.ok;
20+
export const rejects = assert.rejects;
21+
export const strict = assert.strict;
22+
export const strictEqual = assert.strictEqual;
23+
export const throws = assert.throws;
Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import async_hooks from "node:async_hooks";
1+
import { AsyncLocalStorage, AsyncResource } from "node:async_hooks";
22

3-
export class AsyncHooksModule {
4-
AsyncLocalStorage = async_hooks.AsyncLocalStorage;
5-
AsyncResource = async_hooks.AsyncResource;
6-
}
3+
export { AsyncLocalStorage, AsyncResource };
4+
5+
export default {
6+
AsyncLocalStorage,
7+
AsyncResource,
8+
};
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import {
2+
Buffer,
3+
SlowBuffer,
4+
constants,
5+
kMaxLength,
6+
kStringMaxLength,
7+
} from "node:buffer";
8+
9+
export { Buffer, constants, kMaxLength, kStringMaxLength, SlowBuffer };
10+
11+
export default {
12+
Buffer,
13+
constants,
14+
kMaxLength,
15+
kStringMaxLength,
16+
SlowBuffer,
17+
};
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import events from "node:events";
2+
3+
export default events;
4+
5+
export const EventEmitter = events.EventEmitter;
6+
// @ts-expect-error `EventEmitterAsyncResource` is defined on `EventEmitter`
7+
export const EventEmitterAsyncResource = events.EventEmitterAsyncResource;
8+
export const captureRejectionSymbol = events.captureRejectionSymbol;
9+
export const defaultMaxListeners = events.defaultMaxListeners;
10+
export const errorMonitor = events.errorMonitor;
11+
export const getEventListeners = events.getEventListeners;
12+
export const listenerCount = events.listenerCount;
13+
export const on = events.on;
14+
export const once = events.once;
15+
export const setMaxListeners = events.setMaxListeners;
Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
1-
import { AsyncHooksModule } from "./async_hooks";
1+
import type { AdditionalModules } from "@miniflare/shared";
22

3-
export const additionalModules = {
4-
"node:async_hooks": { default: new AsyncHooksModule() },
5-
};
3+
import * as assert from "./assert";
4+
import * as async_hooks from "./async_hooks";
5+
import * as buffer from "./buffer";
6+
import * as events from "./events";
7+
import * as util from "./util";
8+
9+
export function additionalNodeModules(experimental: boolean) {
10+
const modules: AdditionalModules = {
11+
"node:async_hooks": async_hooks,
12+
"node:events": events,
13+
};
14+
15+
if (experimental) {
16+
// TODO(soon): remove experimental designations when removed in `workerd`
17+
modules["node:assert"] = assert;
18+
modules["node:buffer"] = buffer;
19+
modules["node:util"] = util;
20+
}
21+
22+
return modules;
23+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import {
2+
// @ts-expect-error `_extend` is deprecated, but exported by `node:util`
3+
// https://nodejs.org/api/util.html#util_extendtarget-source
4+
_extend,
5+
callbackify,
6+
format,
7+
inherits,
8+
promisify,
9+
types,
10+
} from "node:util";
11+
12+
export { types, callbackify, promisify, format, inherits, _extend };
13+
14+
export default {
15+
types,
16+
callbackify,
17+
promisify,
18+
format,
19+
inherits,
20+
_extend,
21+
};

packages/core/test/plugins/core.spec.ts

Lines changed: 86 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -654,18 +654,93 @@ test("CorePlugin: setup: uses actual time if option enabled", async (t) => {
654654
});
655655
});
656656

657-
test("CorePlugin: nodejs_compat compatibiltiy flag includes Node.js modules", async (t) => {
658-
const compat = new Compatibility(undefined, ["nodejs_compat"]);
659-
660-
const plugin = new CorePlugin(
661-
{ ...ctx, compat },
662-
{ compatibilityFlags: ["nodejs_compat"] }
663-
);
664-
const additionalModules = (await plugin.setup()).additionalModules;
665-
t.deepEqual(
666-
Object.keys(additionalModules?.["node:async_hooks"].default ?? {}),
667-
["AsyncLocalStorage", "AsyncResource"]
657+
test("CorePlugin: nodejs_compat compatibility flag includes Node.js modules", async (t) => {
658+
let compat = new Compatibility(undefined, ["nodejs_compat"]);
659+
let plugin = new CorePlugin({ ...ctx, compat });
660+
let modules = (await plugin.setup()).additionalModules!;
661+
const names = Object.keys(modules).sort();
662+
t.deepEqual(names, ["node:async_hooks", "node:events"]);
663+
664+
compat = new Compatibility(undefined, ["nodejs_compat", "experimental"]);
665+
plugin = new CorePlugin({ ...ctx, compat });
666+
modules = (await plugin.setup()).additionalModules!;
667+
const experimentalNames = Object.keys(modules).filter(
668+
(name) => !names.includes(name)
668669
);
670+
t.deepEqual(experimentalNames, ["node:assert", "node:buffer", "node:util"]);
671+
672+
// We're using Node's implementations of these modules' exports, so don't
673+
// bother testing their functionality. Instead, just check we've got the
674+
// same export types as `workerd`.
675+
676+
function exportTypes(name: string): Record<string, string> {
677+
return Object.fromEntries(
678+
Object.entries(modules[name]).map(([key, value]) => [key, typeof value])
679+
);
680+
}
681+
682+
t.deepEqual(exportTypes("node:assert"), {
683+
AssertionError: "function",
684+
deepEqual: "function",
685+
deepStrictEqual: "function",
686+
default: "function",
687+
doesNotMatch: "function",
688+
doesNotReject: "function",
689+
doesNotThrow: "function",
690+
equal: "function",
691+
fail: "function",
692+
ifError: "function",
693+
match: "function",
694+
notDeepEqual: "function",
695+
notDeepStrictEqual: "function",
696+
notEqual: "function",
697+
notStrictEqual: "function",
698+
ok: "function",
699+
rejects: "function",
700+
strict: "function",
701+
strictEqual: "function",
702+
throws: "function",
703+
});
704+
t.deepEqual(exportTypes("node:async_hooks"), {
705+
AsyncLocalStorage: "function",
706+
AsyncResource: "function",
707+
default: "object",
708+
});
709+
t.deepEqual(exportTypes("node:buffer"), {
710+
Buffer: "function",
711+
SlowBuffer: "function",
712+
constants: "object",
713+
default: "object",
714+
kMaxLength: "number",
715+
kStringMaxLength: "number",
716+
});
717+
t.deepEqual(exportTypes("node:events"), {
718+
EventEmitter: "function",
719+
// Miniflare's minimum support Node version is `16.13.0`, but
720+
// `EventEmitterAsyncResource` was only added in `16.14.0`:
721+
// https://nodejs.org/api/events.html#class-eventseventemitterasyncresource-extends-eventemitter
722+
EventEmitterAsyncResource: process.versions.node.startsWith("16.13.")
723+
? "undefined"
724+
: "function",
725+
captureRejectionSymbol: "symbol",
726+
default: "function",
727+
defaultMaxListeners: "number",
728+
errorMonitor: "symbol",
729+
getEventListeners: "function",
730+
listenerCount: "function",
731+
on: "function",
732+
once: "function",
733+
setMaxListeners: "function",
734+
});
735+
t.deepEqual(exportTypes("node:util"), {
736+
_extend: "function",
737+
callbackify: "function",
738+
default: "object",
739+
format: "function",
740+
inherits: "function",
741+
promisify: "function",
742+
types: "object",
743+
});
669744
});
670745

671746
// Test stream constructors

packages/shared/src/compat.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ export type CompatibilityEnableFlag =
2020
| "durable_object_fetch_requires_full_url"
2121
| "fetch_refuses_unknown_protocols"
2222
| "formdata_parser_supports_files"
23-
| "html_rewriter_treats_esi_include_as_void_tag";
23+
| "html_rewriter_treats_esi_include_as_void_tag"
24+
| "experimental";
2425
export type CompatibilityDisableFlag =
2526
| "streams_disable_constructors"
2627
| "transformstream_disable_standard_constructor"
@@ -79,6 +80,9 @@ const FEATURES: CompatibilityFeature[] = [
7980
{
8081
enableFlag: "html_rewriter_treats_esi_include_as_void_tag",
8182
},
83+
{
84+
enableFlag: "experimental",
85+
},
8286
];
8387

8488
export class Compatibility {

0 commit comments

Comments
 (0)