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
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.