Skip to content

Commit d04c69f

Browse files
petebacondarwindario-piotrowiczjamesopstad
authored
vite-plugin: add support for Node.js ALS compat mode (#8878)
* vite-plugin: add support for Node.js ALS compat mode * Update packages/vite-plugin-cloudflare/src/index.ts Co-authored-by: Dario Piotrowicz <[email protected]> * Update packages/vite-plugin-cloudflare/src/index.ts Co-authored-by: James Opstad <[email protected]> * Update packages/vite-plugin-cloudflare/src/index.ts Co-authored-by: James Opstad <[email protected]> * refactor regex into `isNodeAlsModule()` shared helper --------- Co-authored-by: Dario Piotrowicz <[email protected]> Co-authored-by: James Opstad <[email protected]>
1 parent f2802f9 commit d04c69f

File tree

9 files changed

+111
-2
lines changed

9 files changed

+111
-2
lines changed

.changeset/nasty-mice-dance.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@cloudflare/vite-plugin": patch
3+
---
4+
5+
add support for Node.js ALS compat mode
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { expect, test } from "vitest";
2+
import { getTextResponse, serverLogs } from "../../../__test-utils__";
3+
4+
test("supports Node.js ALS mode", async () => {
5+
const result = await getTextResponse();
6+
// It won't log any node.js compat warnings
7+
expect(serverLogs.warns).toEqual([]);
8+
expect(result).toEqual("OK!");
9+
});

packages/vite-plugin-cloudflare/playground/node-compat/tsconfig.worker.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"extends": ["@cloudflare/workers-tsconfig/worker.json"],
33
"include": [
4+
"worker-als",
45
"worker-basic",
56
"worker-cross-env",
67
"worker-crypto",
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { cloudflare } from "@cloudflare/vite-plugin";
2+
import { defineConfig } from "vite";
3+
4+
export default defineConfig({
5+
build: {
6+
outDir: "dist/worker-als",
7+
},
8+
plugins: [
9+
cloudflare({
10+
configPath: "./worker-als/wrangler.jsonc",
11+
inspectorPort: false,
12+
persistState: false,
13+
}),
14+
],
15+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { AsyncLocalStorage } from "node:async_hooks";
2+
3+
export default {
4+
async fetch() {
5+
const storage = new AsyncLocalStorage();
6+
return storage.run({}, () => new Response("OK!"));
7+
},
8+
} satisfies ExportedHandler;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "worker",
3+
"main": "./index.ts",
4+
"compatibility_date": "2024-12-30",
5+
"compatibility_flags": ["nodejs_als"],
6+
}

packages/vite-plugin-cloudflare/src/index.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ import {
3030
} from "./miniflare-options";
3131
import {
3232
injectGlobalCode,
33+
isNodeAls,
34+
isNodeAlsModule,
3335
isNodeCompat,
3436
nodeCompatEntries,
3537
nodeCompatExternals,
@@ -655,6 +657,26 @@ export function cloudflare(pluginConfig: PluginConfig = {}): vite.Plugin[] {
655657
);
656658
},
657659
},
660+
// Plugin that handles Node.js Async Local Storage (ALS) compatibility support for Vite Environments that are hosted in Cloudflare Workers.
661+
{
662+
name: "vite-plugin-cloudflare:nodejs-als",
663+
apply(_config, env) {
664+
// Skip this whole plugin if we are in preview mode
665+
return !env.isPreview;
666+
},
667+
configEnvironment(name, config) {
668+
if (isNodeAls(getWorkerConfig(name))) {
669+
return {
670+
resolve: {
671+
builtins: ["async_hooks", "node:async_hooks"],
672+
},
673+
optimizeDeps: {
674+
exclude: ["async_hooks", "node:async_hooks"],
675+
},
676+
};
677+
}
678+
},
679+
},
658680
// Plugin that provides an __debug path for debugging the Cloudflare Workers.
659681
{
660682
name: "vite-plugin-cloudflare:debug",
@@ -736,6 +758,14 @@ export function cloudflare(pluginConfig: PluginConfig = {}): vite.Plugin[] {
736758
build.onResolve(
737759
{ filter: NODEJS_MODULES_RE },
738760
({ path, importer }) => {
761+
if (
762+
isNodeAls(workerConfig) &&
763+
isNodeAlsModule(path)
764+
) {
765+
// Skip if this is just async_hooks and Node.js ALS support is on.
766+
return;
767+
}
768+
739769
const nodeJsCompatWarnings =
740770
nodeJsCompatWarningsMap.get(workerConfig);
741771
nodeJsCompatWarnings?.registerImport(path, importer);
@@ -770,6 +800,11 @@ export function cloudflare(pluginConfig: PluginConfig = {}): vite.Plugin[] {
770800
const workerConfig = getWorkerConfig(this.environment.name);
771801

772802
if (workerConfig && !isNodeCompat(workerConfig)) {
803+
if (isNodeAls(workerConfig) && isNodeAlsModule(source)) {
804+
// Skip if this is just async_hooks and Node.js ALS support is on.
805+
return;
806+
}
807+
773808
const nodeJsCompatWarnings =
774809
nodeJsCompatWarningsMap.get(workerConfig);
775810

packages/vite-plugin-cloudflare/src/node-js-compat.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,23 @@ export function isNodeCompat(workerConfig: WorkerConfig | undefined) {
5050
return false;
5151
}
5252

53+
/**
54+
* Returns true if Node.js async local storage (ALS) is enabled (and not full Node.js compatibility mode).
55+
*/
56+
export function isNodeAls(workerConfig: WorkerConfig | undefined) {
57+
return (
58+
workerConfig !== undefined &&
59+
getNodeCompat(
60+
workerConfig.compatibility_date,
61+
workerConfig.compatibility_flags ?? []
62+
).mode === "als"
63+
);
64+
}
65+
66+
export function isNodeAlsModule(path: string) {
67+
return /^(node:)?async_hooks$/.test(path);
68+
}
69+
5370
/**
5471
* Gets the necessary global polyfills to inject into the entry-point of the user's code.
5572
*/

packages/vite-plugin-cloudflare/src/worker-environments-validation.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import assert from "node:assert";
22
import { cloudflareBuiltInModules } from "./cloudflare-environment";
3-
import { isNodeCompat, NODEJS_MODULES_RE } from "./node-js-compat";
3+
import {
4+
isNodeAls,
5+
isNodeAlsModule,
6+
isNodeCompat,
7+
NODEJS_MODULES_RE,
8+
} from "./node-js-compat";
49
import type { WorkerPluginConfig } from "./plugin-config";
510
import type * as vite from "vite";
611

@@ -41,11 +46,19 @@ export function validateWorkerEnvironmentsResolvedConfigs(
4146
return false;
4247
}
4348

49+
if (
50+
isNodeAlsModule(entry) &&
51+
isNodeAls(resolvedPluginConfig.workers[envName])
52+
) {
53+
// `node:async_hooks` is allowed when nodejs_als compat is enabled
54+
return false;
55+
}
56+
4457
if (
4558
NODEJS_MODULES_RE.test(entry) &&
4659
isNodeCompat(resolvedPluginConfig.workers[envName])
4760
) {
48-
// node builtin modules are allowed when but nodejs compat is enabled
61+
// node builtin modules are allowed when nodejs compat is enabled
4962
return false;
5063
}
5164

0 commit comments

Comments
 (0)