Skip to content

add command to build and run OpenNext locally with dev overrides #853

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions packages/open-next/src/build.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { spawnSync } from "node:child_process";
import path from "node:path";
import url from "node:url";

import {
Expand Down Expand Up @@ -25,6 +27,7 @@ export type PublicFiles = {
export async function build(
openNextConfigPath?: string,
nodeExternals?: string,
dev = false,
) {
showWarningOnWindows();

Expand All @@ -35,6 +38,7 @@ export async function build(
baseDir,
openNextConfigPath,
{ nodeExternals },
dev,
);

// Initialize options
Expand Down Expand Up @@ -80,4 +84,23 @@ export async function build(
await createWarmerBundle(options);
await generateOutput(options);
logger.info("OpenNext build complete.");

if (dev) {
printHeader("Starting local OpenNext server");
spawnSync(
"node",
[
path.join(
options.outputDir,
"server-functions",
"default",
"index.mjs",
),
],
{
stdio: "inherit",
shell: process.platform === "win32",
},
);
}
}
85 changes: 69 additions & 16 deletions packages/open-next/src/build/compileConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { buildSync } from "esbuild";
import type { OpenNextConfig } from "types/open-next.js";

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

/**
Expand All @@ -23,6 +24,7 @@ export async function compileOpenNextConfig(
baseDir: string,
openNextConfigPath?: string,
{ nodeExternals = "", compileEdge = false } = {},
dev = false,
) {
const sourcePath = path.join(
baseDir,
Expand All @@ -34,6 +36,7 @@ export async function compileOpenNextConfig(
sourcePath,
buildDir,
nodeExternals.split(","),
dev,
);

// On Windows, we need to use file:// protocol to load the config file using import()
Expand Down Expand Up @@ -69,32 +72,32 @@ export function compileOpenNextConfigNode(
sourcePath: string,
outputDir: string,
externals: string[],
dev = false,
) {
const outputPath = path.join(outputDir, "open-next.config.mjs");
logger.debug("Compiling open-next.config.ts for Node.", outputPath);

if (dev) {
buildConfig({
sourcePath,
externals,
outputPath,
dev,
});
return outputPath;
}

//Check if open-next.config.ts exists
if (!fs.existsSync(sourcePath)) {
//Create a simple open-next.config.mjs file
logger.debug("Cannot find open-next.config.ts. Using default config.");
fs.writeFileSync(outputPath, "export default { default: { } };");
} else {
buildSync({
entryPoints: [sourcePath],
outfile: outputPath,
bundle: true,
format: "esm",
target: ["node18"],
external: externals,
platform: "node",
banner: {
js: [
"import { createRequire as topLevelCreateRequire } from 'module';",
"const require = topLevelCreateRequire(import.meta.url);",
"import bannerUrl from 'url';",
"const __dirname = bannerUrl.fileURLToPath(new URL('.', import.meta.url));",
].join(""),
},
buildConfig({
sourcePath,
externals,
outputPath,
dev,
});
}

Expand All @@ -120,3 +123,53 @@ export function compileOpenNextConfigEdge(
external: externals,
});
}

/**
* Builds the OpenNext configuration using esbuild.
*
* @param sourcePath - Path to the source configuration file.
* @param outputPath - Path where the compiled configuration will be written.
* @param externals - Array of package names to exclude from bundling.
* @param dev - Whether to use development configuration (using DEV_CONFIG constant).
* @returns void
*/
function buildConfig({
sourcePath,
outputPath,
externals,
dev,
}: {
sourcePath: string;
outputPath: string;
externals: string[];
dev: boolean;
}) {
buildSync({
...(dev
? {
stdin: {
contents: DEV_CONFIG,
resolveDir: process.cwd(),
loader: "ts",
sourcefile: "open-next.config.ts",
},
}
: {
entryPoints: [sourcePath],
}),
outfile: outputPath,
bundle: true,
format: "esm",
target: ["node18"],
external: externals,
platform: "node",
banner: {
js: [
"import { createRequire as topLevelCreateRequire } from 'module';",
"const require = topLevelCreateRequire(import.meta.url);",
"import bannerUrl from 'url';",
"const __dirname = bannerUrl.fileURLToPath(new URL('.', import.meta.url));",
].join(""),
},
});
}
26 changes: 26 additions & 0 deletions packages/open-next/src/build/constant.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,29 @@
//TODO: Move all other manifest path here as well
export const MIDDLEWARE_TRACE_FILE = "server/middleware.js.nft.json";
export const INSTRUMENTATION_TRACE_FILE = "server/instrumentation.js.nft.json";

// This is an OpenNext config to run the default server function locally
// https://opennext.js.org/aws/contribute/local_run
export const DEV_CONFIG = `
export default {
default: {
override: {
wrapper: "express-dev",
converter: "node",
incrementalCache: "fs-dev",
queue: "direct",
tagCache: "fs-dev",
},
},
imageOptimization: {
override: {
wrapper: "dummy",
converter: "dummy",
},
loader: "fs-dev",
install: {
arch: "x64",
packages: ["sharp"],
},
},
}`;
12 changes: 10 additions & 2 deletions packages/open-next/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@
import { build } from "./build.js";

const command = process.argv[2];
if (command !== "build") printHelp();
if (command !== "build" && command !== "dev") printHelp();

const args = parseArgs();
if (Object.keys(args).includes("--help")) printHelp();

await build(args["--config-path"], args["--node-externals"]);
if (command === "build") {
await build(args["--config-path"], args["--node-externals"]);
} else if (command === "dev") {
await build(undefined, args["--node-externals"], true);
}

function parseArgs() {
return process.argv.slice(2).reduce(
Expand Down Expand Up @@ -43,6 +47,10 @@ function printHelp() {
);
console.log(" npx open-next build --node-externals aws-sdk,sharp,sqlite3");
console.log("");
console.log(
"This is to start OpenNext locally for testing/debugging purposes",
);
console.log(" npx open-next dev");

process.exit(1);
}
Loading