Skip to content

Commit 33460c3

Browse files
conico974fwang
andauthored
Fix react dependencies resolution (#129)
* fix: incorrect react resolution in non-hoisted monorepo * fix: replace getMaybePagePath to handle dynamic routes * Sync --------- Co-authored-by: Frank <[email protected]>
1 parent b687b16 commit 33460c3

File tree

5 files changed

+105
-67
lines changed

5 files changed

+105
-67
lines changed

.changeset/flat-monkeys-behave.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"open-next": patch
3+
---
4+
5+
server: Fix react dependencies resolution

packages/open-next/src/adapters/next-types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,13 @@ export interface NextConfig {
3535
};
3636
images: ImageConfig;
3737
}
38+
39+
interface RouteDefinition {
40+
page: string;
41+
regex: string;
42+
}
43+
44+
export interface RoutesManifest {
45+
dynamicRoutes: RouteDefinition[];
46+
staticRoutes: RouteDefinition[];
47+
}

packages/open-next/src/adapters/require-hooks.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@ export function overrideDefault() {
3131
["styled-jsx", require.resolve("styled-jsx")],
3232
["styled-jsx/style", require.resolve("styled-jsx/style")],
3333
["styled-jsx/style", require.resolve("styled-jsx/style")],
34-
["server-only", require.resolve("next/dist/compiled/server-only")],
35-
["client-only", require.resolve("next/dist/compiled/client-only")],
3634
],
3735
"app"
3836
);
@@ -42,17 +40,20 @@ export function overrideDefault() {
4240
export function overrideReact(config: NextConfig) {
4341
addHookAliases(
4442
[
45-
["react", `/var/task/node_modules/react`],
46-
["react/jsx-runtime", `/var/task/node_modules/react/jsx-runtime`],
47-
["react/jsx-dev-runtime", `/var/task/node_modules/react/jsx-dev-runtime`],
48-
["react-dom", `/var/task/node_modules/react-dom`],
49-
[
50-
"react-dom/server.browser",
51-
`/var/task/node_modules/react-dom/server.browser`,
52-
],
43+
["react", require.resolve(`react`)],
44+
["react/jsx-runtime", require.resolve(`react/jsx-runtime`)],
5345
],
5446
"page"
5547
);
48+
49+
// ignore: react/jsx-dev-runtime is not available on older version of Next.js ie. v13.1.6
50+
try {
51+
addHookAliases(
52+
[["react/jsx-dev-runtime", require.resolve(`react/jsx-dev-runtime`)]],
53+
"page"
54+
);
55+
} catch (e) {}
56+
5657
if (config.experimental.appDir) {
5758
if (config.experimental.serverActions) {
5859
addHookAliases(

packages/open-next/src/adapters/server-adapter.ts

Lines changed: 45 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import fs from "node:fs";
21
import path from "node:path";
32
import { IncomingMessage } from "./request.js";
43
import { ServerResponse } from "./response.js";
@@ -7,32 +6,41 @@ import type {
76
APIGatewayProxyEvent,
87
CloudFrontRequestEvent,
98
} from "aws-lambda";
10-
// @ts-ignore
11-
import NextServer from "next/dist/server/next-server.js";
12-
//@ts-ignore
13-
import { getMaybePagePath } from "next/dist/server/require.js";
14-
import { generateUniqueId, loadConfig, setNodeEnv } from "./util.js";
9+
import {
10+
generateUniqueId,
11+
loadAppPathsManifest,
12+
loadConfig,
13+
loadHtmlPages,
14+
loadPublicAssets,
15+
loadRoutesManifest,
16+
setNodeEnv,
17+
} from "./util.js";
1518
import { isBinaryContentType } from "./binary.js";
1619
import { debug } from "./logger.js";
17-
import type { PublicFiles } from "../build.js";
1820
import { convertFrom, convertTo } from "./event-mapper.js";
1921
import { overrideDefault, overrideReact } from "./require-hooks.js";
2022
import type { WarmerEvent, WarmerResponse } from "./warmer-function.js";
2123

2224
const NEXT_DIR = path.join(__dirname, ".next");
2325
const OPEN_NEXT_DIR = path.join(__dirname, ".open-next");
24-
const NODE_MODULES_DIR = path.join(__dirname, "node_modules");
2526
debug({ NEXT_DIR, OPEN_NEXT_DIR });
2627

2728
setNodeEnv();
2829
setNextjsServerWorkingDirectory();
2930
const config = loadConfig(NEXT_DIR);
30-
const htmlPages = loadHtmlPages();
31-
const publicAssets = loadPublicAssets();
32-
initializeNextjsRequireHooks(config);
33-
31+
const htmlPages = loadHtmlPages(NEXT_DIR);
32+
const routesManifest = loadRoutesManifest(NEXT_DIR);
33+
const appPathsManifest = loadAppPathsManifest(NEXT_DIR);
34+
const publicAssets = loadPublicAssets(OPEN_NEXT_DIR);
3435
// Generate a 6 letter unique server ID
3536
const serverId = `server-${generateUniqueId()}`;
37+
38+
// Need to override the require hooks for React before Next.js server
39+
// overrides them with prebundled ones in the case of app dir
40+
overrideNextjsRequireHooks(config);
41+
42+
// @ts-ignore
43+
import NextServer from "next/dist/server/next-server.js";
3644
const requestHandler = new NextServer.default({
3745
hostname: "localhost",
3846
port: Number(process.env.PORT) || 3000,
@@ -127,34 +135,40 @@ function setNextjsServerWorkingDirectory() {
127135
process.chdir(__dirname);
128136
}
129137

130-
function initializeNextjsRequireHooks(config: any) {
138+
function overrideNextjsRequireHooks(config: any) {
131139
// WORKAROUND: Set `__NEXT_PRIVATE_PREBUNDLED_REACT` to use prebundled React — https://github.com/serverless-stack/open-next#workaround-set-__next_private_prebundled_react-to-use-prebundled-react
132-
if (!isNextjsVersionAtLeast("13.1.3")) return;
133-
overrideDefault();
134-
overrideReact(config);
140+
try {
141+
overrideDefault();
142+
overrideReact(config);
143+
} catch (e) {
144+
console.error("Failed to override Next.js require hooks.", e);
145+
throw e;
146+
}
135147
}
136148

137149
function setNextjsPrebundledReact(req: IncomingMessage, config: any) {
138150
// WORKAROUND: Set `__NEXT_PRIVATE_PREBUNDLED_REACT` to use prebundled React — https://github.com/serverless-stack/open-next#workaround-set-__next_private_prebundled_react-to-use-prebundled-react
139151

140-
// "getMaybePagePath" is not present in older version of next.js
141-
// => use node_modules React
142-
if (!getMaybePagePath) {
143-
process.env.__NEXT_PRIVATE_PREBUNDLED_REACT = undefined;
152+
// Get route pattern
153+
const route = [
154+
...routesManifest.staticRoutes,
155+
...routesManifest.dynamicRoutes,
156+
].find((route) => new RegExp(route.regex).test(req.url ?? ""));
157+
158+
const isApp = appPathsManifest[`${route?.page}/page`];
159+
debug("setNextjsPrebundledReact", { url: req.url, isApp });
160+
161+
// app routes => use prebundled React
162+
if (isApp) {
163+
process.env.__NEXT_PRIVATE_PREBUNDLED_REACT = config.experimental
164+
.serverActions
165+
? "experimental"
166+
: "next";
144167
return;
145168
}
146169

147-
// pages route => use node_modules React
148-
if (getMaybePagePath(req.url, NEXT_DIR, config.i18n?.locales, false)) {
149-
process.env.__NEXT_PRIVATE_PREBUNDLED_REACT = undefined;
150-
return;
151-
}
152-
153-
// app router => use prebundled React
154-
process.env.__NEXT_PRIVATE_PREBUNDLED_REACT = config.experimental
155-
.serverActions
156-
? "experimental"
157-
: "next";
170+
// page routes => use node_modules React
171+
process.env.__NEXT_PRIVATE_PREBUNDLED_REACT = undefined;
158172
}
159173

160174
async function processRequest(req: IncomingMessage, res: ServerResponse) {
@@ -197,28 +211,3 @@ function formatWarmerResponse(event: WarmerEvent) {
197211
}, event.delay);
198212
});
199213
}
200-
201-
function isNextjsVersionAtLeast(required: `${number}.${number}.${number}`) {
202-
const version = require("next/package.json").version;
203-
const [major, minor, patch] = version.split("-")[0].split(".").map(Number);
204-
const [reqMajor, reqMinor, reqPatch] = required.split(".").map(Number);
205-
return (
206-
major > reqMajor ||
207-
(major === reqMajor && minor > reqMinor) ||
208-
(major === reqMajor && minor === reqMinor && patch >= reqPatch)
209-
);
210-
}
211-
212-
function loadHtmlPages() {
213-
const filePath = path.join(NEXT_DIR, "server", "pages-manifest.json");
214-
const json = fs.readFileSync(filePath, "utf-8");
215-
return Object.entries(JSON.parse(json))
216-
.filter(([_, value]) => (value as string).endsWith(".html"))
217-
.map(([key]) => key);
218-
}
219-
220-
function loadPublicAssets() {
221-
const filePath = path.join(OPEN_NEXT_DIR, "public-files.json");
222-
const json = fs.readFileSync(filePath, "utf-8");
223-
return JSON.parse(json) as PublicFiles;
224-
}

packages/open-next/src/adapters/util.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import fs from "node:fs";
22
import path from "node:path";
3-
import type { NextConfig } from "./next-types.js";
3+
import type { NextConfig, RoutesManifest } from "./next-types.js";
4+
import type { PublicFiles } from "../build.js";
45

56
export function setNodeEnv() {
67
process.env.NODE_ENV = process.env.NODE_ENV ?? "production";
@@ -16,3 +17,35 @@ export function loadConfig(nextDir: string) {
1617
const { config } = JSON.parse(json);
1718
return config as NextConfig;
1819
}
20+
21+
export function loadHtmlPages(nextDir: string) {
22+
const filePath = path.join(nextDir, "server", "pages-manifest.json");
23+
const json = fs.readFileSync(filePath, "utf-8");
24+
return Object.entries(JSON.parse(json))
25+
.filter(([_, value]) => (value as string).endsWith(".html"))
26+
.map(([key]) => key);
27+
}
28+
29+
export function loadPublicAssets(openNextDir: string) {
30+
const filePath = path.join(openNextDir, "public-files.json");
31+
const json = fs.readFileSync(filePath, "utf-8");
32+
return JSON.parse(json) as PublicFiles;
33+
}
34+
35+
export function loadRoutesManifest(nextDir: string) {
36+
const filePath = path.join(nextDir, "routes-manifest.json");
37+
const json = fs.readFileSync(filePath, "utf-8");
38+
return JSON.parse(json) as RoutesManifest;
39+
}
40+
41+
export function loadAppPathsManifest(nextDir: string) {
42+
const appPathsManifestPath = path.join(
43+
nextDir,
44+
"server",
45+
"app-paths-manifest.json"
46+
);
47+
const appPathsManifestJson = fs.existsSync(appPathsManifestPath)
48+
? fs.readFileSync(appPathsManifestPath, "utf-8")
49+
: "{}";
50+
return JSON.parse(appPathsManifestJson) as Record<string, string>;
51+
}

0 commit comments

Comments
 (0)