1- import { readFileSync } from "node:fs" ;
1+ import fs from "node:fs" ;
22import { readFile , writeFile } from "node:fs/promises" ;
3- import { dirname , join } from "node:path" ;
3+ import path from "node:path" ;
44import { fileURLToPath } from "node:url" ;
55
6+ import type { BuildOptions } from "@opennextjs/aws/build/helper.js" ;
67import { build , Plugin } from "esbuild" ;
78
89import { Config } from "../config" ;
@@ -20,37 +21,37 @@ import { patchWranglerDeps } from "./patches/to-investigate/wrangler-deps";
2021import { copyPrerenderedRoutes } from "./utils" ;
2122
2223/** The dist directory of the Cloudflare adapter package */
23- const packageDistDir = join ( dirname ( fileURLToPath ( import . meta. url ) ) , ".." ) ;
24+ const packageDistDir = path . join ( path . dirname ( fileURLToPath ( import . meta. url ) ) , ".." ) ;
2425
2526/**
26- * Using the Next.js build output in the `.next` directory builds a workerd compatible output
27- *
28- * @param outputDir the directory where to save the output
29- * @param config
27+ * Bundle the Open Next server.
3028 */
31- export async function buildWorker ( config : Config ) : Promise < void > {
29+ export async function bundleServer ( config : Config , openNextOptions : BuildOptions ) : Promise < void > {
3230 // Copy over prerendered assets (e.g. SSG routes)
3331 copyPrerenderedRoutes ( config ) ;
3432
35- copyPackageCliFiles ( packageDistDir , config ) ;
36-
37- const workerEntrypoint = join ( config . paths . internal . templates , "worker.ts" ) ;
38- const workerOutputFile = join ( config . paths . output . root , "index.mjs" ) ;
33+ copyPackageCliFiles ( packageDistDir , config , openNextOptions ) ;
3934
4035 const nextConfigStr =
41- readFileSync ( join ( config . paths . output . standaloneApp , "/server.js" ) , "utf8" ) ?. match (
42- / c o n s t n e x t C o n f i g = ( { . + ? } ) \n /
43- ) ?. [ 1 ] ?? { } ;
36+ fs
37+ . readFileSync ( path . join ( config . paths . output . standaloneApp , "/server.js" ) , "utf8" )
38+ ?. match ( / c o n s t n e x t C o n f i g = ( { . + ? } ) \n / ) ?. [ 1 ] ?? { } ;
4439
45- console . log ( `\x1b[35m⚙️ Bundling the worker file ...\n\x1b[0m` ) ;
40+ console . log ( `\x1b[35m⚙️ Bundling the OpenNext server ...\n\x1b[0m` ) ;
4641
4742 patchWranglerDeps ( config ) ;
4843 updateWebpackChunksFile ( config ) ;
4944
45+ const { appBuildOutputPath, appPath, outputDir, monorepoRoot } = openNextOptions ;
46+ const outputPath = path . join ( outputDir , "server-functions" , "default" ) ;
47+ const packagePath = path . relative ( monorepoRoot , appBuildOutputPath ) ;
48+ const openNextServer = path . join ( outputPath , packagePath , `index.mjs` ) ;
49+ const openNextServerBundle = path . join ( outputPath , packagePath , `handler.mjs` ) ;
50+
5051 await build ( {
51- entryPoints : [ workerEntrypoint ] ,
52+ entryPoints : [ openNextServer ] ,
5253 bundle : true ,
53- outfile : workerOutputFile ,
54+ outfile : openNextServerBundle ,
5455 format : "esm" ,
5556 target : "esnext" ,
5657 minify : false ,
@@ -60,15 +61,15 @@ export async function buildWorker(config: Config): Promise<void> {
6061 // Note: we apply an empty shim to next/dist/compiled/ws because it generates two `eval`s:
6162 // eval("require")("bufferutil");
6263 // eval("require")("utf-8-validate");
63- "next/dist/compiled/ws" : join ( config . paths . internal . templates , "shims" , "empty.ts" ) ,
64+ "next/dist/compiled/ws" : path . join ( config . paths . internal . templates , "shims" , "empty.ts" ) ,
6465 // Note: we apply an empty shim to next/dist/compiled/edge-runtime since (amongst others) it generated the following `eval`:
6566 // eval(getModuleCode)(module, module.exports, throwingRequire, params.context, ...Object.values(params.scopedContext));
6667 // which comes from https://github.com/vercel/edge-runtime/blob/6e96b55f/packages/primitives/src/primitives/load.js#L57-L63
6768 // QUESTION: Why did I encountered this but mhart didn't?
68- "next/dist/compiled/edge-runtime" : join ( config . paths . internal . templates , "shims" , "empty.ts" ) ,
69+ "next/dist/compiled/edge-runtime" : path . join ( config . paths . internal . templates , "shims" , "empty.ts" ) ,
6970 // `@next/env` is a library Next.js uses for loading dotenv files, for obvious reasons we need to stub it here
7071 // source: https://github.com/vercel/next.js/tree/0ac10d79720/packages/next-env
71- "@next/env" : join ( config . paths . internal . templates , "shims" , "env.ts" ) ,
72+ "@next/env" : path . join ( config . paths . internal . templates , "shims" , "env.ts" ) ,
7273 } ,
7374 define : {
7475 // config file used by Next.js, see: https://github.com/vercel/next.js/blob/68a7128/packages/next/src/build/utils.ts#L2137-L2139
@@ -86,15 +87,11 @@ export async function buildWorker(config: Config): Promise<void> {
8687 // We need to set platform to node so that esbuild doesn't complain about the node imports
8788 platform : "node" ,
8889 banner : {
90+ // `__dirname` is used by unbundled js files (which don't inherit the `__dirname` present in the `define` field)
91+ // so we also need to set it on the global scope
92+ // Note: this was hit in the `next/dist/compiled/@opentelemetry/api` module
8993 js : `
90- ${
91- /*
92- `__dirname` is used by unbundled js files (which don't inherit the `__dirname` present in the `define` field)
93- so we also need to set it on the global scope
94- Note: this was hit in the `next/dist/compiled/@opentelemetry/api` module
95- */ ""
96- }
97- globalThis.__dirname ??= "";
94+ globalThis.__dirname ??= "";
9895
9996// Do not crash on cache not supported
10097// https://github.com/cloudflare/workerd/pull/2434
@@ -106,15 +103,15 @@ globalThis.fetch = (input, init) => {
106103 }
107104 return curFetch(input, init);
108105};
109- import { Readable } from 'node:stream';
106+ import __cf_stream from 'node:stream';
110107fetch = globalThis.fetch;
111108const CustomRequest = class extends globalThis.Request {
112109 constructor(input, init) {
113110 if (init) {
114111 delete init.cache;
115112 if (init.body?.__node_stream__ === true) {
116113 // https://github.com/cloudflare/workerd/issues/2746
117- init.body = Readable.toWeb(init.body);
114+ init.body = __cf_stream. Readable.toWeb(init.body);
118115 }
119116 }
120117 super(input, init);
@@ -128,9 +125,18 @@ globalThis.__dangerous_ON_edge_converter_returns_request = true;
128125 } ,
129126 } ) ;
130127
131- await updateWorkerBundledCode ( workerOutputFile , config ) ;
128+ await updateWorkerBundledCode ( openNextServerBundle , config , openNextOptions ) ;
132129
133- console . log ( `\x1b[35mWorker saved in \`${ workerOutputFile } \` 🚀\n\x1b[0m` ) ;
130+ const isMonorepo = monorepoRoot !== appPath ;
131+ if ( isMonorepo ) {
132+ const packagePosixPath = packagePath . split ( path . sep ) . join ( path . posix . sep ) ;
133+ fs . writeFileSync (
134+ path . join ( outputPath , "handler.mjs" ) ,
135+ `export * from "./${ packagePosixPath } /handler.mjs";`
136+ ) ;
137+ }
138+
139+ console . log ( `\x1b[35mWorker saved in \`${ openNextServerBundle } \` 🚀\n\x1b[0m` ) ;
134140}
135141
136142/**
@@ -141,7 +147,11 @@ globalThis.__dangerous_ON_edge_converter_returns_request = true;
141147 * @param workerOutputFile
142148 * @param config
143149 */
144- async function updateWorkerBundledCode ( workerOutputFile : string , config : Config ) : Promise < void > {
150+ async function updateWorkerBundledCode (
151+ workerOutputFile : string ,
152+ config : Config ,
153+ openNextOptions : BuildOptions
154+ ) : Promise < void > {
145155 const originalCode = await readFile ( workerOutputFile , "utf8" ) ;
146156
147157 let patchedCode = originalCode ;
@@ -151,10 +161,15 @@ async function updateWorkerBundledCode(workerOutputFile: string, config: Config)
151161 patchedCode = inlineNextRequire ( patchedCode , config ) ;
152162 patchedCode = patchFindDir ( patchedCode , config ) ;
153163 patchedCode = inlineEvalManifest ( patchedCode , config ) ;
154- patchedCode = await patchCache ( patchedCode , config ) ;
164+ patchedCode = await patchCache ( patchedCode , openNextOptions ) ;
155165 patchedCode = inlineMiddlewareManifestRequire ( patchedCode , config ) ;
156166 patchedCode = patchExceptionBubbling ( patchedCode ) ;
157167
168+ patchedCode = patchedCode
169+ // workers do not support dynamic require nor require.resolve
170+ . replace ( "patchAsyncStorage();" , "//patchAsyncStorage();" )
171+ . replace ( 'require.resolve("./cache.cjs")' , '"unused"' ) ;
172+
158173 await writeFile ( workerOutputFile , patchedCode ) ;
159174}
160175
@@ -164,10 +179,10 @@ function createFixRequiresESBuildPlugin(config: Config): Plugin {
164179 setup ( build ) {
165180 // Note: we (empty) shim require-hook modules as they generate problematic code that uses requires
166181 build . onResolve ( { filter : / ^ \. \/ r e q u i r e - h o o k $ / } , ( ) => ( {
167- path : join ( config . paths . internal . templates , "shims" , "empty.ts" ) ,
182+ path : path . join ( config . paths . internal . templates , "shims" , "empty.ts" ) ,
168183 } ) ) ;
169184 build . onResolve ( { filter : / \. \/ l i b \/ n o d e - f s - m e t h o d s $ / } , ( ) => ( {
170- path : join ( config . paths . internal . templates , "shims" , "empty.ts" ) ,
185+ path : path . join ( config . paths . internal . templates , "shims" , "empty.ts" ) ,
171186 } ) ) ;
172187 } ,
173188 } ;
0 commit comments