diff --git a/packages/cloudflare/CHANGELOG.md b/packages/cloudflare/CHANGELOG.md index f114594..6f4bb84 100644 --- a/packages/cloudflare/CHANGELOG.md +++ b/packages/cloudflare/CHANGELOG.md @@ -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 diff --git a/packages/cloudflare/TODO.md b/packages/cloudflare/TODO.md index bd1cff4..f25e84f 100644 --- a/packages/cloudflare/TODO.md +++ b/packages/cloudflare/TODO.md @@ -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: diff --git a/packages/cloudflare/package.json b/packages/cloudflare/package.json index 8b221af..375390d 100644 --- a/packages/cloudflare/package.json +++ b/packages/cloudflare/package.json @@ -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", diff --git a/packages/cloudflare/src/cli/args.ts b/packages/cloudflare/src/cli/args.ts index deed6b9..1760ba4 100644 --- a/packages/cloudflare/src/cli/args.ts +++ b/packages/cloudflare/src/cli/args.ts @@ -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, }; } diff --git a/packages/cloudflare/src/cli/build/build-next-app.ts b/packages/cloudflare/src/cli/build/build-next-app.ts index 14efa60..8d53eaa 100644 --- a/packages/cloudflare/src/cli/build/build-next-app.ts +++ b/packages/cloudflare/src/cli/build/build-next-app.ts @@ -18,7 +18,7 @@ export async function buildNextjsApp(nextAppDir: string): Promise { 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, { @@ -26,7 +26,7 @@ function runNextBuildCommand(packager: PackageManager, nextAppDir: string) { 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", }, diff --git a/packages/cloudflare/src/cli/build/build-worker.ts b/packages/cloudflare/src/cli/build/build-worker.ts index 81c0dbb..0fe9dd9 100644 --- a/packages/cloudflare/src/cli/build/build-worker.ts +++ b/packages/cloudflare/src/cli/build/build-worker.ts @@ -32,16 +32,16 @@ export async function buildWorker(config: Config): Promise { // 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 { 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( diff --git a/packages/cloudflare/src/cli/build/index.ts b/packages/cloudflare/src/cli/build/index.ts index e132658..bd0577c 100644 --- a/packages/cloudflare/src/cli/build/index.ts +++ b/packages/cloudflare/src/cli/build/index.ts @@ -1,8 +1,9 @@ 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"; /** @@ -10,37 +11,29 @@ import { rm } from "node:fs/promises"; * * 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 { - if (!opts.skipBuild) { +export async function build(projectOpts: ProjectOptions): Promise { + 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 { return await rm(path, { recursive: true, force: true }); } diff --git a/packages/cloudflare/src/cli/build/patches/investigated/patch-cache.ts b/packages/cloudflare/src/cli/build/patches/investigated/patch-cache.ts index daba370..9e12225 100644 --- a/packages/cloudflare/src/cli/build/patches/investigated/patch-cache.ts +++ b/packages/cloudflare/src/cli/build/patches/investigated/patch-cache.ts @@ -18,7 +18,7 @@ export async function patchCache(code: string, config: Config): Promise 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], @@ -26,7 +26,7 @@ export async function patchCache(code: string, config: Config): Promise outfile: cacheHandlerOutputFile, format: "esm", target: "esnext", - minify: true, + minify: config.build.shouldMinify, define: { "process.env.__OPENNEXT_KV_BINDING_NAME": `"${config.cache.kvBindingName}"`, }, diff --git a/packages/cloudflare/src/cli/build/utils/copy-prerendered-routes.ts b/packages/cloudflare/src/cli/build/utils/copy-prerendered-routes.ts index e2cecb6..8395213 100644 --- a/packages/cloudflare/src/cli/build/utils/copy-prerendered-routes.ts +++ b/packages/cloudflare/src/cli/build/utils/copy-prerendered-routes.ts @@ -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); } diff --git a/packages/cloudflare/src/cli/config.ts b/packages/cloudflare/src/cli/config.ts index 238a74d..59256e8 100644 --- a/packages/cloudflare/src/cli/config.ts +++ b/packages/cloudflare/src/cli/config.ts @@ -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. diff --git a/packages/cloudflare/src/cli/index.ts b/packages/cloudflare/src/cli/index.ts index 24ee2bf..3c40e1d 100644 --- a/packages/cloudflare/src/cli/index.ts +++ b/packages/cloudflare/src/cli/index.ts @@ -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, });