From f767ad81ceb90f7d0c3c2ffc5a6c1cce052b4e77 Mon Sep 17 00:00:00 2001 From: Enrico Ros Date: Sat, 17 Feb 2024 18:03:21 -0800 Subject: [PATCH] Export Frontend: work around NextJS aborting on the nodejs API. This introduces a pre-build step on Next Build, which hides the files in the app/api directory when the EXPORT_FRONTEND environment variable is true-ish. Hopefully there won't be disruption due to the post-processing step. Also check https://github.com/vercel/next.js/issues/61213 for upstream updates. --- .gitignore | 3 +++ next.config.mjs | 4 ++-- package.json | 1 + scripts/prebuild.mjs | 47 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 scripts/prebuild.mjs diff --git a/.gitignore b/.gitignore index e4038b126..6f40ade05 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. +# Frontend Build: ignore API files disabled for this build +/app/**/*.backup + # dependencies /node_modules /.pnp diff --git a/next.config.mjs b/next.config.mjs index 61974ca23..66f917b66 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,6 +1,6 @@ const BuildOptions = { // Future: Electron/Frontend-only builds - exportFrontend: false, + exportFrontend: !!process.env.EXPORT_FRONTEND, }; /** @type {import('next').NextConfig} */ @@ -11,7 +11,7 @@ let nextConfig = { ...BuildOptions.exportFrontend && { // Export the frontend to ./dist output: 'export', - distDir: 'dist', + distDir: 'out', // Disable Image optimization images: { unoptimized: true }, diff --git a/package.json b/package.json index 8e5bb2d1a..3d27b8007 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "start": "next start", "lint": "next lint", "postinstall": "prisma generate", + "prebuild": "node scripts/prebuild.mjs", "db:push": "prisma db push", "db:studio": "prisma studio", "vercel:env:pull": "npx vercel env pull .env.development.local" diff --git a/scripts/prebuild.mjs b/scripts/prebuild.mjs new file mode 100644 index 000000000..c7c32ea39 --- /dev/null +++ b/scripts/prebuild.mjs @@ -0,0 +1,47 @@ +import { default as fs, renameSync } from 'fs'; +import { dirname as pathDirName, join as pathJoin } from 'path'; +import { fileURLToPath } from 'url'; + + +// build-time configuration +const buildOnlyFrontend = !!process.env.EXPORT_FRONTEND; + + +function getApiDirName() { + const __filename = fileURLToPath(import.meta.url); + const __dirname = pathDirName(__filename); + return pathJoin(__dirname, '../app/api'); +} + +function findAllFiles(startDir) { + return fs.readdirSync(startDir).flatMap((file) => { + const fullPath = pathJoin(startDir, file); + if (fs.statSync(fullPath).isDirectory()) + return findAllFiles(fullPath); + return fullPath; + }, + ); +} + +/** + * Hide/show API routes depending on the build type + * Due to an upstream bug, NextJS will not ignore the nodejs API routes and choose to abort instead. + */ +function prebuildFrontendHotFixes(hideFiles) { + const apiDirName = getApiDirName(); + const apiRoutesPaths = findAllFiles(apiDirName) + .filter((path) => path.endsWith('.ts') || path.endsWith('.ts.backup')); + + apiRoutesPaths.forEach((path) => { + const isBackup = path.endsWith('.backup'); + if (hideFiles) { + // If building the frontend, rename (effectively hide) the file + !isBackup && renameSync(path, `${path}.backup`); + } else { + // If it's a normal build and including API routes and a backup exists, restore it + isBackup && renameSync(path, path.slice(0, -7)); + } + }); +} + +prebuildFrontendHotFixes(buildOnlyFrontend);