Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/smart-boxes-destroy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@opennextjs/cloudflare": patch
---

fix: patch the webpack runtime when there is a single chunk
143 changes: 96 additions & 47 deletions packages/cloudflare/src/cli/build/patches/ast/webpack-runtime.spec.ts
Original file line number Diff line number Diff line change
@@ -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 {}
}
}
}

"
`
);
"
`);
});
});
});
53 changes: 47 additions & 6 deletions packages/cloudflare/src/cli/build/patches/ast/webpack-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,28 @@
*
* 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;
* // ...
* 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) => { $$$ }
Expand All @@ -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;
Expand All @@ -57,14 +84,28 @@ 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))
.map((chunk) => {
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);
}
}