@opennextjs/cloudflare 0.6.4 → 0.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/cloudflare-context.d.ts +3 -1
- package/dist/api/cloudflare-context.js +12 -5
- package/dist/api/durable-objects/queue.js +4 -6
- package/dist/api/durable-objects/sharded-tag-cache.js +3 -4
- package/dist/api/overrides/tag-cache/d1-next-tag-cache.js +3 -5
- package/dist/cli/build/bundle-server.js +1 -1
- package/dist/cli/build/open-next/createServerBundle.js +12 -8
- package/dist/cli/build/patches/plugins/dynamic-requires.js +16 -2
- package/dist/cli/build/patches/plugins/next-minimal.js +11 -19
- package/dist/cli/templates/worker.js +7 -1
- package/package.json +3 -3
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { GetPlatformProxyOptions } from "wrangler";
|
|
1
2
|
import type { DurableObjectQueueHandler } from "./durable-objects/queue";
|
|
2
3
|
import { DOShardedTagCache } from "./durable-objects/sharded-tag-cache";
|
|
3
4
|
declare global {
|
|
@@ -49,5 +50,6 @@ export declare function getCloudflareContext<CfProperties extends Record<string,
|
|
|
49
50
|
* with the open-next Cloudflare adapter
|
|
50
51
|
*
|
|
51
52
|
* Note: this function should only be called inside the Next.js config file, and although async it doesn't need to be `await`ed
|
|
53
|
+
* @param options options on how the function should operate and if/where to persist the platform data
|
|
52
54
|
*/
|
|
53
|
-
export declare function initOpenNextCloudflareForDev(): Promise<void>;
|
|
55
|
+
export declare function initOpenNextCloudflareForDev(options?: GetPlatformProxyOptions): Promise<void>;
|
|
@@ -71,12 +71,17 @@ async function getCloudflareContextAsync() {
|
|
|
71
71
|
* with the open-next Cloudflare adapter
|
|
72
72
|
*
|
|
73
73
|
* Note: this function should only be called inside the Next.js config file, and although async it doesn't need to be `await`ed
|
|
74
|
+
* @param options options on how the function should operate and if/where to persist the platform data
|
|
74
75
|
*/
|
|
75
|
-
export async function initOpenNextCloudflareForDev() {
|
|
76
|
+
export async function initOpenNextCloudflareForDev(options) {
|
|
76
77
|
const shouldInitializationRun = shouldContextInitializationRun();
|
|
77
78
|
if (!shouldInitializationRun)
|
|
78
79
|
return;
|
|
79
|
-
|
|
80
|
+
if (options?.environment && process.env.NEXT_DEV_WRANGLER_ENV) {
|
|
81
|
+
console.warn(`'initOpenNextCloudflareForDev' has been called with an environment option while NEXT_DEV_WRANGLER_ENV is set.` +
|
|
82
|
+
` NEXT_DEV_WRANGLER_ENV will be ignored and the environment will be set to: '${options.environment}'`);
|
|
83
|
+
}
|
|
84
|
+
const context = await getCloudflareContextFromWrangler(options);
|
|
80
85
|
addCloudflareContextToNodejsGlobal(context);
|
|
81
86
|
await monkeyPatchVmModuleEdgeContext(context);
|
|
82
87
|
}
|
|
@@ -131,12 +136,14 @@ async function monkeyPatchVmModuleEdgeContext(cloudflareContext) {
|
|
|
131
136
|
*
|
|
132
137
|
* @returns the cloudflare context ready for use
|
|
133
138
|
*/
|
|
134
|
-
async function getCloudflareContextFromWrangler() {
|
|
139
|
+
async function getCloudflareContextFromWrangler(options) {
|
|
135
140
|
// Note: we never want wrangler to be bundled in the Next.js app, that's why the import below looks like it does
|
|
136
141
|
const { getPlatformProxy } = await import(/* webpackIgnore: true */ `${"__wrangler".replaceAll("_", "")}`);
|
|
142
|
+
// This allows the selection of a wrangler environment while running in next dev mode
|
|
143
|
+
const environment = options?.environment ?? process.env.NEXT_DEV_WRANGLER_ENV;
|
|
137
144
|
const { env, cf, ctx } = await getPlatformProxy({
|
|
138
|
-
|
|
139
|
-
environment
|
|
145
|
+
...options,
|
|
146
|
+
environment,
|
|
140
147
|
});
|
|
141
148
|
return {
|
|
142
149
|
env,
|
|
@@ -221,13 +221,11 @@ export class DurableObjectQueueHandler extends DurableObject {
|
|
|
221
221
|
try {
|
|
222
222
|
if (this.disableSQLite)
|
|
223
223
|
return false;
|
|
224
|
-
|
|
225
|
-
.exec("SELECT
|
|
226
|
-
.
|
|
227
|
-
return numNewer > 0;
|
|
228
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
224
|
+
return (this.sql
|
|
225
|
+
.exec("SELECT 1 FROM sync WHERE id = ? AND lastSuccess > ? LIMIT 1", `${msg.MessageBody.host}${msg.MessageBody.url}`, Math.round(msg.MessageBody.lastModified / 1000))
|
|
226
|
+
.toArray().length > 0);
|
|
229
227
|
}
|
|
230
|
-
catch
|
|
228
|
+
catch {
|
|
231
229
|
return false;
|
|
232
230
|
}
|
|
233
231
|
}
|
|
@@ -9,10 +9,9 @@ export class DOShardedTagCache extends DurableObject {
|
|
|
9
9
|
});
|
|
10
10
|
}
|
|
11
11
|
async hasBeenRevalidated(tags, lastModified) {
|
|
12
|
-
|
|
13
|
-
.exec(`SELECT
|
|
14
|
-
.
|
|
15
|
-
return result.cnt > 0;
|
|
12
|
+
return (this.sql
|
|
13
|
+
.exec(`SELECT 1 FROM revalidations WHERE tag IN (${tags.map(() => "?").join(", ")}) AND revalidatedAt > ? LIMIT 1`, ...tags, lastModified ?? Date.now())
|
|
14
|
+
.toArray().length > 0);
|
|
16
15
|
}
|
|
17
16
|
async writeTags(tags, lastModified) {
|
|
18
17
|
tags.forEach((tag) => {
|
|
@@ -10,12 +10,10 @@ export class D1NextModeTagCache {
|
|
|
10
10
|
return false;
|
|
11
11
|
try {
|
|
12
12
|
const result = await db
|
|
13
|
-
.prepare(`SELECT
|
|
13
|
+
.prepare(`SELECT 1 FROM revalidations WHERE tag IN (${tags.map(() => "?").join(", ")}) AND revalidatedAt > ? LIMIT 1`)
|
|
14
14
|
.bind(...tags.map((tag) => this.getCacheKey(tag)), lastModified ?? Date.now())
|
|
15
|
-
.
|
|
16
|
-
|
|
17
|
-
throw new RecoverableError(`D1 select failed for ${tags}`);
|
|
18
|
-
return result.cnt > 0;
|
|
15
|
+
.raw();
|
|
16
|
+
return result.length > 0;
|
|
19
17
|
}
|
|
20
18
|
catch (e) {
|
|
21
19
|
error(e);
|
|
@@ -87,7 +87,7 @@ export async function bundleServer(buildOpts) {
|
|
|
87
87
|
// Apply updater updates, must be the last plugin
|
|
88
88
|
updater.plugin,
|
|
89
89
|
],
|
|
90
|
-
external: ["./middleware/handler.mjs"],
|
|
90
|
+
external: ["./middleware/handler.mjs", "*.wasm"],
|
|
91
91
|
alias: {
|
|
92
92
|
// Note: it looks like node-fetch is actually not necessary for us, so we could replace it with an empty shim
|
|
93
93
|
// but just to be safe we replace it with a module that re-exports the native fetch
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
// Adapted for cloudflare workers
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import { loadMiddlewareManifest } from "@opennextjs/aws/adapters/config/util.js";
|
|
5
6
|
import { bundleNextServer } from "@opennextjs/aws/build/bundleNextServer.js";
|
|
6
7
|
import { compileCache } from "@opennextjs/aws/build/compileCache.js";
|
|
7
8
|
import { copyTracedFiles } from "@opennextjs/aws/build/copyTracedFiles.js";
|
|
8
|
-
import { generateEdgeBundle } from "@opennextjs/aws/build/edge/createEdgeBundle.js";
|
|
9
|
+
import { copyMiddlewareResources, generateEdgeBundle } from "@opennextjs/aws/build/edge/createEdgeBundle.js";
|
|
9
10
|
import * as buildHelper from "@opennextjs/aws/build/helper.js";
|
|
10
11
|
import { installDependencies } from "@opennextjs/aws/build/installDeps.js";
|
|
11
12
|
import { applyCodePatches } from "@opennextjs/aws/build/patch/codePatcher.js";
|
|
@@ -86,25 +87,28 @@ async function generateBundle(name, options, fnOptions, codeCustomization) {
|
|
|
86
87
|
// `.next/standalone/package/path` (ie. `.next`, `server.js`).
|
|
87
88
|
// We need to output the handler file inside the package path.
|
|
88
89
|
const packagePath = buildHelper.getPackagePath(options);
|
|
89
|
-
|
|
90
|
+
const outPackagePath = path.join(outputPath, packagePath);
|
|
91
|
+
fs.mkdirSync(outPackagePath, { recursive: true });
|
|
90
92
|
const ext = fnOptions.runtime === "deno" ? "mjs" : "cjs";
|
|
91
|
-
fs.copyFileSync(path.join(options.buildDir, `cache.${ext}`), path.join(
|
|
93
|
+
fs.copyFileSync(path.join(options.buildDir, `cache.${ext}`), path.join(outPackagePath, "cache.cjs"));
|
|
92
94
|
if (fnOptions.runtime === "deno") {
|
|
93
95
|
addDenoJson(outputPath, packagePath);
|
|
94
96
|
}
|
|
95
97
|
// Bundle next server if necessary
|
|
96
98
|
const isBundled = fnOptions.experimentalBundledNextServer ?? false;
|
|
97
99
|
if (isBundled) {
|
|
98
|
-
await bundleNextServer(
|
|
100
|
+
await bundleNextServer(outPackagePath, appPath, {
|
|
99
101
|
minify: options.minify,
|
|
100
102
|
});
|
|
101
103
|
}
|
|
102
104
|
// Copy middleware
|
|
103
105
|
if (!config.middleware?.external) {
|
|
104
|
-
fs.copyFileSync(path.join(options.buildDir, "middleware.mjs"), path.join(
|
|
106
|
+
fs.copyFileSync(path.join(options.buildDir, "middleware.mjs"), path.join(outPackagePath, "middleware.mjs"));
|
|
107
|
+
const middlewareManifest = loadMiddlewareManifest(path.join(options.appBuildOutputPath, ".next"));
|
|
108
|
+
copyMiddlewareResources(options, middlewareManifest.middleware["/"], outPackagePath);
|
|
105
109
|
}
|
|
106
110
|
// Copy open-next.config.mjs
|
|
107
|
-
buildHelper.copyOpenNextConfig(options.buildDir,
|
|
111
|
+
buildHelper.copyOpenNextConfig(options.buildDir, outPackagePath, true);
|
|
108
112
|
// Copy env files
|
|
109
113
|
buildHelper.copyEnvFile(appBuildOutputPath, packagePath, outputPath);
|
|
110
114
|
// Copy all necessary traced files
|
|
@@ -164,7 +168,7 @@ async function generateBundle(name, options, fnOptions, codeCustomization) {
|
|
|
164
168
|
: []),
|
|
165
169
|
openNextEdgePlugins({
|
|
166
170
|
nextDir: path.join(options.appBuildOutputPath, ".next"),
|
|
167
|
-
|
|
171
|
+
isInCloudflare: true,
|
|
168
172
|
}),
|
|
169
173
|
];
|
|
170
174
|
const outfileExt = fnOptions.runtime === "deno" ? "ts" : "mjs";
|
|
@@ -228,7 +232,7 @@ function addMonorepoEntrypoint(outputPath, packagePath) {
|
|
|
228
232
|
// the Lambda function to be able to find the handler at
|
|
229
233
|
// the root of the bundle. We will create a dummy `index.mjs`
|
|
230
234
|
// that re-exports the real handler.
|
|
231
|
-
fs.writeFileSync(path.join(outputPath, "index.mjs"), `export
|
|
235
|
+
fs.writeFileSync(path.join(outputPath, "index.mjs"), `export { handler } from "./${normalizePath(packagePath)}/index.mjs";`);
|
|
232
236
|
}
|
|
233
237
|
async function minifyServerBundle(outputDir) {
|
|
234
238
|
logger.info("Minimizing server function...");
|
|
@@ -82,7 +82,14 @@ rule:
|
|
|
82
82
|
regex: ^NodeModuleLoader$
|
|
83
83
|
fix: |
|
|
84
84
|
async load($ID) {
|
|
85
|
-
${
|
|
85
|
+
${buildOpts.debug
|
|
86
|
+
? ` try {
|
|
87
|
+
${getRequires("$ID", files, serverDir)}
|
|
88
|
+
} catch (e) {
|
|
89
|
+
console.error('Exception in NodeModuleLoader', e);
|
|
90
|
+
throw e;
|
|
91
|
+
}`
|
|
92
|
+
: getRequires("$ID", files, serverDir)}
|
|
86
93
|
}`;
|
|
87
94
|
}
|
|
88
95
|
async function getRequirePageRule(buildOpts) {
|
|
@@ -112,7 +119,14 @@ function requirePage($PAGE, $DIST_DIR, $IS_APP_PATH) {
|
|
|
112
119
|
process.env.__NEXT_PRIVATE_RUNTIME_TYPE = $IS_APP_PATH ? 'app' : 'pages';
|
|
113
120
|
try {
|
|
114
121
|
${getRequires("pagePath", jsFiles, serverDir)}
|
|
115
|
-
}
|
|
122
|
+
} ${buildOpts.debug
|
|
123
|
+
? `
|
|
124
|
+
catch (e) {
|
|
125
|
+
console.error("Exception in requirePage", e);
|
|
126
|
+
throw e;
|
|
127
|
+
}`
|
|
128
|
+
: ``}
|
|
129
|
+
finally {
|
|
116
130
|
process.env.__NEXT_PRIVATE_RUNTIME_TYPE = '';
|
|
117
131
|
}
|
|
118
132
|
}`,
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { patchCode } from "@opennextjs/aws/build/patch/astCodePatcher.js";
|
|
2
|
+
// Remove an instantiation of `AbortController` from the runtime.
|
|
3
|
+
//
|
|
4
|
+
// Solves https://github.com/cloudflare/workerd/issues/3657:
|
|
5
|
+
// - The `AbortController` is meant for the client side, but ends in the server code somehow.
|
|
6
|
+
// That's why we can get ride of it. See https://github.com/vercel/next.js/pull/73975/files.
|
|
7
|
+
// - Top level instantiation of `AbortController` are not supported by workerd as of March, 2025.
|
|
8
|
+
// See https://github.com/cloudflare/workerd/issues/3657
|
|
9
|
+
// - As Next code is not more executed at top level, we do not need to apply this patch
|
|
10
|
+
// See https://github.com/opennextjs/opennextjs-cloudflare/pull/497
|
|
11
|
+
//
|
|
2
12
|
// We try to be as specific as possible to avoid patching the wrong thing here
|
|
3
|
-
// It seems that there is a bug in the worker runtime. When the AbortController is created outside of the request context it throws an error (not sure if it's expected or not) except in this case. https://github.com/cloudflare/workerd/issues/3657
|
|
4
|
-
// It fails while requiring the `app-page.runtime.prod.js` file, but instead of throwing an error, it just return an empty object for the `require('app-page.runtime.prod.js')` call which makes every request to an app router page fail.
|
|
5
|
-
// If it's a bug in workerd and it's not expected to throw an error, we can remove this patch.
|
|
6
13
|
export const abortControllerRule = `
|
|
7
14
|
rule:
|
|
8
15
|
all:
|
|
@@ -65,18 +72,7 @@ fix:
|
|
|
65
72
|
'true'
|
|
66
73
|
`;
|
|
67
74
|
export function patchNextMinimal(updater) {
|
|
68
|
-
updater.updateContent("patch-
|
|
69
|
-
{
|
|
70
|
-
field: {
|
|
71
|
-
filter: /app-page(-experimental)?\.runtime\.prod\.js$/,
|
|
72
|
-
contentFilter: /new AbortController/,
|
|
73
|
-
callback: ({ contents }) => {
|
|
74
|
-
return patchCode(contents, abortControllerRule);
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
},
|
|
78
|
-
]);
|
|
79
|
-
updater.updateContent("patch-next-minimal", [
|
|
75
|
+
return updater.updateContent("patch-next-minimal", [
|
|
80
76
|
{
|
|
81
77
|
field: {
|
|
82
78
|
filter: /next-server\.(js)$/,
|
|
@@ -87,8 +83,4 @@ export function patchNextMinimal(updater) {
|
|
|
87
83
|
},
|
|
88
84
|
},
|
|
89
85
|
]);
|
|
90
|
-
return {
|
|
91
|
-
name: "patch-abortController",
|
|
92
|
-
setup() { },
|
|
93
|
-
};
|
|
94
86
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import process from "node:process";
|
|
2
3
|
// @ts-expect-error: resolved by wrangler build
|
|
3
4
|
import * as nextEnvVars from "./env/next-env.mjs";
|
|
4
5
|
const cloudflareContextALS = new AsyncLocalStorage();
|
|
@@ -54,6 +55,11 @@ function populateProcessEnv(url, env) {
|
|
|
54
55
|
if (processEnvPopulated) {
|
|
55
56
|
return;
|
|
56
57
|
}
|
|
58
|
+
// Some packages rely on `process.version` and `process.versions.node` (i.e. Jose@4)
|
|
59
|
+
// TODO: Remove when https://github.com/unjs/unenv/pull/493 is merged
|
|
60
|
+
Object.assign(process, { version: process.version || "v22.14.0" });
|
|
61
|
+
// @ts-expect-error Node type does not match workerd
|
|
62
|
+
Object.assign(process.versions, { node: "22.14.0", ...process.versions });
|
|
57
63
|
processEnvPopulated = true;
|
|
58
64
|
for (const [key, value] of Object.entries(env)) {
|
|
59
65
|
if (typeof value === "string") {
|
|
@@ -63,7 +69,7 @@ function populateProcessEnv(url, env) {
|
|
|
63
69
|
const mode = env.NEXTJS_ENV ?? "production";
|
|
64
70
|
if (nextEnvVars[mode]) {
|
|
65
71
|
for (const key in nextEnvVars[mode]) {
|
|
66
|
-
process.env[key]
|
|
72
|
+
process.env[key] ??= nextEnvVars[mode][key];
|
|
67
73
|
}
|
|
68
74
|
}
|
|
69
75
|
// Set the default Origin for the origin resolver.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opennextjs/cloudflare",
|
|
3
3
|
"description": "Cloudflare builder for next apps",
|
|
4
|
-
"version": "0.6.
|
|
4
|
+
"version": "0.6.6",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"opennextjs-cloudflare": "dist/cli/index.js"
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"homepage": "https://github.com/opennextjs/opennextjs-cloudflare",
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@dotenvx/dotenvx": "1.31.0",
|
|
46
|
-
"@opennextjs/aws": "https://pkg.pr.new/@opennextjs/aws@
|
|
46
|
+
"@opennextjs/aws": "https://pkg.pr.new/@opennextjs/aws@802",
|
|
47
47
|
"enquirer": "^2.4.1",
|
|
48
48
|
"glob": "^11.0.0"
|
|
49
49
|
},
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"vitest": "^2.1.1"
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
70
|
-
"wrangler": "^3.114.1 || ^4.
|
|
70
|
+
"wrangler": "^3.114.1 || ^4.6.0"
|
|
71
71
|
},
|
|
72
72
|
"scripts": {
|
|
73
73
|
"clean": "rimraf dist",
|