From d7ce16bc76ff51e0ee381e78fc3eb8cb2c752b2b Mon Sep 17 00:00:00 2001 From: Dorseuil Nicolas Date: Tue, 29 Apr 2025 18:34:01 +0200 Subject: [PATCH 1/6] fix for timers --- packages/cloudflare/src/cli/templates/init.ts | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/packages/cloudflare/src/cli/templates/init.ts b/packages/cloudflare/src/cli/templates/init.ts index 9d6278033..c091aedec 100644 --- a/packages/cloudflare/src/cli/templates/init.ts +++ b/packages/cloudflare/src/cli/templates/init.ts @@ -20,6 +20,35 @@ Object.defineProperty(globalThis, Symbol.for("__cloudflare-context__"), { }, }); +class Timeout implements NodeJS.Timeout { + constructor(private id: number, private disposer: (id: number) => void) {} + + ref(): this { + return this; + } + + close() { + this.disposer(this.id); + } + + unref(): this { + return this; + } + hasRef(): boolean { + return false; + } + refresh(): this { + throw new Error("Method not implemented."); + } + [Symbol.toPrimitive](): number { + return this.id; + } + [Symbol.dispose](): void { + this.disposer(this.id); + } + +} + /** * Executes the handler with the Cloudflare context. */ @@ -94,6 +123,21 @@ function initRuntime() { __BUILD_TIMESTAMP_MS__: __BUILD_TIMESTAMP_MS__, __NEXT_BASE_PATH__: __NEXT_BASE_PATH__, }); + + const oldSetInterval = globalThis.setInterval; + const oldSetTimeout = globalThis.setTimeout; + + // @ts-expect-error: workerd does not have the same types as node + globalThis.setInterval = (cb: (...args: unknown[]) => void, ms: number, ...args: unknown[]) => { + const id = oldSetInterval(cb, ms, ...args) as unknown as number; + return new Timeout(id, globalThis.clearInterval); + }; + + // @ts-expect-error: workerd does not have the same types as node + globalThis.setTimeout = (cb: (...args: unknown[]) => void, ms: number, ...args: unknown[]) => { + const id = oldSetTimeout(cb, ms, ...args) as unknown as number; + return new Timeout(id, globalThis.clearTimeout); + } } /** From 948d424537e88d0980456d69cdc52e8ec69b1fd0 Mon Sep 17 00:00:00 2001 From: Dorseuil Nicolas Date: Wed, 30 Apr 2025 12:58:45 +0200 Subject: [PATCH 2/6] move to banner --- examples/playground15/instrumentation.js | 6 +++ .../cloudflare/src/cli/build/bundle-server.ts | 4 ++ packages/cloudflare/src/cli/templates/init.ts | 44 ------------------- 3 files changed, 10 insertions(+), 44 deletions(-) diff --git a/examples/playground15/instrumentation.js b/examples/playground15/instrumentation.js index 828854aed..fdc193a8b 100644 --- a/examples/playground15/instrumentation.js +++ b/examples/playground15/instrumentation.js @@ -3,6 +3,12 @@ export function register() { // variable as recommended in the official docs: // https://nextjs.org/docs/app/building-your-application/optimizing/instrumentation#importing-runtime-specific-code + const timeout = setTimeout(() => { + console.log("This is a delayed log from the instrumentation register callback"); + }, 0); + // This is to test that we have access to the node version of setTimeout + timeout.unref(); + if (process.env.NEXT_RUNTIME === "nodejs") { globalThis["__NODEJS_INSTRUMENTATION_SETUP"] = "this value has been set by calling the instrumentation `register` callback in the nodejs runtime"; diff --git a/packages/cloudflare/src/cli/build/bundle-server.ts b/packages/cloudflare/src/cli/build/bundle-server.ts index 56f29608f..efbe7ffa0 100644 --- a/packages/cloudflare/src/cli/build/bundle-server.ts +++ b/packages/cloudflare/src/cli/build/bundle-server.ts @@ -144,6 +144,10 @@ export async function bundleServer(buildOpts: BuildOptions): Promise { // This define should be safe to use for Next 14.2+, earlier versions (13.5 and less) will cause trouble "process.env.__NEXT_EXPERIMENTAL_REACT": `${needsExperimentalReact(nextConfig)}`, }, + banner: { + // We need to import them here, assigning them to `globalThis` does not work because node:timers use `globalThis` and thus create an infinite loop + js: `import {setInterval, setTimeout, setImmediate} from "node:timers"`, + }, platform: "node", }); diff --git a/packages/cloudflare/src/cli/templates/init.ts b/packages/cloudflare/src/cli/templates/init.ts index c091aedec..9d6278033 100644 --- a/packages/cloudflare/src/cli/templates/init.ts +++ b/packages/cloudflare/src/cli/templates/init.ts @@ -20,35 +20,6 @@ Object.defineProperty(globalThis, Symbol.for("__cloudflare-context__"), { }, }); -class Timeout implements NodeJS.Timeout { - constructor(private id: number, private disposer: (id: number) => void) {} - - ref(): this { - return this; - } - - close() { - this.disposer(this.id); - } - - unref(): this { - return this; - } - hasRef(): boolean { - return false; - } - refresh(): this { - throw new Error("Method not implemented."); - } - [Symbol.toPrimitive](): number { - return this.id; - } - [Symbol.dispose](): void { - this.disposer(this.id); - } - -} - /** * Executes the handler with the Cloudflare context. */ @@ -123,21 +94,6 @@ function initRuntime() { __BUILD_TIMESTAMP_MS__: __BUILD_TIMESTAMP_MS__, __NEXT_BASE_PATH__: __NEXT_BASE_PATH__, }); - - const oldSetInterval = globalThis.setInterval; - const oldSetTimeout = globalThis.setTimeout; - - // @ts-expect-error: workerd does not have the same types as node - globalThis.setInterval = (cb: (...args: unknown[]) => void, ms: number, ...args: unknown[]) => { - const id = oldSetInterval(cb, ms, ...args) as unknown as number; - return new Timeout(id, globalThis.clearInterval); - }; - - // @ts-expect-error: workerd does not have the same types as node - globalThis.setTimeout = (cb: (...args: unknown[]) => void, ms: number, ...args: unknown[]) => { - const id = oldSetTimeout(cb, ms, ...args) as unknown as number; - return new Timeout(id, globalThis.clearTimeout); - } } /** From 660cc24396870bad8733bfea34f2523774c5b344 Mon Sep 17 00:00:00 2001 From: Dorseuil Nicolas Date: Wed, 30 Apr 2025 13:00:58 +0200 Subject: [PATCH 3/6] chnageset --- .changeset/famous-weeks-deliver.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/famous-weeks-deliver.md diff --git a/.changeset/famous-weeks-deliver.md b/.changeset/famous-weeks-deliver.md new file mode 100644 index 000000000..9d615076a --- /dev/null +++ b/.changeset/famous-weeks-deliver.md @@ -0,0 +1,5 @@ +--- +"@opennextjs/cloudflare": patch +--- + +global `setTimeout`, `setInterval` and `setImmediate` now use the one from node From bbfdf42bfe000532ea767603309140d4a6b7e047 Mon Sep 17 00:00:00 2001 From: Dorseuil Nicolas Date: Wed, 30 Apr 2025 13:10:51 +0200 Subject: [PATCH 4/6] also import corresponding clear function from node --- examples/playground15/instrumentation.js | 1 + packages/cloudflare/src/cli/build/bundle-server.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/playground15/instrumentation.js b/examples/playground15/instrumentation.js index fdc193a8b..5c32b944a 100644 --- a/examples/playground15/instrumentation.js +++ b/examples/playground15/instrumentation.js @@ -8,6 +8,7 @@ export function register() { }, 0); // This is to test that we have access to the node version of setTimeout timeout.unref(); + clearTimeout(timeout); if (process.env.NEXT_RUNTIME === "nodejs") { globalThis["__NODEJS_INSTRUMENTATION_SETUP"] = diff --git a/packages/cloudflare/src/cli/build/bundle-server.ts b/packages/cloudflare/src/cli/build/bundle-server.ts index efbe7ffa0..aa821c411 100644 --- a/packages/cloudflare/src/cli/build/bundle-server.ts +++ b/packages/cloudflare/src/cli/build/bundle-server.ts @@ -146,7 +146,7 @@ export async function bundleServer(buildOpts: BuildOptions): Promise { }, banner: { // We need to import them here, assigning them to `globalThis` does not work because node:timers use `globalThis` and thus create an infinite loop - js: `import {setInterval, setTimeout, setImmediate} from "node:timers"`, + js: `import {setInterval, clearInterval, setTimeout, clearTimeout, setImmediate, clearImmediate} from "node:timers"`, }, platform: "node", }); From 507ca5000bb0014644a87785e56c7ba0cae997b4 Mon Sep 17 00:00:00 2001 From: Dorseuil Nicolas Date: Wed, 30 Apr 2025 13:40:59 +0200 Subject: [PATCH 5/6] fix dev --- examples/playground15/instrumentation.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/playground15/instrumentation.js b/examples/playground15/instrumentation.js index 5c32b944a..dbbd21bad 100644 --- a/examples/playground15/instrumentation.js +++ b/examples/playground15/instrumentation.js @@ -6,13 +6,13 @@ export function register() { const timeout = setTimeout(() => { console.log("This is a delayed log from the instrumentation register callback"); }, 0); - // This is to test that we have access to the node version of setTimeout - timeout.unref(); - clearTimeout(timeout); if (process.env.NEXT_RUNTIME === "nodejs") { globalThis["__NODEJS_INSTRUMENTATION_SETUP"] = "this value has been set by calling the instrumentation `register` callback in the nodejs runtime"; + // This is to test that we have access to the node version of setTimeout + timeout.unref(); + clearTimeout(timeout); } if (process.env.NEXT_RUNTIME === "edge") { From 6b466c936e08d74f196a1a158aef0e30d7d6db1b Mon Sep 17 00:00:00 2001 From: conico974 Date: Fri, 2 May 2025 10:35:07 +0200 Subject: [PATCH 6/6] Apply suggestions from code review Co-authored-by: Victor Berchet --- .changeset/famous-weeks-deliver.md | 2 +- packages/cloudflare/src/cli/build/bundle-server.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.changeset/famous-weeks-deliver.md b/.changeset/famous-weeks-deliver.md index 9d615076a..f996e6fd6 100644 --- a/.changeset/famous-weeks-deliver.md +++ b/.changeset/famous-weeks-deliver.md @@ -2,4 +2,4 @@ "@opennextjs/cloudflare": patch --- -global `setTimeout`, `setInterval` and `setImmediate` now use the one from node +global timer functions now use the one from node:timers diff --git a/packages/cloudflare/src/cli/build/bundle-server.ts b/packages/cloudflare/src/cli/build/bundle-server.ts index aa821c411..84317a5b5 100644 --- a/packages/cloudflare/src/cli/build/bundle-server.ts +++ b/packages/cloudflare/src/cli/build/bundle-server.ts @@ -146,6 +146,7 @@ export async function bundleServer(buildOpts: BuildOptions): Promise { }, banner: { // We need to import them here, assigning them to `globalThis` does not work because node:timers use `globalThis` and thus create an infinite loop + // See https://github.com/cloudflare/workerd/blob/d6a764c/src/node/internal/internal_timers.ts#L56-L70 js: `import {setInterval, clearInterval, setTimeout, clearTimeout, setImmediate, clearImmediate} from "node:timers"`, }, platform: "node",