Skip to content

Commit

Permalink
Improve custom config support (#416)
Browse files Browse the repository at this point in the history
* fix missing fnName in the function

* compile open-next to node by default

* compile config for edge
  • Loading branch information
conico974 authored May 24, 2024
1 parent f83d636 commit e98e014
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 45 deletions.
5 changes: 5 additions & 0 deletions .changeset/orange-bees-lie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"open-next": patch
---

Improve custom config support
65 changes: 28 additions & 37 deletions packages/open-next/src/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ import os from "node:os";
import path from "node:path";
import url from "node:url";

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

import { isBinaryContentType } from "./adapters/binary.js";
import {
compileOpenNextConfigEdge,
compileOpenNextConfigNode,
} from "./build/compileConfig.js";
import { createServerBundle } from "./build/createServerBundle.js";
import { buildEdgeBundle } from "./build/edge/createEdgeBundle.js";
import { generateOutput } from "./build/generateOutput.js";
Expand Down Expand Up @@ -39,15 +42,24 @@ export type PublicFiles = {
files: string[];
};

export async function build(openNextConfigPath?: string) {
export async function build(
openNextConfigPath?: string,
nodeExternals?: string,
) {
showWindowsWarning();

// Load open-next.config.ts
const tempDir = initTempDir();
const configPath = compileOpenNextConfig(tempDir, openNextConfigPath);
const configPath = compileOpenNextConfigNode(
tempDir,
openNextConfigPath,
nodeExternals,
);
config = (await import(configPath)).default as OpenNextConfig;
validateConfig(config);

compileOpenNextConfigEdge(tempDir, config, openNextConfigPath);

const { root: monorepoRoot, packager } = findMonorepoRoot(
path.join(process.cwd(), config.appPath || "."),
);
Expand Down Expand Up @@ -107,40 +119,6 @@ function initTempDir() {
return tempDir;
}

function compileOpenNextConfig(tempDir: string, openNextConfigPath?: string) {
const sourcePath = path.join(
process.cwd(),
openNextConfigPath ?? "open-next.config.ts",
);
const outputPath = path.join(tempDir, "open-next.config.mjs");

//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,
[
"var config = { default: { } };",
"var open_next_config_default = config;",
"export { open_next_config_default as default };",
].join("\n"),
);
} else {
buildSync({
entryPoints: [sourcePath],
outfile: outputPath,
bundle: true,
format: "esm",
target: ["node18"],
external: ["node:*"],
platform: "neutral",
});
}

return outputPath;
}

function checkRunningInsideNextjsApp() {
const { appPath } = options;
const extension = ["js", "cjs", "mjs"].find((ext) =>
Expand Down Expand Up @@ -228,9 +206,22 @@ function initOutputDir() {
path.join(tempDir, "open-next.config.mjs"),
"utf8",
);
let openNextConfigEdge: string | null = null;
if (fs.existsSync(path.join(tempDir, "open-next.config.edge.mjs"))) {
openNextConfigEdge = readFileSync(
path.join(tempDir, "open-next.config.edge.mjs"),
"utf8",
);
}
fs.rmSync(outputDir, { recursive: true, force: true });
fs.mkdirSync(tempDir, { recursive: true });
fs.writeFileSync(path.join(tempDir, "open-next.config.mjs"), openNextConfig);
if (openNextConfigEdge) {
fs.writeFileSync(
path.join(tempDir, "open-next.config.edge.mjs"),
openNextConfigEdge,
);
}
}

async function createWarmerBundle(config: OpenNextConfig) {
Expand Down
90 changes: 90 additions & 0 deletions packages/open-next/src/build/compileConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import fs from "node:fs";
import path from "node:path";

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

import logger from "../logger.js";

export function compileOpenNextConfigNode(
tempDir: string,
openNextConfigPath?: string,
nodeExternals?: string,
) {
const sourcePath = path.join(
process.cwd(),
openNextConfigPath ?? "open-next.config.ts",
);
const outputPath = path.join(tempDir, "open-next.config.mjs");

//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,
[
"var config = { default: { } };",
"var open_next_config_default = config;",
"export { open_next_config_default as default };",
].join("\n"),
);
} else {
buildSync({
entryPoints: [sourcePath],
outfile: outputPath,
bundle: true,
format: "esm",
target: ["node18"],
external: nodeExternals ? nodeExternals.split(",") : [],
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(""),
},
});
}

return outputPath;
}

export function compileOpenNextConfigEdge(
tempDir: string,
config: OpenNextConfig,
openNextConfigPath?: string,
) {
const sourcePath = path.join(
process.cwd(),
openNextConfigPath ?? "open-next.config.ts",
);
const outputPath = path.join(tempDir, "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.");
}
}
1 change: 1 addition & 0 deletions packages/open-next/src/build/createServerBundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ async function generateBundle(
"const require = topLevelCreateRequire(import.meta.url);",
"import bannerUrl from 'url';",
"const __dirname = bannerUrl.fileURLToPath(new URL('.', import.meta.url));",
name === "default" ? "" : `globalThis.fnName = "${name}";`,
].join(""),
},
plugins,
Expand Down
2 changes: 1 addition & 1 deletion packages/open-next/src/build/edge/createEdgeBundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export async function generateEdgeBundle(
fs.mkdirSync(outputPath, { recursive: true });

// Copy open-next.config.mjs
copyOpenNextConfig(path.join(outputDir, ".build"), outputPath);
copyOpenNextConfig(path.join(outputDir, ".build"), outputPath, true);

// Load middleware manifest
const middlewareManifest = JSON.parse(
Expand Down
11 changes: 9 additions & 2 deletions packages/open-next/src/build/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,10 +240,17 @@ export function compareSemver(v1: string, v2: string): number {
return patch1 - patch2;
}

export function copyOpenNextConfig(tempDir: string, outputPath: string) {
export function copyOpenNextConfig(
tempDir: string,
outputPath: string,
isEdge = false,
) {
// Copy open-next.config.mjs
fs.copyFileSync(
path.join(tempDir, "open-next.config.mjs"),
path.join(
tempDir,
isEdge ? "open-next.config.edge.mjs" : "open-next.config.mjs",
),
path.join(outputPath, "open-next.config.mjs"),
);
}
7 changes: 6 additions & 1 deletion packages/open-next/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ if (command !== "build") printHelp();
const args = parseArgs();
if (Object.keys(args).includes("--help")) printHelp();

build(args["--config-path"]);
build(args["--config-path"], args["--node-externals"]);

function parseArgs() {
return process.argv.slice(2).reduce(
Expand All @@ -33,9 +33,14 @@ function printHelp() {
console.log("");
console.log("Usage:");
console.log(" npx open-next build");
console.log("You can use a custom config path here");
console.log(
" npx open-next build --config-path ./path/to/open-next.config.ts",
);
console.log(
"You can configure externals for the esbuild compilation of the open-next.config.ts file",
);
console.log(" npx open-next build --node-externals aws-sdk,sharp,sqlite3");
console.log("");

process.exit(1);
Expand Down
17 changes: 13 additions & 4 deletions packages/open-next/src/types/open-next.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,10 @@ export interface OpenNextConfig {
tagCache?: "dynamodb" | LazyLoadedOverride<TagCache>;
};

/**
* Dangerous options. This break some functionnality but can be useful in some cases.
*/
dangerous?: DangerousOptions;
/**
* The command to build the Next.js app.
* @default `npm run build`, `yarn build`, or `pnpm build` based on the lock file found in the app's directory or any of its parent directories.
Expand All @@ -316,10 +320,6 @@ export interface OpenNextConfig {
* });
* ```
*/
/**
* Dangerous options. This break some functionnality but can be useful in some cases.
*/
dangerous?: DangerousOptions;
buildCommand?: string;
/**
* The path to the target folder of build output from the `buildCommand` option (the path which will contain the `.next` and `.open-next` folders). This path is relative from the current process.cwd().
Expand All @@ -336,4 +336,13 @@ export interface OpenNextConfig {
* @default "."
*/
packageJsonPath?: string;
/**
* **Advanced usage**
* If you use the edge runtime somewhere (either in the middleware or in the functions), we compile 2 versions of the open-next.config.ts file.
* One for the node runtime and one for the edge runtime.
* This option allows you to specify the externals for the edge runtime used in esbuild for the compilation of open-next.config.ts
* It is especially useful if you use some custom overrides only in node
* @default []
*/
edgeExternals?: string[];
}

0 comments on commit e98e014

Please sign in to comment.