Skip to content
697 changes: 23 additions & 674 deletions packages/open-next/src/build.ts

Large diffs are not rendered by default.

24 changes: 24 additions & 0 deletions packages/open-next/src/build/buildNextApp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import cp from "node:child_process";
import path from "node:path";

import * as buildHelper from "./helper.js";

export function setStandaloneBuildMode(options: buildHelper.BuildOptions) {
// Equivalent to setting `output: "standalone"` in next.config.js
process.env.NEXT_PRIVATE_STANDALONE = "true";
// Equivalent to setting `experimental.outputFileTracingRoot` in next.config.js
process.env.NEXT_PRIVATE_OUTPUT_TRACE_ROOT = options.monorepoRoot;
}

export function buildNextjsApp(options: buildHelper.BuildOptions) {
const { config, packager } = options;
const command =
config.buildCommand ??
(["bun", "npm"].includes(packager)
? `${packager} run build`
: `${packager} build`);
cp.execSync(command, {
stdio: "inherit",
cwd: path.dirname(options.appPackageJsonPath),
});
}
4 changes: 2 additions & 2 deletions packages/open-next/src/build/bundleNextServer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createRequire as topLevelCreateRequire } from "node:module";
import { createRequire } from "node:module";

import { build } from "esbuild";
import path from "path";
Expand Down Expand Up @@ -43,7 +43,7 @@ const externals = [
];

export async function bundleNextServer(outputDir: string, appPath: string) {
const require = topLevelCreateRequire(`${appPath}/package.json`);
const require = createRequire(`${appPath}/package.json`);
const entrypoint = require.resolve("next/dist/esm/server/next-server.js");

await build({
Expand Down
45 changes: 45 additions & 0 deletions packages/open-next/src/build/compileCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import path from "node:path";

import * as buildHelper from "./helper.js";

/**
* Compiles the cache adapter.
*
* @param options Build options.
* @param format Output format.
* @returns The path to the compiled file.
*/
export function compileCache(
options: buildHelper.BuildOptions,
format: "cjs" | "esm" = "cjs",
) {
const { config } = options;
const ext = format === "cjs" ? "cjs" : "mjs";
const outFile = path.join(options.buildDir, `cache.${ext}`);

const isAfter15 =
buildHelper.compareSemver(options.nextVersion, "15.0.0") >= 0;

buildHelper.esbuildSync(
{
external: ["next", "styled-jsx", "react", "@aws-sdk/*"],
entryPoints: [path.join(options.openNextDistDir, "adapters", "cache.js")],
outfile: outFile,
target: ["node18"],
format,
banner: {
js: [
`globalThis.disableIncrementalCache = ${
config.dangerous?.disableIncrementalCache ?? false
};`,
`globalThis.disableDynamoDBCache = ${
config.dangerous?.disableTagCache ?? false
};`,
`globalThis.isNextAfter15 = ${isAfter15};`,
].join(""),
},
},
options,
);
return outFile;
}
110 changes: 74 additions & 36 deletions packages/open-next/src/build/compileConfig.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,74 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";

import { buildSync } from "esbuild";
import { OpenNextConfig } from "types/open-next.js";

import logger from "../logger.js";
import { validateConfig } from "./validateConfig.js";

export function compileOpenNextConfigNode(
outputDir: string,
/**
* Compiles the OpenNext configuration.
*
* The configuration is always compiled for Node.js and for the edge only if needed.
*
* @param baseDir Directory where to look for the configuration.
* @param openNextConfigPath Override the default configuration when provided. Relative to baseDir.
* @param nodeExternals Externals for the Node.js compilation.
* @return The configuration and the build directory.
*/
export async function compileOpenNextConfig(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an added function.

It calls the two existing function for Node and edge if needed.

The edge test has been moved to this function.

baseDir: string,
openNextConfigPath?: string,
nodeExternals?: string,
) {
const sourcePath = path.join(
process.cwd(),
baseDir,
openNextConfigPath ?? "open-next.config.ts",
);

const buildDir = fs.mkdtempSync(path.join(os.tmpdir(), "open-next-tmp"));
let configPath = compileOpenNextConfigNode(
sourcePath,
buildDir,
nodeExternals ? nodeExternals.split(",") : [],
);

// On Windows, we need to use file:// protocol to load the config file using import()
if (process.platform === "win32") configPath = `file://${configPath}`;
const config = (await import(configPath)).default as OpenNextConfig;
if (!config || !config.default) {
logger.error(
`config.default cannot be empty, it should be at least {}, see more info here: https://open-next.js.org/config#configuration-file`,
);
process.exit(1);
}

validateConfig(config);

// We need to check if the config uses the edge runtime at any point
// If it does, we need to compile it with the edge runtime
const usesEdgeRuntime =
config.middleware?.external ||
Object.values(config.functions || {}).some((fn) => fn.runtime === "edge");
if (!usesEdgeRuntime) {
logger.debug(
"No edge runtime found in the open-next.config.ts. Using default config.",
);
//Nothing to do here
} else {
compileOpenNextConfigEdge(baseDir, buildDir, config.edgeExternals ?? []);
}

return { config, buildDir };
}

export function compileOpenNextConfigNode(
sourcePath: string,
outputDir: string,
externals: string[],
) {
const outputPath = path.join(outputDir, "open-next.config.mjs");

//Check if open-next.config.ts exists
Expand All @@ -29,7 +83,7 @@ export function compileOpenNextConfigNode(
bundle: true,
format: "esm",
target: ["node18"],
external: nodeExternals ? nodeExternals.split(",") : [],
external: externals,
platform: "node",
banner: {
js: [
Expand All @@ -46,38 +100,22 @@ export function compileOpenNextConfigNode(
}

export function compileOpenNextConfigEdge(
tempDir: string,
config: OpenNextConfig,
openNextConfigPath?: string,
sourcePath: string,
outputDir: string,
externals: string[],
) {
const sourcePath = path.join(
process.cwd(),
openNextConfigPath ?? "open-next.config.ts",
);
const outputPath = path.join(tempDir, "open-next.config.edge.mjs");
const outputPath = path.join(outputDir, "open-next.config.edge.mjs");

// We need to check if the config uses the edge runtime at any point
// If it does, we need to compile it with the edge runtime
const usesEdgeRuntime =
config.middleware?.external ||
Object.values(config.functions || {}).some((fn) => fn.runtime === "edge");
if (!usesEdgeRuntime) {
logger.debug(
"No edge runtime found in the open-next.config.ts. Using default config.",
);
//Nothing to do here
} else {
logger.info("Compiling open-next.config.ts for edge runtime.", outputPath);
buildSync({
entryPoints: [sourcePath],
outfile: outputPath,
bundle: true,
format: "esm",
target: ["es2020"],
conditions: ["worker", "browser"],
platform: "browser",
external: config.edgeExternals ?? [],
});
logger.info("Compiled open-next.config.ts for edge runtime.");
}
logger.info("Compiling open-next.config.ts for edge runtime.", outputPath);
buildSync({
entryPoints: [sourcePath],
outfile: outputPath,
bundle: true,
format: "esm",
target: ["es2020"],
conditions: ["worker", "browser"],
platform: "browser",
external: externals,
});
logger.info("Compiled open-next.config.ts for edge runtime.");
}
Loading
Loading