Skip to content
Merged
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
8 changes: 4 additions & 4 deletions packages/cloudflare/src/cli/build/build-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
}
Expand All @@ -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(
Expand Down
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.skipBuild) {
// 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
Expand Up @@ -10,7 +10,7 @@ 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],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand All @@ -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);
}
Expand Down
40 changes: 27 additions & 13 deletions packages/cloudflare/src/cli/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,18 @@ const UserConfig = {
};

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;
};

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
Expand Down Expand Up @@ -46,13 +50,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);
Expand All @@ -64,11 +66,14 @@ export function getConfig(appDir: string, outputDir: string): Config {
const internalTemplates = path.join(internalPackage, "cli", "templates");

return {
buildTimestamp: Date.now(),
build: {
timestamp: Date.now(),
skipNextBuild: !!projectOpts.skipBuild,
},

paths: {
nextApp: appDir,
builderOutput: outputDir,
sourceDir: projectOpts.sourceDir,
outputDir: projectOpts.outputDir,
dotNext,
standaloneRoot,
standaloneApp,
Expand All @@ -94,6 +99,15 @@ 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)
skipBuild?: 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.
Expand Down
7 changes: 4 additions & 3 deletions packages/cloudflare/src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ if (!["js", "cjs", "mjs", "ts"].some((ext) => existsSync(`./next.config.${ext}`)

const { skipBuild, outputDir } = getArgs();

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