Skip to content

Commit 83abcfe

Browse files
authored
refactor: retrieve cache handler kv instance inside constructor (#72)
1 parent 8f36d47 commit 83abcfe

File tree

11 files changed

+59
-31
lines changed

11 files changed

+59
-31
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@opennextjs/cloudflare": patch
3+
---
4+
5+
refactor: retrieve cache handler kv instance inside constructor
6+
7+
The cache handler was retrieving it's KV instance as a static property on the class that was defined at some point during the execution of the Next.js server. This moves the retrieval of the KV instance to happen inside the constructor for the class, so that it is retrieved during instantiation instead.

packages/cloudflare/env.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ declare global {
55
__NEXT_PRIVATE_STANDALONE_CONFIG?: string;
66
SKIP_NEXT_APP_BUILD?: string;
77
NEXT_PRIVATE_DEBUG_CACHE?: string;
8+
__OPENNEXT_KV_BINDING_NAME: string;
89
[key: string]: string | Fetcher;
910
}
1011
}

packages/cloudflare/src/cli/build/build-worker.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,7 @@ export async function buildWorker(config: Config): Promise<void> {
5151

5252
copyPackageCliFiles(packageDistDir, config);
5353

54-
const templateDir = path.join(config.paths.internalPackage, "cli", "templates");
55-
56-
const workerEntrypoint = path.join(templateDir, "worker.ts");
54+
const workerEntrypoint = path.join(config.paths.internalTemplates, "worker.ts");
5755
const workerOutputFile = path.join(config.paths.builderOutput, "index.mjs");
5856

5957
const nextConfigStr =
@@ -73,20 +71,20 @@ export async function buildWorker(config: Config): Promise<void> {
7371
format: "esm",
7472
target: "esnext",
7573
minify: false,
76-
plugins: [createFixRequiresESBuildPlugin(templateDir)],
74+
plugins: [createFixRequiresESBuildPlugin(config)],
7775
alias: {
7876
// Note: we apply an empty shim to next/dist/compiled/ws because it generates two `eval`s:
7977
// eval("require")("bufferutil");
8078
// eval("require")("utf-8-validate");
81-
"next/dist/compiled/ws": path.join(templateDir, "shims", "empty.ts"),
79+
"next/dist/compiled/ws": path.join(config.paths.internalTemplates, "shims", "empty.ts"),
8280
// Note: we apply an empty shim to next/dist/compiled/edge-runtime since (amongst others) it generated the following `eval`:
8381
// eval(getModuleCode)(module, module.exports, throwingRequire, params.context, ...Object.values(params.scopedContext));
8482
// which comes from https://github.com/vercel/edge-runtime/blob/6e96b55f/packages/primitives/src/primitives/load.js#L57-L63
8583
// QUESTION: Why did I encountered this but mhart didn't?
86-
"next/dist/compiled/edge-runtime": path.join(templateDir, "shims", "empty.ts"),
84+
"next/dist/compiled/edge-runtime": path.join(config.paths.internalTemplates, "shims", "empty.ts"),
8785
// `@next/env` is a library Next.js uses for loading dotenv files, for obvious reasons we need to stub it here
8886
// source: https://github.com/vercel/next.js/tree/0ac10d79720/packages/next-env
89-
"@next/env": path.join(templateDir, "shims", "env.ts"),
87+
"@next/env": path.join(config.paths.internalTemplates, "shims", "env.ts"),
9088
},
9189
define: {
9290
// config file used by Next.js, see: https://github.com/vercel/next.js/blob/68a7128/packages/next/src/build/utils.ts#L2137-L2139
@@ -167,23 +165,23 @@ async function updateWorkerBundledCode(workerOutputFile: string, config: Config)
167165
patchedCode = inlineNextRequire(patchedCode, config);
168166
patchedCode = patchFindDir(patchedCode, config);
169167
patchedCode = inlineEvalManifest(patchedCode, config);
170-
patchedCode = patchCache(patchedCode, config);
168+
patchedCode = await patchCache(patchedCode, config);
171169
patchedCode = inlineMiddlewareManifestRequire(patchedCode, config);
172170
patchedCode = patchExceptionBubbling(patchedCode);
173171

174172
await writeFile(workerOutputFile, patchedCode);
175173
}
176174

177-
function createFixRequiresESBuildPlugin(templateDir: string): Plugin {
175+
function createFixRequiresESBuildPlugin(config: Config): Plugin {
178176
return {
179177
name: "replaceRelative",
180178
setup(build) {
181179
// Note: we (empty) shim require-hook modules as they generate problematic code that uses requires
182180
build.onResolve({ filter: /^\.\/require-hook$/ }, () => ({
183-
path: path.join(templateDir, "shims", "empty.ts"),
181+
path: path.join(config.paths.internalTemplates, "shims", "empty.ts"),
184182
}));
185183
build.onResolve({ filter: /\.\/lib\/node-fs-methods$/ }, () => ({
186-
path: path.join(templateDir, "shims", "empty.ts"),
184+
path: path.join(config.paths.internalTemplates, "shims", "empty.ts"),
187185
}));
188186
},
189187
};

packages/cloudflare/src/cli/build/patches/investigated/patch-cache.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,38 @@
11
import { Config } from "../../../config";
2-
import path from "node:path";
2+
import { build } from "esbuild";
3+
import { join } from "node:path";
34

45
/**
56
* Install the cloudflare KV cache handler
67
*/
7-
export function patchCache(code: string, config: Config): string {
8+
export async function patchCache(code: string, config: Config): Promise<string> {
89
console.log("# patchCache");
910

10-
const cacheHandler = path.join(config.paths.internalPackage, "cli", "cache-handler", "index.mjs");
11+
const cacheHandlerFileName = "cache-handler.mjs";
12+
const cacheHandlerEntrypoint = join(config.paths.internalTemplates, "cache-handler", "index.ts");
13+
const cacheHandlerOutputFile = join(config.paths.builderOutput, cacheHandlerFileName);
14+
15+
await build({
16+
entryPoints: [cacheHandlerEntrypoint],
17+
bundle: true,
18+
outfile: cacheHandlerOutputFile,
19+
format: "esm",
20+
target: "esnext",
21+
minify: true,
22+
define: {
23+
"process.env.__OPENNEXT_KV_BINDING_NAME": `"${config.cache.kvBindingName}"`,
24+
},
25+
});
1126

1227
const patchedCode = code.replace(
1328
"const { cacheHandler } = this.nextConfig;",
1429
`const cacheHandler = null;
15-
CacheHandler = (await import('${cacheHandler}')).OpenNextCacheHandler;
16-
CacheHandler.maybeKVNamespace = process.env["${config.cache.kvBindingName}"];
30+
CacheHandler = (await import('./${cacheHandlerFileName}')).OpenNextCacheHandler;
1731
`
1832
);
1933

2034
if (patchedCode === code) {
21-
throw new Error("Cache patch not applied");
35+
throw new Error("Patch `patchCache` not applied");
2236
}
2337

2438
return patchedCode;

packages/cloudflare/src/cli/build/utils/copy-prerendered-routes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { NEXT_META_SUFFIX, SEED_DATA_DIR } from "../../cache-handler";
1+
import { NEXT_META_SUFFIX, SEED_DATA_DIR } from "../../constants/incremental-cache";
22
import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
33
import { dirname, join } from "node:path";
44
import { Config } from "../../config";

packages/cloudflare/src/cli/config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ export type Config = {
3131
standaloneAppServer: string;
3232
// Package in the standalone node_modules
3333
internalPackage: string;
34+
// Templates in the package in the standalone node_modules
35+
internalTemplates: string;
3436
};
3537

3638
cache: {
@@ -59,6 +61,7 @@ export function getConfig(appDir: string, outputDir: string): Config {
5961

6062
const nodeModules = path.join(standaloneApp, "node_modules");
6163
const internalPackage = path.join(nodeModules, ...PACKAGE_NAME.split("/"));
64+
const internalTemplates = path.join(internalPackage, "cli", "templates");
6265

6366
return {
6467
buildTimestamp: Date.now(),
@@ -72,6 +75,7 @@ export function getConfig(appDir: string, outputDir: string): Config {
7275
standaloneAppDotNext,
7376
standaloneAppServer,
7477
internalPackage,
78+
internalTemplates,
7579
},
7680

7781
cache: {
File renamed without changes.
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
export * from "./constants";
21
export * from "./open-next-cache-handler";

packages/cloudflare/src/cli/cache-handler/open-next-cache-handler.ts renamed to packages/cloudflare/src/cli/templates/cache-handler/open-next-cache-handler.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,32 +10,34 @@ import {
1010
RSC_PREFETCH_SUFFIX,
1111
RSC_SUFFIX,
1212
SEED_DATA_DIR,
13-
} from "./constants";
13+
} from "../../constants/incremental-cache";
1414
import { getSeedBodyFile, getSeedMetaFile, getSeedTextFile, parseCtx } from "./utils";
1515
import type { IncrementalCacheValue } from "next/dist/server/response-cache";
16-
import { KVNamespace } from "@cloudflare/workers-types";
16+
import type { KVNamespace } from "@cloudflare/workers-types";
1717

1818
type CacheEntry = {
1919
lastModified: number;
2020
value: IncrementalCacheValue | null;
2121
};
2222

2323
export class OpenNextCacheHandler implements CacheHandler {
24-
static maybeKVNamespace: KVNamespace | undefined = undefined;
24+
protected kv: KVNamespace | undefined;
2525

2626
protected debug: boolean = !!process.env.NEXT_PRIVATE_DEBUG_CACHE;
2727

28-
constructor(protected ctx: CacheHandlerContext) {}
28+
constructor(protected ctx: CacheHandlerContext) {
29+
this.kv = process.env[process.env.__OPENNEXT_KV_BINDING_NAME] as KVNamespace | undefined;
30+
}
2931

3032
async get(...args: Parameters<CacheHandler["get"]>): Promise<CacheHandlerValue | null> {
3133
const [key, _ctx] = args;
3234
const ctx = parseCtx(_ctx);
3335

3436
if (this.debug) console.log(`cache - get: ${key}, ${ctx?.kind}`);
3537

36-
if (OpenNextCacheHandler.maybeKVNamespace !== undefined) {
38+
if (this.kv !== undefined) {
3739
try {
38-
const value = await OpenNextCacheHandler.maybeKVNamespace.get<CacheEntry>(key, "json");
40+
const value = await this.kv.get<CacheEntry>(key, "json");
3941
if (value) return value;
4042
} catch (e) {
4143
console.error(`Failed to get value for key = ${key}: ${e}`);
@@ -115,7 +117,7 @@ export class OpenNextCacheHandler implements CacheHandler {
115117
// eslint-disable-next-line @typescript-eslint/no-unused-vars
116118
const [key, entry, _ctx] = args;
117119

118-
if (OpenNextCacheHandler.maybeKVNamespace === undefined) {
120+
if (this.kv === undefined) {
119121
return;
120122
}
121123

@@ -127,15 +129,15 @@ export class OpenNextCacheHandler implements CacheHandler {
127129
};
128130

129131
try {
130-
await OpenNextCacheHandler.maybeKVNamespace.put(key, JSON.stringify(data));
132+
await this.kv.put(key, JSON.stringify(data));
131133
} catch (e) {
132134
console.error(`Failed to set value for key = ${key}: ${e}`);
133135
}
134136
}
135137

136138
async revalidateTag(...args: Parameters<CacheHandler["revalidateTag"]>) {
137139
const [tags] = args;
138-
if (OpenNextCacheHandler.maybeKVNamespace === undefined) {
140+
if (this.kv === undefined) {
139141
return;
140142
}
141143

packages/cloudflare/src/cli/cache-handler/utils.ts renamed to packages/cloudflare/src/cli/templates/cache-handler/utils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { IncrementalCache } from "next/dist/server/lib/incremental-cache";
2-
import { NEXT_META_SUFFIX } from "./constants";
1+
import type { IncrementalCache } from "next/dist/server/lib/incremental-cache";
2+
import { NEXT_META_SUFFIX } from "../../constants/incremental-cache";
33

44
type PrerenderedRouteMeta = {
55
lastModified: number;

0 commit comments

Comments
 (0)