Skip to content

Commit d90ddb7

Browse files
refactor: patch the webpack runtime using ast-grep (#351)
Co-authored-by: Dario Piotrowicz <dario@cloudflare.com>
1 parent f30a5fe commit d90ddb7

15 files changed

+136
-1005
lines changed

packages/cloudflare/src/cli/build/bundle-server.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { getCrossPlatformPathRegex } from "@opennextjs/aws/utils/regex.js";
88
import { build, Plugin } from "esbuild";
99

1010
import { patchVercelOgLibrary } from "./patches/ast/patch-vercel-og-library.js";
11+
import { patchWebpackRuntime } from "./patches/ast/webpack-runtime.js";
1112
import * as patches from "./patches/index.js";
1213
import { handleOptionalDependencies } from "./patches/plugins/optional-deps.js";
1314
import { fixRequire } from "./patches/plugins/require.js";
@@ -49,7 +50,7 @@ export async function bundleServer(buildOpts: BuildOptions): Promise<void> {
4950

5051
console.log(`\x1b[35m⚙️ Bundling the OpenNext server...\n\x1b[0m`);
5152

52-
await patches.updateWebpackChunksFile(buildOpts);
53+
await patchWebpackRuntime(buildOpts);
5354
patchVercelOgLibrary(buildOpts);
5455

5556
const outputPath = path.join(outputDir, "server-functions", "default");
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { describe, expect, test } from "vitest";
2+
3+
import { patchCode } from "./util.js";
4+
import { buildInlineChunksRule } from "./webpack-runtime.js";
5+
6+
describe("webpack runtime", () => {
7+
test("patch runtime", () => {
8+
const code = `
9+
/******/ // require() chunk loading for javascript
10+
/******/ __webpack_require__.f.require = (chunkId, promises) => {
11+
/******/ // "1" is the signal for "already loaded"
12+
/******/ if (!installedChunks[chunkId]) {
13+
/******/ if (658 != chunkId) {
14+
/******/ installChunk(require("./chunks/" + __webpack_require__.u(chunkId)));
15+
/******/
16+
} else installedChunks[chunkId] = 1;
17+
/******/
18+
}
19+
/******/
20+
};
21+
`;
22+
23+
expect(patchCode(code, buildInlineChunksRule([1, 2, 3]))).toMatchInlineSnapshot(`
24+
"/******/ // require() chunk loading for javascript
25+
/******/ __webpack_require__.f.require = (chunkId, _) => {
26+
if (!installedChunks[chunkId]) {
27+
switch (chunkId) {
28+
case 1: installChunk(require("./chunks/1.js")); break;
29+
case 2: installChunk(require("./chunks/2.js")); break;
30+
case 3: installChunk(require("./chunks/3.js")); break;
31+
case 658: installedChunks[chunkId] = 1; break;
32+
default: throw new Error(\`Unknown chunk \${chunkId}\`);
33+
}
34+
}
35+
}
36+
;
37+
"
38+
`);
39+
});
40+
41+
test("patch minified runtime", () => {
42+
const code = `
43+
t.f.require=(o,n)=>{e[o]||(658!=o?r(require("./chunks/"+t.u(o))):e[o]=1)}
44+
`;
45+
46+
expect(patchCode(code, buildInlineChunksRule([1, 2, 3]))).toMatchInlineSnapshot(
47+
`
48+
"t.f.require=(o, _) => {
49+
if (!e[o]) {
50+
switch (o) {
51+
case 1: r(require("./chunks/1.js")); break;
52+
case 2: r(require("./chunks/2.js")); break;
53+
case 3: r(require("./chunks/3.js")); break;
54+
case 658: e[o] = 1; break;
55+
default: throw new Error(\`Unknown chunk \${o}\`);
56+
}
57+
}
58+
}
59+
60+
"
61+
`
62+
);
63+
});
64+
});
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/**
2+
* Inline dynamic requires in the webpack runtime.
3+
*
4+
* The webpack runtime has dynamic requires that would not be bundled by ESBuild:
5+
*
6+
* installChunk(require("./chunks/" + __webpack_require__.u(chunkId)));
7+
*
8+
* This patch unrolls the dynamic require for all the existing chunks:
9+
*
10+
* switch (chunkId) {
11+
* case ID1: installChunk(require("./chunks/ID1"); break;
12+
* case ID2: installChunk(require("./chunks/ID2"); break;
13+
* // ...
14+
* case SELF_ID: installedChunks[chunkId] = 1; break;
15+
* default: throw new Error(`Unknown chunk ${chunkId}`);
16+
* }
17+
*/
18+
19+
import { readdirSync, readFileSync, writeFileSync } from "node:fs";
20+
import { join } from "node:path";
21+
22+
import { type BuildOptions, getPackagePath } from "@opennextjs/aws/build/helper.js";
23+
24+
import { patchCode } from "./util.js";
25+
26+
export function buildInlineChunksRule(chunks: number[]) {
27+
return `
28+
rule:
29+
pattern: ($CHUNK_ID, $_PROMISES) => { $$$ }
30+
inside: {pattern: $_.$_.require = $$$_, stopBy: end}
31+
all:
32+
- has: {pattern: $INSTALL(require("./chunks/" + $$$)), stopBy: end}
33+
- has: {pattern: $SELF_ID != $CHUNK_ID, stopBy: end}
34+
- has: {pattern: "$INSTALLED_CHUNK[$CHUNK_ID] = 1", stopBy: end}
35+
fix: |
36+
($CHUNK_ID, _) => {
37+
if (!$INSTALLED_CHUNK[$CHUNK_ID]) {
38+
switch ($CHUNK_ID) {
39+
${chunks.map((chunk) => ` case ${chunk}: $INSTALL(require("./chunks/${chunk}.js")); break;`).join("\n")}
40+
case $SELF_ID: $INSTALLED_CHUNK[$CHUNK_ID] = 1; break;
41+
default: throw new Error(\`Unknown chunk \${$CHUNK_ID}\`);
42+
}
43+
}
44+
}`;
45+
}
46+
47+
/**
48+
* Fixes the webpack-runtime.js file by removing its webpack dynamic requires.
49+
*/
50+
export async function patchWebpackRuntime(buildOpts: BuildOptions) {
51+
const { outputDir } = buildOpts;
52+
53+
const dotNextServerDir = join(
54+
outputDir,
55+
"server-functions/default",
56+
getPackagePath(buildOpts),
57+
".next/server"
58+
);
59+
60+
const runtimeFile = join(dotNextServerDir, "webpack-runtime.js");
61+
const runtimeCode = readFileSync(runtimeFile, "utf-8");
62+
// Look for all the chunks.
63+
const chunks = readdirSync(join(dotNextServerDir, "chunks"))
64+
.filter((chunk) => /^\d+\.js$/.test(chunk))
65+
.map((chunk) => {
66+
return Number(chunk.replace(/\.js$/, ""));
67+
});
68+
69+
writeFileSync(runtimeFile, patchCode(runtimeCode, buildInlineChunksRule(chunks)));
70+
}
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
export * from "./copy-package-cli-files.js";
22
export * from "./patch-cache.js";
33
export * from "./patch-require.js";
4-
export * from "./update-webpack-chunks-file/index.js";

packages/cloudflare/src/cli/build/patches/investigated/update-webpack-chunks-file/get-chunk-installation-identifiers.test.ts

Lines changed: 0 additions & 30 deletions
This file was deleted.

packages/cloudflare/src/cli/build/patches/investigated/update-webpack-chunks-file/get-chunk-installation-identifiers.ts

Lines changed: 0 additions & 102 deletions
This file was deleted.

packages/cloudflare/src/cli/build/patches/investigated/update-webpack-chunks-file/get-file-content-with-updated-webpack-f-require-code.test.ts

Lines changed: 0 additions & 46 deletions
This file was deleted.

0 commit comments

Comments
 (0)