diff --git a/packages/cloudflare/src/api/config.ts b/packages/cloudflare/src/api/config.ts index d625f8bd..e52f3d51 100644 --- a/packages/cloudflare/src/api/config.ts +++ b/packages/cloudflare/src/api/config.ts @@ -11,10 +11,21 @@ import type { Queue, TagCache, } from "@opennextjs/aws/types/overrides.js"; +import type { BuildOptions as EsbuildBuildOptions } from "esbuild"; import assetResolver from "./overrides/asset-resolver/index.js"; export type Override = "dummy" | T | LazyLoadedOverride; +export type EsbuildOverride = + | Partial + | ((options: EsbuildBuildOptions) => EsbuildBuildOptions); + +export type CloudflareEsbuildOverrides = { + /** + * Allows customizing the esbuild configuration used when bundling the worker server. + */ + bundleServer?: EsbuildOverride; +}; /** * Cloudflare specific overrides. @@ -55,6 +66,11 @@ export type CloudflareOverrides = { * @default "none" */ routePreloadingBehavior?: RoutePreloadingBehavior; + + /** + * Low-level overrides for the esbuild invocations performed by the Cloudflare adapter. + */ + esbuild?: CloudflareEsbuildOverrides; }; /** @@ -71,6 +87,7 @@ export function defineCloudflareConfig(config: CloudflareOverrides = {}): OpenNe cachePurge, enableCacheInterception = false, routePreloadingBehavior = "none", + esbuild: esbuildOverrides, } = config; return { @@ -90,6 +107,7 @@ export function defineCloudflareConfig(config: CloudflareOverrides = {}): OpenNe edgeExternals: ["node:crypto"], cloudflare: { useWorkerdCondition: true, + ...(esbuildOverrides ? { esbuild: esbuildOverrides } : {}), }, dangerous: { enableCacheInterception, @@ -179,6 +197,11 @@ interface OpenNextConfig extends AwsOpenNextConfig { // @default 7 maxVersionAgeDays?: number; }; + + /** + * Low-level overrides for the esbuild invocations performed by the Cloudflare adapter. + */ + esbuild?: CloudflareEsbuildOverrides; }; } diff --git a/packages/cloudflare/src/cli/build/bundle-server.ts b/packages/cloudflare/src/cli/build/bundle-server.ts index 95402451..c40fcf42 100644 --- a/packages/cloudflare/src/cli/build/bundle-server.ts +++ b/packages/cloudflare/src/cli/build/bundle-server.ts @@ -5,8 +5,9 @@ import { fileURLToPath } from "node:url"; import { type BuildOptions, getPackagePath } from "@opennextjs/aws/build/helper.js"; import { ContentUpdater } from "@opennextjs/aws/plugins/content-updater.js"; -import { build, type Plugin } from "esbuild"; +import { build, type BuildOptions as EsbuildBuildOptions, type Plugin } from "esbuild"; +import type { EsbuildOverride } from "../../api/config.js"; import { getOpenNextConfig } from "../../api/config.js"; import type { ProjectOptions } from "../project-options.js"; import { patchVercelOgLibrary } from "./patches/ast/patch-vercel-og-library.js"; @@ -50,6 +51,7 @@ export async function bundleServer(buildOpts: BuildOptions, projectOpts: Project copyPackageCliFiles(packageDistDir, buildOpts); const { appPath, outputDir, monorepoRoot, debug } = buildOpts; + const cloudflareConfig = getOpenNextConfig(buildOpts).cloudflare; const baseManifestPath = path.join( outputDir, "server-functions/default", @@ -71,7 +73,7 @@ export async function bundleServer(buildOpts: BuildOptions, projectOpts: Project const updater = new ContentUpdater(buildOpts); - const result = await build({ + const baseEsbuildOptions: EsbuildBuildOptions = { entryPoints: [openNextServer], bundle: true, outfile: openNextServerBundle, @@ -92,7 +94,7 @@ export async function bundleServer(buildOpts: BuildOptions, projectOpts: Project // - default nft conditions: https://github.com/vercel/nft/blob/2b55b01/readme.md#exports--imports // - Next no explicit override: https://github.com/vercel/next.js/blob/2efcf11/packages/next/src/build/collect-build-traces.ts#L287 // - ESBuild `node` platform: https://esbuild.github.io/api/#platform - conditions: getOpenNextConfig(buildOpts).cloudflare?.useWorkerdCondition === false ? [] : ["workerd"], + conditions: cloudflareConfig?.useWorkerdCondition === false ? [] : ["workerd"], plugins: [ shimRequireHook(buildOpts), inlineDynamicRequires(updater, buildOpts), @@ -159,7 +161,14 @@ export async function bundleServer(buildOpts: BuildOptions, projectOpts: Project js: `import {setInterval, clearInterval, setTimeout, clearTimeout, setImmediate, clearImmediate} from "node:timers"`, }, platform: "node", - }); + }; + + const resolvedEsbuildOptions = applyEsbuildOverride( + baseEsbuildOptions, + cloudflareConfig?.esbuild?.bundleServer + ); + + const result = await build(resolvedEsbuildOptions); fs.writeFileSync(openNextServerBundle + ".meta.json", JSON.stringify(result.metafile, null, 2)); @@ -187,6 +196,33 @@ export async function updateWorkerBundledCode(workerOutputFile: string): Promise await writeFile(workerOutputFile, patchedCode); } +function applyEsbuildOverride( + baseOptions: EsbuildBuildOptions, + override: EsbuildOverride | undefined +): EsbuildBuildOptions { + if (!override) { + return baseOptions; + } + + if (typeof override === "function") { + return override(baseOptions); + } + + const merged: EsbuildBuildOptions = { + ...baseOptions, + ...override, + }; + + if (override.logOverride || baseOptions.logOverride) { + merged.logOverride = { + ...baseOptions.logOverride, + ...override.logOverride, + }; + } + + return merged; +} + /** * Gets the path of the worker.js file generated by the build process *