From 61484b673487cdafe7b26bb65cf32ef394f92b99 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Mon, 10 Feb 2025 18:32:44 +0000 Subject: [PATCH 01/15] fix: make sure that fetch cache `set`s are properly awaited Next.js does not await promises that update the incremental cache for fetch requests, that is needed in our runtime otherwise the cache updates get lost, so this change makes sure that the promise is properly awaited via `waitUntil` Co-authored-by: Victor Berchet --- .changeset/few-ducks-listen.md | 9 ++ examples/e2e/app-router/e2e/ssr.test.ts | 2 +- .../cloudflare/src/cli/build/bundle-server.ts | 2 + .../plugins/fetch-cache-wait-until.spec.ts | 107 ++++++++++++++++++ .../patches/plugins/fetch-cache-wait-until.ts | 55 +++++++++ 5 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 .changeset/few-ducks-listen.md create mode 100644 packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.spec.ts create mode 100644 packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts diff --git a/.changeset/few-ducks-listen.md b/.changeset/few-ducks-listen.md new file mode 100644 index 00000000..21decb8a --- /dev/null +++ b/.changeset/few-ducks-listen.md @@ -0,0 +1,9 @@ +--- +"@opennextjs/cloudflare": patch +--- + +fix: make sure that fetch cache `set`s are properly awaited + +Next.js does not await promises that update the incremental cache for fetch requests, +that is needed in our runtime otherwise the cache updates get lost, so this change +makes sure that the promise is properly awaited via `waitUntil` diff --git a/examples/e2e/app-router/e2e/ssr.test.ts b/examples/e2e/app-router/e2e/ssr.test.ts index c2156714..ec6f0bfe 100644 --- a/examples/e2e/app-router/e2e/ssr.test.ts +++ b/examples/e2e/app-router/e2e/ssr.test.ts @@ -28,7 +28,7 @@ test.skip("Server Side Render and loading.tsx", async ({ page }) => { } }); -test.skip("Fetch cache properly cached", async ({ page }) => { +test("Fetch cache properly cached", async ({ page }) => { await page.goto("/ssr"); const originalDate = await page.getByText("Cached fetch:").textContent(); await page.waitForTimeout(2000); diff --git a/packages/cloudflare/src/cli/build/bundle-server.ts b/packages/cloudflare/src/cli/build/bundle-server.ts index 025c1453..e4cc837e 100644 --- a/packages/cloudflare/src/cli/build/bundle-server.ts +++ b/packages/cloudflare/src/cli/build/bundle-server.ts @@ -10,6 +10,7 @@ import { patchVercelOgLibrary } from "./patches/ast/patch-vercel-og-library.js"; import { patchWebpackRuntime } from "./patches/ast/webpack-runtime.js"; import * as patches from "./patches/index.js"; import { ContentUpdater } from "./patches/plugins/content-updater.js"; +import { patchFetchCacheSetMissingWaitUntil } from "./patches/plugins/fetch-cache-wait-until.js"; import { patchLoadInstrumentation } from "./patches/plugins/load-instrumentation.js"; import { handleOptionalDependencies } from "./patches/plugins/optional-deps.js"; import { fixRequire } from "./patches/plugins/require.js"; @@ -87,6 +88,7 @@ export async function bundleServer(buildOpts: BuildOptions): Promise { fixRequire(updater), handleOptionalDependencies(optionalDependencies), patchLoadInstrumentation(updater), + patchFetchCacheSetMissingWaitUntil(updater), // Apply updater updaters, must be the last plugin updater.plugin, ], diff --git a/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.spec.ts b/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.spec.ts new file mode 100644 index 00000000..3b06977d --- /dev/null +++ b/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.spec.ts @@ -0,0 +1,107 @@ +import { describe, expect, test } from "vitest"; + +import { patchCode } from "../ast/util.js"; +import { ruleForMinifiedCode, ruleForNonMinifiedCode } from "./fetch-cache-wait-until.js"; + +describe("patchFetchCacheSetMissingWaitUntil", () => { + test("on minified code", () => { + const code = ` +{ + let [o4, a2] = (0, d2.cloneResponse)(e3); + return o4.arrayBuffer().then(async (e4) => { + var a3; + let i4 = Buffer.from(e4), s3 = { headers: Object.fromEntries(o4.headers.entries()), body: i4.toString("base64"), status: o4.status, url: o4.url }; + null == $ || null == (a3 = $.serverComponentsHmrCache) || a3.set(n2, s3), F && await H.set(n2, { kind: c2.CachedRouteKind.FETCH, data: s3, revalidate: t5 }, { fetchCache: true, revalidate: r4, fetchUrl: _, fetchIdx: q, tags: A2 }); + }).catch((e4) => console.warn("Failed to set fetch cache", u4, e4)).finally(X), a2; +}`; + + expect(patchCode(code, ruleForMinifiedCode)).toMatchInlineSnapshot(` + "{ + let [o4, a2] = (0, d2.cloneResponse)(e3); + globalThis.__openNextAls?.getStore()?.waitUntil?.(o4.arrayBuffer().then(async (e4) => { + var a3; + let i4 = Buffer.from(e4), s3 = { headers: Object.fromEntries(o4.headers.entries()), body: i4.toString("base64"), status: o4.status, url: o4.url }; + null == $ || null == (a3 = $.serverComponentsHmrCache) || a3.set(n2, s3), F && await H.set(n2, { kind: c2.CachedRouteKind.FETCH, data: s3, revalidate: t5 }, { fetchCache: true, revalidate: r4, fetchUrl: _, fetchIdx: q, tags: A2 }); + }).catch((e4) => console.warn("Failed to set fetch cache", u4, e4)).finally(X)); + return a2; + + }" + `); + }); + + test("on non-minified code", () => { + const code = ` + // We're cloning the response using this utility because there + // exists a bug in the undici library around response cloning. + // See the following pull request for more details: + // https://github.com/vercel/next.js/pull/73274 + const [cloned1, cloned2] = (0, _cloneresponse.cloneResponse)(res); + // We are dynamically rendering including dev mode. We want to return + // the response to the caller as soon as possible because it might stream + // over a very long time. + cloned1.arrayBuffer().then(async (arrayBuffer)=>{ + var _requestStore_serverComponentsHmrCache; + const bodyBuffer = Buffer.from(arrayBuffer); + const fetchedData = { + headers: Object.fromEntries(cloned1.headers.entries()), + body: bodyBuffer.toString('base64'), + status: cloned1.status, + url: cloned1.url + }; + requestStore == null ? void 0 : (_requestStore_serverComponentsHmrCache = requestStore.serverComponentsHmrCache) == null ? void 0 : _requestStore_serverComponentsHmrCache.set(cacheKey, fetchedData); + if (isCacheableRevalidate) { + await incrementalCache.set(cacheKey, { + kind: _responsecache.CachedRouteKind.FETCH, + data: fetchedData, + revalidate: normalizedRevalidate + }, { + fetchCache: true, + revalidate: externalRevalidate, + fetchUrl, + fetchIdx, + tags + }); + } + }).catch((error)=>console.warn(\`Failed to set fetch cache\`, input, error)).finally(handleUnlock); + return cloned2; + `; + + expect(patchCode(code, ruleForNonMinifiedCode)).toMatchInlineSnapshot(` + "// We're cloning the response using this utility because there + // exists a bug in the undici library around response cloning. + // See the following pull request for more details: + // https://github.com/vercel/next.js/pull/73274 + const [cloned1, cloned2] = (0, _cloneresponse.cloneResponse)(res); + // We are dynamically rendering including dev mode. We want to return + // the response to the caller as soon as possible because it might stream + // over a very long time. + globalThis.__openNextAls?.getStore()?.waitUntil?.(cloned1.arrayBuffer().then(async (arrayBuffer)=>{ + var _requestStore_serverComponentsHmrCache; + const bodyBuffer = Buffer.from(arrayBuffer); + const fetchedData = { + headers: Object.fromEntries(cloned1.headers.entries()), + body: bodyBuffer.toString('base64'), + status: cloned1.status, + url: cloned1.url + }; + requestStore == null ? void 0 : (_requestStore_serverComponentsHmrCache = requestStore.serverComponentsHmrCache) == null ? void 0 : _requestStore_serverComponentsHmrCache.set(cacheKey, fetchedData); + if (isCacheableRevalidate) { + await incrementalCache.set(cacheKey, { + kind: _responsecache.CachedRouteKind.FETCH, + data: fetchedData, + revalidate: normalizedRevalidate + }, { + fetchCache: true, + revalidate: externalRevalidate, + fetchUrl, + fetchIdx, + tags + }); + } + }).catch((error)=>console.warn(\`Failed to set fetch cache\`, input, error)).finally(handleUnlock)); + + return cloned2; + " + `); + }); +}); diff --git a/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts b/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts new file mode 100644 index 00000000..d738d58d --- /dev/null +++ b/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts @@ -0,0 +1,55 @@ +import { patchCode } from "../ast/util.js"; +import type { ContentUpdater } from "./content-updater.js"; + +/** + * The following Next.js code sets values in the incremental cache for fetch calls: + * https://github.com/vercel/next.js/blob/e5fc495e3d4/packages/next/src/server/lib/patch-fetch.ts#L690-L728 + * + * The issue here is that this promise is never awaited in the Next.js code (since in a standard node.js server + * the promise will eventually simply just run) but we do need to run it inside `waitUntil` (so that the worker + * is not killed before the promise is fully executed), without that this promise gets discarded and values + * don't get saved in the incremental cache. + * + * This function wraps the promise in a `waitUntil` call (retrieved from `globalThis.__openNextAls.getStore()`). + */ +export function patchFetchCacheSetMissingWaitUntil(updater: ContentUpdater) { + return updater.updateContent( + "patch-fetch-cache-set-missing-wait-until", + { filter: /\.(js|mjs|cjs|jsx|ts|tsx)$/, contentFilter: /Failed to set fetch cache/ }, + ({ contents }) => { + contents = patchCode(contents, ruleForMinifiedCode); + return patchCode(contents, ruleForNonMinifiedCode); + } + ); +} + +export const ruleForMinifiedCode = ` +rule: + pattern: return $PROMISE, $CLONED2 + regex: Failed to set fetch cache + follows: + kind: lexical_declaration + pattern: let [$CLONED1, $CLONED2] + +fix: | + globalThis.__openNextAls?.getStore()?.waitUntil?.($PROMISE); + return $CLONED2; +`; + +export const ruleForNonMinifiedCode = ` +rule: + regex: Failed to set fetch cache + pattern: $PROMISE; + follows: + kind: comment + follows: + kind: comment + follows: + kind: comment + follows: + kind: lexical_declaration + pattern: const [cloned1, cloned2] + +fix: | + globalThis.__openNextAls?.getStore()?.waitUntil?.($PROMISE); +`; From 6166646a1f10978b4ab50200b527c2b0ef34fa63 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Tue, 11 Feb 2025 11:02:52 +0000 Subject: [PATCH 02/15] apply more strict filtering --- .../src/cli/build/patches/plugins/fetch-cache-wait-until.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts b/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts index d738d58d..9e1fad6a 100644 --- a/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts +++ b/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts @@ -15,7 +15,10 @@ import type { ContentUpdater } from "./content-updater.js"; export function patchFetchCacheSetMissingWaitUntil(updater: ContentUpdater) { return updater.updateContent( "patch-fetch-cache-set-missing-wait-until", - { filter: /\.(js|mjs|cjs|jsx|ts|tsx)$/, contentFilter: /Failed to set fetch cache/ }, + { + filter: /(server\/chunks\/.*\.js|.*\.runtime\..*\.js|patch-fetch\.js)$/, + contentFilter: /Failed to set fetch cache/, + }, ({ contents }) => { contents = patchCode(contents, ruleForMinifiedCode); return patchCode(contents, ruleForNonMinifiedCode); From 05f722517b198622ec6270e05c91950aed33e3ae Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Tue, 11 Feb 2025 11:12:39 +0000 Subject: [PATCH 03/15] update filtering --- .../src/cli/build/patches/plugins/fetch-cache-wait-until.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts b/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts index 9e1fad6a..e4a32461 100644 --- a/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts +++ b/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts @@ -17,7 +17,7 @@ export function patchFetchCacheSetMissingWaitUntil(updater: ContentUpdater) { "patch-fetch-cache-set-missing-wait-until", { filter: /(server\/chunks\/.*\.js|.*\.runtime\..*\.js|patch-fetch\.js)$/, - contentFilter: /Failed to set fetch cache/, + contentFilter: /arrayBuffer\(\)\s*\.then/, }, ({ contents }) => { contents = patchCode(contents, ruleForMinifiedCode); From 984f22ddcdb37a72710a83c6a102f8b9c1811a1e Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Tue, 11 Feb 2025 11:21:27 +0000 Subject: [PATCH 04/15] use `getCrossPlatformPathRegex` --- .../src/cli/build/patches/plugins/fetch-cache-wait-until.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts b/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts index e4a32461..ccca50aa 100644 --- a/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts +++ b/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts @@ -1,3 +1,4 @@ +import { getCrossPlatformPathRegex } from "@opennextjs/aws/utils/regex.js"; import { patchCode } from "../ast/util.js"; import type { ContentUpdater } from "./content-updater.js"; @@ -16,7 +17,10 @@ export function patchFetchCacheSetMissingWaitUntil(updater: ContentUpdater) { return updater.updateContent( "patch-fetch-cache-set-missing-wait-until", { - filter: /(server\/chunks\/.*\.js|.*\.runtime\..*\.js|patch-fetch\.js)$/, + filter: getCrossPlatformPathRegex( + String.raw`(server/chunks/.*\.js|.*\.runtime\..*\.js|patch-fetch\.js)$`, + { escape: false } + ), contentFilter: /arrayBuffer\(\)\s*\.then/, }, ({ contents }) => { From c6ccf772624b1430842876b7c875432253faeddc Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Tue, 11 Feb 2025 11:24:12 +0000 Subject: [PATCH 05/15] fix imports ordering --- .../src/cli/build/patches/plugins/fetch-cache-wait-until.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts b/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts index ccca50aa..cfe36218 100644 --- a/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts +++ b/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts @@ -1,4 +1,5 @@ import { getCrossPlatformPathRegex } from "@opennextjs/aws/utils/regex.js"; + import { patchCode } from "../ast/util.js"; import type { ContentUpdater } from "./content-updater.js"; From 9e5b1bb42e1ea19185b0d2ba9898350d15725019 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Tue, 11 Feb 2025 12:06:49 +0000 Subject: [PATCH 06/15] apply a single rule --- .../plugins/fetch-cache-wait-until.spec.ts | 6 +-- .../patches/plugins/fetch-cache-wait-until.ts | 39 +++++-------------- 2 files changed, 13 insertions(+), 32 deletions(-) diff --git a/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.spec.ts b/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.spec.ts index 3b06977d..2c16ebcf 100644 --- a/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.spec.ts +++ b/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from "vitest"; import { patchCode } from "../ast/util.js"; -import { ruleForMinifiedCode, ruleForNonMinifiedCode } from "./fetch-cache-wait-until.js"; +import { rule } from "./fetch-cache-wait-until.js"; describe("patchFetchCacheSetMissingWaitUntil", () => { test("on minified code", () => { @@ -15,7 +15,7 @@ describe("patchFetchCacheSetMissingWaitUntil", () => { }).catch((e4) => console.warn("Failed to set fetch cache", u4, e4)).finally(X), a2; }`; - expect(patchCode(code, ruleForMinifiedCode)).toMatchInlineSnapshot(` + expect(patchCode(code, rule)).toMatchInlineSnapshot(` "{ let [o4, a2] = (0, d2.cloneResponse)(e3); globalThis.__openNextAls?.getStore()?.waitUntil?.(o4.arrayBuffer().then(async (e4) => { @@ -66,7 +66,7 @@ describe("patchFetchCacheSetMissingWaitUntil", () => { return cloned2; `; - expect(patchCode(code, ruleForNonMinifiedCode)).toMatchInlineSnapshot(` + expect(patchCode(code, rule)).toMatchInlineSnapshot(` "// We're cloning the response using this utility because there // exists a bug in the undici library around response cloning. // See the following pull request for more details: diff --git a/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts b/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts index cfe36218..fc67cc2e 100644 --- a/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts +++ b/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts @@ -24,40 +24,21 @@ export function patchFetchCacheSetMissingWaitUntil(updater: ContentUpdater) { ), contentFilter: /arrayBuffer\(\)\s*\.then/, }, - ({ contents }) => { - contents = patchCode(contents, ruleForMinifiedCode); - return patchCode(contents, ruleForNonMinifiedCode); - } + ({ contents }) => patchCode(contents, rule) ); } -export const ruleForMinifiedCode = ` +export const rule = ` rule: - pattern: return $PROMISE, $CLONED2 - regex: Failed to set fetch cache - follows: - kind: lexical_declaration - pattern: let [$CLONED1, $CLONED2] + kind: call_expression + pattern: $PROMISE + all: + - has: { pattern: "$$$_.then($$$_)", stopBy: end } + - has: { pattern: "Buffer.from", stopBy: end } + - has: { regex: "CachedRouteKind.FETCH" } + - has: { regex: "finally(.*?)$" } -fix: | - globalThis.__openNextAls?.getStore()?.waitUntil?.($PROMISE); - return $CLONED2; -`; - -export const ruleForNonMinifiedCode = ` -rule: - regex: Failed to set fetch cache - pattern: $PROMISE; - follows: - kind: comment - follows: - kind: comment - follows: - kind: comment - follows: - kind: lexical_declaration - pattern: const [cloned1, cloned2] fix: | - globalThis.__openNextAls?.getStore()?.waitUntil?.($PROMISE); + globalThis.__openNextAls.getStore().waitUntil($PROMISE) `; From 5af63495ba1fe0da33e2388c7144c54723dd9624 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Tue, 11 Feb 2025 12:37:40 +0000 Subject: [PATCH 07/15] update tests --- .../plugins/fetch-cache-wait-until.spec.ts | 489 +++++++++++++++--- 1 file changed, 421 insertions(+), 68 deletions(-) diff --git a/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.spec.ts b/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.spec.ts index 2c16ebcf..c3d7747e 100644 --- a/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.spec.ts +++ b/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.spec.ts @@ -18,90 +18,443 @@ describe("patchFetchCacheSetMissingWaitUntil", () => { expect(patchCode(code, rule)).toMatchInlineSnapshot(` "{ let [o4, a2] = (0, d2.cloneResponse)(e3); - globalThis.__openNextAls?.getStore()?.waitUntil?.(o4.arrayBuffer().then(async (e4) => { + return globalThis.__openNextAls.getStore().waitUntil(o4.arrayBuffer().then(async (e4) => { var a3; let i4 = Buffer.from(e4), s3 = { headers: Object.fromEntries(o4.headers.entries()), body: i4.toString("base64"), status: o4.status, url: o4.url }; null == $ || null == (a3 = $.serverComponentsHmrCache) || a3.set(n2, s3), F && await H.set(n2, { kind: c2.CachedRouteKind.FETCH, data: s3, revalidate: t5 }, { fetchCache: true, revalidate: r4, fetchUrl: _, fetchIdx: q, tags: A2 }); - }).catch((e4) => console.warn("Failed to set fetch cache", u4, e4)).finally(X)); - return a2; - + }).catch((e4) => console.warn("Failed to set fetch cache", u4, e4)).finally(X)) + , a2; }" `); }); - test("on non-minified code", () => { - const code = ` - // We're cloning the response using this utility because there - // exists a bug in the undici library around response cloning. - // See the following pull request for more details: - // https://github.com/vercel/next.js/pull/73274 - const [cloned1, cloned2] = (0, _cloneresponse.cloneResponse)(res); - // We are dynamically rendering including dev mode. We want to return - // the response to the caller as soon as possible because it might stream - // over a very long time. - cloned1.arrayBuffer().then(async (arrayBuffer)=>{ - var _requestStore_serverComponentsHmrCache; - const bodyBuffer = Buffer.from(arrayBuffer); - const fetchedData = { - headers: Object.fromEntries(cloned1.headers.entries()), + describe("on non-minified code", () => { + test("15.1.0", () => { + // source: https://github.com/vercel/next.js/blob/fe45b74fdac83d3/packages/next/src/server/lib/patch-fetch.ts#L627-L732 + const code = `if ( + res.status === 200 && + incrementalCache && + cacheKey && + (isCacheableRevalidate || + useCacheOrRequestStore?.serverComponentsHmrCache) + ) { + const normalizedRevalidate = + finalRevalidate >= INFINITE_CACHE + ? CACHE_ONE_YEAR + : finalRevalidate + const externalRevalidate = + finalRevalidate >= INFINITE_CACHE ? false : finalRevalidate + + if (workUnitStore && workUnitStore.type === 'prerender') { + // We are prerendering at build time or revalidate time with dynamicIO so we need to + // buffer the response so we can guarantee it can be read in a microtask + const bodyBuffer = await res.arrayBuffer() + + const fetchedData = { + headers: Object.fromEntries(res.headers.entries()), + body: Buffer.from(bodyBuffer).toString('base64'), + status: res.status, + url: res.url, + } + + // We can skip checking the serverComponentsHmrCache because we aren't in + // dev mode. + + await incrementalCache.set( + cacheKey, + { + kind: CachedRouteKind.FETCH, + data: fetchedData, + revalidate: normalizedRevalidate, + }, + { + fetchCache: true, + revalidate: externalRevalidate, + fetchUrl, + fetchIdx, + tags, + } + ) + await handleUnlock() + + // We return a new Response to the caller. + return new Response(bodyBuffer, { + headers: res.headers, + status: res.status, + statusText: res.statusText, + }) + } else { + // We're cloning the response using this utility because there + // exists a bug in the undici library around response cloning. + // See the following pull request for more details: + // https://github.com/vercel/next.js/pull/73274 + + const [cloned1, cloned2] = cloneResponse(res) + + // We are dynamically rendering including dev mode. We want to return + // the response to the caller as soon as possible because it might stream + // over a very long time. + cloned1 + .arrayBuffer() + .then(async (arrayBuffer) => { + const bodyBuffer = Buffer.from(arrayBuffer) + + const fetchedData = { + headers: Object.fromEntries(cloned1.headers.entries()), + body: bodyBuffer.toString('base64'), + status: cloned1.status, + url: cloned1.url, + } + + useCacheOrRequestStore?.serverComponentsHmrCache?.set( + cacheKey, + fetchedData + ) + + if (isCacheableRevalidate) { + await incrementalCache.set( + cacheKey, + { + kind: CachedRouteKind.FETCH, + data: fetchedData, + revalidate: normalizedRevalidate, + }, + { + fetchCache: true, + revalidate: externalRevalidate, + fetchUrl, + fetchIdx, + tags, + } + ) + } + }) + .catch((error) => + console.warn(\`Failed to set fetch cache\`, input, error) + ) + .finally(handleUnlock) + + return cloned2 + } + } + `; + + expect(patchCode(code, rule)).toMatchInlineSnapshot(` + "if ( + res.status === 200 && + incrementalCache && + cacheKey && + (isCacheableRevalidate || + useCacheOrRequestStore?.serverComponentsHmrCache) + ) { + const normalizedRevalidate = + finalRevalidate >= INFINITE_CACHE + ? CACHE_ONE_YEAR + : finalRevalidate + const externalRevalidate = + finalRevalidate >= INFINITE_CACHE ? false : finalRevalidate + + if (workUnitStore && workUnitStore.type === 'prerender') { + // We are prerendering at build time or revalidate time with dynamicIO so we need to + // buffer the response so we can guarantee it can be read in a microtask + const bodyBuffer = await res.arrayBuffer() + + const fetchedData = { + headers: Object.fromEntries(res.headers.entries()), + body: Buffer.from(bodyBuffer).toString('base64'), + status: res.status, + url: res.url, + } + + // We can skip checking the serverComponentsHmrCache because we aren't in + // dev mode. + + await incrementalCache.set( + cacheKey, + { + kind: CachedRouteKind.FETCH, + data: fetchedData, + revalidate: normalizedRevalidate, + }, + { + fetchCache: true, + revalidate: externalRevalidate, + fetchUrl, + fetchIdx, + tags, + } + ) + await handleUnlock() + + // We return a new Response to the caller. + return new Response(bodyBuffer, { + headers: res.headers, + status: res.status, + statusText: res.statusText, + }) + } else { + // We're cloning the response using this utility because there + // exists a bug in the undici library around response cloning. + // See the following pull request for more details: + // https://github.com/vercel/next.js/pull/73274 + + const [cloned1, cloned2] = cloneResponse(res) + + // We are dynamically rendering including dev mode. We want to return + // the response to the caller as soon as possible because it might stream + // over a very long time. + globalThis.__openNextAls.getStore().waitUntil(cloned1 + .arrayBuffer() + .then(async (arrayBuffer) => { + const bodyBuffer = Buffer.from(arrayBuffer) + + const fetchedData = { + headers: Object.fromEntries(cloned1.headers.entries()), + body: bodyBuffer.toString('base64'), + status: cloned1.status, + url: cloned1.url, + } + + useCacheOrRequestStore?.serverComponentsHmrCache?.set( + cacheKey, + fetchedData + ) + + if (isCacheableRevalidate) { + await incrementalCache.set( + cacheKey, + { + kind: CachedRouteKind.FETCH, + data: fetchedData, + revalidate: normalizedRevalidate, + }, + { + fetchCache: true, + revalidate: externalRevalidate, + fetchUrl, + fetchIdx, + tags, + } + ) + } + }) + .catch((error) => + console.warn(\`Failed to set fetch cache\`, input, error) + ) + .finally(handleUnlock)) + + + return cloned2 + } + } + " + `); + }); + + test("Next.js 15.0.4", () => { + // source: https://github.com/vercel/next.js/blob/d6a6aa14069/packages/next/src/server/lib/patch-fetch.ts#L627-L725 + const code = `if ( + res.status === 200 && + incrementalCache && + cacheKey && + (isCacheableRevalidate || requestStore?.serverComponentsHmrCache) + ) { + const normalizedRevalidate = + finalRevalidate >= INFINITE_CACHE + ? CACHE_ONE_YEAR + : finalRevalidate + const externalRevalidate = + finalRevalidate >= INFINITE_CACHE ? false : finalRevalidate + + if (workUnitStore && workUnitStore.type === 'prerender') { + // We are prerendering at build time or revalidate time with dynamicIO so we need to + // buffer the response so we can guarantee it can be read in a microtask + const bodyBuffer = await res.arrayBuffer() + + const fetchedData = { + headers: Object.fromEntries(res.headers.entries()), + body: Buffer.from(bodyBuffer).toString('base64'), + status: res.status, + url: res.url, + } + + // We can skip checking the serverComponentsHmrCache because we aren't in + // dev mode. + + await incrementalCache.set( + cacheKey, + { + kind: CachedRouteKind.FETCH, + data: fetchedData, + revalidate: normalizedRevalidate, + }, + { + fetchCache: true, + revalidate: externalRevalidate, + fetchUrl, + fetchIdx, + tags, + } + ) + await handleUnlock() + + // We we return a new Response to the caller. + return new Response(bodyBuffer, { + headers: res.headers, + status: res.status, + statusText: res.statusText, + }) + } else { + // We are dynamically rendering including dev mode. We want to return + // the response to the caller as soon as possible because it might stream + // over a very long time. + res + .clone() + .arrayBuffer() + .then(async (arrayBuffer) => { + const bodyBuffer = Buffer.from(arrayBuffer) + + const fetchedData = { + headers: Object.fromEntries(res.headers.entries()), body: bodyBuffer.toString('base64'), - status: cloned1.status, - url: cloned1.url - }; - requestStore == null ? void 0 : (_requestStore_serverComponentsHmrCache = requestStore.serverComponentsHmrCache) == null ? void 0 : _requestStore_serverComponentsHmrCache.set(cacheKey, fetchedData); - if (isCacheableRevalidate) { - await incrementalCache.set(cacheKey, { - kind: _responsecache.CachedRouteKind.FETCH, + status: res.status, + url: res.url, + } + + requestStore?.serverComponentsHmrCache?.set( + cacheKey, + fetchedData + ) + + if (isCacheableRevalidate) { + await incrementalCache.set( + cacheKey, + { + kind: CachedRouteKind.FETCH, data: fetchedData, - revalidate: normalizedRevalidate - }, { + revalidate: normalizedRevalidate, + }, + { fetchCache: true, revalidate: externalRevalidate, fetchUrl, fetchIdx, - tags - }); - } - }).catch((error)=>console.warn(\`Failed to set fetch cache\`, input, error)).finally(handleUnlock); - return cloned2; - `; + tags, + } + ) + } + }) + .catch((error) => + console.warn(\`Failed to set fetch cache\`, input, error) + ) + .finally(handleUnlock) + + return res + } + }`; + + expect(patchCode(code, rule)).toMatchInlineSnapshot(` + "if ( + res.status === 200 && + incrementalCache && + cacheKey && + (isCacheableRevalidate || requestStore?.serverComponentsHmrCache) + ) { + const normalizedRevalidate = + finalRevalidate >= INFINITE_CACHE + ? CACHE_ONE_YEAR + : finalRevalidate + const externalRevalidate = + finalRevalidate >= INFINITE_CACHE ? false : finalRevalidate + + if (workUnitStore && workUnitStore.type === 'prerender') { + // We are prerendering at build time or revalidate time with dynamicIO so we need to + // buffer the response so we can guarantee it can be read in a microtask + const bodyBuffer = await res.arrayBuffer() - expect(patchCode(code, rule)).toMatchInlineSnapshot(` - "// We're cloning the response using this utility because there - // exists a bug in the undici library around response cloning. - // See the following pull request for more details: - // https://github.com/vercel/next.js/pull/73274 - const [cloned1, cloned2] = (0, _cloneresponse.cloneResponse)(res); - // We are dynamically rendering including dev mode. We want to return - // the response to the caller as soon as possible because it might stream - // over a very long time. - globalThis.__openNextAls?.getStore()?.waitUntil?.(cloned1.arrayBuffer().then(async (arrayBuffer)=>{ - var _requestStore_serverComponentsHmrCache; - const bodyBuffer = Buffer.from(arrayBuffer); const fetchedData = { - headers: Object.fromEntries(cloned1.headers.entries()), - body: bodyBuffer.toString('base64'), - status: cloned1.status, - url: cloned1.url - }; - requestStore == null ? void 0 : (_requestStore_serverComponentsHmrCache = requestStore.serverComponentsHmrCache) == null ? void 0 : _requestStore_serverComponentsHmrCache.set(cacheKey, fetchedData); - if (isCacheableRevalidate) { - await incrementalCache.set(cacheKey, { - kind: _responsecache.CachedRouteKind.FETCH, - data: fetchedData, - revalidate: normalizedRevalidate - }, { - fetchCache: true, - revalidate: externalRevalidate, - fetchUrl, - fetchIdx, - tags - }); + headers: Object.fromEntries(res.headers.entries()), + body: Buffer.from(bodyBuffer).toString('base64'), + status: res.status, + url: res.url, } - }).catch((error)=>console.warn(\`Failed to set fetch cache\`, input, error)).finally(handleUnlock)); - return cloned2; - " - `); + // We can skip checking the serverComponentsHmrCache because we aren't in + // dev mode. + + await incrementalCache.set( + cacheKey, + { + kind: CachedRouteKind.FETCH, + data: fetchedData, + revalidate: normalizedRevalidate, + }, + { + fetchCache: true, + revalidate: externalRevalidate, + fetchUrl, + fetchIdx, + tags, + } + ) + await handleUnlock() + + // We we return a new Response to the caller. + return new Response(bodyBuffer, { + headers: res.headers, + status: res.status, + statusText: res.statusText, + }) + } else { + // We are dynamically rendering including dev mode. We want to return + // the response to the caller as soon as possible because it might stream + // over a very long time. + globalThis.__openNextAls.getStore().waitUntil(res + .clone() + .arrayBuffer() + .then(async (arrayBuffer) => { + const bodyBuffer = Buffer.from(arrayBuffer) + + const fetchedData = { + headers: Object.fromEntries(res.headers.entries()), + body: bodyBuffer.toString('base64'), + status: res.status, + url: res.url, + } + + requestStore?.serverComponentsHmrCache?.set( + cacheKey, + fetchedData + ) + + if (isCacheableRevalidate) { + await incrementalCache.set( + cacheKey, + { + kind: CachedRouteKind.FETCH, + data: fetchedData, + revalidate: normalizedRevalidate, + }, + { + fetchCache: true, + revalidate: externalRevalidate, + fetchUrl, + fetchIdx, + tags, + } + ) + } + }) + .catch((error) => + console.warn(\`Failed to set fetch cache\`, input, error) + ) + .finally(handleUnlock)) + + + return res + } + }" + `); + }); }); }); From d169c6a23852a93d16a860f6340609760c27b567 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Tue, 11 Feb 2025 14:27:02 +0000 Subject: [PATCH 08/15] remove extra line --- .../src/cli/build/patches/plugins/fetch-cache-wait-until.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts b/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts index fc67cc2e..b7c52c63 100644 --- a/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts +++ b/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts @@ -38,7 +38,6 @@ rule: - has: { regex: "CachedRouteKind.FETCH" } - has: { regex: "finally(.*?)$" } - fix: | globalThis.__openNextAls.getStore().waitUntil($PROMISE) `; From 1a6baa2e897291d4e1ee25450c70901fd79d3475 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Tue, 11 Feb 2025 17:28:54 +0000 Subject: [PATCH 09/15] get everything to work with a single rule --- .../plugins/fetch-cache-wait-until.spec.ts | 6 +++--- .../patches/plugins/fetch-cache-wait-until.ts | 18 ++++++++++++++---- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.spec.ts b/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.spec.ts index c3d7747e..f51a110f 100644 --- a/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.spec.ts +++ b/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.spec.ts @@ -18,7 +18,7 @@ describe("patchFetchCacheSetMissingWaitUntil", () => { expect(patchCode(code, rule)).toMatchInlineSnapshot(` "{ let [o4, a2] = (0, d2.cloneResponse)(e3); - return globalThis.__openNextAls.getStore().waitUntil(o4.arrayBuffer().then(async (e4) => { + return globalThis.__openNextAls?.getStore()?.waitUntil?.(o4.arrayBuffer().then(async (e4) => { var a3; let i4 = Buffer.from(e4), s3 = { headers: Object.fromEntries(o4.headers.entries()), body: i4.toString("base64"), status: o4.status, url: o4.url }; null == $ || null == (a3 = $.serverComponentsHmrCache) || a3.set(n2, s3), F && await H.set(n2, { kind: c2.CachedRouteKind.FETCH, data: s3, revalidate: t5 }, { fetchCache: true, revalidate: r4, fetchUrl: _, fetchIdx: q, tags: A2 }); @@ -203,7 +203,7 @@ describe("patchFetchCacheSetMissingWaitUntil", () => { // We are dynamically rendering including dev mode. We want to return // the response to the caller as soon as possible because it might stream // over a very long time. - globalThis.__openNextAls.getStore().waitUntil(cloned1 + globalThis.__openNextAls?.getStore()?.waitUntil?.(cloned1 .arrayBuffer() .then(async (arrayBuffer) => { const bodyBuffer = Buffer.from(arrayBuffer) @@ -409,7 +409,7 @@ describe("patchFetchCacheSetMissingWaitUntil", () => { // We are dynamically rendering including dev mode. We want to return // the response to the caller as soon as possible because it might stream // over a very long time. - globalThis.__openNextAls.getStore().waitUntil(res + globalThis.__openNextAls?.getStore()?.waitUntil?.(res .clone() .arrayBuffer() .then(async (arrayBuffer) => { diff --git a/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts b/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts index b7c52c63..dc4c0412 100644 --- a/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts +++ b/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts @@ -33,11 +33,21 @@ rule: kind: call_expression pattern: $PROMISE all: - - has: { pattern: "$$$_.then($$$_)", stopBy: end } + - has: { pattern: $_.arrayBuffer().then, stopBy: end } - has: { pattern: "Buffer.from", stopBy: end } - - has: { regex: "CachedRouteKind.FETCH" } - - has: { regex: "finally(.*?)$" } + - any: + - inside: + kind: sequence_expression + inside: + kind: return_statement + - inside: + kind: expression_statement + precedes: + kind: return_statement + any: + - has: { pattern: $_.FETCH, stopBy: end } + - has: { pattern: FETCH, stopBy: end } fix: | - globalThis.__openNextAls.getStore().waitUntil($PROMISE) + globalThis.__openNextAls?.getStore()?.waitUntil?.($PROMISE) `; From 669d67c8359f4f007830090db93e0a8e654eb4e7 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Tue, 11 Feb 2025 17:46:14 +0000 Subject: [PATCH 10/15] Update packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts Co-authored-by: Victor Berchet --- .../src/cli/build/patches/plugins/fetch-cache-wait-until.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts b/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts index dc4c0412..bd37aa2c 100644 --- a/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts +++ b/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts @@ -44,9 +44,7 @@ rule: kind: expression_statement precedes: kind: return_statement - any: - has: { pattern: $_.FETCH, stopBy: end } - - has: { pattern: FETCH, stopBy: end } fix: | globalThis.__openNextAls?.getStore()?.waitUntil?.($PROMISE) From 5e979641dad3e786e4db2be04ac8bfb7eeb1b609 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Tue, 11 Feb 2025 18:07:24 +0000 Subject: [PATCH 11/15] revert `getCrossPlatformPathRegex` usage --- .../cli/build/patches/plugins/fetch-cache-wait-until.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts b/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts index bd37aa2c..16cd43ba 100644 --- a/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts +++ b/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts @@ -1,5 +1,3 @@ -import { getCrossPlatformPathRegex } from "@opennextjs/aws/utils/regex.js"; - import { patchCode } from "../ast/util.js"; import type { ContentUpdater } from "./content-updater.js"; @@ -18,10 +16,7 @@ export function patchFetchCacheSetMissingWaitUntil(updater: ContentUpdater) { return updater.updateContent( "patch-fetch-cache-set-missing-wait-until", { - filter: getCrossPlatformPathRegex( - String.raw`(server/chunks/.*\.js|.*\.runtime\..*\.js|patch-fetch\.js)$`, - { escape: false } - ), + filter: /(server\/chunks\/.*\.js|.*\.runtime\..*\.js|patch-fetch\.js)$/, contentFilter: /arrayBuffer\(\)\s*\.then/, }, ({ contents }) => patchCode(contents, rule) From bd15f56a96fdad8e75e3c5a9fac857a6d9d6efd5 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Tue, 11 Feb 2025 19:53:58 +0000 Subject: [PATCH 12/15] Revert "revert `getCrossPlatformPathRegex` usage" This reverts commit 5e979641dad3e786e4db2be04ac8bfb7eeb1b609. --- .../cli/build/patches/plugins/fetch-cache-wait-until.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts b/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts index 16cd43ba..bd37aa2c 100644 --- a/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts +++ b/packages/cloudflare/src/cli/build/patches/plugins/fetch-cache-wait-until.ts @@ -1,3 +1,5 @@ +import { getCrossPlatformPathRegex } from "@opennextjs/aws/utils/regex.js"; + import { patchCode } from "../ast/util.js"; import type { ContentUpdater } from "./content-updater.js"; @@ -16,7 +18,10 @@ export function patchFetchCacheSetMissingWaitUntil(updater: ContentUpdater) { return updater.updateContent( "patch-fetch-cache-set-missing-wait-until", { - filter: /(server\/chunks\/.*\.js|.*\.runtime\..*\.js|patch-fetch\.js)$/, + filter: getCrossPlatformPathRegex( + String.raw`(server/chunks/.*\.js|.*\.runtime\..*\.js|patch-fetch\.js)$`, + { escape: false } + ), contentFilter: /arrayBuffer\(\)\s*\.then/, }, ({ contents }) => patchCode(contents, rule) From 7ed84ee12c0e8097dcd35881e7d9a30faf7dca91 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Tue, 11 Feb 2025 19:59:34 +0000 Subject: [PATCH 13/15] reset `filter.lastIndex` in content-updater --- .../src/cli/build/patches/plugins/content-updater.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/cloudflare/src/cli/build/patches/plugins/content-updater.ts b/packages/cloudflare/src/cli/build/patches/plugins/content-updater.ts index 5687d833..273407fe 100644 --- a/packages/cloudflare/src/cli/build/patches/plugins/content-updater.ts +++ b/packages/cloudflare/src/cli/build/patches/plugins/content-updater.ts @@ -59,6 +59,9 @@ export class ContentUpdater { build.onLoad({ filter: /\.(js|mjs|cjs|jsx|ts|tsx)$/ }, async (args: OnLoadArgs) => { let contents = await readFile(args.path, "utf-8"); for (const { filter, namespace, contentFilter, callback } of this.updaters.values()) { + // if the regex is sticky let's max sure we rewind it here + filter.lastIndex = 0; + if (namespace !== undefined && args.namespace !== namespace) { continue; } From 96d716282a3d6882004cbc69810a48324392ec97 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Tue, 11 Feb 2025 20:06:09 +0000 Subject: [PATCH 14/15] fix typo --- .../cloudflare/src/cli/build/patches/plugins/content-updater.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cloudflare/src/cli/build/patches/plugins/content-updater.ts b/packages/cloudflare/src/cli/build/patches/plugins/content-updater.ts index 273407fe..3de4e945 100644 --- a/packages/cloudflare/src/cli/build/patches/plugins/content-updater.ts +++ b/packages/cloudflare/src/cli/build/patches/plugins/content-updater.ts @@ -59,7 +59,7 @@ export class ContentUpdater { build.onLoad({ filter: /\.(js|mjs|cjs|jsx|ts|tsx)$/ }, async (args: OnLoadArgs) => { let contents = await readFile(args.path, "utf-8"); for (const { filter, namespace, contentFilter, callback } of this.updaters.values()) { - // if the regex is sticky let's max sure we rewind it here + // if the regex is sticky let's make sure we rewind it filter.lastIndex = 0; if (namespace !== undefined && args.namespace !== namespace) { From 107b285a090ee64b07016cb37f767e6bd47be572 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Tue, 11 Feb 2025 21:12:36 +0000 Subject: [PATCH 15/15] use `match` instead of resetting the regex --- .../src/cli/build/patches/plugins/content-updater.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/cloudflare/src/cli/build/patches/plugins/content-updater.ts b/packages/cloudflare/src/cli/build/patches/plugins/content-updater.ts index 3de4e945..964c69e9 100644 --- a/packages/cloudflare/src/cli/build/patches/plugins/content-updater.ts +++ b/packages/cloudflare/src/cli/build/patches/plugins/content-updater.ts @@ -59,16 +59,13 @@ export class ContentUpdater { build.onLoad({ filter: /\.(js|mjs|cjs|jsx|ts|tsx)$/ }, async (args: OnLoadArgs) => { let contents = await readFile(args.path, "utf-8"); for (const { filter, namespace, contentFilter, callback } of this.updaters.values()) { - // if the regex is sticky let's make sure we rewind it - filter.lastIndex = 0; - if (namespace !== undefined && args.namespace !== namespace) { continue; } - if (!filter.test(args.path)) { + if (!args.path.match(filter)) { continue; } - if (!contentFilter.test(contents)) { + if (!contents.match(contentFilter)) { continue; } contents = (await callback({ contents, path: args.path })) ?? contents;