1
1
import { readFileSync , statSync , writeFileSync } from "node:fs" ;
2
2
import { join } from "node:path" ;
3
3
4
+ import * as ts from "ts-morph" ;
5
+
4
6
import { Config } from "../../../config.js" ;
7
+ import { tsParseFile } from "../../utils/index.js" ;
5
8
6
9
export function patchWranglerDeps ( config : Config ) {
7
10
console . log ( "# patchWranglerDeps" ) ;
@@ -23,6 +26,8 @@ export function patchWranglerDeps(config: Config) {
23
26
24
27
writeFileSync ( pagesRuntimeFile , patchedPagesRuntime ) ;
25
28
29
+ patchRequireReactDomServerEdge ( config ) ;
30
+
26
31
// Patch .next/standalone/node_modules/next/dist/server/lib/trace/tracer.js
27
32
//
28
33
// Remove the need for an alias in wrangler.toml:
@@ -67,3 +72,120 @@ function getDistPath(config: Config): string {
67
72
68
73
throw new Error ( "Unexpected error: unable to detect the node_modules/next/dist directory" ) ;
69
74
}
75
+
76
+ /**
77
+ * `react-dom` v>=19 has a `server.edge` export: https://github.com/facebook/react/blob/a160102f3/packages/react-dom/package.json#L79
78
+ * but version of `react-dom` <= 18 do not have this export but have a `server.browser` export instead: https://github.com/facebook/react/blob/8a015b68/packages/react-dom/package.json#L49
79
+ *
80
+ * Next.js also try-catches importing the `server.edge` export:
81
+ * https://github.com/vercel/next.js/blob/6784575/packages/next/src/server/ReactDOMServerPages.js
82
+ *
83
+ * The issue here is that in the `.next/standalone/node_modules/next/dist/compiled/next-server/pages.runtime.prod.js`
84
+ * file for whatever reason there is a non `try-catch`ed require for the `server.edge` export
85
+ *
86
+ * This functions fixes this issue by wrapping the require in a try-catch block in the same way Next.js does it
87
+ * (note: this will make the build succeed but doesn't guarantee that everything will necessarily work at runtime since
88
+ * it's not clear what code and how might be rely on this require call)
89
+ *
90
+ */
91
+ function patchRequireReactDomServerEdge ( config : Config ) {
92
+ const distPath = getDistPath ( config ) ;
93
+
94
+ // Patch .next/standalone/node_modules/next/dist/compiled/next-server/pages.runtime.prod.js
95
+ const pagesRuntimeFile = join ( distPath , "compiled" , "next-server" , "pages.runtime.prod.js" ) ;
96
+
97
+ const code = readFileSync ( pagesRuntimeFile , "utf-8" ) ;
98
+ const file = tsParseFile ( code ) ;
99
+
100
+ // we need to update this function: `e=>{"use strict";e.exports=require("react-dom/server.edge")}`
101
+ file . getDescendantsOfKind ( ts . SyntaxKind . ArrowFunction ) . forEach ( ( arrowFunction ) => {
102
+ // the function has a single parameter
103
+ const p = arrowFunction . getParameters ( ) ;
104
+ if ( p . length !== 1 ) {
105
+ return ;
106
+ }
107
+ const parameterName = p [ 0 ] ! . getName ( ) ;
108
+ const bodyChildren = arrowFunction . getBody ( ) . getChildren ( ) ;
109
+ if (
110
+ ! (
111
+ bodyChildren . length === 3 &&
112
+ bodyChildren [ 0 ] ! . getFullText ( ) === "{" &&
113
+ bodyChildren [ 2 ] ! . getFullText ( ) === "}"
114
+ )
115
+ ) {
116
+ return ;
117
+ }
118
+ const bodyStatements = bodyChildren [ 1 ] ?. getChildren ( ) ;
119
+
120
+ // the function has only two statements: "use strict" and e.exports=require("react-dom/server.edge")
121
+ if (
122
+ ! (
123
+ bodyStatements ?. length === 2 &&
124
+ bodyStatements . every ( ( statement ) => statement . isKind ( ts . SyntaxKind . ExpressionStatement ) )
125
+ )
126
+ ) {
127
+ return ;
128
+ }
129
+ const bodyExpressionStatements = bodyStatements as [ ts . ExpressionStatement , ts . ExpressionStatement ] ;
130
+
131
+ const stringLiteralExpression = bodyExpressionStatements [ 0 ] . getExpressionIfKind (
132
+ ts . SyntaxKind . StringLiteral
133
+ ) ;
134
+
135
+ // the first statement needs to be "use strict"
136
+ if ( stringLiteralExpression ?. getText ( ) !== '"use strict"' ) {
137
+ return ;
138
+ }
139
+
140
+ // the second statement (e.exports=require("react-dom/server.edge")) needs to be a binary expression
141
+ const binaryExpression = bodyExpressionStatements [ 1 ] . getExpressionIfKind ( ts . SyntaxKind . BinaryExpression ) ;
142
+ if ( ! binaryExpression ?. getOperatorToken ( ) . isKind ( ts . SyntaxKind . EqualsToken ) ) {
143
+ return ;
144
+ }
145
+
146
+ // on the left we have `${parameterName}.exports`
147
+ const binaryLeft = binaryExpression . getLeft ( ) ;
148
+ if (
149
+ ! binaryLeft . isKind ( ts . SyntaxKind . PropertyAccessExpression ) ||
150
+ binaryLeft . getExpressionIfKind ( ts . SyntaxKind . Identifier ) ?. getText ( ) !== parameterName ||
151
+ binaryLeft . getName ( ) !== "exports"
152
+ ) {
153
+ return ;
154
+ }
155
+
156
+ // on the right we have `require("react-dom/server.edge")`
157
+ const binaryRight = binaryExpression . getRight ( ) ;
158
+ if (
159
+ ! binaryRight . isKind ( ts . SyntaxKind . CallExpression ) ||
160
+ binaryRight . getExpressionIfKind ( ts . SyntaxKind . Identifier ) ?. getText ( ) !== "require"
161
+ ) {
162
+ return ;
163
+ }
164
+ const requireArgs = binaryRight . getArguments ( ) ;
165
+ if ( requireArgs . length !== 1 || requireArgs [ 0 ] ! . getText ( ) !== '"react-dom/server.edge"' ) {
166
+ return ;
167
+ }
168
+
169
+ arrowFunction . setBodyText (
170
+ `
171
+ "use strict";
172
+ let ReactDOMServer;
173
+ try {
174
+ ReactDOMServer = require('react-dom/server.edge');
175
+ } catch (error) {
176
+ if (
177
+ error.code !== 'MODULE_NOT_FOUND' &&
178
+ error.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED'
179
+ ) {
180
+ throw error;
181
+ }
182
+ ReactDOMServer = require('react-dom/server.browser');
183
+ }
184
+ ${ parameterName } .exports = ReactDOMServer;
185
+ }` . replace ( / \n s * / g, " " )
186
+ ) ;
187
+ } ) ;
188
+
189
+ const updatedCode = file . print ( ) ;
190
+ writeFileSync ( pagesRuntimeFile , updatedCode ) ;
191
+ }
0 commit comments