Skip to content

Commit

Permalink
refactor: migrate the requirePage patch to ast-grep (#287)
Browse files Browse the repository at this point in the history
  • Loading branch information
vicb authored Jan 28, 2025
1 parent 1a2b815 commit 3b20bc6
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 59 deletions.
17 changes: 14 additions & 3 deletions packages/cloudflare/src/cli/build/bundle-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ 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 setWranglerExternal from "./patches/plugins/wrangler-external.js";
import { normalizePath, patchCodeWithValidations } from "./utils/index.js";

/** The dist directory of the Cloudflare adapter package */
Expand Down Expand Up @@ -48,8 +50,18 @@ export async function bundleServer(buildOpts: BuildOptions): Promise<void> {
format: "esm",
target: "esnext",
minify: false,
plugins: [createFixRequiresESBuildPlugin(buildOpts)],
external: ["./middleware/handler.mjs", "caniuse-lite"],
plugins: [
createFixRequiresESBuildPlugin(buildOpts),
inlineRequirePagePlugin(buildOpts),
setWranglerExternal(),
],
external: [
"./middleware/handler.mjs",
// Next optional dependencies.
"caniuse-lite",
"jimp",
"probe-image-size",
],
alias: {
// Note: we apply an empty shim to next/dist/compiled/ws because it generates two `eval`s:
// eval("require")("bufferutil");
Expand Down Expand Up @@ -146,7 +158,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)],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { type SgNode } from "@ast-grep/napi";
import { applyRule } from "./util.js";

/**
* Handle optional dependencies.
* Handles optional dependencies.
*
* A top level `require(optionalDep)` would throw when the dep is not installed.
*
Expand All @@ -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
Expand Down
88 changes: 88 additions & 0 deletions packages/cloudflare/src/cli/build/patches/plugins/require-page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
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 { getCrossPlatformPathRegex } from "@opennextjs/aws/utils/regex.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: getCrossPlatformPathRegex(String.raw`/next/dist/server/require\.js$`, { escape: false }),
},
async ({ path }) => {
const jsCode = await readFile(path, "utf8");
if (/function requirePage\(/.test(jsCode)) {
return { contents: patchCode(jsCode, getRule(buildOpts)) };
}
}
);
},
};
}

function getRule(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 htmlFiles = manifests.filter((file) => file.endsWith(".html"));
const jsFiles = manifests.filter((file) => file.endsWith(".js"));

// Inline fs access and dynamic require that are not supported by workerd.
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);
$$$_BODY
}`,
},
fix: `
function requirePage($PAGE, $DIST_DIR, $IS_APPP_ATH) {
const pagePath = getPagePath($$$ARGS);
${fnBody}
}`,
} satisfies RuleConfig;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* ESBuild plugin to mark files bundled by wrangler as external.
*
* `.wasm` and `.bin` will ultimately be bundled by wrangler.
* We should only mark them as external in the adapter.
*
* However simply marking them as external would copy the import path to the bundle,
* i.e. `import("./file.wasm?module")` and given than the bundle is generated in a
* different location than the input files, the relative path would not be valid.
*
* This ESBuild plugin convert relative paths to absolute paths so that they are
* still valid from inside the bundle.
*
* ref: https://developers.cloudflare.com/workers/wrangler/bundling/
*/

import { dirname, resolve } from "node:path";

import type { PluginBuild } from "esbuild";

export default function setWranglerExternal() {
return {
name: "wrangler-externals",

setup: async (build: PluginBuild) => {
const namespace = "wrangler-externals-plugin";

build.onResolve({ filter: /(\.bin|\.wasm\?module)$/ }, ({ path, importer }) => {
return {
path: resolve(dirname(importer), path),
namespace,
external: true,
};
});

build.onLoad({ filter: /.*/, namespace }, async ({ path }) => {
return {
contents: `export * from '${path}';`,
};
});
},
};
}
Original file line number Diff line number Diff line change
@@ -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";
Expand Down

This file was deleted.

0 comments on commit 3b20bc6

Please sign in to comment.