diff --git a/.changeset/perfect-cobras-move.md b/.changeset/perfect-cobras-move.md new file mode 100644 index 000000000..ff013ebc1 --- /dev/null +++ b/.changeset/perfect-cobras-move.md @@ -0,0 +1,5 @@ +--- +"@opennextjs/aws": patch +--- + +fix(middleware): copy wasm files for bundled middleware diff --git a/packages/open-next/src/build/createServerBundle.ts b/packages/open-next/src/build/createServerBundle.ts index 645fb110b..df185b06f 100644 --- a/packages/open-next/src/build/createServerBundle.ts +++ b/packages/open-next/src/build/createServerBundle.ts @@ -3,6 +3,7 @@ import path from "node:path"; import type { FunctionOptions, SplittedFunctionOptions } from "types/open-next"; +import { loadMiddlewareManifest } from "config/util.js"; import type { Plugin } from "esbuild"; import logger from "../logger.js"; import { minifyAll } from "../minimize-js.js"; @@ -13,7 +14,10 @@ import { getCrossPlatformPathRegex } from "../utils/regex.js"; import { bundleNextServer } from "./bundleNextServer.js"; import { compileCache } from "./compileCache.js"; import { copyTracedFiles } from "./copyTracedFiles.js"; -import { generateEdgeBundle } from "./edge/createEdgeBundle.js"; +import { + copyMiddlewareResources, + generateEdgeBundle, +} from "./edge/createEdgeBundle.js"; import * as buildHelper from "./helper.js"; import { installDependencies } from "./installDeps.js"; import { type CodePatcher, applyCodePatches } from "./patch/codePatcher.js"; @@ -135,12 +139,14 @@ async function generateBundle( // `.next/standalone/package/path` (ie. `.next`, `server.js`). // We need to output the handler file inside the package path. const packagePath = buildHelper.getPackagePath(options); - fs.mkdirSync(path.join(outputPath, packagePath), { recursive: true }); + const outPackagePath = path.join(outputPath, packagePath); + + fs.mkdirSync(outPackagePath, { recursive: true }); const ext = fnOptions.runtime === "deno" ? "mjs" : "cjs"; fs.copyFileSync( path.join(options.buildDir, `cache.${ext}`), - path.join(outputPath, packagePath, "cache.cjs"), + path.join(outPackagePath, "cache.cjs"), ); if (fnOptions.runtime === "deno") { @@ -150,7 +156,7 @@ async function generateBundle( // Bundle next server if necessary const isBundled = fnOptions.experimentalBundledNextServer ?? false; if (isBundled) { - await bundleNextServer(path.join(outputPath, packagePath), appPath, { + await bundleNextServer(outPackagePath, appPath, { minify: options.minify, }); } @@ -159,15 +165,22 @@ async function generateBundle( if (!config.middleware?.external) { fs.copyFileSync( path.join(options.buildDir, "middleware.mjs"), - path.join(outputPath, packagePath, "middleware.mjs"), + path.join(outPackagePath, "middleware.mjs"), + ); + + const middlewareManifest = loadMiddlewareManifest( + path.join(options.appBuildOutputPath, ".next"), + ); + + copyMiddlewareResources( + options, + middlewareManifest.middleware["/"], + outPackagePath, ); } // Copy open-next.config.mjs - buildHelper.copyOpenNextConfig( - options.buildDir, - path.join(outputPath, packagePath), - ); + buildHelper.copyOpenNextConfig(options.buildDir, outPackagePath); // Copy env files buildHelper.copyEnvFile(appBuildOutputPath, packagePath, outputPath); diff --git a/packages/open-next/src/build/edge/createEdgeBundle.ts b/packages/open-next/src/build/edge/createEdgeBundle.ts index 9c8104644..2ca114c24 100644 --- a/packages/open-next/src/build/edge/createEdgeBundle.ts +++ b/packages/open-next/src/build/edge/createEdgeBundle.ts @@ -57,7 +57,7 @@ export async function buildEdgeBundle({ name, additionalPlugins: additionalPluginsFn, }: BuildEdgeBundleOptions) { - const isInCloudfare = await isEdgeRuntime(overrides); + const isInCloudflare = await isEdgeRuntime(overrides); function override(target: T) { return typeof overrides?.[target] === "string" ? overrides[target] @@ -103,7 +103,7 @@ export async function buildEdgeBundle({ openNextEdgePlugins({ middlewareInfo, nextDir: path.join(options.appBuildOutputPath, ".next"), - isInCloudfare, + isInCloudflare, }), ...additionalPlugins, // The content updater plugin must be the last plugin @@ -146,7 +146,7 @@ Object.defineProperty = function(o, p, a) { }; ${ - isInCloudfare + isInCloudflare ? "" : ` const require = (await import("node:module")).createRequire(import.meta.url); @@ -209,25 +209,7 @@ export async function generateEdgeBundle( } const middlewareInfo = functions[0]; - //Copy wasm files - const wasmFiles = middlewareInfo.wasm; - mkdirSync(path.join(outputDir, "wasm"), { recursive: true }); - for (const wasmFile of wasmFiles) { - fs.copyFileSync( - path.join(buildOutputDotNextDir, wasmFile.filePath), - path.join(outputDir, `wasm/${wasmFile.name}.wasm`), - ); - } - - // Copy assets - const assets = middlewareInfo.assets; - mkdirSync(path.join(outputDir, "assets"), { recursive: true }); - for (const asset of assets) { - fs.copyFileSync( - path.join(buildOutputDotNextDir, asset.filePath), - path.join(outputDir, `assets/${asset.name}`), - ); - } + copyMiddlewareResources(options, middlewareInfo, outputDir); await buildEdgeBundle({ middlewareInfo, @@ -240,3 +222,28 @@ export async function generateEdgeBundle( additionalPlugins, }); } + +/** + * Copy wasm files and assets into the destDir. + */ +export function copyMiddlewareResources( + options: BuildOptions, + middlewareInfo: MiddlewareInfo | undefined, + destDir: string, +) { + mkdirSync(path.join(destDir, "wasm"), { recursive: true }); + for (const file of middlewareInfo?.wasm ?? []) { + fs.copyFileSync( + path.join(options.appBuildOutputPath, ".next", file.filePath), + path.join(destDir, `wasm/${file.name}.wasm`), + ); + } + + mkdirSync(path.join(destDir, "assets"), { recursive: true }); + for (const file of middlewareInfo?.assets ?? []) { + fs.copyFileSync( + path.join(options.appBuildOutputPath, ".next", file.filePath), + path.join(destDir, `assets/${file.name}`), + ); + } +} diff --git a/packages/open-next/src/plugins/edge.ts b/packages/open-next/src/plugins/edge.ts index 29761eb20..3509fde33 100644 --- a/packages/open-next/src/plugins/edge.ts +++ b/packages/open-next/src/plugins/edge.ts @@ -24,19 +24,19 @@ import { getCrossPlatformPathRegex } from "../utils/regex.js"; export interface IPluginSettings { nextDir: string; middlewareInfo?: MiddlewareInfo; - isInCloudfare?: boolean; + isInCloudflare?: boolean; } /** * @param opts.nextDir - The path to the .next directory * @param opts.middlewareInfo - Information about the middleware - * @param opts.isInCloudfare - Whether the code runs on the cloudflare runtime + * @param opts.isInCloudflare - Whether the code runs on the cloudflare runtime * @returns */ export function openNextEdgePlugins({ nextDir, middlewareInfo, - isInCloudfare, + isInCloudflare, }: IPluginSettings): Plugin { const entryFiles = middlewareInfo?.files.map((file: string) => path.join(nextDir, file)) ?? []; @@ -94,7 +94,7 @@ globalThis.self = globalThis; globalThis._ROUTES = ${JSON.stringify(routes)}; ${ - isInCloudfare + isInCloudflare ? "" : ` import {readFileSync} from "node:fs"; @@ -138,16 +138,11 @@ if (!globalThis.URLPattern) { } ` } -${wasmFiles - .map((file) => - isInCloudfare - ? `import ${file.name} from './wasm/${file.name}.wasm';` - : `const ${file.name} = readFileSync(path.join(__dirname,'/wasm/${file.name}.wasm'));`, - ) - .join("\n")} +${importWasm(wasmFiles, { isInCloudflare })} ${entryFiles.map((file) => `require("${file}");`).join("\n")} ${contents} `; + return { contents, }; @@ -202,3 +197,22 @@ ${contents} }, }; } + +function importWasm( + files: MiddlewareInfo["wasm"], + { isInCloudflare }: { isInCloudflare?: boolean }, +) { + return files + .map(({ name }) => { + if (isInCloudflare) { + // As `.next/server/src/middleware.js` references the name, + // using `import ${name} from '...'` would cause ESBuild to rename the import. + // We use `globalThis.${name}` to make sure `middleware.js` reference name will match. + return `import __onw_${name}__ from './wasm/${name}.wasm' +globalThis.${name} = __onw_${name}__`; + } + + return `const ${name} = readFileSync(path.join(__dirname,'/wasm/${name}.wasm'));`; + }) + .join("\n"); +}