From e29a59f7e5359fc1cc7c78e673ab78504c0436e3 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Mon, 27 Jan 2025 20:43:56 +0100 Subject: [PATCH] refactor: migrate the requirePage patch to ast-grep --- .../cloudflare/src/cli/build/bundle-server.ts | 15 +++- .../cli/build/patches/ast/optional-deps.ts | 2 +- .../cli/build/patches/plugins/require-page.ts | 82 +++++++++++++++++++ .../cli/build/patches/to-investigate/index.ts | 1 - .../to-investigate/inline-next-require.ts | 53 ------------ 5 files changed, 95 insertions(+), 58 deletions(-) create mode 100644 packages/cloudflare/src/cli/build/patches/plugins/require-page.ts delete mode 100644 packages/cloudflare/src/cli/build/patches/to-investigate/inline-next-require.ts diff --git a/packages/cloudflare/src/cli/build/bundle-server.ts b/packages/cloudflare/src/cli/build/bundle-server.ts index 72f254c3..3d120809 100644 --- a/packages/cloudflare/src/cli/build/bundle-server.ts +++ b/packages/cloudflare/src/cli/build/bundle-server.ts @@ -10,6 +10,7 @@ import { build, Plugin } from "esbuild"; import { patchOptionalDependencies } from "./patches/ast/optional-deps.js"; import * as patches from "./patches/index.js"; +import InlineRequirePagePlugin from "./patches/plugins/require-page.js"; import { normalizePath, patchCodeWithValidations } from "./utils/index.js"; /** The dist directory of the Cloudflare adapter package */ @@ -48,8 +49,17 @@ export async function bundleServer(buildOpts: BuildOptions): Promise { format: "esm", target: "esnext", minify: false, - plugins: [createFixRequiresESBuildPlugin(buildOpts)], - external: ["./middleware/handler.mjs", "caniuse-lite"], + plugins: [createFixRequiresESBuildPlugin(buildOpts), InlineRequirePagePlugin(buildOpts)], + external: [ + "./middleware/handler.mjs", + // Next optional dependencies. + "caniuse-lite", + "jimp", + "probe-image-size", + // Dependencies handled by wrangler. + "*.bin", + "*.wasm?module", + ], alias: { // Note: we apply an empty shim to next/dist/compiled/ws because it generates two `eval`s: // eval("require")("bufferutil"); @@ -146,7 +156,6 @@ async function updateWorkerBundledCode(workerOutputFile: string, buildOpts: Buil ["require", patches.patchRequire], ["`buildId` function", (code) => patches.patchBuildId(code, buildOpts)], ["`loadManifest` function", (code) => patches.patchLoadManifest(code, buildOpts)], - ["next's require", (code) => patches.inlineNextRequire(code, buildOpts)], ["`findDir` function", (code) => patches.patchFindDir(code, buildOpts)], ["`evalManifest` function", (code) => patches.inlineEvalManifest(code, buildOpts)], ["cacheHandler", (code) => patches.patchCache(code, buildOpts)], diff --git a/packages/cloudflare/src/cli/build/patches/ast/optional-deps.ts b/packages/cloudflare/src/cli/build/patches/ast/optional-deps.ts index 9ca7be80..7df5acc5 100644 --- a/packages/cloudflare/src/cli/build/patches/ast/optional-deps.ts +++ b/packages/cloudflare/src/cli/build/patches/ast/optional-deps.ts @@ -16,7 +16,7 @@ rule: pattern: $MOD kind: string_fragment stopBy: end - regex: ^caniuse-lite(/|$) + regex: ^caniuse-lite(/|$)|jimp(/|$)|probe-image-size(/|$) not: inside: kind: try_statement diff --git a/packages/cloudflare/src/cli/build/patches/plugins/require-page.ts b/packages/cloudflare/src/cli/build/patches/plugins/require-page.ts new file mode 100644 index 00000000..1c368c80 --- /dev/null +++ b/packages/cloudflare/src/cli/build/patches/plugins/require-page.ts @@ -0,0 +1,82 @@ +import { existsSync, readFileSync } from "node:fs"; +import { readFile } from "node:fs/promises"; +import { join } from "node:path"; + +import { type BuildOptions, getPackagePath } from "@opennextjs/aws/build/helper.js"; +import type { PluginBuild } from "esbuild"; + +import { patchCode, type RuleConfig } from "../ast/util.js"; + +export default function InlineRequirePagePlugin(buildOpts: BuildOptions) { + return { + name: "inline-require-page", + + setup: async (build: PluginBuild) => { + build.onLoad({ filter: /\/next\/dist\/server\/require\.js$/ }, async ({ path }) => { + const jsCode = await readFile(path, "utf8"); + if (/function requirePage\(/.test(jsCode)) { + return { contents: patchCode(jsCode, getRule(buildOpts)) }; + } + }); + }, + }; +} + +function getRule(buildOpts: BuildOptions) { + // Load manifests + const { outputDir } = buildOpts; + const serverDir = join(outputDir, "server-functions/default", getPackagePath(buildOpts), ".next/server"); + + const pagesManifestFile = join(serverDir, "pages-manifest.json"); + const appPathsManifestFile = join(serverDir, "app-paths-manifest.json"); + + const pagesManifests: string[] = existsSync(pagesManifestFile) + ? Object.values(JSON.parse(readFileSync(pagesManifestFile, "utf-8"))) + : []; + const appPathsManifests: string[] = existsSync(appPathsManifestFile) + ? Object.values(JSON.parse(readFileSync(appPathsManifestFile, "utf-8"))) + : []; + const manifests = pagesManifests.concat(appPathsManifests); + + const htmlFiles = manifests.filter((file) => file.endsWith(".html")); + const jsFiles = manifests.filter((file) => file.endsWith(".js")); + + const fnBody = ` + // html + ${htmlFiles + .map( + (file) => `if (pagePath.endsWith("${file}")) { + return ${JSON.stringify(readFileSync(join(serverDir, file), "utf-8"))}; + }` + ) + .join("\n")} + // js + process.env.__NEXT_PRIVATE_RUNTIME_TYPE = isAppPath ? 'app' : 'pages'; + try { + ${jsFiles + .map( + (file) => `if (pagePath.endsWith("${file}")) { + return require(${JSON.stringify(join(serverDir, file))}); + }` + ) + .join("\n")} +} finally { + process.env.__NEXT_PRIVATE_RUNTIME_TYPE = ''; +} +`; + + return { + rule: { + pattern: ` +function requirePage($PAGE, $DIST_DIR, $IS_APPP_ATH) { + const $_ = getPagePath($$$ARGS); + $$$ +}`, + }, + fix: ` +function requirePage($PAGE, $DIST_DIR, $IS_APPP_ATH) { + const pagePath = getPagePath($$$ARGS); + ${fnBody} +}`, + } satisfies RuleConfig; +} diff --git a/packages/cloudflare/src/cli/build/patches/to-investigate/index.ts b/packages/cloudflare/src/cli/build/patches/to-investigate/index.ts index 5c431dc1..81a40a5d 100644 --- a/packages/cloudflare/src/cli/build/patches/to-investigate/index.ts +++ b/packages/cloudflare/src/cli/build/patches/to-investigate/index.ts @@ -1,6 +1,5 @@ export * from "./inline-eval-manifest.js"; export * from "./inline-middleware-manifest-require.js"; -export * from "./inline-next-require.js"; export * from "./patch-exception-bubbling.js"; export * from "./patch-find-dir.js"; export * from "./patch-load-instrumentation-module.js"; diff --git a/packages/cloudflare/src/cli/build/patches/to-investigate/inline-next-require.ts b/packages/cloudflare/src/cli/build/patches/to-investigate/inline-next-require.ts deleted file mode 100644 index ab45ec3e..00000000 --- a/packages/cloudflare/src/cli/build/patches/to-investigate/inline-next-require.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { existsSync, readFileSync } from "node:fs"; -import { join } from "node:path"; - -import { type BuildOptions, getPackagePath } from "@opennextjs/aws/build/helper.js"; - -/** - * The following avoid various Next.js specific files `require`d at runtime since we can just read - * and inline their content during build time - */ -// TODO(vicb): __NEXT_PRIVATE_RUNTIME_TYPE is not handled by this patch -export function inlineNextRequire(code: string, buildOpts: BuildOptions) { - const { outputDir } = buildOpts; - const serverDir = join(outputDir, "server-functions/default", getPackagePath(buildOpts), ".next/server"); - - const pagesManifestFile = join(serverDir, "pages-manifest.json"); - const appPathsManifestFile = join(serverDir, "app-paths-manifest.json"); - - const pagesManifests: string[] = existsSync(pagesManifestFile) - ? Object.values(JSON.parse(readFileSync(pagesManifestFile, "utf-8"))) - : []; - const appPathsManifests: string[] = existsSync(appPathsManifestFile) - ? Object.values(JSON.parse(readFileSync(appPathsManifestFile, "utf-8"))) - : []; - const manifests = pagesManifests.concat(appPathsManifests); - - const htmlPages = manifests.filter((file) => file.endsWith(".html")); - const pageModules = manifests.filter((file) => file.endsWith(".js")); - - return code.replace( - /const pagePath = getPagePath\(.+?\);/, - `$& - ${htmlPages - .map( - (htmlPage) => ` - if (pagePath.endsWith("${htmlPage}")) { - return ${JSON.stringify(readFileSync(join(serverDir, htmlPage), "utf-8"))}; - } - ` - ) - .join("\n")} - ${pageModules - .map( - (module) => ` - if (pagePath.endsWith("${module}")) { - return require(${JSON.stringify(join(serverDir, module))}); - } - ` - ) - .join("\n")} - throw new Error("Unknown pagePath: " + pagePath); - ` - ); -}