diff --git a/packages/core/src/plugins/core.ts b/packages/core/src/plugins/core.ts index 87652cf1b..e0c97ed2d 100644 --- a/packages/core/src/plugins/core.ts +++ b/packages/core/src/plugins/core.ts @@ -1,5 +1,6 @@ import { Blob } from "buffer"; import fs from "fs/promises"; +import asyncHooks from "node:async_hooks"; import path from "path"; import { ByteLengthQueuingStrategy, @@ -25,6 +26,7 @@ import webStreams from "stream/web"; import { URL, URLSearchParams } from "url"; import { TextDecoder, TextEncoder } from "util"; import { + AdditionalModules, CompatibilityFlag, Context, ModuleRule, @@ -70,6 +72,7 @@ import { } from "../standards"; import { assertsInRequest } from "../standards/helpers"; import type { BindingsOptions } from "./bindings"; +import { additionalModules } from "./node"; const DEFAULT_MODULE_RULES: ModuleRule[] = [ { type: "ESModule", include: ["**/*.mjs"] }, @@ -411,6 +414,7 @@ export class CorePlugin extends Plugin implements CoreOptions { readonly upstreamURL?: URL; readonly #globals: Context; + readonly #additionalModules?: AdditionalModules; constructor(ctx: PluginContext, options?: CoreOptions) { super(ctx); @@ -421,6 +425,11 @@ export class CorePlugin extends Plugin implements CoreOptions { ); } + const nodejsCompat = ctx.compat.isEnabled("nodejs_compat"); + if (nodejsCompat) { + this.#additionalModules = additionalModules; + } + const extraGlobals: Context = {}; // Make sure the kFormDataFiles flag is set correctly when constructing @@ -652,11 +661,13 @@ export class CorePlugin extends Plugin implements CoreOptions { async setup(): Promise { const globals = this.#globals; + const additionalModules = this.#additionalModules; // First, try to load script from string, no need to watch any files if (this.script !== undefined) { return { globals, + additionalModules, script: { filePath: STRING_SCRIPT_PATH, code: this.script }, }; } @@ -689,11 +700,16 @@ export class CorePlugin extends Plugin implements CoreOptions { scriptPath = path.resolve(this.ctx.rootPath, scriptPath); const code = await fs.readFile(scriptPath, "utf8"); watch.push(scriptPath); - return { globals, script: { filePath: scriptPath, code }, watch }; + return { + globals, + additionalModules, + script: { filePath: scriptPath, code }, + watch, + }; } // If we couldn't load a script yet, keep watching package.json anyways, it // might get edited with a path - return { globals, watch }; + return { globals, additionalModules, watch }; } } diff --git a/packages/core/src/plugins/node/async_hooks.ts b/packages/core/src/plugins/node/async_hooks.ts new file mode 100644 index 000000000..a4040f9e4 --- /dev/null +++ b/packages/core/src/plugins/node/async_hooks.ts @@ -0,0 +1,6 @@ +import async_hooks from "node:async_hooks"; + +export class AsyncHooksModule { + AsyncLocalStorage = async_hooks.AsyncLocalStorage; + AsyncResource = async_hooks.AsyncResource; +} diff --git a/packages/core/src/plugins/node/index.ts b/packages/core/src/plugins/node/index.ts new file mode 100644 index 000000000..2765b0670 --- /dev/null +++ b/packages/core/src/plugins/node/index.ts @@ -0,0 +1,5 @@ +import { AsyncHooksModule } from "./async_hooks"; + +export const additionalModules = { + "node:async_hooks": { default: new AsyncHooksModule() }, +}; diff --git a/packages/core/test/plugins/core.spec.ts b/packages/core/test/plugins/core.spec.ts index 1c77ee4e8..410e12716 100644 --- a/packages/core/test/plugins/core.spec.ts +++ b/packages/core/test/plugins/core.spec.ts @@ -654,6 +654,20 @@ test("CorePlugin: setup: uses actual time if option enabled", async (t) => { }); }); +test("CorePlugin: nodejs_compat compatibiltiy flag includes Node.js modules", async (t) => { + const compat = new Compatibility(undefined, ["nodejs_compat"]); + + const plugin = new CorePlugin( + { ...ctx, compat }, + { compatibilityFlags: ["nodejs_compat"] } + ); + const additionalModules = (await plugin.setup()).additionalModules; + t.deepEqual( + Object.keys(additionalModules?.["node:async_hooks"].default ?? {}), + ["AsyncLocalStorage", "AsyncResource"] + ); +}); + // Test stream constructors test("CorePlugin: setup: ReadableStream/WriteableStream constructors only enabled if compatibility flag enabled", async (t) => { // Check without "streams_enable_constructors" compatibility flag (should throw) diff --git a/packages/shared/src/compat.ts b/packages/shared/src/compat.ts index 8ad5de862..36f1e42dd 100644 --- a/packages/shared/src/compat.ts +++ b/packages/shared/src/compat.ts @@ -11,6 +11,7 @@ export interface CompatibilityFeature { // will get a type error if they try to use an unsupported flag via the API, // and they won't be logged in the "Enabled Compatibility Flags" section. export type CompatibilityEnableFlag = + | "nodejs_compat" | "streams_enable_constructors" | "transformstream_enable_standard_constructor" | "global_navigator" @@ -30,6 +31,9 @@ export type CompatibilityFlag = | CompatibilityDisableFlag; const FEATURES: CompatibilityFeature[] = [ + { + enableFlag: "nodejs_compat", + }, { defaultAsOf: "2022-11-30", enableFlag: "streams_enable_constructors",