From e6e4218f5b71ebcb7ed6f7e659da44aa751d2e43 Mon Sep 17 00:00:00 2001 From: Nicholas Cunningham Date: Tue, 9 May 2023 13:55:21 -0600 Subject: [PATCH] feat(nextjs): Use next.js cli for build and serve targets --- .../packages/next/executors/server.json | 4 + .../next/src/executors/build/build.impl.ts | 21 +-- .../next/src/executors/export/export.impl.ts | 7 +- .../executors/server/custom-server.impl.ts | 67 ++++++++++ .../next/src/executors/server/schema.json | 4 + .../next/src/executors/server/server.impl.ts | 124 +++++------------- packages/next/src/utils/types.ts | 1 + 7 files changed, 124 insertions(+), 104 deletions(-) create mode 100644 packages/next/src/executors/server/custom-server.impl.ts diff --git a/docs/generated/packages/next/executors/server.json b/docs/generated/packages/next/executors/server.json index d269906a9d9c32..eedb9c624d9afb 100644 --- a/docs/generated/packages/next/executors/server.json +++ b/docs/generated/packages/next/executors/server.json @@ -53,6 +53,10 @@ "type": "boolean", "description": "Read buildable libraries from source instead of building them separately.", "default": true + }, + "keepAliveTimeout": { + "type": "number", + "description": "Max milliseconds to wait before closing inactive connection." } }, "required": ["buildTarget"], diff --git a/packages/next/src/executors/build/build.impl.ts b/packages/next/src/executors/build/build.impl.ts index 0eba85430b08be..4bcefe00f7dc41 100644 --- a/packages/next/src/executors/build/build.impl.ts +++ b/packages/next/src/executors/build/build.impl.ts @@ -4,6 +4,7 @@ import { readJsonFile, workspaceRoot, writeJsonFile, + logger, } from '@nx/devkit'; import { createLockFile, createPackageJson, getLockFileName } from '@nx/js'; import build from 'next/dist/build'; @@ -17,6 +18,7 @@ import { updatePackageJson } from './lib/update-package-json'; import { createNextConfigFile } from './lib/create-next-config-file'; import { checkPublicDirectory } from './lib/check-project'; import { NextBuildBuilderOptions } from '../../utils/types'; +import { ExecSyncOptions, execSync } from 'child_process'; export default async function buildExecutor( options: NextBuildBuilderOptions, @@ -50,14 +52,17 @@ export default async function buildExecutor( const debug = !!process.env.NX_VERBOSE_LOGGING || options.debug; - // Check the major and minor version numbers - if (lt(nextVersion, '13.2.0')) { - // If the version is lower than 13.2.0, use the second parameter as the config object - await build(root, null, false, debug); - } else { - // Otherwise, use the third parameter as a boolean flag for verbose logging - // @ts-ignore - await build(root, false, debug); + const command = `next build ${debug ? '--verbose' : ''}`; + const execSyncOptions: ExecSyncOptions = { + stdio: 'inherit', + encoding: 'utf-8', + cwd: root, + }; + try { + execSync(command, execSyncOptions); + } catch (error) { + logger.error(`Error occurred while trying to run the ${command}`); + logger.error(error); } if (!directoryExists(options.outputPath)) { diff --git a/packages/next/src/executors/export/export.impl.ts b/packages/next/src/executors/export/export.impl.ts index c397ccfea68bab..87d4929dbb927d 100644 --- a/packages/next/src/executors/export/export.impl.ts +++ b/packages/next/src/executors/export/export.impl.ts @@ -16,7 +16,7 @@ import { NextBuildBuilderOptions, NextExportBuilderOptions, } from '../../utils/types'; -import { PHASE_EXPORT } from '../../utils/constants'; + import nextTrace = require('next/dist/trace'); import { platform } from 'os'; import { execFileSync } from 'child_process'; @@ -25,6 +25,10 @@ import * as chalk from 'chalk'; // platform specific command name const pmCmd = platform() === 'win32' ? `npx.cmd` : 'npx'; +/** + * @deprecated use output inside of your next.config.js + * Read https://nextjs.org/docs/pages/building-your-application/deploying/static-exports + **/ export default async function exportExecutor( options: NextExportBuilderOptions, context: ExecutorContext @@ -41,7 +45,6 @@ export default async function exportExecutor( dependencies = result.dependencies; } - const libsDir = join(context.root, workspaceLayout().libsDir); const buildTarget = parseTargetString( options.buildTarget, context.projectGraph diff --git a/packages/next/src/executors/server/custom-server.impl.ts b/packages/next/src/executors/server/custom-server.impl.ts new file mode 100644 index 00000000000000..cf2e89fcd80091 --- /dev/null +++ b/packages/next/src/executors/server/custom-server.impl.ts @@ -0,0 +1,67 @@ +import 'dotenv/config'; +import { + ExecutorContext, + parseTargetString, + readTargetOptions, + runExecutor, +} from '@nx/devkit'; +import { join, resolve } from 'path'; + +import { + NextBuildBuilderOptions, + NextServeBuilderOptions, +} from '../../utils/types'; + +export default async function* serveExecutor( + options: NextServeBuilderOptions, + context: ExecutorContext +) { + // Cast to any to overwrite NODE_ENV + (process.env as any).NODE_ENV = process.env.NODE_ENV + ? process.env.NODE_ENV + : options.dev + ? 'development' + : 'production'; + + // Setting port that the custom server should use. + (process.env as any).PORT = options.port; + + const buildOptions = readTargetOptions( + parseTargetString(options.buildTarget, context.projectGraph), + context + ); + const root = resolve(context.root, buildOptions.root); + + yield* runCustomServer(root, options, context); +} + +async function* runCustomServer( + root: string, + options: NextServeBuilderOptions, + context: ExecutorContext +) { + process.env.NX_NEXT_DIR = root; + process.env.NX_NEXT_PUBLIC_DIR = join(root, 'public'); + + const baseUrl = `http://${options.hostname || 'localhost'}:${options.port}`; + + const customServerBuild = await runExecutor( + parseTargetString(options.customServerTarget, context.projectGraph), + { + watch: options.dev ? true : false, + }, + context + ); + + for await (const result of customServerBuild) { + if (!result.success) { + return result; + } + yield { + success: true, + baseUrl, + }; + } + + return { success: true }; +} diff --git a/packages/next/src/executors/server/schema.json b/packages/next/src/executors/server/schema.json index b060e03f46c1c7..8e82a96d05460b 100644 --- a/packages/next/src/executors/server/schema.json +++ b/packages/next/src/executors/server/schema.json @@ -50,6 +50,10 @@ "type": "boolean", "description": "Read buildable libraries from source instead of building them separately.", "default": true + }, + "keepAliveTimeout": { + "type": "number", + "description": "Max milliseconds to wait before closing inactive connection." } }, "required": ["buildTarget"] diff --git a/packages/next/src/executors/server/server.impl.ts b/packages/next/src/executors/server/server.impl.ts index a532e0de7db91d..da5e339a484345 100644 --- a/packages/next/src/executors/server/server.impl.ts +++ b/packages/next/src/executors/server/server.impl.ts @@ -1,27 +1,25 @@ import 'dotenv/config'; import { ExecutorContext, - logger, parseTargetString, readTargetOptions, - runExecutor, } from '@nx/devkit'; -import * as chalk from 'chalk'; -import { existsSync } from 'fs'; -import { join, resolve } from 'path'; +import { resolve } from 'path'; import { NextBuildBuilderOptions, NextServeBuilderOptions, - NextServerOptions, - ProxyConfig, } from '../../utils/types'; -import { defaultServer } from './lib/default-server'; +import { spawn } from 'child_process'; +import customServer from './custom-server.impl'; export default async function* serveExecutor( options: NextServeBuilderOptions, context: ExecutorContext ) { + if (options.customServerTarget) { + return yield* customServer(options, context); + } // Cast to any to overwrite NODE_ENV (process.env as any).NODE_ENV = process.env.NODE_ENV ? process.env.NODE_ENV @@ -38,96 +36,34 @@ export default async function* serveExecutor( ); const root = resolve(context.root, buildOptions.root); - if (options.customServerTarget) { - yield* runCustomServer(root, options, buildOptions, context); - } else { - yield* runNextDevServer(root, options, context); - } -} + const { port, keepAliveTimeout, hostname } = options; -async function* runNextDevServer( - root: string, - options: NextServeBuilderOptions, - context: ExecutorContext -) { - const baseUrl = `http://${options.hostname || 'localhost'}:${options.port}`; - const settings: NextServerOptions = { - dev: options.dev, - dir: root, - staticMarkup: options.staticMarkup, - quiet: options.quiet, - port: options.port, - customServer: !!options.customServerTarget, - hostname: options.hostname || 'localhost', - }; - - // look for the proxy.conf.json - let proxyConfig: ProxyConfig; - const proxyConfigPath = options.proxyConfig - ? join(context.root, options.proxyConfig) - : join(root, 'proxy.conf.json'); - - // TODO(v16): Remove proxy support. - if (existsSync(proxyConfigPath)) { - logger.warn( - `The "proxyConfig" option will be removed in Nx 16. Use the "rewrites" feature from Next.js instead. See: https://nextjs.org/docs/api-reference/next.config.js/rewrites` - ); - proxyConfig = require(proxyConfigPath); - } + const args = formatObjectToCli({ port, keepAliveTimeout, hostname }); + const nextDir = resolve(context.root, buildOptions.outputPath); - try { - await defaultServer(settings, proxyConfig); - logger.info(`[ ${chalk.green('ready')} ] on ${baseUrl}`); + const command = `next ${ + options.dev ? `dev ${args}` : `start ${nextDir} ${args}` + }`; - yield { - baseUrl, - success: true, - }; + return yield new Promise<{ success: boolean }>((res, rej) => { + const server = spawn(command, { + cwd: options.dev ? root : nextDir, + stdio: 'inherit', + shell: true, + }); - // This Promise intentionally never resolves, leaving the process running - await new Promise<{ success: boolean }>(() => {}); - } catch (e) { - if (options.dev) { - throw e; - } else { - if (process.env.NX_VERBOSE_LOGGING) { - console.error(e); - } - throw new Error( - `Could not start production server. Try building your app with \`nx build ${context.projectName}\`.` - ); - } - } + server.on('exit', (code) => { + code != 0 ? rej({ success: false }) : res({ success: true }); + }); + }); } -async function* runCustomServer( - root: string, - options: NextServeBuilderOptions, - buildOptions: NextBuildBuilderOptions, - context: ExecutorContext -) { - process.env.NX_NEXT_DIR = root; - process.env.NX_NEXT_PUBLIC_DIR = join(root, 'public'); - - const baseUrl = `http://${options.hostname || 'localhost'}:${options.port}`; - - const customServerBuild = await runExecutor( - parseTargetString(options.customServerTarget, context.projectGraph), - { - watch: options.dev ? true : false, - }, - context - ); - - for await (const result of customServerBuild) { - if (!result.success) { - return result; - } - yield { - success: true, - baseUrl, - }; - } - - return { success: true }; +function formatObjectToCli(obj: { [key: string]: any }): string { + return Object.entries(obj) + .reduce( + (arr, [key, value]) => + value !== undefined ? `${arr}--${key}=${value} ` : arr, + '' + ) + .trimEnd(); } diff --git a/packages/next/src/utils/types.ts b/packages/next/src/utils/types.ts index 432b483fc22684..9703dd798a7ee9 100644 --- a/packages/next/src/utils/types.ts +++ b/packages/next/src/utils/types.ts @@ -50,6 +50,7 @@ export interface NextServeBuilderOptions { hostname?: string; proxyConfig?: string; buildLibsFromSource?: boolean; + keepAliveTimeout?: number; } export interface NextExportBuilderOptions {