Skip to content

Commit 203a94c

Browse files
authored
Fix server-function bundle structure when Next.js is inside a monorepo (#21)
1 parent 30d60f4 commit 203a94c

File tree

1 file changed

+54
-14
lines changed

1 file changed

+54
-14
lines changed

src/build.ts

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,22 @@ const outputDir = ".open-next";
1111
const tempDir = path.join(outputDir, ".build");
1212

1313
export async function build() {
14+
printVersion();
15+
1416
// Pre-build validation
17+
printHeader("Validating Next.js app");
1518
checkRunningInsideNextjsApp();
19+
setStandaloneBuildMode();
20+
const monorepoRoot = findMonorepoRoot();
1621

1722
// Build Next.js app
18-
setStandaloneBuildMode();
19-
const buildOutput = await buildNextjsApp();
23+
printHeader("Building Next.js app");
24+
const buildOutput = await buildNextjsApp(monorepoRoot);
2025

2126
// Generate deployable bundle
22-
printHeader("Generating OpenNext bundle");
23-
printVersion();
27+
printHeader("Generating bundle");
2428
initOutputDir();
25-
createServerBundle();
29+
createServerBundle(monorepoRoot);
2630
createImageOptimizationBundle();
2731
createMiddlewareBundle(buildOutput);
2832
createAssets();
@@ -35,14 +39,35 @@ function checkRunningInsideNextjsApp() {
3539
}
3640
}
3741

42+
function findMonorepoRoot() {
43+
let currentPath = appPath;
44+
while (currentPath !== "/") {
45+
if (fs.existsSync(path.join(currentPath, "package-lock.json"))
46+
|| fs.existsSync(path.join(currentPath, "yarn.lock"))
47+
|| fs.existsSync(path.join(currentPath, "pnpm-lock.yaml"))) {
48+
if (currentPath !== appPath) {
49+
console.info("Monorepo root detected at", currentPath);
50+
}
51+
return currentPath;
52+
}
53+
currentPath = path.dirname(currentPath);
54+
}
55+
56+
// note: a lock file (package-lock.json, yarn.lock, or pnpm-lock.yaml) is
57+
// not found in the app's directory or any of its parent directories.
58+
// We are going to assume that the app is not part of a monorepo.
59+
return appPath;
60+
}
61+
3862
function setStandaloneBuildMode() {
3963
// Equivalent to setting `target: 'standalone'` in next.config.js
4064
process.env.NEXT_PRIVATE_STANDALONE = "true";
4165
}
4266

43-
function buildNextjsApp() {
67+
function buildNextjsApp(monorepoRoot: string) {
4468
return nextBuild({
4569
files: [],
70+
repoRootPath: monorepoRoot,
4671
workPath: appPath,
4772
entrypoint: "next.config.js",
4873
config: {},
@@ -51,7 +76,8 @@ function buildNextjsApp() {
5176
}
5277

5378
function printHeader(header: string) {
54-
console.log([
79+
header = `OpenNext — ${header}`;
80+
console.info([
5581
"┌" + "─".repeat(header.length + 2) + "┐",
5682
`│ ${header} │`,
5783
"└" + "─".repeat(header.length + 2) + "┘",
@@ -61,7 +87,7 @@ function printHeader(header: string) {
6187
function printVersion() {
6288
const pathToPackageJson = path.join(__dirname, "../package.json");
6389
const pkg = JSON.parse(fs.readFileSync(pathToPackageJson, "utf-8"));
64-
console.log(`Using v${pkg.version}`);
90+
console.info(`Using v${pkg.version}`);
6591
}
6692

6793
function initOutputDir() {
@@ -75,8 +101,8 @@ function isMiddlewareEnabled() {
75101
return JSON.parse(json).sortedMiddleware.length > 0;
76102
}
77103

78-
function createServerBundle() {
79-
console.debug(`Bundling server function...`);
104+
function createServerBundle(monorepoRoot: string) {
105+
console.info(`Bundling server function...`);
80106

81107
// Create output folder
82108
const outputPath = path.join(outputDir, "server-function");
@@ -90,6 +116,20 @@ function createServerBundle() {
90116
path.join(outputPath),
91117
{ recursive: true, verbatimSymlinks: true }
92118
);
119+
// note: if user's app is inside a monorepo, standalone mode places
120+
// `node_modules` inside `.next/standalone`, and others inside
121+
// `.next/standalone/package/path` (ie. `.next`, `server.js`).
122+
// We need to move them to the root of the output folder.
123+
if (monorepoRoot) {
124+
const packagePath = path.relative(monorepoRoot, appPath);
125+
fs.readdirSync(path.join(outputPath, packagePath))
126+
.forEach(file => {
127+
fs.renameSync(
128+
path.join(outputPath, packagePath, file),
129+
path.join(outputPath, file)
130+
);
131+
});
132+
}
93133

94134
// Standalone output already has a Node server "server.js", remove it.
95135
// It will be replaced with the Lambda handler.
@@ -118,7 +158,7 @@ function createServerBundle() {
118158
}
119159

120160
function createImageOptimizationBundle() {
121-
console.debug(`Bundling image optimization function...`);
161+
console.info(`Bundling image optimization function...`);
122162

123163
// Create output folder
124164
const outputPath = path.join(outputDir, "image-optimization-function");
@@ -170,10 +210,10 @@ function createImageOptimizationBundle() {
170210

171211
function createMiddlewareBundle(buildOutput: any) {
172212
if (isMiddlewareEnabled()) {
173-
console.debug(`Bundling middleware edge function...`);
213+
console.info(`Bundling middleware edge function...`);
174214
}
175215
else {
176-
console.debug(`Bundling middleware edge function... \x1b[36m%s\x1b[0m`, "skipped");
216+
console.info(`Bundling middleware edge function... \x1b[36m%s\x1b[0m`, "skipped");
177217
return;
178218
}
179219

@@ -221,7 +261,7 @@ function createMiddlewareBundle(buildOutput: any) {
221261
}
222262

223263
function createAssets() {
224-
console.debug(`Bundling assets...`);
264+
console.info(`Bundling assets...`);
225265

226266
// Create output folder
227267
const outputPath = path.join(outputDir, "assets");

0 commit comments

Comments
 (0)