@opennextjs/cloudflare 1.11.0 → 1.12.0

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.
@@ -13,6 +13,7 @@ import { compileInit } from "./open-next/compile-init.js";
13
13
  import { compileSkewProtection } from "./open-next/compile-skew-protection.js";
14
14
  import { compileDurableObjects } from "./open-next/compileDurableObjects.js";
15
15
  import { createServerBundle } from "./open-next/createServerBundle.js";
16
+ import { useNodeMiddleware } from "./utils/middleware.js";
16
17
  import { getVersion } from "./utils/version.js";
17
18
  /**
18
19
  * Builds the application in a format that can be passed to workerd
@@ -45,13 +46,18 @@ export async function build(options, config, projectOpts, wranglerConfig) {
45
46
  setStandaloneBuildMode(options);
46
47
  buildNextjsApp(options);
47
48
  }
49
+ // Make sure no Node.js middleware is used
50
+ if (useNodeMiddleware(options)) {
51
+ logger.error("Node.js middleware is not currently supported. Consider switching to Edge Middleware.");
52
+ process.exit(1);
53
+ }
48
54
  // Generate deployable bundle
49
55
  printHeader("Generating bundle");
50
56
  compileCache(options);
51
57
  compileEnvFiles(options);
52
- compileInit(options, wranglerConfig);
53
- compileImages(options);
54
- compileSkewProtection(options, config);
58
+ await compileInit(options, wranglerConfig);
59
+ await compileImages(options);
60
+ await compileSkewProtection(options, config);
55
61
  // Compile middleware
56
62
  await createMiddleware(options, { forceOnlyBuildOnce: true });
57
63
  createStaticAssets(options, { useBasePath: true });
@@ -43,9 +43,10 @@ const optionalDependencies = [
43
43
  export async function bundleServer(buildOpts, projectOpts) {
44
44
  copyPackageCliFiles(packageDistDir, buildOpts);
45
45
  const { appPath, outputDir, monorepoRoot, debug } = buildOpts;
46
- const baseManifestPath = path.join(outputDir, "server-functions/default", getPackagePath(buildOpts), ".next");
47
- const serverFiles = path.join(baseManifestPath, "required-server-files.json");
46
+ const dotNextPath = path.join(outputDir, "server-functions/default", getPackagePath(buildOpts), ".next");
47
+ const serverFiles = path.join(dotNextPath, "required-server-files.json");
48
48
  const nextConfig = JSON.parse(fs.readFileSync(serverFiles, "utf-8")).config;
49
+ const useTurbopack = fs.existsSync(path.join(dotNextPath, "server/chunks/[turbopack]_runtime.js"));
49
50
  console.log(`\x1b[35m⚙️ Bundling the OpenNext server...\n\x1b[0m`);
50
51
  await patchWebpackRuntime(buildOpts);
51
52
  patchVercelOgLibrary(buildOpts);
@@ -118,13 +119,12 @@ export async function bundleServer(buildOpts, projectOpts) {
118
119
  // Note: we need the __non_webpack_require__ variable declared as it is used by next-server:
119
120
  // https://github.com/vercel/next.js/blob/be0c3283/packages/next/src/server/next-server.ts#L116-L119
120
121
  __non_webpack_require__: "require",
122
+ // The 2 following defines are used to reduce the bundle size by removing unnecessary code
123
+ // Next uses different precompiled renderers (i.e. `app-page.runtime.prod.js`) based on if you use `TURBOPACK` or some experimental React features
124
+ ...(useTurbopack ? {} : { "process.env.TURBOPACK": "false" }),
121
125
  // We make sure that environment variables that Next.js expects are properly defined
122
126
  "process.env.NEXT_RUNTIME": '"nodejs"',
123
127
  "process.env.NODE_ENV": '"production"',
124
- // The 2 following defines are used to reduce the bundle size by removing unnecessary code
125
- // Next uses different precompiled renderers (i.e. `app-page.runtime.prod.js`) based on if you use `TURBOPACK` or some experimental React features
126
- // Turbopack is not supported for build at the moment, so we disable it
127
- "process.env.TURBOPACK": "false",
128
128
  // This define should be safe to use for Next 14.2+, earlier versions (13.5 and less) will cause trouble
129
129
  "process.env.__NEXT_EXPERIMENTAL_REACT": `${needsExperimentalReact(nextConfig)}`,
130
130
  // Fix `res.validate` in Next 15.4 (together with the `route-module` patch)
@@ -1,7 +1,7 @@
1
1
  import { createRequire } from "node:module";
2
2
  import path from "node:path";
3
3
  import { loadBuildId, loadPrerenderManifest } from "@opennextjs/aws/adapters/config/util.js";
4
- import { esbuildSync, getPackagePath } from "@opennextjs/aws/build/helper.js";
4
+ import { esbuildSync } from "@opennextjs/aws/build/helper.js";
5
5
  export function compileDurableObjects(buildOpts) {
6
6
  const _require = createRequire(import.meta.url);
7
7
  const entryPoints = [
@@ -9,10 +9,10 @@ export function compileDurableObjects(buildOpts) {
9
9
  _require.resolve("@opennextjs/cloudflare/durable-objects/sharded-tag-cache"),
10
10
  _require.resolve("@opennextjs/cloudflare/durable-objects/bucket-cache-purge"),
11
11
  ];
12
- const baseManifestPath = path.join(buildOpts.outputDir, "server-functions/default", getPackagePath(buildOpts), ".next");
13
- const prerenderManifest = loadPrerenderManifest(baseManifestPath);
12
+ const buildOutputDotNextDir = path.join(buildOpts.appBuildOutputPath, ".next");
13
+ const prerenderManifest = loadPrerenderManifest(buildOutputDotNextDir);
14
14
  const previewModeId = prerenderManifest.preview.previewModeId;
15
- const BUILD_ID = loadBuildId(baseManifestPath);
15
+ const BUILD_ID = loadBuildId(buildOutputDotNextDir);
16
16
  return esbuildSync({
17
17
  entryPoints,
18
18
  bundle: true,
@@ -20,6 +20,7 @@ import { openNextResolvePlugin } from "@opennextjs/aws/plugins/resolve.js";
20
20
  import { getCrossPlatformPathRegex } from "@opennextjs/aws/utils/regex.js";
21
21
  import { getOpenNextConfig } from "../../../api/config.js";
22
22
  import { patchResRevalidate } from "../patches/plugins/res-revalidate.js";
23
+ import { patchTurbopackRuntime } from "../patches/plugins/turbopack.js";
23
24
  import { patchUseCacheIO } from "../patches/plugins/use-cache.js";
24
25
  import { normalizePath } from "../utils/index.js";
25
26
  import { copyWorkerdPackages } from "../utils/workerd.js";
@@ -142,6 +143,7 @@ async function generateBundle(name, options, fnOptions, codeCustomization) {
142
143
  // Cloudflare specific patches
143
144
  patchResRevalidate,
144
145
  patchUseCacheIO,
146
+ patchTurbopackRuntime,
145
147
  ...additionalCodePatches,
146
148
  ]);
147
149
  // Build Lambda code
@@ -16,6 +16,7 @@ export function patchVercelOgLibrary(buildOpts) {
16
16
  for (const traceInfoPath of globSync(path.join(appBuildOutputPath, ".next/server/**/*.nft.json"), {
17
17
  windowsPathsNoEscape: true,
18
18
  })) {
19
+ let edgeFilePatched = false;
19
20
  const traceInfo = JSON.parse(readFileSync(traceInfoPath, { encoding: "utf8" }));
20
21
  const tracedNodePath = traceInfo.files.find((p) => p.endsWith("@vercel/og/index.node.js"));
21
22
  if (!tracedNodePath)
@@ -26,14 +27,20 @@ export function patchVercelOgLibrary(buildOpts) {
26
27
  if (!existsSync(outputEdgePath)) {
27
28
  const tracedEdgePath = path.join(path.dirname(traceInfoPath), tracedNodePath.replace("index.node.js", "index.edge.js"));
28
29
  copyFileSync(tracedEdgePath, outputEdgePath);
30
+ }
31
+ if (!edgeFilePatched) {
32
+ edgeFilePatched = true;
29
33
  // Change font fetches in the library to use imports.
30
34
  const node = parseFile(outputEdgePath);
31
35
  const { edits, matches } = patchVercelOgFallbackFont(node);
32
36
  writeFileSync(outputEdgePath, node.commitEdits(edits));
33
- const fontFileName = matches[0].getMatch("PATH").text();
34
- renameSync(path.join(outputDir, fontFileName), path.join(outputDir, `${fontFileName}.bin`));
37
+ if (matches.length > 0) {
38
+ const fontFileName = matches[0].getMatch("PATH").text();
39
+ renameSync(path.join(outputDir, fontFileName), path.join(outputDir, `${fontFileName}.bin`));
40
+ }
35
41
  }
36
42
  // Change node imports for the library to edge imports.
43
+ // This is only useful when turbopack is not used to bundle the function.
37
44
  const routeFilePath = traceInfoPath.replace(appBuildOutputPath, packagePath).replace(".nft.json", "");
38
45
  const node = parseFile(routeFilePath);
39
46
  const { edits } = patchVercelOgImport(node);
@@ -0,0 +1,2 @@
1
+ import type { CodePatcher } from "@opennextjs/aws/build/patch/codePatcher.js";
2
+ export declare const patchTurbopackRuntime: CodePatcher;
@@ -0,0 +1,78 @@
1
+ import { patchCode } from "@opennextjs/aws/build/patch/astCodePatcher.js";
2
+ import { getCrossPlatformPathRegex } from "@opennextjs/aws/utils/regex.js";
3
+ const inlineChunksRule = `
4
+ rule:
5
+ kind: call_expression
6
+ pattern: require(resolved)
7
+ fix:
8
+ requireChunk(chunkPath)
9
+ `;
10
+ export const patchTurbopackRuntime = {
11
+ name: "inline-turbopack-chunks",
12
+ patches: [
13
+ {
14
+ versions: ">=15.0.0",
15
+ pathFilter: getCrossPlatformPathRegex(String.raw `\[turbopack\]_runtime\.js$`, {
16
+ escape: false,
17
+ }),
18
+ contentFilter: /loadRuntimeChunkPath/,
19
+ patchCode: async ({ code, tracedFiles }) => {
20
+ let patched = patchCode(code, inlineExternalImportRule);
21
+ patched = patchCode(patched, inlineChunksRule);
22
+ return `${patched}\n${inlineChunksFn(tracedFiles)}`;
23
+ },
24
+ },
25
+ ],
26
+ };
27
+ function getInlinableChunks(tracedFiles) {
28
+ const chunks = new Set();
29
+ for (const file of tracedFiles) {
30
+ if (file === "[turbopack]_runtime.js") {
31
+ continue;
32
+ }
33
+ if (file.includes(".next/server/chunks/")) {
34
+ chunks.add(file);
35
+ }
36
+ }
37
+ return Array.from(chunks);
38
+ }
39
+ function inlineChunksFn(tracedFiles) {
40
+ // From the outputs, we extract every chunks
41
+ const chunks = getInlinableChunks(tracedFiles);
42
+ return `
43
+ function requireChunk(chunkPath) {
44
+ switch(chunkPath) {
45
+ ${chunks
46
+ .map((chunk) => ` case "${
47
+ // we only want the path after /path/to/.next/
48
+ chunk.replace(/.*\/\.next\//, "")}": return require("${chunk}");`)
49
+ .join("\n")}
50
+ default:
51
+ throw new Error(\`Not found \${chunkPath}\`);
52
+ }
53
+ }
54
+ `;
55
+ }
56
+ // Turbopack imports `og` via `externalImport`.
57
+ // We patch it to:
58
+ // - add the explicit path so that the file is inlined by wrangler
59
+ // - use the edge version of the module instead of the node version.
60
+ //
61
+ // Modules that are not inlined (no added to the switch), would generate an error similar to:
62
+ // Failed to load external module path/to/module: Error: No such module "path/to/module"
63
+ const inlineExternalImportRule = `
64
+ rule:
65
+ pattern: "$RAW = await import($ID)"
66
+ inside:
67
+ regex: "externalImport"
68
+ kind: function_declaration
69
+ stopBy: end
70
+ fix: |-
71
+ switch ($ID) {
72
+ case "next/dist/compiled/@vercel/og/index.node.js":
73
+ $RAW = await import("next/dist/compiled/@vercel/og/index.edge.js");
74
+ break;
75
+ default:
76
+ $RAW = await import($ID);
77
+ }
78
+ `;
@@ -0,0 +1,8 @@
1
+ import * as buildHelper from "@opennextjs/aws/build/helper.js";
2
+ /**
3
+ * Returns whether the project is using a Node.js middleware.
4
+ *
5
+ * @param options
6
+ * @returns Whether the project is using a Node.js middleware
7
+ */
8
+ export declare function useNodeMiddleware(options: buildHelper.BuildOptions): boolean;
@@ -0,0 +1,21 @@
1
+ import path from "node:path";
2
+ import { loadFunctionsConfigManifest, loadMiddlewareManifest } from "@opennextjs/aws/adapters/config/util.js";
3
+ /**
4
+ * Returns whether the project is using a Node.js middleware.
5
+ *
6
+ * @param options
7
+ * @returns Whether the project is using a Node.js middleware
8
+ */
9
+ export function useNodeMiddleware(options) {
10
+ const buildOutputDotNextDir = path.join(options.appBuildOutputPath, ".next");
11
+ // Look for the edge middleware
12
+ const middlewareManifest = loadMiddlewareManifest(buildOutputDotNextDir);
13
+ const edgeMiddleware = middlewareManifest.middleware["/"];
14
+ if (edgeMiddleware) {
15
+ // The app uses an edge middleware
16
+ return false;
17
+ }
18
+ // Look for the node middleware
19
+ const functionsConfigManifest = loadFunctionsConfigManifest(buildOutputDotNextDir);
20
+ return Boolean(functionsConfigManifest?.functions["/_middleware"]);
21
+ }
@@ -71,6 +71,18 @@ export async function getEnvFromPlatformProxy(options, buildOpts) {
71
71
  * @returns escaped arg
72
72
  */
73
73
  export function quoteShellMeta(arg) {
74
+ if (process.platform === "win32") {
75
+ if (arg.length === 0) {
76
+ return '""';
77
+ }
78
+ const needsEscaping = /[&|<>^()%!"]/;
79
+ const needsQuotes = /\s/.test(arg) || needsEscaping.test(arg);
80
+ let escaped = arg.replace(/"/g, '""');
81
+ if (/[&|<>^()%!]/.test(arg)) {
82
+ escaped = escaped.replace(/[&|<>^()%!]/g, "^$&");
83
+ }
84
+ return needsQuotes ? `"${escaped}"` : escaped;
85
+ }
74
86
  if (/["\s]/.test(arg) && !/'/.test(arg)) {
75
87
  return `'${arg.replace(/(['\\])/g, "\\$1")}'`;
76
88
  }
@@ -1,6 +1,7 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { createRequire } from "node:module";
3
3
  import path from "node:path";
4
+ import url from "node:url";
4
5
  import { compileOpenNextConfig } from "@opennextjs/aws/build/compileConfig.js";
5
6
  import { normalizeOptions } from "@opennextjs/aws/build/helper.js";
6
7
  import { printHeader, showWarningOnWindows } from "@opennextjs/aws/build/utils.js";
@@ -50,7 +51,7 @@ export async function retrieveCompiledConfig() {
50
51
  logger.error("Could not find compiled Open Next config, did you run the build command?");
51
52
  process.exit(1);
52
53
  }
53
- const config = await import(configPath).then((mod) => mod.default);
54
+ const config = await import(url.pathToFileURL(configPath).href).then((mod) => mod.default);
54
55
  ensureCloudflareConfig(config);
55
56
  return { config };
56
57
  }
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.11.0",
4
+ "version": "1.12.0",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "opennextjs-cloudflare": "dist/cli/index.js"