Skip to content

Commit

Permalink
Merge branch 'main' into vercel-blog-example
Browse files Browse the repository at this point in the history
  • Loading branch information
shashankboosi committed Oct 12, 2024
2 parents 2d48c79 + e7311df commit ab5f1f7
Showing 11 changed files with 93 additions and 52 deletions.
18 changes: 18 additions & 0 deletions packages/cloudflare/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
# @opennextjs/cloudflare

## 0.2.0

### Minor Changes

- 6acf0fd: feat: cli arg to disable minification

The cache handler currently forces minification. There is now a CLI arg to disable minification for the build. At the moment, this only applies to the cache handler but may be used for other parts of the build in the future when minification is introduced to them. By default, minification is enabled, but can be disabled by passing `--noMinify`.

## 0.1.1

### Patch Changes

- 66ba0ff: enhancement: Expand missing next.config error message

Found out that next dev can run the a Next.js app without next.config but
if we are using the adapter we throw an error if we don't find the config.
So expanded the error for users.

## 0.1.0

### Minor Changes
2 changes: 1 addition & 1 deletion packages/cloudflare/TODO.md
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ DONE:
- figure out the assets
- copy the template folders

## Open next [example app](https://github.com/sst/open-next/tree/main/example)
## Open next [example app](https://github.com/opennextjs/opennextjs-aws/tree/main/example)

Changes:

2 changes: 1 addition & 1 deletion packages/cloudflare/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@opennextjs/cloudflare",
"description": "Cloudflare builder for next apps",
"version": "0.1.0",
"version": "0.2.0",
"scripts": {
"build": "tsup",
"build:watch": "tsup --watch src",
12 changes: 9 additions & 3 deletions packages/cloudflare/src/cli/args.ts
Original file line number Diff line number Diff line change
@@ -3,11 +3,12 @@ import { parseArgs } from "node:util";
import { resolve } from "node:path";

export function getArgs(): {
skipBuild: boolean;
skipNextBuild: boolean;
outputDir?: string;
minify: boolean;
} {
const {
values: { skipBuild, output },
values: { skipBuild, output, noMinify },
} = parseArgs({
options: {
skipBuild: {
@@ -19,6 +20,10 @@ export function getArgs(): {
type: "string",
short: "o",
},
noMinify: {
type: "boolean",
default: false,
},
},
allowPositionals: false,
});
@@ -31,7 +36,8 @@ export function getArgs(): {

return {
outputDir,
skipBuild: skipBuild || ["1", "true", "yes"].includes(String(process.env.SKIP_NEXT_APP_BUILD)),
skipNextBuild: skipBuild || ["1", "true", "yes"].includes(String(process.env.SKIP_NEXT_APP_BUILD)),
minify: !noMinify,
};
}

4 changes: 2 additions & 2 deletions packages/cloudflare/src/cli/build/build-next-app.ts
Original file line number Diff line number Diff line change
@@ -18,15 +18,15 @@ export async function buildNextjsApp(nextAppDir: string): Promise<void> {
runNextBuildCommand(pm.name, nextAppDir);
}

// equivalent to: https://github.com/sst/open-next/blob/f61b0e94/packages/open-next/src/build.ts#L175-L186
// equivalent to: https://github.com/opennextjs/opennextjs-aws/blob/f61b0e94/packages/open-next/src/build.ts#L175-L186
function runNextBuildCommand(packager: PackageManager, nextAppDir: string) {
const command = `${packager === "npm" ? "npx" : packager} next build`;
execSync(command, {
stdio: "inherit",
cwd: nextAppDir,
env: {
...process.env,
// equivalent to: https://github.com/sst/open-next/blob/f61b0e9/packages/open-next/src/build.ts#L168-L173
// equivalent to: https://github.com/opennextjs/opennextjs-aws/blob/f61b0e9/packages/open-next/src/build.ts#L168-L173
// Equivalent to setting `output: "standalone"` in next.config.js
NEXT_PRIVATE_STANDALONE: "true",
},
8 changes: 4 additions & 4 deletions packages/cloudflare/src/cli/build/build-worker.ts
Original file line number Diff line number Diff line change
@@ -32,16 +32,16 @@ export async function buildWorker(config: Config): Promise<void> {
// Copy over client-side generated files
await cp(
path.join(config.paths.dotNext, "static"),
path.join(config.paths.builderOutput, "assets", "_next", "static"),
path.join(config.paths.outputDir, "assets", "_next", "static"),
{
recursive: true,
}
);

// Copy over any static files (e.g. images) from the source project
const publicDir = path.join(config.paths.nextApp, "public");
const publicDir = path.join(config.paths.sourceDir, "public");
if (existsSync(publicDir)) {
await cp(publicDir, path.join(config.paths.builderOutput, "assets"), {
await cp(publicDir, path.join(config.paths.outputDir, "assets"), {
recursive: true,
});
}
@@ -52,7 +52,7 @@ export async function buildWorker(config: Config): Promise<void> {
copyPackageCliFiles(packageDistDir, config);

const workerEntrypoint = path.join(config.paths.internalTemplates, "worker.ts");
const workerOutputFile = path.join(config.paths.builderOutput, "index.mjs");
const workerOutputFile = path.join(config.paths.outputDir, "index.mjs");

const nextConfigStr =
readFileSync(path.join(config.paths.standaloneApp, "/server.js"), "utf8")?.match(
31 changes: 12 additions & 19 deletions packages/cloudflare/src/cli/build/index.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,39 @@
import { containsDotNextDir, getConfig } from "../config";
import type { ProjectOptions } from "../config";
import { buildNextjsApp } from "./build-next-app";
import { buildWorker } from "./build-worker";
import { cpSync } from "node:fs";
import path from "node:path";
import { join } from "node:path";
import { rm } from "node:fs/promises";

/**
* Builds the application in a format that can be passed to workerd
*
* It saves the output in a `.worker-next` directory
*
* @param appDir the directory of the Next.js app to build
* @param opts.outputDir the directory where to save the output (defaults to the app's directory)
* @param opts.skipBuild boolean indicating whether the Next.js build should be skipped (i.e. if the `.next` dir is already built)
* @param projectOpts The options for the project
*/
export async function build(appDir: string, opts: BuildOptions): Promise<void> {
if (!opts.skipBuild) {
export async function build(projectOpts: ProjectOptions): Promise<void> {
if (!projectOpts.skipNextBuild) {
// Build the next app
await buildNextjsApp(appDir);
await buildNextjsApp(projectOpts.sourceDir);
}

if (!containsDotNextDir(appDir)) {
throw new Error(`.next folder not found in ${appDir}`);
if (!containsDotNextDir(projectOpts.sourceDir)) {
throw new Error(`.next folder not found in ${projectOpts.sourceDir}`);
}

// Create a clean output directory
const outputDir = path.resolve(opts.outputDir ?? appDir, ".worker-next");
await cleanDirectory(outputDir);
// Clean the output directory
await cleanDirectory(projectOpts.outputDir);

// Copy the .next directory to the output directory so it can be mutated.
cpSync(path.join(appDir, ".next"), path.join(outputDir, ".next"), { recursive: true });
cpSync(join(projectOpts.sourceDir, ".next"), join(projectOpts.outputDir, ".next"), { recursive: true });

const config = getConfig(appDir, outputDir);
const config = getConfig(projectOpts);

await buildWorker(config);
}

type BuildOptions = {
skipBuild: boolean;
outputDir?: string;
};

async function cleanDirectory(path: string): Promise<void> {
return await rm(path, { recursive: true, force: true });
}
Original file line number Diff line number Diff line change
@@ -18,15 +18,15 @@ export async function patchCache(code: string, config: Config): Promise<string>

const cacheHandlerFileName = "cache-handler.mjs";
const cacheHandlerEntrypoint = join(config.paths.internalTemplates, "cache-handler", "index.ts");
const cacheHandlerOutputFile = join(config.paths.builderOutput, cacheHandlerFileName);
const cacheHandlerOutputFile = join(config.paths.outputDir, cacheHandlerFileName);

await build({
entryPoints: [cacheHandlerEntrypoint],
bundle: true,
outfile: cacheHandlerOutputFile,
format: "esm",
target: "esnext",
minify: true,
minify: config.build.shouldMinify,
define: {
"process.env.__OPENNEXT_KV_BINDING_NAME": `"${config.cache.kvBindingName}"`,
},
Original file line number Diff line number Diff line change
@@ -19,7 +19,7 @@ export function copyPrerenderedRoutes(config: Config) {

const serverAppDirPath = join(config.paths.standaloneAppServer, "app");
const prerenderManifestPath = join(config.paths.standaloneAppDotNext, "prerender-manifest.json");
const outputPath = join(config.paths.builderOutput, "assets", SEED_DATA_DIR);
const outputPath = join(config.paths.outputDir, "assets", SEED_DATA_DIR);

const prerenderManifest: PrerenderManifest = existsSync(prerenderManifestPath)
? JSON.parse(readFileSync(prerenderManifestPath, "utf8"))
@@ -38,7 +38,7 @@ export function copyPrerenderedRoutes(config: Config) {

if (fullPath.endsWith(NEXT_META_SUFFIX)) {
const data = JSON.parse(readFileSync(fullPath, "utf8"));
writeFileSync(destPath, JSON.stringify({ ...data, lastModified: config.buildTimestamp }));
writeFileSync(destPath, JSON.stringify({ ...data, lastModified: config.build.timestamp }));
} else {
copyFileSync(fullPath, destPath);
}
45 changes: 32 additions & 13 deletions packages/cloudflare/src/cli/config.ts
Original file line number Diff line number Diff line change
@@ -4,14 +4,20 @@ import { readdirSync, statSync } from "node:fs";
const PACKAGE_NAME = "@opennextjs/cloudflare";

export type Config = {
// Timestamp for when the build was started
buildTimestamp: number;
build: {
// Timestamp for when the build was started
timestamp: number;
// Whether to skip building the Next.js app or not
skipNextBuild: boolean;
// Whether minification should be enabled or not
shouldMinify: boolean;
};

paths: {
// Path to the next application
nextApp: string;
sourceDir: string;
// Path to the output folder
builderOutput: string;
outputDir: string;
// Path to the app's `.next` directory (where `next build` saves the build output)
dotNext: string;
// Path to the application standalone root directory
@@ -39,13 +45,11 @@ export type Config = {
/**
* Computes the configuration.
*
* @param appDir Next app root folder
* @param outputDir Output of the cloudflare builder
*
* @returns the configuration, see `Config`
* @param projectOpts The options for the project
* @returns The configuration, see `Config`
*/
export function getConfig(appDir: string, outputDir: string): Config {
const dotNext = path.join(outputDir, ".next");
export function getConfig(projectOpts: ProjectOptions): Config {
const dotNext = path.join(projectOpts.outputDir, ".next");
const appPath = getNextjsApplicationPath(dotNext).replace(/\/$/, "");
const standaloneRoot = path.join(dotNext, "standalone");
const standaloneApp = path.join(standaloneRoot, appPath);
@@ -59,11 +63,15 @@ export function getConfig(appDir: string, outputDir: string): Config {
process.env.__OPENNEXT_KV_BINDING_NAME ??= "NEXT_CACHE_WORKERS_KV";

return {
buildTimestamp: Date.now(),
build: {
timestamp: Date.now(),
skipNextBuild: projectOpts.skipNextBuild,
shouldMinify: projectOpts.minify,
},

paths: {
nextApp: appDir,
builderOutput: outputDir,
sourceDir: projectOpts.sourceDir,
outputDir: projectOpts.outputDir,
dotNext,
standaloneRoot,
standaloneApp,
@@ -89,6 +97,17 @@ export function containsDotNextDir(folder: string): boolean {
}
}

export type ProjectOptions = {
// Next app root folder
sourceDir: string;
// The directory to save the output to (defaults to the app's directory)
outputDir: string;
// Whether the Next.js build should be skipped (i.e. if the `.next` dir is already built)
skipNextBuild: boolean;
// Whether minification of the worker should be enabled
minify: boolean;
};

/**
* It basically tries to find the path that the application is under inside the `.next/standalone` directory, using the `.next/server` directory
* presence as the condition that needs to be met.
15 changes: 10 additions & 5 deletions packages/cloudflare/src/cli/index.ts
Original file line number Diff line number Diff line change
@@ -10,12 +10,17 @@ console.log(`Building the Next.js app in the current folder (${nextAppDir})`);

if (!["js", "cjs", "mjs", "ts"].some((ext) => existsSync(`./next.config.${ext}`))) {
// TODO: we can add more validation later
throw new Error("Error: Not in a Next.js app project");
console.error(
"Error: next.config file not found. Please make sure you run the command inside a Next.js app"
);
process.exit(1);
}

const { skipBuild, outputDir } = getArgs();
const { skipNextBuild, outputDir, minify } = getArgs();

await build(nextAppDir, {
outputDir,
skipBuild: !!skipBuild,
await build({
sourceDir: nextAppDir,
outputDir: resolve(outputDir ?? nextAppDir, ".worker-next"),
skipNextBuild,
minify,
});

0 comments on commit ab5f1f7

Please sign in to comment.