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
18 changes: 18 additions & 0 deletions .changeset/nodejs-compat-default-on.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
"miniflare": minor
"wrangler": minor
---

Enable Node.js compatibility by default for compatibility dates of 2026-08-04 or later

Workers with a compatibility date of `2026-08-04` or later now have Node.js compatibility enabled automatically. Previously the `nodejs_compat` flag had to be set explicitly.

Set the `no_nodejs_compat` compatibility flag to opt out.

```jsonc
// wrangler.json
{
"compatibility_date": "2026-08-04",
"compatibility_flags": ["no_nodejs_compat"]
}
```
17 changes: 17 additions & 0 deletions fixtures/nodejs-compat-default/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "@fixture/nodejs-compat-default",
"private": true,
"scripts": {
"build": "wrangler deploy --dry-run --outdir=./dist",
"dev": "wrangler dev",
"test:ci": "vitest run",
"test:watch": "vitest"
},
"devDependencies": {
"@cloudflare/workers-tsconfig": "workspace:*",
"@cloudflare/workers-types": "catalog:default",
"undici": "catalog:default",
"vitest": "catalog:default",
"wrangler": "workspace:*"
}
}
26 changes: 26 additions & 0 deletions fixtures/nodejs-compat-default/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import assert from "node:assert/strict";
import { Buffer } from "node:buffer";
// Unprefixed import: only resolvable under Node.js compat v2, which is implied
// by the compatibility date without needing the `nodejs_compat` flag.
import { Stream } from "stream";

export default {
async fetch(req: Request, env: unknown, ctx: ExecutionContext) {
if (new URL(req.url).pathname !== "/") {
return new Response("Not Found", { status: 404 });
}

// `node:` prefixed builtin
const buffer = Buffer.from("nodejs", "utf8");
assert.strictEqual(buffer.toString("base64"), "bm9kZWpz");

// Unprefixed builtin (v2 only)
const stream = new Stream();
assert.ok(stream instanceof Stream);

// `Buffer` global (v2 only)
assert.strictEqual(typeof Buffer, "function");

return new Response("OK");
},
};
22 changes: 22 additions & 0 deletions fixtures/nodejs-compat-default/tests/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { resolve } from "node:path";
import { fetch } from "undici";
import { describe, it } from "vitest";
import { runWranglerDev } from "../../shared/src/run-wrangler-long-lived";

describe("nodejs compat default", () => {
it("imports node.js builtins without the nodejs_compat flag when the compatibility date implies it", async ({
expect,
}) => {
const { ip, port, stop } = await runWranglerDev(
resolve(__dirname, "../src"),
["--port=0", "--inspector-port=0"]
);
try {
const response = await fetch(`http://${ip}:${port}/`);
const body = await response.text();
expect(body).toMatchInlineSnapshot(`"OK"`);
} finally {
await stop();
}
});
});
7 changes: 7 additions & 0 deletions fixtures/nodejs-compat-default/tests/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "@cloudflare/workers-tsconfig/tsconfig.json",
"compilerOptions": {
"types": ["node"]
},
"include": ["**/*.ts"]
}
14 changes: 14 additions & 0 deletions fixtures/nodejs-compat-default/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"module": "esnext",
"target": "esnext",
"lib": ["esnext"],
"strict": true,
"isolatedModules": true,
"noEmit": true,
"types": ["@cloudflare/workers-types/experimental", "node"],
"allowJs": true,
"allowSyntheticDefaultImports": true
},
"include": ["src"]
}
9 changes: 9 additions & 0 deletions fixtures/nodejs-compat-default/turbo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"$schema": "http://turbo.build/schema.json",
"extends": ["//"],
"tasks": {
"build": {
"outputs": ["dist/**"]
}
}
}
9 changes: 9 additions & 0 deletions fixtures/nodejs-compat-default/vitest.config.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { defineProject, mergeConfig } from "vitest/config";
import configShared from "../../vitest.shared";

export default mergeConfig(
configShared,
defineProject({
test: {},
})
);
7 changes: 7 additions & 0 deletions fixtures/nodejs-compat-default/wrangler.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "nodejs-compat-default",
"main": "src/index.ts",
// From 2026-08-04 onwards, `nodejs_compat` is enabled by default, so we
// intentionally do NOT set the `nodejs_compat` compatibility flag here.
"compatibility_date": "2026-08-04",
}
3 changes: 2 additions & 1 deletion packages/deploy-helpers/src/deploy/helpers/node-compat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import type { NodeJSCompatMode } from "miniflare";
* Computes and validates the Node.js compatibility mode we are running.
*
* NOTES:
* - The v2 mode is configured via `nodejs_compat_v2` compat flag or via `nodejs_compat` plus a compatibility date of Sept 23rd. 2024 or later.
* - Node.js compatibility is enabled by default for a compatibility date of Aug 4th. 2026 or later (unless the `no_nodejs_compat` flag is set), or explicitly via the `nodejs_compat`/`nodejs_compat_v2` flags.
* - The v2 mode is configured via `nodejs_compat_v2` compat flag or via `nodejs_compat` (explicit or implied by the compatibility date) plus a compatibility date of Sept 23rd. 2024 or later.
* - See `EnvironmentInheritable` for `noBundle`.
*
* @param compatibilityDateStr The compatibility date
Expand Down
21 changes: 18 additions & 3 deletions packages/miniflare/src/plugins/core/node-compat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ export type NodeJSCompatMode = "als" | "v1" | "v2" | null;
* Computes the Node.js compatibility mode we are running.
*
* NOTES:
* - The v2 mode is configured via `nodejs_compat_v2` compat flag or via `nodejs_compat` plus a compatibility date of Sept 23rd. 2024 or later.
* - Node.js compatibility is enabled by default for a compatibility date of Aug 4th. 2026 or later,
* unless the `no_nodejs_compat` compat flag is set. It can also be enabled explicitly via the
* `nodejs_compat` or `nodejs_compat_v2` compat flags.
* - The v2 mode is configured via `nodejs_compat_v2` compat flag or via `nodejs_compat` (whether set
* explicitly or implied by the compatibility date) plus a compatibility date of Sept 23rd. 2024 or later.
* - See `EnvironmentInheritable` for `nodeCompat` and `noBundle`.
*
* @param compatibilityDateStr The compatibility date
Expand All @@ -26,21 +30,30 @@ export function getNodeCompat(
const {
hasNodejsAlsFlag,
hasNodejsCompatFlag,
hasNoNodejsCompatFlag,
hasNodejsCompatV2Flag,
hasNoNodejsCompatV2Flag,
hasExperimentalNodejsCompatV2Flag,
} = parseNodeCompatibilityFlags(compatibilityFlags);

const nodeCompatSwitchOverDate = "2024-09-23";
const nodeCompatDefaultOnDate = "2026-08-04";

// From `nodeCompatDefaultOnDate` onwards, `nodejs_compat` is enabled by default (matching
// workerd's `$compatEnableDate` for the flag) unless explicitly disabled via `no_nodejs_compat`.
const nodejsCompatEnabled =
hasNodejsCompatFlag ||
(compatibilityDate >= nodeCompatDefaultOnDate && !hasNoNodejsCompatFlag);

let mode: NodeJSCompatMode = null;
if (
hasNodejsCompatV2Flag ||
(hasNodejsCompatFlag &&
(nodejsCompatEnabled &&
compatibilityDate >= nodeCompatSwitchOverDate &&
!hasNoNodejsCompatV2Flag)
) {
mode = "v2";
} else if (hasNodejsCompatFlag) {
} else if (nodejsCompatEnabled) {
mode = "v1";
} else if (hasNodejsAlsFlag) {
mode = "als";
Expand All @@ -50,6 +63,7 @@ export function getNodeCompat(
mode,
hasNodejsAlsFlag,
hasNodejsCompatFlag,
hasNoNodejsCompatFlag,
hasNodejsCompatV2Flag,
hasNoNodejsCompatV2Flag,
hasExperimentalNodejsCompatV2Flag,
Expand All @@ -60,6 +74,7 @@ function parseNodeCompatibilityFlags(compatibilityFlags: string[]) {
return {
hasNodejsAlsFlag: compatibilityFlags.includes("nodejs_als"),
hasNodejsCompatFlag: compatibilityFlags.includes("nodejs_compat"),
hasNoNodejsCompatFlag: compatibilityFlags.includes("no_nodejs_compat"),
hasNodejsCompatV2Flag: compatibilityFlags.includes("nodejs_compat_v2"),
hasNoNodejsCompatV2Flag: compatibilityFlags.includes("no_nodejs_compat_v2"),
hasExperimentalNodejsCompatV2Flag: compatibilityFlags.includes(
Expand Down
61 changes: 61 additions & 0 deletions packages/miniflare/test/plugins/core/node-compat.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { getNodeCompat } from "miniflare";
import { test } from "vitest";

test("no flags and old date => no Node.js compat", ({ expect }) => {
expect(getNodeCompat("2000-01-01", []).mode).toBe(null);
});

test("nodejs_compat flag with old date => v1", ({ expect }) => {
expect(getNodeCompat("2000-01-01", ["nodejs_compat"]).mode).toBe("v1");
});

test("nodejs_compat flag on/after the v2 switch-over date => v2", ({
expect,
}) => {
expect(getNodeCompat("2024-09-23", ["nodejs_compat"]).mode).toBe("v2");
});

test("nodejs_compat_v2 flag => v2 regardless of date", ({ expect }) => {
expect(getNodeCompat("2000-01-01", ["nodejs_compat_v2"]).mode).toBe("v2");
});

test("nodejs_als flag => als", ({ expect }) => {
expect(getNodeCompat("2000-01-01", ["nodejs_als"]).mode).toBe("als");
});

test("date on/after the default-on date implies Node.js compat (v2)", ({
expect,
}) => {
expect(getNodeCompat("2026-08-04", []).mode).toBe("v2");
});

test("date before the default-on date does not imply Node.js compat", ({
expect,
}) => {
expect(getNodeCompat("2026-08-03", []).mode).toBe(null);
});

test("no_nodejs_compat opts out of the date-implied default", ({ expect }) => {
expect(getNodeCompat("2026-08-04", ["no_nodejs_compat"]).mode).toBe(null);
});

test("no_nodejs_compat_v2 opts out of v2 but keeps date-implied v1", ({
expect,
}) => {
expect(getNodeCompat("2026-08-04", ["no_nodejs_compat_v2"]).mode).toBe("v1");
});

test("no_nodejs_compat takes precedence over no_nodejs_compat_v2", ({
expect,
}) => {
expect(
getNodeCompat("2026-08-04", ["no_nodejs_compat", "no_nodejs_compat_v2"])
.mode
).toBe(null);
});

test("explicit nodejs_compat flag enables compat before the default-on date", ({
expect,
}) => {
expect(getNodeCompat("2025-01-01", ["nodejs_compat"]).mode).toBe("v2");
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { test, vi } from "vitest";
import { getTextResponse, WAIT_FOR_OPTIONS } from "../../../__test-utils__";

test("imports node.js builtins without the nodejs_compat flag when the compatibility date implies it", async ({
expect,
}) => {
await vi.waitFor(
async () => expect(await getTextResponse()).toEqual(`"OK!"`),
WAIT_FOR_OPTIONS
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"basic:dev": "vite dev -c vite.config.worker-basic.ts",
"basic:preview": "vite preview -c vite.config.worker-basic.ts",
"basic:test": "vitest run -c ../vitest.config.e2e.ts worker-basic",
"build:default": "pnpm run basic:build && pnpm run cross-env:build && pnpm run crypto:build && pnpm run postgres:build && pnpm run process:build && pnpm run random:build",
"build:default": "pnpm run basic:build && pnpm run cross-env:build && pnpm run crypto:build && pnpm run default-compat:build && pnpm run postgres:build && pnpm run process:build && pnpm run random:build",
"check:type": "tsc --build",
"cloudflare-node:build": "vite build -c vite.config.cloudflare-node.ts",
"cloudflare-node:dev": "vite dev -c vite.config.cloudflare-node.ts",
Expand All @@ -21,6 +21,10 @@
"crypto:dev": "vite dev -c vite.config.worker-crypto.ts",
"crypto:preview": "vite preview -c vite.config.worker-crypto.ts",
"crypto:test": "vitest run -c ../vitest.config.e2e.ts worker-crypto",
"default-compat:build": "vite build --app -c vite.config.worker-default-compat.ts",
"default-compat:dev": "vite dev -c vite.config.worker-default-compat.ts",
"default-compat:preview": "vite preview -c vite.config.worker-default-compat.ts",
"default-compat:test": "vitest run -c ../vitest.config.e2e.ts worker-default-compat",
"debug:build": "vite build --app -c vite.config.worker-debug.ts",
"debug:dev": "vite dev -c vite.config.worker-debug.ts",
"debug:preview": "vite preview -c vite.config.worker-debug.ts",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"worker-basic",
"worker-cross-env",
"worker-crypto",
"worker-default-compat",
"worker-debug",
"worker-https",
"worker-postgres",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { cloudflare } from "@cloudflare/vite-plugin";
import { defineConfig } from "vite";

export default defineConfig({
build: {
outDir: "dist/worker-default-compat",
},
plugins: [
cloudflare({
configPath: "./worker-default-compat/wrangler.jsonc",
inspectorPort: false,
persistState: false,
}),
],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import assert from "node:assert/strict";
// Unprefixed import: only resolvable under Node.js compat v2, which is implied
// by the compatibility date without needing the `nodejs_compat` flag.
import { join } from "path";

export default {
async fetch() {
return testNodejsCompatDefault();
},
} satisfies ExportedHandler;

function testNodejsCompatDefault() {
assert(join("a", "b") === "a/b", "expected posix path joining");

const buffer = Buffer.of(1);
assert(buffer.toJSON().data[0] === 1, "Buffer global is broken");

return new Response(`"OK!"`);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "worker",
"main": "./index.ts",
// From 2026-08-04 onwards, `nodejs_compat` is enabled by default, so we
// intentionally do NOT set the `nodejs_compat` compatibility flag here.
"compatibility_date": "2026-08-04",
}
20 changes: 20 additions & 0 deletions packages/wrangler/src/__tests__/process-env-populated.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,26 @@ describe("isProcessEnvPopulated", () => {
).toBe(false);
});

test("nodejs_compat default-on date implies process.env population without a flag", ({
expect,
}) => {
expect(isProcessEnvPopulated("2026-08-04")).toBe(true);
});

test("no_nodejs_compat opts out of date-implied process.env population", ({
expect,
}) => {
expect(isProcessEnvPopulated("2026-08-04", ["no_nodejs_compat"])).toBe(
false
);
});

test("date before nodejs_compat default-on date does not populate process.env without a flag", ({
expect,
}) => {
expect(isProcessEnvPopulated("2026-01-01")).toBe(false);
});

test("errors with disable and enable flags specified", ({ expect }) => {
try {
isProcessEnvPopulated("2024-01-01", [
Expand Down
Loading
Loading