@opennextjs/cloudflare 1.1.0 → 1.2.1

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.
Files changed (31) hide show
  1. package/dist/api/config.d.ts +7 -1
  2. package/dist/api/config.js +2 -2
  3. package/dist/cli/build/bundle-server.d.ts +1 -1
  4. package/dist/cli/build/bundle-server.js +22 -38
  5. package/dist/cli/build/open-next/createServerBundle.js +9 -10
  6. package/dist/cli/build/patches/index.d.ts +0 -1
  7. package/dist/cli/build/patches/index.js +0 -1
  8. package/dist/cli/build/patches/investigated/index.d.ts +0 -1
  9. package/dist/cli/build/patches/investigated/index.js +0 -1
  10. package/dist/cli/build/patches/plugins/load-manifest.d.ts +3 -1
  11. package/dist/cli/build/patches/plugins/load-manifest.js +49 -7
  12. package/dist/cli/build/patches/plugins/next-server.d.ts +25 -0
  13. package/dist/cli/build/patches/plugins/next-server.js +110 -0
  14. package/dist/cli/build/patches/plugins/next-server.spec.js +429 -0
  15. package/dist/cli/build/patches/plugins/open-next.d.ts +8 -0
  16. package/dist/cli/build/patches/plugins/open-next.js +38 -0
  17. package/dist/cli/build/utils/apply-patches.js +0 -1
  18. package/dist/cli/templates/init.js +5 -0
  19. package/dist/cli/templates/shims/throw.d.ts +2 -0
  20. package/dist/cli/templates/shims/throw.js +2 -0
  21. package/package.json +3 -3
  22. package/dist/cli/build/patches/investigated/patch-cache.d.ts +0 -14
  23. package/dist/cli/build/patches/investigated/patch-cache.js +0 -40
  24. package/dist/cli/build/patches/plugins/build-id.d.ts +0 -6
  25. package/dist/cli/build/patches/plugins/build-id.js +0 -29
  26. package/dist/cli/build/patches/plugins/build-id.spec.js +0 -82
  27. package/dist/cli/build/patches/plugins/eval-manifest.d.ts +0 -7
  28. package/dist/cli/build/patches/plugins/eval-manifest.js +0 -61
  29. package/dist/cli/build/patches/to-investigate/inline-middleware-manifest.d.ts +0 -6
  30. package/dist/cli/build/patches/to-investigate/inline-middleware-manifest.js +0 -15
  31. /package/dist/cli/build/patches/plugins/{build-id.spec.d.ts → next-server.spec.d.ts} +0 -0
@@ -1,5 +1,5 @@
1
1
  import type { BuildOptions } from "@opennextjs/aws/build/helper";
2
- import { BaseOverride, LazyLoadedOverride, OpenNextConfig as AwsOpenNextConfig } from "@opennextjs/aws/types/open-next";
2
+ import { BaseOverride, LazyLoadedOverride, OpenNextConfig as AwsOpenNextConfig, type RoutePreloadingBehavior } from "@opennextjs/aws/types/open-next";
3
3
  import type { CDNInvalidationHandler, IncrementalCache, Queue, TagCache } from "@opennextjs/aws/types/overrides";
4
4
  export type Override<T extends BaseOverride> = "dummy" | T | LazyLoadedOverride<T>;
5
5
  /**
@@ -30,6 +30,12 @@ export type CloudflareOverrides = {
30
30
  * @default false
31
31
  */
32
32
  enableCacheInterception?: boolean;
33
+ /**
34
+ * Route preloading behavior.
35
+ * Using a value other than "none" can result in higher CPU usage on cold starts.
36
+ * @default "none"
37
+ */
38
+ routePreloadingBehavior?: RoutePreloadingBehavior;
33
39
  };
34
40
  /**
35
41
  * Defines the OpenNext configuration that targets the Cloudflare adapter
@@ -5,7 +5,7 @@
5
5
  * @returns the OpenNext configuration object
6
6
  */
7
7
  export function defineCloudflareConfig(config = {}) {
8
- const { incrementalCache, tagCache, queue, cachePurge, enableCacheInterception = false } = config;
8
+ const { incrementalCache, tagCache, queue, cachePurge, enableCacheInterception = false, routePreloadingBehavior = "none", } = config;
9
9
  return {
10
10
  default: {
11
11
  override: {
@@ -17,7 +17,7 @@ export function defineCloudflareConfig(config = {}) {
17
17
  queue: resolveQueue(queue),
18
18
  cdnInvalidation: resolveCdnInvalidation(cachePurge),
19
19
  },
20
- routePreloadingBehavior: "withWaitUntil",
20
+ routePreloadingBehavior,
21
21
  },
22
22
  // node:crypto is used to compute cache keys
23
23
  edgeExternals: ["node:crypto"],
@@ -6,7 +6,7 @@ export declare function bundleServer(buildOpts: BuildOptions): Promise<void>;
6
6
  /**
7
7
  * This function applies patches required for the code to run on workers.
8
8
  */
9
- export declare function updateWorkerBundledCode(workerOutputFile: string, buildOpts: BuildOptions): Promise<void>;
9
+ export declare function updateWorkerBundledCode(workerOutputFile: string): Promise<void>;
10
10
  /**
11
11
  * Gets the path of the worker.js file generated by the build process
12
12
  *
@@ -9,12 +9,12 @@ import { getOpenNextConfig } from "../../api/config.js";
9
9
  import { patchVercelOgLibrary } from "./patches/ast/patch-vercel-og-library.js";
10
10
  import { patchWebpackRuntime } from "./patches/ast/webpack-runtime.js";
11
11
  import * as patches from "./patches/index.js";
12
- import { inlineBuildId } from "./patches/plugins/build-id.js";
13
12
  import { inlineDynamicRequires } from "./patches/plugins/dynamic-requires.js";
14
- import { inlineEvalManifest } from "./patches/plugins/eval-manifest.js";
15
13
  import { inlineFindDir } from "./patches/plugins/find-dir.js";
16
14
  import { patchInstrumentation } from "./patches/plugins/instrumentation.js";
17
15
  import { inlineLoadManifest } from "./patches/plugins/load-manifest.js";
16
+ import { patchNextServer } from "./patches/plugins/next-server.js";
17
+ import { patchResolveCache } from "./patches/plugins/open-next.js";
18
18
  import { handleOptionalDependencies } from "./patches/plugins/optional-deps.js";
19
19
  import { patchPagesRouterContext } from "./patches/plugins/pages-router-context.js";
20
20
  import { patchDepdDeprecations } from "./patches/plugins/patch-depd-deprecations.js";
@@ -42,7 +42,7 @@ const optionalDependencies = [
42
42
  */
43
43
  export async function bundleServer(buildOpts) {
44
44
  patches.copyPackageCliFiles(packageDistDir, buildOpts);
45
- const { appPath, outputDir, monorepoRoot } = buildOpts;
45
+ const { appPath, outputDir, monorepoRoot, debug } = buildOpts;
46
46
  const baseManifestPath = path.join(outputDir, "server-functions/default", getPackagePath(buildOpts), ".next");
47
47
  const serverFiles = path.join(baseManifestPath, "required-server-files.json");
48
48
  const nextConfig = JSON.parse(fs.readFileSync(serverFiles, "utf-8")).config;
@@ -60,7 +60,11 @@ export async function bundleServer(buildOpts) {
60
60
  outfile: openNextServerBundle,
61
61
  format: "esm",
62
62
  target: "esnext",
63
- minify: false,
63
+ // Minify code as much as possible but stay safe by not renaming identifiers
64
+ minifyWhitespace: !debug,
65
+ minifyIdentifiers: false,
66
+ minifySyntax: !debug,
67
+ legalComments: "none",
64
68
  metafile: true,
65
69
  // Next traces files using the default conditions from `nft` (`node`, `require`, `import` and `default`)
66
70
  //
@@ -80,31 +84,28 @@ export async function bundleServer(buildOpts) {
80
84
  handleOptionalDependencies(optionalDependencies),
81
85
  patchInstrumentation(updater, buildOpts),
82
86
  patchPagesRouterContext(buildOpts),
83
- inlineEvalManifest(updater, buildOpts),
84
87
  inlineFindDir(updater, buildOpts),
85
88
  inlineLoadManifest(updater, buildOpts),
86
- inlineBuildId(updater),
89
+ patchNextServer(updater, buildOpts),
87
90
  patchDepdDeprecations(updater),
91
+ patchResolveCache(updater, buildOpts),
88
92
  // Apply updater updates, must be the last plugin
89
93
  updater.plugin,
90
94
  ],
91
95
  external: ["./middleware/handler.mjs"],
92
96
  alias: {
93
- // Note: it looks like node-fetch is actually not necessary for us, so we could replace it with an empty shim
94
- // but just to be safe we replace it with a module that re-exports the native fetch
95
- // we do this to both save on bundle size (there isn't really any benefit in us shipping the node-fetch code)
96
- // and also get rid of a warning in the terminal caused by the package (because it performs an === comparison with -0)
97
+ // Workers have `fetch` so the `node-fetch` polyfill is not needed
97
98
  "next/dist/compiled/node-fetch": path.join(buildOpts.outputDir, "cloudflare-templates/shims/fetch.js"),
98
- // Note: we apply an empty shim to next/dist/compiled/ws because it generates two `eval`s:
99
- // eval("require")("bufferutil");
100
- // eval("require")("utf-8-validate");
99
+ // Workers have builtin Web Sockets
101
100
  "next/dist/compiled/ws": path.join(buildOpts.outputDir, "cloudflare-templates/shims/empty.js"),
102
- // Note: we apply an empty shim to next/dist/compiled/edge-runtime since (amongst others) it generated the following `eval`:
103
- // eval(getModuleCode)(module, module.exports, throwingRequire, params.context, ...Object.values(params.scopedContext));
104
- // which comes from https://github.com/vercel/edge-runtime/blob/6e96b55f/packages/primitives/src/primitives/load.js#L57-L63
101
+ // The toolbox optimizer pulls severals MB of dependencies (`caniuse-lite`, `terser`, `acorn`, ...)
102
+ // Drop it to optimize the code size
103
+ // See https://github.com/vercel/next.js/blob/6eb235c/packages/next/src/server/optimize-amp.ts
104
+ "next/dist/compiled/@ampproject/toolbox-optimizer": path.join(buildOpts.outputDir, "cloudflare-templates/shims/throw.js"),
105
+ // The edge runtime is not supported
105
106
  "next/dist/compiled/edge-runtime": path.join(buildOpts.outputDir, "cloudflare-templates/shims/empty.js"),
106
- // `@next/env` is a library Next.js uses for loading dotenv files, for obvious reasons we need to stub it here
107
- // source: https://github.com/vercel/next.js/tree/0ac10d79720/packages/next-env
107
+ // `@next/env` is used by Next to load environment variables from files.
108
+ // OpenNext inlines the values at build time so this is not needed.
108
109
  "@next/env": path.join(buildOpts.outputDir, "cloudflare-templates/shims/env.js"),
109
110
  },
110
111
  define: {
@@ -133,7 +134,7 @@ export async function bundleServer(buildOpts) {
133
134
  platform: "node",
134
135
  });
135
136
  fs.writeFileSync(openNextServerBundle + ".meta.json", JSON.stringify(result.metafile, null, 2));
136
- await updateWorkerBundledCode(openNextServerBundle, buildOpts);
137
+ await updateWorkerBundledCode(openNextServerBundle);
137
138
  const isMonorepo = monorepoRoot !== appPath;
138
139
  if (isMonorepo) {
139
140
  fs.writeFileSync(path.join(outputPath, "handler.mjs"), `export { handler } from "./${normalizePath(packagePath)}/handler.mjs";`);
@@ -143,27 +144,10 @@ export async function bundleServer(buildOpts) {
143
144
  /**
144
145
  * This function applies patches required for the code to run on workers.
145
146
  */
146
- export async function updateWorkerBundledCode(workerOutputFile, buildOpts) {
147
+ export async function updateWorkerBundledCode(workerOutputFile) {
147
148
  const code = await readFile(workerOutputFile, "utf8");
148
149
  const patchedCode = await patchCodeWithValidations(code, [
149
- ["require", patches.patchRequire],
150
- ["cacheHandler", (code) => patches.patchCache(code, buildOpts)],
151
- ["composableCache", (code) => patches.patchComposableCache(code, buildOpts), { isOptional: true }],
152
- [
153
- "'require(this.middlewareManifestPath)'",
154
- (code) => patches.inlineMiddlewareManifestRequire(code, buildOpts),
155
- { isOptional: true },
156
- ],
157
- [
158
- "`require.resolve` call",
159
- // workers do not support dynamic require nor require.resolve
160
- (code) => code.replace('require.resolve("./cache.cjs")', '"unused"'),
161
- ],
162
- [
163
- "`require.resolve composable cache` call",
164
- // workers do not support dynamic require nor require.resolve
165
- (code) => code.replace('require.resolve("./composable-cache.cjs")', '"unused"'),
166
- ],
150
+ ["require", patches.patchRequire, { isOptional: true }],
167
151
  ]);
168
152
  await writeFile(workerOutputFile, patchedCode);
169
153
  }
@@ -10,9 +10,7 @@ import { copyMiddlewareResources, generateEdgeBundle } from "@opennextjs/aws/bui
10
10
  import * as buildHelper from "@opennextjs/aws/build/helper.js";
11
11
  import { installDependencies } from "@opennextjs/aws/build/installDeps.js";
12
12
  import { applyCodePatches } from "@opennextjs/aws/build/patch/codePatcher.js";
13
- import { patchEnvVars, patchFetchCacheForISR, patchFetchCacheSetMissingWaitUntil, patchNextServer, patchUnstableCacheForISR, patchUseCacheForISR, } from "@opennextjs/aws/build/patch/patches/index.js";
14
- // TODO: import from patches/index.js when https://github.com/opennextjs/opennextjs-aws/pull/827 is released
15
- import { patchBackgroundRevalidation } from "@opennextjs/aws/build/patch/patches/patchBackgroundRevalidation.js";
13
+ import * as awsPatches from "@opennextjs/aws/build/patch/patches/index.js";
16
14
  import logger from "@opennextjs/aws/logger.js";
17
15
  import { minifyAll } from "@opennextjs/aws/minimize-js.js";
18
16
  import { openNextEdgePlugins } from "@opennextjs/aws/plugins/edge.js";
@@ -133,13 +131,14 @@ async function generateBundle(name, options, fnOptions, codeCustomization) {
133
131
  }
134
132
  const additionalCodePatches = codeCustomization?.additionalCodePatches ?? [];
135
133
  await applyCodePatches(options, tracedFiles, manifests, [
136
- patchFetchCacheSetMissingWaitUntil,
137
- patchFetchCacheForISR,
138
- patchUnstableCacheForISR,
139
- patchUseCacheForISR,
140
- patchNextServer,
141
- patchEnvVars,
142
- patchBackgroundRevalidation,
134
+ awsPatches.patchFetchCacheSetMissingWaitUntil,
135
+ awsPatches.patchFetchCacheForISR,
136
+ awsPatches.patchUnstableCacheForISR,
137
+ awsPatches.patchUseCacheForISR,
138
+ awsPatches.patchNextServer,
139
+ awsPatches.patchEnvVars,
140
+ awsPatches.patchBackgroundRevalidation,
141
+ awsPatches.patchDropBabel,
143
142
  // Cloudflare specific patches
144
143
  patchResRevalidate,
145
144
  patchUseCacheIO,
@@ -1,2 +1 @@
1
1
  export * from "./investigated/index.js";
2
- export * from "./to-investigate/inline-middleware-manifest.js";
@@ -1,2 +1 @@
1
1
  export * from "./investigated/index.js";
2
- export * from "./to-investigate/inline-middleware-manifest.js";
@@ -1,3 +1,2 @@
1
1
  export * from "./copy-package-cli-files.js";
2
- export * from "./patch-cache.js";
3
2
  export * from "./patch-require.js";
@@ -1,3 +1,2 @@
1
1
  export * from "./copy-package-cli-files.js";
2
- export * from "./patch-cache.js";
3
2
  export * from "./patch-require.js";
@@ -1,5 +1,7 @@
1
1
  /**
2
- * Inline `loadManifest` as it relies on `readFileSync` that is not supported by workerd.
2
+ * Inline `loadManifest` and `evalManifest` from `load-manifest.js`
3
+ *
4
+ * They rely on `readFileSync` that is not supported by workerd.
3
5
  */
4
6
  import { type BuildOptions } from "@opennextjs/aws/build/helper.js";
5
7
  import type { ContentUpdater, Plugin } from "@opennextjs/aws/plugins/content-updater.js";
@@ -1,5 +1,7 @@
1
1
  /**
2
- * Inline `loadManifest` as it relies on `readFileSync` that is not supported by workerd.
2
+ * Inline `loadManifest` and `evalManifest` from `load-manifest.js`
3
+ *
4
+ * They rely on `readFileSync` that is not supported by workerd.
3
5
  */
4
6
  import { readFile } from "node:fs/promises";
5
7
  import { join, posix, relative, sep } from "node:path";
@@ -16,21 +18,24 @@ export function inlineLoadManifest(updater, buildOpts) {
16
18
  escape: false,
17
19
  }),
18
20
  contentFilter: /function loadManifest\(/,
19
- callback: async ({ contents }) => patchCode(contents, await getRule(buildOpts)),
21
+ callback: async ({ contents }) => {
22
+ contents = await patchCode(contents, await getLoadManifestRule(buildOpts));
23
+ contents = await patchCode(contents, await getEvalManifestRule(buildOpts));
24
+ return contents;
25
+ },
20
26
  },
21
27
  },
22
28
  ]);
23
29
  }
24
- async function getRule(buildOpts) {
30
+ async function getLoadManifestRule(buildOpts) {
25
31
  const { outputDir } = buildOpts;
26
32
  const baseDir = join(outputDir, "server-functions/default", getPackagePath(buildOpts));
27
33
  const dotNextDir = join(baseDir, ".next");
28
34
  const manifests = await glob(join(dotNextDir, "**/*-manifest.json"), { windowsPathsNoEscape: true });
29
35
  const returnManifests = (await Promise.all(manifests.map(async (manifest) => `
30
- if ($PATH.endsWith("${normalizePath("/" + relative(dotNextDir, manifest))}")) {
31
- return ${await readFile(manifest, "utf-8")};
32
- }
33
- `))).join("\n");
36
+ if ($PATH.endsWith("${normalizePath("/" + relative(dotNextDir, manifest))}")) {
37
+ return ${await readFile(manifest, "utf-8")};
38
+ }`))).join("\n");
34
39
  return {
35
40
  rule: {
36
41
  pattern: `
@@ -46,3 +51,40 @@ function loadManifest($PATH, $$$ARGS) {
46
51
  }`,
47
52
  };
48
53
  }
54
+ async function getEvalManifestRule(buildOpts) {
55
+ const { outputDir } = buildOpts;
56
+ const baseDir = join(outputDir, "server-functions/default", getPackagePath(buildOpts), ".next");
57
+ const appDir = join(baseDir, "server/app");
58
+ const manifests = await glob(join(baseDir, "**/*_client-reference-manifest.js"), {
59
+ windowsPathsNoEscape: true,
60
+ });
61
+ const returnManifests = manifests
62
+ .map((manifest) => {
63
+ const endsWith = normalizePath(relative(baseDir, manifest));
64
+ const key = normalizePath("/" + relative(appDir, manifest)).replace("_client-reference-manifest.js", "");
65
+ return `
66
+ if ($PATH.endsWith("${endsWith}")) {
67
+ require(${JSON.stringify(manifest)});
68
+ return {
69
+ __RSC_MANIFEST: {
70
+ "${key}": globalThis.__RSC_MANIFEST["${key}"],
71
+ },
72
+ };
73
+ }`;
74
+ })
75
+ .join("\n");
76
+ return {
77
+ rule: {
78
+ pattern: `
79
+ function evalManifest($PATH, $$$ARGS) {
80
+ $$$_
81
+ }`,
82
+ },
83
+ fix: `
84
+ function evalManifest($PATH, $$$ARGS) {
85
+ $PATH = $PATH.replaceAll(${JSON.stringify(sep)}, ${JSON.stringify(posix.sep)});
86
+ ${returnManifests}
87
+ throw new Error(\`Unexpected evalManifest(\${$PATH}) call!\`);
88
+ }`,
89
+ };
90
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Misc patches for `next-server.js`
3
+ *
4
+ * Note: we will probably need to revisit the patches when the Next adapter API lands
5
+ *
6
+ * - Inline `getBuildId` as it relies on `readFileSync` that is not supported by workerd
7
+ * - Inline the middleware manifest
8
+ * - Override the cache and composable cache handlers
9
+ */
10
+ import { type BuildOptions } from "@opennextjs/aws/build/helper.js";
11
+ import type { ContentUpdater, Plugin } from "@opennextjs/aws/plugins/content-updater.js";
12
+ export declare function patchNextServer(updater: ContentUpdater, buildOpts: BuildOptions): Plugin;
13
+ export declare const buildIdRule = "\nrule:\n pattern:\n selector: method_definition\n context: \"class { getBuildId($$$PARAMS) { $$$_ } }\"\nfix: |-\n getBuildId($$$PARAMS) {\n return process.env.NEXT_BUILD_ID;\n }\n";
14
+ export declare function createMiddlewareManifestRule(manifest: unknown): string;
15
+ /**
16
+ * The cache handler used by Next.js is normally defined in the config file as a path. At runtime,
17
+ * Next.js would then do a dynamic require on a transformed version of the path to retrieve the
18
+ * cache handler and create a new instance of it.
19
+ *
20
+ * This is problematic in workerd due to the dynamic import of the file that is not known from
21
+ * build-time. Therefore, we have to manually override the default way that the cache handler is
22
+ * instantiated with a dynamic require that uses a string literal for the path.
23
+ */
24
+ export declare function createCacheHandlerRule(handlerPath: string): string;
25
+ export declare function createComposableCacheHandlersRule(handlerPath: string): string;
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Misc patches for `next-server.js`
3
+ *
4
+ * Note: we will probably need to revisit the patches when the Next adapter API lands
5
+ *
6
+ * - Inline `getBuildId` as it relies on `readFileSync` that is not supported by workerd
7
+ * - Inline the middleware manifest
8
+ * - Override the cache and composable cache handlers
9
+ */
10
+ import { existsSync, readFileSync } from "node:fs";
11
+ import path from "node:path";
12
+ import { getPackagePath } from "@opennextjs/aws/build/helper.js";
13
+ import { patchCode } from "@opennextjs/aws/build/patch/astCodePatcher.js";
14
+ import { getCrossPlatformPathRegex } from "@opennextjs/aws/utils/regex.js";
15
+ import { normalizePath } from "../../utils/index.js";
16
+ export function patchNextServer(updater, buildOpts) {
17
+ return updater.updateContent("next-server", [
18
+ {
19
+ field: {
20
+ filter: getCrossPlatformPathRegex(String.raw `/next/dist/server/next-server\.js$`, {
21
+ escape: false,
22
+ }),
23
+ contentFilter: /getBuildId\(/,
24
+ callback: async ({ contents }) => {
25
+ const { outputDir } = buildOpts;
26
+ contents = patchCode(contents, buildIdRule);
27
+ const manifestPath = path.join(outputDir, "server-functions/default", getPackagePath(buildOpts), ".next/server/middleware-manifest.json");
28
+ const manifest = existsSync(manifestPath)
29
+ ? JSON.parse(await readFileSync(manifestPath, "utf-8"))
30
+ : {};
31
+ contents = patchCode(contents, createMiddlewareManifestRule(manifest));
32
+ const outputPath = path.join(outputDir, "server-functions/default");
33
+ const cacheHandler = path.join(outputPath, getPackagePath(buildOpts), "cache.cjs");
34
+ contents = patchCode(contents, createCacheHandlerRule(cacheHandler));
35
+ const composableCacheHandler = path.join(outputPath, getPackagePath(buildOpts), "composable-cache.cjs");
36
+ contents = patchCode(contents, createComposableCacheHandlersRule(composableCacheHandler));
37
+ return contents;
38
+ },
39
+ },
40
+ },
41
+ ]);
42
+ }
43
+ export const buildIdRule = `
44
+ rule:
45
+ pattern:
46
+ selector: method_definition
47
+ context: "class { getBuildId($$$PARAMS) { $$$_ } }"
48
+ fix: |-
49
+ getBuildId($$$PARAMS) {
50
+ return process.env.NEXT_BUILD_ID;
51
+ }
52
+ `;
53
+ export function createMiddlewareManifestRule(manifest) {
54
+ return `
55
+ rule:
56
+ pattern:
57
+ selector: method_definition
58
+ context: "class { getMiddlewareManifest($$$PARAMS) { $$$_ } }"
59
+ fix: |-
60
+ getMiddlewareManifest($$$PARAMS) {
61
+ return ${JSON.stringify(manifest)};
62
+ }
63
+ `;
64
+ }
65
+ /**
66
+ * The cache handler used by Next.js is normally defined in the config file as a path. At runtime,
67
+ * Next.js would then do a dynamic require on a transformed version of the path to retrieve the
68
+ * cache handler and create a new instance of it.
69
+ *
70
+ * This is problematic in workerd due to the dynamic import of the file that is not known from
71
+ * build-time. Therefore, we have to manually override the default way that the cache handler is
72
+ * instantiated with a dynamic require that uses a string literal for the path.
73
+ */
74
+ export function createCacheHandlerRule(handlerPath) {
75
+ return `
76
+ rule:
77
+ pattern: "const { cacheHandler } = this.nextConfig;"
78
+ inside:
79
+ kind: method_definition
80
+ has:
81
+ field: name
82
+ regex: ^getIncrementalCache$
83
+ stopBy: end
84
+
85
+ fix: |-
86
+ const cacheHandler = null;
87
+ CacheHandler = require('${normalizePath(handlerPath)}').default;
88
+ `;
89
+ }
90
+ export function createComposableCacheHandlersRule(handlerPath) {
91
+ return `
92
+ rule:
93
+ pattern: "const { cacheHandlers } = this.nextConfig.experimental;"
94
+ inside:
95
+ kind: method_definition
96
+ has:
97
+ field: name
98
+ regex: ^loadCustomCacheHandlers$
99
+ stopBy: end
100
+
101
+ fix: |-
102
+ const cacheHandlers = null;
103
+ const handlersSymbol = Symbol.for('@next/cache-handlers');
104
+ const handlersMapSymbol = Symbol.for('@next/cache-handlers-map');
105
+ const handlersSetSymbol = Symbol.for('@next/cache-handlers-set');
106
+ globalThis[handlersMapSymbol] = new Map();
107
+ globalThis[handlersMapSymbol].set("default", require('${normalizePath(handlerPath)}').default);
108
+ globalThis[handlersSetSymbol] = new Set(globalThis[handlersMapSymbol].values());
109
+ `;
110
+ }
@@ -0,0 +1,429 @@
1
+ import { patchCode } from "@opennextjs/aws/build/patch/astCodePatcher.js";
2
+ import { describe, expect, test } from "vitest";
3
+ import { buildIdRule, createCacheHandlerRule, createComposableCacheHandlersRule, createMiddlewareManifestRule, } from "./next-server.js";
4
+ describe("Next Server", () => {
5
+ const nextServerCode = `
6
+ class NextNodeServer extends _baseserver.default {
7
+ constructor(options){
8
+ // Initialize super class
9
+ super(options);
10
+ this.handleNextImageRequest = async (req, res, parsedUrl) => { /* ... */ };
11
+ }
12
+ async handleUpgrade() {
13
+ // The web server does not support web sockets, it's only used for HMR in
14
+ // development.
15
+ }
16
+ loadEnvConfig({ dev, forceReload, silent }) {
17
+ (0, _env.loadEnvConfig)(this.dir, dev, silent ? {
18
+ info: ()=>{},
19
+ error: ()=>{}
20
+ } : _log, forceReload);
21
+ }
22
+ async hasPage(pathname) {
23
+ var _this_nextConfig_i18n;
24
+ return !!(0, _require.getMaybePagePath)(pathname, this.distDir, (_this_nextConfig_i18n = this.nextConfig.i18n) == null ? void 0 : _this_nextConfig_i18n.locales, this.enabledDirectories.app);
25
+ }
26
+ getBuildId() {
27
+ const buildIdFile = (0, _path.join)(this.distDir, _constants.BUILD_ID_FILE);
28
+ try {
29
+ return _fs.default.readFileSync(buildIdFile, "utf8").trim();
30
+ } catch (err) {
31
+ if (err.code === "ENOENT") {
32
+ throw new Error(\`Could not find a production build in the '\${this.distDir}' directory. Try building your app with 'next build' before starting the production server. https://nextjs.org/docs/messages/production-start-no-build-id\`);
33
+ }
34
+ throw err;
35
+ }
36
+ }
37
+ getMiddlewareManifest() {
38
+ if (this.minimalMode) return null;
39
+ const manifest = require(this.middlewareManifestPath);
40
+ return manifest;
41
+ }
42
+ async loadCustomCacheHandlers() {
43
+ const { cacheHandlers } = this.nextConfig.experimental;
44
+ if (!cacheHandlers) return;
45
+ // If we've already initialized the cache handlers interface, don't do it
46
+ // again.
47
+ if (!(0, _handlers.initializeCacheHandlers)()) return;
48
+ for (const [kind, handler] of Object.entries(cacheHandlers)){
49
+ if (!handler) continue;
50
+ (0, _handlers.setCacheHandler)(kind, (0, _interopdefault.interopDefault)(await dynamicImportEsmDefault((0, _formatdynamicimportpath.formatDynamicImportPath)(this.distDir, handler))));
51
+ }
52
+ }
53
+ async getIncrementalCache({ requestHeaders, requestProtocol }) {
54
+ const dev = !!this.renderOpts.dev;
55
+ let CacheHandler;
56
+ const { cacheHandler } = this.nextConfig;
57
+ if (cacheHandler) {
58
+ CacheHandler = (0, _interopdefault.interopDefault)(await dynamicImportEsmDefault((0, _formatdynamicimportpath.formatDynamicImportPath)(this.distDir, cacheHandler)));
59
+ }
60
+ await this.loadCustomCacheHandlers();
61
+ // incremental-cache is request specific
62
+ // although can have shared caches in module scope
63
+ // per-cache handler
64
+ return new _incrementalcache.IncrementalCache({
65
+ fs: this.getCacheFilesystem(),
66
+ dev,
67
+ requestHeaders,
68
+ requestProtocol,
69
+ allowedRevalidateHeaderKeys: this.nextConfig.experimental.allowedRevalidateHeaderKeys,
70
+ minimalMode: this.minimalMode,
71
+ serverDistDir: this.serverDistDir,
72
+ fetchCacheKeyPrefix: this.nextConfig.experimental.fetchCacheKeyPrefix,
73
+ maxMemoryCacheSize: this.nextConfig.cacheMaxMemorySize,
74
+ flushToDisk: !this.minimalMode && this.nextConfig.experimental.isrFlushToDisk,
75
+ getPrerenderManifest: ()=>this.getPrerenderManifest(),
76
+ CurCacheHandler: CacheHandler
77
+ });
78
+ }
79
+ getEnabledDirectories(dev) {
80
+ const dir = dev ? this.dir : this.serverDistDir;
81
+ return {
82
+ app: (0, _findpagesdir.findDir)(dir, "app") ? true : false,
83
+ pages: (0, _findpagesdir.findDir)(dir, "pages") ? true : false
84
+ };
85
+ }
86
+ // ...
87
+ }`;
88
+ test("build ID", () => {
89
+ expect(patchCode(nextServerCode, buildIdRule)).toMatchInlineSnapshot(`
90
+ "class NextNodeServer extends _baseserver.default {
91
+ constructor(options){
92
+ // Initialize super class
93
+ super(options);
94
+ this.handleNextImageRequest = async (req, res, parsedUrl) => { /* ... */ };
95
+ }
96
+ async handleUpgrade() {
97
+ // The web server does not support web sockets, it's only used for HMR in
98
+ // development.
99
+ }
100
+ loadEnvConfig({ dev, forceReload, silent }) {
101
+ (0, _env.loadEnvConfig)(this.dir, dev, silent ? {
102
+ info: ()=>{},
103
+ error: ()=>{}
104
+ } : _log, forceReload);
105
+ }
106
+ async hasPage(pathname) {
107
+ var _this_nextConfig_i18n;
108
+ return !!(0, _require.getMaybePagePath)(pathname, this.distDir, (_this_nextConfig_i18n = this.nextConfig.i18n) == null ? void 0 : _this_nextConfig_i18n.locales, this.enabledDirectories.app);
109
+ }
110
+ getBuildId() {
111
+ return process.env.NEXT_BUILD_ID;
112
+ }
113
+ getMiddlewareManifest() {
114
+ if (this.minimalMode) return null;
115
+ const manifest = require(this.middlewareManifestPath);
116
+ return manifest;
117
+ }
118
+ async loadCustomCacheHandlers() {
119
+ const { cacheHandlers } = this.nextConfig.experimental;
120
+ if (!cacheHandlers) return;
121
+ // If we've already initialized the cache handlers interface, don't do it
122
+ // again.
123
+ if (!(0, _handlers.initializeCacheHandlers)()) return;
124
+ for (const [kind, handler] of Object.entries(cacheHandlers)){
125
+ if (!handler) continue;
126
+ (0, _handlers.setCacheHandler)(kind, (0, _interopdefault.interopDefault)(await dynamicImportEsmDefault((0, _formatdynamicimportpath.formatDynamicImportPath)(this.distDir, handler))));
127
+ }
128
+ }
129
+ async getIncrementalCache({ requestHeaders, requestProtocol }) {
130
+ const dev = !!this.renderOpts.dev;
131
+ let CacheHandler;
132
+ const { cacheHandler } = this.nextConfig;
133
+ if (cacheHandler) {
134
+ CacheHandler = (0, _interopdefault.interopDefault)(await dynamicImportEsmDefault((0, _formatdynamicimportpath.formatDynamicImportPath)(this.distDir, cacheHandler)));
135
+ }
136
+ await this.loadCustomCacheHandlers();
137
+ // incremental-cache is request specific
138
+ // although can have shared caches in module scope
139
+ // per-cache handler
140
+ return new _incrementalcache.IncrementalCache({
141
+ fs: this.getCacheFilesystem(),
142
+ dev,
143
+ requestHeaders,
144
+ requestProtocol,
145
+ allowedRevalidateHeaderKeys: this.nextConfig.experimental.allowedRevalidateHeaderKeys,
146
+ minimalMode: this.minimalMode,
147
+ serverDistDir: this.serverDistDir,
148
+ fetchCacheKeyPrefix: this.nextConfig.experimental.fetchCacheKeyPrefix,
149
+ maxMemoryCacheSize: this.nextConfig.cacheMaxMemorySize,
150
+ flushToDisk: !this.minimalMode && this.nextConfig.experimental.isrFlushToDisk,
151
+ getPrerenderManifest: ()=>this.getPrerenderManifest(),
152
+ CurCacheHandler: CacheHandler
153
+ });
154
+ }
155
+ getEnabledDirectories(dev) {
156
+ const dir = dev ? this.dir : this.serverDistDir;
157
+ return {
158
+ app: (0, _findpagesdir.findDir)(dir, "app") ? true : false,
159
+ pages: (0, _findpagesdir.findDir)(dir, "pages") ? true : false
160
+ };
161
+ }
162
+ // ...
163
+ }"
164
+ `);
165
+ });
166
+ test("middleware manifest", () => {
167
+ expect(patchCode(nextServerCode, createMiddlewareManifestRule("manifest"))).toMatchInlineSnapshot(`
168
+ "class NextNodeServer extends _baseserver.default {
169
+ constructor(options){
170
+ // Initialize super class
171
+ super(options);
172
+ this.handleNextImageRequest = async (req, res, parsedUrl) => { /* ... */ };
173
+ }
174
+ async handleUpgrade() {
175
+ // The web server does not support web sockets, it's only used for HMR in
176
+ // development.
177
+ }
178
+ loadEnvConfig({ dev, forceReload, silent }) {
179
+ (0, _env.loadEnvConfig)(this.dir, dev, silent ? {
180
+ info: ()=>{},
181
+ error: ()=>{}
182
+ } : _log, forceReload);
183
+ }
184
+ async hasPage(pathname) {
185
+ var _this_nextConfig_i18n;
186
+ return !!(0, _require.getMaybePagePath)(pathname, this.distDir, (_this_nextConfig_i18n = this.nextConfig.i18n) == null ? void 0 : _this_nextConfig_i18n.locales, this.enabledDirectories.app);
187
+ }
188
+ getBuildId() {
189
+ const buildIdFile = (0, _path.join)(this.distDir, _constants.BUILD_ID_FILE);
190
+ try {
191
+ return _fs.default.readFileSync(buildIdFile, "utf8").trim();
192
+ } catch (err) {
193
+ if (err.code === "ENOENT") {
194
+ throw new Error(\`Could not find a production build in the '\${this.distDir}' directory. Try building your app with 'next build' before starting the production server. https://nextjs.org/docs/messages/production-start-no-build-id\`);
195
+ }
196
+ throw err;
197
+ }
198
+ }
199
+ getMiddlewareManifest() {
200
+ return "manifest";
201
+ }
202
+ async loadCustomCacheHandlers() {
203
+ const { cacheHandlers } = this.nextConfig.experimental;
204
+ if (!cacheHandlers) return;
205
+ // If we've already initialized the cache handlers interface, don't do it
206
+ // again.
207
+ if (!(0, _handlers.initializeCacheHandlers)()) return;
208
+ for (const [kind, handler] of Object.entries(cacheHandlers)){
209
+ if (!handler) continue;
210
+ (0, _handlers.setCacheHandler)(kind, (0, _interopdefault.interopDefault)(await dynamicImportEsmDefault((0, _formatdynamicimportpath.formatDynamicImportPath)(this.distDir, handler))));
211
+ }
212
+ }
213
+ async getIncrementalCache({ requestHeaders, requestProtocol }) {
214
+ const dev = !!this.renderOpts.dev;
215
+ let CacheHandler;
216
+ const { cacheHandler } = this.nextConfig;
217
+ if (cacheHandler) {
218
+ CacheHandler = (0, _interopdefault.interopDefault)(await dynamicImportEsmDefault((0, _formatdynamicimportpath.formatDynamicImportPath)(this.distDir, cacheHandler)));
219
+ }
220
+ await this.loadCustomCacheHandlers();
221
+ // incremental-cache is request specific
222
+ // although can have shared caches in module scope
223
+ // per-cache handler
224
+ return new _incrementalcache.IncrementalCache({
225
+ fs: this.getCacheFilesystem(),
226
+ dev,
227
+ requestHeaders,
228
+ requestProtocol,
229
+ allowedRevalidateHeaderKeys: this.nextConfig.experimental.allowedRevalidateHeaderKeys,
230
+ minimalMode: this.minimalMode,
231
+ serverDistDir: this.serverDistDir,
232
+ fetchCacheKeyPrefix: this.nextConfig.experimental.fetchCacheKeyPrefix,
233
+ maxMemoryCacheSize: this.nextConfig.cacheMaxMemorySize,
234
+ flushToDisk: !this.minimalMode && this.nextConfig.experimental.isrFlushToDisk,
235
+ getPrerenderManifest: ()=>this.getPrerenderManifest(),
236
+ CurCacheHandler: CacheHandler
237
+ });
238
+ }
239
+ getEnabledDirectories(dev) {
240
+ const dir = dev ? this.dir : this.serverDistDir;
241
+ return {
242
+ app: (0, _findpagesdir.findDir)(dir, "app") ? true : false,
243
+ pages: (0, _findpagesdir.findDir)(dir, "pages") ? true : false
244
+ };
245
+ }
246
+ // ...
247
+ }"
248
+ `);
249
+ });
250
+ test("cache handler", () => {
251
+ expect(patchCode(nextServerCode, createCacheHandlerRule("manifest"))).toMatchInlineSnapshot(`
252
+ "class NextNodeServer extends _baseserver.default {
253
+ constructor(options){
254
+ // Initialize super class
255
+ super(options);
256
+ this.handleNextImageRequest = async (req, res, parsedUrl) => { /* ... */ };
257
+ }
258
+ async handleUpgrade() {
259
+ // The web server does not support web sockets, it's only used for HMR in
260
+ // development.
261
+ }
262
+ loadEnvConfig({ dev, forceReload, silent }) {
263
+ (0, _env.loadEnvConfig)(this.dir, dev, silent ? {
264
+ info: ()=>{},
265
+ error: ()=>{}
266
+ } : _log, forceReload);
267
+ }
268
+ async hasPage(pathname) {
269
+ var _this_nextConfig_i18n;
270
+ return !!(0, _require.getMaybePagePath)(pathname, this.distDir, (_this_nextConfig_i18n = this.nextConfig.i18n) == null ? void 0 : _this_nextConfig_i18n.locales, this.enabledDirectories.app);
271
+ }
272
+ getBuildId() {
273
+ const buildIdFile = (0, _path.join)(this.distDir, _constants.BUILD_ID_FILE);
274
+ try {
275
+ return _fs.default.readFileSync(buildIdFile, "utf8").trim();
276
+ } catch (err) {
277
+ if (err.code === "ENOENT") {
278
+ throw new Error(\`Could not find a production build in the '\${this.distDir}' directory. Try building your app with 'next build' before starting the production server. https://nextjs.org/docs/messages/production-start-no-build-id\`);
279
+ }
280
+ throw err;
281
+ }
282
+ }
283
+ getMiddlewareManifest() {
284
+ if (this.minimalMode) return null;
285
+ const manifest = require(this.middlewareManifestPath);
286
+ return manifest;
287
+ }
288
+ async loadCustomCacheHandlers() {
289
+ const { cacheHandlers } = this.nextConfig.experimental;
290
+ if (!cacheHandlers) return;
291
+ // If we've already initialized the cache handlers interface, don't do it
292
+ // again.
293
+ if (!(0, _handlers.initializeCacheHandlers)()) return;
294
+ for (const [kind, handler] of Object.entries(cacheHandlers)){
295
+ if (!handler) continue;
296
+ (0, _handlers.setCacheHandler)(kind, (0, _interopdefault.interopDefault)(await dynamicImportEsmDefault((0, _formatdynamicimportpath.formatDynamicImportPath)(this.distDir, handler))));
297
+ }
298
+ }
299
+ async getIncrementalCache({ requestHeaders, requestProtocol }) {
300
+ const dev = !!this.renderOpts.dev;
301
+ let CacheHandler;
302
+ const cacheHandler = null;
303
+ CacheHandler = require('manifest').default;
304
+ if (cacheHandler) {
305
+ CacheHandler = (0, _interopdefault.interopDefault)(await dynamicImportEsmDefault((0, _formatdynamicimportpath.formatDynamicImportPath)(this.distDir, cacheHandler)));
306
+ }
307
+ await this.loadCustomCacheHandlers();
308
+ // incremental-cache is request specific
309
+ // although can have shared caches in module scope
310
+ // per-cache handler
311
+ return new _incrementalcache.IncrementalCache({
312
+ fs: this.getCacheFilesystem(),
313
+ dev,
314
+ requestHeaders,
315
+ requestProtocol,
316
+ allowedRevalidateHeaderKeys: this.nextConfig.experimental.allowedRevalidateHeaderKeys,
317
+ minimalMode: this.minimalMode,
318
+ serverDistDir: this.serverDistDir,
319
+ fetchCacheKeyPrefix: this.nextConfig.experimental.fetchCacheKeyPrefix,
320
+ maxMemoryCacheSize: this.nextConfig.cacheMaxMemorySize,
321
+ flushToDisk: !this.minimalMode && this.nextConfig.experimental.isrFlushToDisk,
322
+ getPrerenderManifest: ()=>this.getPrerenderManifest(),
323
+ CurCacheHandler: CacheHandler
324
+ });
325
+ }
326
+ getEnabledDirectories(dev) {
327
+ const dir = dev ? this.dir : this.serverDistDir;
328
+ return {
329
+ app: (0, _findpagesdir.findDir)(dir, "app") ? true : false,
330
+ pages: (0, _findpagesdir.findDir)(dir, "pages") ? true : false
331
+ };
332
+ }
333
+ // ...
334
+ }"
335
+ `);
336
+ });
337
+ test("composable cache handler", () => {
338
+ expect(patchCode(nextServerCode, createComposableCacheHandlersRule("manifest"))).toMatchInlineSnapshot(`
339
+ "class NextNodeServer extends _baseserver.default {
340
+ constructor(options){
341
+ // Initialize super class
342
+ super(options);
343
+ this.handleNextImageRequest = async (req, res, parsedUrl) => { /* ... */ };
344
+ }
345
+ async handleUpgrade() {
346
+ // The web server does not support web sockets, it's only used for HMR in
347
+ // development.
348
+ }
349
+ loadEnvConfig({ dev, forceReload, silent }) {
350
+ (0, _env.loadEnvConfig)(this.dir, dev, silent ? {
351
+ info: ()=>{},
352
+ error: ()=>{}
353
+ } : _log, forceReload);
354
+ }
355
+ async hasPage(pathname) {
356
+ var _this_nextConfig_i18n;
357
+ return !!(0, _require.getMaybePagePath)(pathname, this.distDir, (_this_nextConfig_i18n = this.nextConfig.i18n) == null ? void 0 : _this_nextConfig_i18n.locales, this.enabledDirectories.app);
358
+ }
359
+ getBuildId() {
360
+ const buildIdFile = (0, _path.join)(this.distDir, _constants.BUILD_ID_FILE);
361
+ try {
362
+ return _fs.default.readFileSync(buildIdFile, "utf8").trim();
363
+ } catch (err) {
364
+ if (err.code === "ENOENT") {
365
+ throw new Error(\`Could not find a production build in the '\${this.distDir}' directory. Try building your app with 'next build' before starting the production server. https://nextjs.org/docs/messages/production-start-no-build-id\`);
366
+ }
367
+ throw err;
368
+ }
369
+ }
370
+ getMiddlewareManifest() {
371
+ if (this.minimalMode) return null;
372
+ const manifest = require(this.middlewareManifestPath);
373
+ return manifest;
374
+ }
375
+ async loadCustomCacheHandlers() {
376
+ const cacheHandlers = null;
377
+ const handlersSymbol = Symbol.for('@next/cache-handlers');
378
+ const handlersMapSymbol = Symbol.for('@next/cache-handlers-map');
379
+ const handlersSetSymbol = Symbol.for('@next/cache-handlers-set');
380
+ globalThis[handlersMapSymbol] = new Map();
381
+ globalThis[handlersMapSymbol].set("default", require('manifest').default);
382
+ globalThis[handlersSetSymbol] = new Set(globalThis[handlersMapSymbol].values());
383
+ if (!cacheHandlers) return;
384
+ // If we've already initialized the cache handlers interface, don't do it
385
+ // again.
386
+ if (!(0, _handlers.initializeCacheHandlers)()) return;
387
+ for (const [kind, handler] of Object.entries(cacheHandlers)){
388
+ if (!handler) continue;
389
+ (0, _handlers.setCacheHandler)(kind, (0, _interopdefault.interopDefault)(await dynamicImportEsmDefault((0, _formatdynamicimportpath.formatDynamicImportPath)(this.distDir, handler))));
390
+ }
391
+ }
392
+ async getIncrementalCache({ requestHeaders, requestProtocol }) {
393
+ const dev = !!this.renderOpts.dev;
394
+ let CacheHandler;
395
+ const { cacheHandler } = this.nextConfig;
396
+ if (cacheHandler) {
397
+ CacheHandler = (0, _interopdefault.interopDefault)(await dynamicImportEsmDefault((0, _formatdynamicimportpath.formatDynamicImportPath)(this.distDir, cacheHandler)));
398
+ }
399
+ await this.loadCustomCacheHandlers();
400
+ // incremental-cache is request specific
401
+ // although can have shared caches in module scope
402
+ // per-cache handler
403
+ return new _incrementalcache.IncrementalCache({
404
+ fs: this.getCacheFilesystem(),
405
+ dev,
406
+ requestHeaders,
407
+ requestProtocol,
408
+ allowedRevalidateHeaderKeys: this.nextConfig.experimental.allowedRevalidateHeaderKeys,
409
+ minimalMode: this.minimalMode,
410
+ serverDistDir: this.serverDistDir,
411
+ fetchCacheKeyPrefix: this.nextConfig.experimental.fetchCacheKeyPrefix,
412
+ maxMemoryCacheSize: this.nextConfig.cacheMaxMemorySize,
413
+ flushToDisk: !this.minimalMode && this.nextConfig.experimental.isrFlushToDisk,
414
+ getPrerenderManifest: ()=>this.getPrerenderManifest(),
415
+ CurCacheHandler: CacheHandler
416
+ });
417
+ }
418
+ getEnabledDirectories(dev) {
419
+ const dir = dev ? this.dir : this.serverDistDir;
420
+ return {
421
+ app: (0, _findpagesdir.findDir)(dir, "app") ? true : false,
422
+ pages: (0, _findpagesdir.findDir)(dir, "pages") ? true : false
423
+ };
424
+ }
425
+ // ...
426
+ }"
427
+ `);
428
+ });
429
+ });
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Removed unused `require.resolve` calls in Open Next.
3
+ */
4
+ import { type BuildOptions } from "@opennextjs/aws/build/helper.js";
5
+ import type { ContentUpdater, Plugin } from "@opennextjs/aws/plugins/content-updater.js";
6
+ export declare function patchResolveCache(updater: ContentUpdater, buildOpts: BuildOptions): Plugin;
7
+ export declare const cacheHandlerRule = "\nrule:\n pattern: var cacheHandlerPath = __require.resolve(\"./cache.cjs\");\nfix: |-\n var cacheHandlerPath = \"\";\n";
8
+ export declare const compositeCacheHandlerRule = "\nrule:\n pattern: var composableCacheHandlerPath = __require.resolve(\"./composable-cache.cjs\");\nfix: |-\n var composableCacheHandlerPath = \"\";\n";
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Removed unused `require.resolve` calls in Open Next.
3
+ */
4
+ import path from "node:path";
5
+ import { getPackagePath } from "@opennextjs/aws/build/helper.js";
6
+ import { patchCode } from "@opennextjs/aws/build/patch/astCodePatcher.js";
7
+ import { getCrossPlatformPathRegex } from "@opennextjs/aws/utils/regex.js";
8
+ export function patchResolveCache(updater, buildOpts) {
9
+ const { outputDir } = buildOpts;
10
+ const packagePath = getPackagePath(buildOpts);
11
+ const outputPath = path.join(outputDir, "server-functions/default");
12
+ const indexPath = path.relative(buildOpts.appBuildOutputPath, path.join(outputPath, packagePath, `index.mjs`));
13
+ return updater.updateContent("patch-resolve-cache", [
14
+ {
15
+ field: {
16
+ filter: getCrossPlatformPathRegex(indexPath),
17
+ contentFilter: /cacheHandlerPath/,
18
+ callback: async ({ contents }) => {
19
+ contents = patchCode(contents, cacheHandlerRule);
20
+ contents = patchCode(contents, compositeCacheHandlerRule);
21
+ return contents;
22
+ },
23
+ },
24
+ },
25
+ ]);
26
+ }
27
+ export const cacheHandlerRule = `
28
+ rule:
29
+ pattern: var cacheHandlerPath = __require.resolve("./cache.cjs");
30
+ fix: |-
31
+ var cacheHandlerPath = "";
32
+ `;
33
+ export const compositeCacheHandlerRule = `
34
+ rule:
35
+ pattern: var composableCacheHandlerPath = __require.resolve("./composable-cache.cjs");
36
+ fix: |-
37
+ var composableCacheHandlerPath = "";
38
+ `;
@@ -18,6 +18,5 @@ export async function patchCodeWithValidations(code, patches) {
18
18
  throw new Error(`Failed to patch ${target}`);
19
19
  }
20
20
  }
21
- console.log(`All ${patches.length} patches applied\n`);
22
21
  return patchedCode;
23
22
  }
@@ -108,5 +108,10 @@ function populateProcessEnv(url, env) {
108
108
  port: url.port,
109
109
  },
110
110
  });
111
+ /* We need to set this environment variable to make redirects work properly in preview mode.
112
+ * Next sets this in standalone mode during `startServer`. Without this the protocol would always be `https` here:
113
+ * https://github.com/vercel/next.js/blob/6b1e48080e896e0d44a05fe009cb79d2d3f91774/packages/next/src/server/app-render/action-handler.ts#L307-L316
114
+ */
115
+ process.env.__NEXT_PRIVATE_ORIGIN = url.origin;
111
116
  }
112
117
  /* eslint-enable no-var */
@@ -0,0 +1,2 @@
1
+ declare const _default: {};
2
+ export default _default;
@@ -0,0 +1,2 @@
1
+ throw "OpenNext shim";
2
+ export default {};
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": "1.1.0",
4
+ "version": "1.2.1",
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": "3.6.4",
46
+ "@opennextjs/aws": "3.6.5",
47
47
  "enquirer": "^2.4.1",
48
48
  "glob": "^11.0.0",
49
49
  "ts-tqdm": "^0.8.6"
@@ -68,7 +68,7 @@
68
68
  "vitest": "^2.1.1"
69
69
  },
70
70
  "peerDependencies": {
71
- "wrangler": "^4.14.0"
71
+ "wrangler": "^4.19.1"
72
72
  },
73
73
  "scripts": {
74
74
  "clean": "rimraf dist",
@@ -1,14 +0,0 @@
1
- import { type BuildOptions } from "@opennextjs/aws/build/helper.js";
2
- /**
3
- * Sets up the OpenNext cache handler in a Next.js build.
4
- *
5
- * The cache handler used by Next.js is normally defined in the config file as a path. At runtime,
6
- * Next.js would then do a dynamic require on a transformed version of the path to retrieve the
7
- * cache handler and create a new instance of it.
8
- *
9
- * This is problematic in workerd due to the dynamic import of the file that is not known from
10
- * build-time. Therefore, we have to manually override the default way that the cache handler is
11
- * instantiated with a dynamic require that uses a string literal for the path.
12
- */
13
- export declare function patchCache(code: string, buildOpts: BuildOptions): Promise<string>;
14
- export declare function patchComposableCache(code: string, buildOpts: BuildOptions): Promise<string>;
@@ -1,40 +0,0 @@
1
- import path from "node:path";
2
- import { getPackagePath } from "@opennextjs/aws/build/helper.js";
3
- import { normalizePath } from "../../utils/index.js";
4
- /**
5
- * Sets up the OpenNext cache handler in a Next.js build.
6
- *
7
- * The cache handler used by Next.js is normally defined in the config file as a path. At runtime,
8
- * Next.js would then do a dynamic require on a transformed version of the path to retrieve the
9
- * cache handler and create a new instance of it.
10
- *
11
- * This is problematic in workerd due to the dynamic import of the file that is not known from
12
- * build-time. Therefore, we have to manually override the default way that the cache handler is
13
- * instantiated with a dynamic require that uses a string literal for the path.
14
- */
15
- export async function patchCache(code, buildOpts) {
16
- const { outputDir } = buildOpts;
17
- // TODO: switch to cache.mjs
18
- const outputPath = path.join(outputDir, "server-functions/default");
19
- const cacheFile = path.join(outputPath, getPackagePath(buildOpts), "cache.cjs");
20
- return code.replace("const { cacheHandler } = this.nextConfig;", `
21
- const cacheHandler = null;
22
- CacheHandler = require('${normalizePath(cacheFile)}').default;
23
- `);
24
- }
25
- export async function patchComposableCache(code, buildOpts) {
26
- const { outputDir } = buildOpts;
27
- // TODO: switch to mjs
28
- const outputPath = path.join(outputDir, "server-functions/default");
29
- const cacheFile = path.join(outputPath, getPackagePath(buildOpts), "composable-cache.cjs");
30
- //TODO: Do we want to move this to the new CodePatcher ?
31
- return code.replace("const { cacheHandlers } = this.nextConfig.experimental", `
32
- const cacheHandlers = null;
33
- const handlersSymbol = Symbol.for('@next/cache-handlers');
34
- const handlersMapSymbol = Symbol.for('@next/cache-handlers-map');
35
- const handlersSetSymbol = Symbol.for('@next/cache-handlers-set');
36
- globalThis[handlersMapSymbol] = new Map();
37
- globalThis[handlersMapSymbol].set("default", require('${normalizePath(cacheFile)}').default);
38
- globalThis[handlersSetSymbol] = new Set(globalThis[handlersMapSymbol].values());
39
- `);
40
- }
@@ -1,6 +0,0 @@
1
- /**
2
- * Inline `getBuildId` as it relies on `readFileSync` that is not supported by workerd.
3
- */
4
- import type { ContentUpdater, Plugin } from "@opennextjs/aws/plugins/content-updater.js";
5
- export declare function inlineBuildId(updater: ContentUpdater): Plugin;
6
- export declare const rule = "\nrule:\n kind: method_definition\n has:\n field: name\n regex: ^getBuildId$\nfix: |-\n getBuildId() {\n return process.env.NEXT_BUILD_ID;\n }\n";
@@ -1,29 +0,0 @@
1
- /**
2
- * Inline `getBuildId` as it relies on `readFileSync` that is not supported by workerd.
3
- */
4
- import { patchCode } from "@opennextjs/aws/build/patch/astCodePatcher.js";
5
- import { getCrossPlatformPathRegex } from "@opennextjs/aws/utils/regex.js";
6
- export function inlineBuildId(updater) {
7
- return updater.updateContent("inline-build-id", [
8
- {
9
- field: {
10
- filter: getCrossPlatformPathRegex(String.raw `/next/dist/server/next-server\.js$`, {
11
- escape: false,
12
- }),
13
- contentFilter: /getBuildId\(/,
14
- callback: ({ contents }) => patchCode(contents, rule),
15
- },
16
- },
17
- ]);
18
- }
19
- export const rule = `
20
- rule:
21
- kind: method_definition
22
- has:
23
- field: name
24
- regex: ^getBuildId$
25
- fix: |-
26
- getBuildId() {
27
- return process.env.NEXT_BUILD_ID;
28
- }
29
- `;
@@ -1,82 +0,0 @@
1
- import { patchCode } from "@opennextjs/aws/build/patch/astCodePatcher.js";
2
- import { describe, expect, test } from "vitest";
3
- import { rule } from "./build-id.js";
4
- describe("getBuildId", () => {
5
- test("patch", () => {
6
- const code = `
7
- class NextNodeServer extends _baseserver.default {
8
- constructor(options){
9
- // Initialize super class
10
- super(options);
11
- this.handleNextImageRequest = async (req, res, parsedUrl) => { /* ... */ };
12
- }
13
- async handleUpgrade() {
14
- // The web server does not support web sockets, it's only used for HMR in
15
- // development.
16
- }
17
- loadEnvConfig({ dev, forceReload, silent }) {
18
- (0, _env.loadEnvConfig)(this.dir, dev, silent ? {
19
- info: ()=>{},
20
- error: ()=>{}
21
- } : _log, forceReload);
22
- }
23
- async hasPage(pathname) {
24
- var _this_nextConfig_i18n;
25
- return !!(0, _require.getMaybePagePath)(pathname, this.distDir, (_this_nextConfig_i18n = this.nextConfig.i18n) == null ? void 0 : _this_nextConfig_i18n.locales, this.enabledDirectories.app);
26
- }
27
- getBuildId() {
28
- const buildIdFile = (0, _path.join)(this.distDir, _constants.BUILD_ID_FILE);
29
- try {
30
- return _fs.default.readFileSync(buildIdFile, "utf8").trim();
31
- } catch (err) {
32
- if (err.code === "ENOENT") {
33
- throw new Error(\`Could not find a production build in the '\${this.distDir}' directory. Try building your app with 'next build' before starting the production server. https://nextjs.org/docs/messages/production-start-no-build-id\`);
34
- }
35
- throw err;
36
- }
37
- }
38
- getEnabledDirectories(dev) {
39
- const dir = dev ? this.dir : this.serverDistDir;
40
- return {
41
- app: (0, _findpagesdir.findDir)(dir, "app") ? true : false,
42
- pages: (0, _findpagesdir.findDir)(dir, "pages") ? true : false
43
- };
44
- }
45
- // ...
46
- }`;
47
- expect(patchCode(code, rule)).toMatchInlineSnapshot(`
48
- "class NextNodeServer extends _baseserver.default {
49
- constructor(options){
50
- // Initialize super class
51
- super(options);
52
- this.handleNextImageRequest = async (req, res, parsedUrl) => { /* ... */ };
53
- }
54
- async handleUpgrade() {
55
- // The web server does not support web sockets, it's only used for HMR in
56
- // development.
57
- }
58
- loadEnvConfig({ dev, forceReload, silent }) {
59
- (0, _env.loadEnvConfig)(this.dir, dev, silent ? {
60
- info: ()=>{},
61
- error: ()=>{}
62
- } : _log, forceReload);
63
- }
64
- async hasPage(pathname) {
65
- var _this_nextConfig_i18n;
66
- return !!(0, _require.getMaybePagePath)(pathname, this.distDir, (_this_nextConfig_i18n = this.nextConfig.i18n) == null ? void 0 : _this_nextConfig_i18n.locales, this.enabledDirectories.app);
67
- }
68
- getBuildId() {
69
- return process.env.NEXT_BUILD_ID;
70
- }
71
- getEnabledDirectories(dev) {
72
- const dir = dev ? this.dir : this.serverDistDir;
73
- return {
74
- app: (0, _findpagesdir.findDir)(dir, "app") ? true : false,
75
- pages: (0, _findpagesdir.findDir)(dir, "pages") ? true : false
76
- };
77
- }
78
- // ...
79
- }"
80
- `);
81
- });
82
- });
@@ -1,7 +0,0 @@
1
- /**
2
- * Inline `evalManifest` as it relies on `readFileSync` and `runInNewContext`
3
- * that are not supported by workerd.
4
- */
5
- import { type BuildOptions } from "@opennextjs/aws/build/helper.js";
6
- import type { ContentUpdater, Plugin } from "@opennextjs/aws/plugins/content-updater.js";
7
- export declare function inlineEvalManifest(updater: ContentUpdater, buildOpts: BuildOptions): Plugin;
@@ -1,61 +0,0 @@
1
- /**
2
- * Inline `evalManifest` as it relies on `readFileSync` and `runInNewContext`
3
- * that are not supported by workerd.
4
- */
5
- import { join, posix, relative, sep } from "node:path";
6
- import { getPackagePath } from "@opennextjs/aws/build/helper.js";
7
- import { patchCode } from "@opennextjs/aws/build/patch/astCodePatcher.js";
8
- import { getCrossPlatformPathRegex } from "@opennextjs/aws/utils/regex.js";
9
- import { glob } from "glob";
10
- import { normalizePath } from "../../utils/normalize-path.js";
11
- export function inlineEvalManifest(updater, buildOpts) {
12
- return updater.updateContent("inline-eval-manifest", [
13
- {
14
- field: {
15
- filter: getCrossPlatformPathRegex(String.raw `/next/dist/server/load-manifest(\.external)?\.js$`, {
16
- escape: false,
17
- }),
18
- contentFilter: /function evalManifest\(/,
19
- callback: async ({ contents }) => patchCode(contents, await getRule(buildOpts)),
20
- },
21
- },
22
- ]);
23
- }
24
- async function getRule(buildOpts) {
25
- const { outputDir } = buildOpts;
26
- const baseDir = join(outputDir, "server-functions/default", getPackagePath(buildOpts), ".next");
27
- const appDir = join(baseDir, "server/app");
28
- const manifests = await glob(join(baseDir, "**/*_client-reference-manifest.js"), {
29
- windowsPathsNoEscape: true,
30
- });
31
- const returnManifests = manifests
32
- .map((manifest) => {
33
- const endsWith = normalizePath(relative(baseDir, manifest));
34
- const key = normalizePath("/" + relative(appDir, manifest)).replace("_client-reference-manifest.js", "");
35
- return `
36
- if ($PATH.endsWith("${endsWith}")) {
37
- require(${JSON.stringify(manifest)});
38
- return {
39
- __RSC_MANIFEST: {
40
- "${key}": globalThis.__RSC_MANIFEST["${key}"],
41
- },
42
- };
43
- }
44
- `;
45
- })
46
- .join("\n");
47
- return {
48
- rule: {
49
- pattern: `
50
- function evalManifest($PATH, $$$ARGS) {
51
- $$$_
52
- }`,
53
- },
54
- fix: `
55
- function evalManifest($PATH, $$$ARGS) {
56
- $PATH = $PATH.replaceAll(${JSON.stringify(sep)}, ${JSON.stringify(posix.sep)});
57
- ${returnManifests}
58
- throw new Error(\`Unexpected evalManifest(\${$PATH}) call!\`);
59
- }`,
60
- };
61
- }
@@ -1,6 +0,0 @@
1
- import { type BuildOptions } from "@opennextjs/aws/build/helper.js";
2
- /**
3
- * Inlines the middleware manifest from the build output to prevent a dynamic require statement
4
- * as they result in runtime failures.
5
- */
6
- export declare function inlineMiddlewareManifestRequire(code: string, buildOpts: BuildOptions): string;
@@ -1,15 +0,0 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import { join } from "node:path";
3
- import { getPackagePath } from "@opennextjs/aws/build/helper.js";
4
- /**
5
- * Inlines the middleware manifest from the build output to prevent a dynamic require statement
6
- * as they result in runtime failures.
7
- */
8
- export function inlineMiddlewareManifestRequire(code, buildOpts) {
9
- const { outputDir } = buildOpts;
10
- const middlewareManifestPath = join(outputDir, "server-functions/default", getPackagePath(buildOpts), ".next/server/middleware-manifest.json");
11
- const middlewareManifest = existsSync(middlewareManifestPath)
12
- ? JSON.parse(readFileSync(middlewareManifestPath, "utf-8"))
13
- : {};
14
- return code.replace("require(this.middlewareManifestPath)", JSON.stringify(middlewareManifest));
15
- }