diff --git a/.changeset/smart-boxes-destroy.md b/.changeset/smart-boxes-destroy.md new file mode 100644 index 00000000..34982eac --- /dev/null +++ b/.changeset/smart-boxes-destroy.md @@ -0,0 +1,5 @@ +--- +"@opennextjs/cloudflare": patch +--- + +fix: patch the webpack runtime when there is a single chunk diff --git a/packages/cloudflare/src/cli/build/patches/ast/webpack-runtime.spec.ts b/packages/cloudflare/src/cli/build/patches/ast/webpack-runtime.spec.ts index 9ce03ad0..c7d66f53 100644 --- a/packages/cloudflare/src/cli/build/patches/ast/webpack-runtime.spec.ts +++ b/packages/cloudflare/src/cli/build/patches/ast/webpack-runtime.spec.ts @@ -1,64 +1,113 @@ import { describe, expect, test } from "vitest"; import { patchCode } from "./util.js"; -import { buildInlineChunksRule } from "./webpack-runtime.js"; +import { buildMultipleChunksRule, singleChunkRule } from "./webpack-runtime.js"; describe("webpack runtime", () => { - test("patch runtime", () => { - const code = ` - /******/ // require() chunk loading for javascript - /******/ __webpack_require__.f.require = (chunkId, promises) => { - /******/ // "1" is the signal for "already loaded" - /******/ if (!installedChunks[chunkId]) { - /******/ if (658 != chunkId) { - /******/ installChunk(require("./chunks/" + __webpack_require__.u(chunkId))); - /******/ - } else installedChunks[chunkId] = 1; - /******/ + describe("multiple chunks", () => { + test("patch runtime", () => { + const code = ` + /******/ // require() chunk loading for javascript + /******/ __webpack_require__.f.require = (chunkId, promises) => { + /******/ // "1" is the signal for "already loaded" + /******/ if (!installedChunks[chunkId]) { + /******/ if (658 != chunkId) { + /******/ installChunk(require("./chunks/" + __webpack_require__.u(chunkId))); + /******/ + } else installedChunks[chunkId] = 1; + /******/ + } + /******/ + }; + `; + + expect(patchCode(code, buildMultipleChunksRule([1, 2, 3]))).toMatchInlineSnapshot(` + "/******/ // require() chunk loading for javascript + /******/ __webpack_require__.f.require = (chunkId, _) => { + if (!installedChunks[chunkId]) { + switch (chunkId) { + case 1: installChunk(require("./chunks/1.js")); break; + case 2: installChunk(require("./chunks/2.js")); break; + case 3: installChunk(require("./chunks/3.js")); break; + case 658: installedChunks[chunkId] = 1; break; + default: throw new Error(\`Unknown chunk \${chunkId}\`); } - /******/ - }; - `; + } + } + ; + " + `); + }); - expect(patchCode(code, buildInlineChunksRule([1, 2, 3]))).toMatchInlineSnapshot(` - "/******/ // require() chunk loading for javascript - /******/ __webpack_require__.f.require = (chunkId, _) => { - if (!installedChunks[chunkId]) { - switch (chunkId) { - case 1: installChunk(require("./chunks/1.js")); break; - case 2: installChunk(require("./chunks/2.js")); break; - case 3: installChunk(require("./chunks/3.js")); break; - case 658: installedChunks[chunkId] = 1; break; - default: throw new Error(\`Unknown chunk \${chunkId}\`); + test("patch minified runtime", () => { + const code = ` + t.f.require=(o,n)=>{e[o]||(658!=o?r(require("./chunks/"+t.u(o))):e[o]=1)} + `; + + expect(patchCode(code, buildMultipleChunksRule([1, 2, 3]))).toMatchInlineSnapshot( + ` + "t.f.require=(o, _) => { + if (!e[o]) { + switch (o) { + case 1: r(require("./chunks/1.js")); break; + case 2: r(require("./chunks/2.js")); break; + case 3: r(require("./chunks/3.js")); break; + case 658: e[o] = 1; break; + default: throw new Error(\`Unknown chunk \${o}\`); + } } } - } - ; - " - `); + + " + ` + ); + }); }); - test("patch minified runtime", () => { - const code = ` - t.f.require=(o,n)=>{e[o]||(658!=o?r(require("./chunks/"+t.u(o))):e[o]=1)} - `; + describe("single chunk", () => { + test("patch runtime", () => { + const code = ` +/******/ // require() chunk loading for javascript +/******/ __webpack_require__.f.require = (chunkId, promises) => { +/******/ // "1" is the signal for "already loaded" +/******/ if(!installedChunks[chunkId]) { +/******/ if(710 == chunkId) { +/******/ installChunk(require("./chunks/" + __webpack_require__.u(chunkId))); +/******/ } else installedChunks[chunkId] = 1; +/******/ } +/******/ }; +`; - expect(patchCode(code, buildInlineChunksRule([1, 2, 3]))).toMatchInlineSnapshot( - ` - "t.f.require=(o, _) => { - if (!e[o]) { - switch (o) { - case 1: r(require("./chunks/1.js")); break; - case 2: r(require("./chunks/2.js")); break; - case 3: r(require("./chunks/3.js")); break; - case 658: e[o] = 1; break; - default: throw new Error(\`Unknown chunk \${o}\`); + expect(patchCode(code, singleChunkRule)).toMatchInlineSnapshot(` + "/******/ // require() chunk loading for javascript + /******/ __webpack_require__.f.require = (chunkId, _) => { + if (!installedChunks[chunkId]) { + try { + installChunk(require("./chunks/710.js")); + } catch {} + } + } + ; + " + `); + }); + + test("patch minified runtime", () => { + const code = ` + o.f.require=(t,a)=>{e[t]||(710==t?r(require("./chunks/"+o.u(t))):e[t]=1)} + `; + + expect(patchCode(code, singleChunkRule)).toMatchInlineSnapshot(` + "o.f.require=(t, _) => { + if (!e[t]) { + try { + r(require("./chunks/710.js")); + } catch {} } } - } - " - ` - ); + " + `); + }); }); }); diff --git a/packages/cloudflare/src/cli/build/patches/ast/webpack-runtime.ts b/packages/cloudflare/src/cli/build/patches/ast/webpack-runtime.ts index 9818cd7f..bfab8ca4 100644 --- a/packages/cloudflare/src/cli/build/patches/ast/webpack-runtime.ts +++ b/packages/cloudflare/src/cli/build/patches/ast/webpack-runtime.ts @@ -7,6 +7,7 @@ * * This patch unrolls the dynamic require for all the existing chunks: * + * For multiple chunks: * switch (chunkId) { * case ID1: installChunk(require("./chunks/ID1"); break; * case ID2: installChunk(require("./chunks/ID2"); break; @@ -14,16 +15,20 @@ * case SELF_ID: installedChunks[chunkId] = 1; break; * default: throw new Error(`Unknown chunk ${chunkId}`); * } + * + * For a single chunk: + * require("./chunks/CHUNK_ID.js"); */ -import { readdirSync, readFileSync, writeFileSync } from "node:fs"; +import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { type BuildOptions, getPackagePath } from "@opennextjs/aws/build/helper.js"; import { patchCode } from "./util.js"; -export function buildInlineChunksRule(chunks: number[]) { +// Inline the code when there are multiple chunks +export function buildMultipleChunksRule(chunks: number[]) { return ` rule: pattern: ($CHUNK_ID, $_PROMISES) => { $$$ } @@ -44,8 +49,30 @@ ${chunks.map((chunk) => ` case ${chunk}: $INSTALL(require("./chunks/${ch }`; } +// Inline the code when there is a single chunk. +// For example when there is a single Pages API route. +// Note: The chunk does not always exist which explain the need for the try...catch. +export const singleChunkRule = ` +rule: + pattern: ($CHUNK_ID, $_PROMISES) => { $$$ } + inside: {pattern: $_.$_.require = $$$_, stopBy: end} + all: + - has: {pattern: $INSTALL(require("./chunks/" + $$$)), stopBy: end} + - has: {pattern: $SELF_ID == $CHUNK_ID, stopBy: end} + - has: {pattern: "$INSTALLED_CHUNK[$CHUNK_ID] = 1", stopBy: end} +fix: | + ($CHUNK_ID, _) => { + if (!$INSTALLED_CHUNK[$CHUNK_ID]) { + try { + $INSTALL(require("./chunks/$SELF_ID.js")); + } catch {} + } + } +`; + /** - * Fixes the webpack-runtime.js file by removing its webpack dynamic requires. + * Fixes the webpack-runtime.js and webpack-api-runtime.js files by inlining + * the webpack dynamic requires. */ export async function patchWebpackRuntime(buildOpts: BuildOptions) { const { outputDir } = buildOpts; @@ -57,8 +84,6 @@ export async function patchWebpackRuntime(buildOpts: BuildOptions) { ".next/server" ); - const runtimeFile = join(dotNextServerDir, "webpack-runtime.js"); - const runtimeCode = readFileSync(runtimeFile, "utf-8"); // Look for all the chunks. const chunks = readdirSync(join(dotNextServerDir, "chunks")) .filter((chunk) => /^\d+\.js$/.test(chunk)) @@ -66,5 +91,21 @@ export async function patchWebpackRuntime(buildOpts: BuildOptions) { return Number(chunk.replace(/\.js$/, "")); }); - writeFileSync(runtimeFile, patchCode(runtimeCode, buildInlineChunksRule(chunks))); + patchFile(join(dotNextServerDir, "webpack-runtime.js"), chunks); + patchFile(join(dotNextServerDir, "webpack-api-runtime.js"), chunks); +} + +/** + * Inline chunks when the file exists. + * + * @param filename Path to the webpack runtime. + * @param chunks List of chunks in the chunks folder. + */ +function patchFile(filename: string, chunks: number[]) { + if (existsSync(filename)) { + let code = readFileSync(filename, "utf-8"); + code = patchCode(code, buildMultipleChunksRule(chunks)); + code = patchCode(code, singleChunkRule); + writeFileSync(filename, code); + } }