UNPKG

@nx/next

Version:

The Next.js plugin for Nx contains executors and generators for managing Next.js applications and libraries within an Nx workspace. It provides: - Scaffolding for creating, building, serving, linting, and testing Next.js applications. - Integration wit

159 lines (158 loc) • 7.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = buildExecutor; const devkit_1 = require("@nx/devkit"); const js_1 = require("@nx/js"); const path_1 = require("path"); const node_fs_1 = require("node:fs"); const promises_1 = require("node:fs/promises"); const semver_1 = require("semver"); const semver_2 = require("@nx/devkit/src/utils/semver"); const update_package_json_1 = require("./lib/update-package-json"); const create_next_config_file_1 = require("./lib/create-next-config-file"); const check_project_1 = require("./lib/check-project"); const child_process_1 = require("child_process"); const create_cli_options_1 = require("../../utils/create-cli-options"); const internal_1 = require("@nx/devkit/internal"); const runtime_version_utils_1 = require("../../utils/runtime-version-utils"); let childProcess; async function buildExecutor(options, context) { // Cast to any to overwrite NODE_ENV process.env.NODE_ENV ||= 'production'; const projectRoot = context.projectGraph.nodes[context.projectName].data.root; (0, check_project_1.checkPublicDirectory)(projectRoot); // Set `__NEXT_REACT_ROOT` based on installed ReactDOM version const packageJsonPath = (0, path_1.join)(projectRoot, 'package.json'); const packageJson = (0, node_fs_1.existsSync)(packageJsonPath) ? (0, devkit_1.readJsonFile)(packageJsonPath) : undefined; const rootPackageJson = (0, devkit_1.readJsonFile)((0, path_1.join)(context.root, 'package.json')); const reactDomVersion = packageJson?.dependencies?.['react-dom'] ?? rootPackageJson.dependencies?.['react-dom']; const hasReact18 = reactDomVersion && (0, semver_1.gte)((0, semver_2.checkAndCleanWithSemver)('react-dom', reactDomVersion), '18.0.0'); if (hasReact18) { process.env['__NEXT_REACT_ROOT'] ||= 'true'; } try { await runCliBuild(devkit_1.workspaceRoot, projectRoot, options); } catch ({ error, code, signal }) { if (code || signal) { devkit_1.logger.error(`Build process exited due to ${code ? 'code ' + code : ''} ${code && signal ? 'and' : ''} ${signal ? 'signal ' + signal : ''}`); } else { devkit_1.logger.error(`Error occurred while trying to run the build command`); devkit_1.logger.error(error); } return { success: false }; } finally { if (childProcess) { childProcess.kill(); } } await (0, promises_1.mkdir)(options.outputPath, { recursive: true }); const builtPackageJson = (0, js_1.createPackageJson)(context.projectName, context.projectGraph, { target: context.targetName, root: context.root, isProduction: !options.includeDevDependenciesInPackageJson, // By default we remove devDependencies since this is a production build. skipOverrides: options.skipOverrides, skipPackageManager: options.skipPackageManager, }); // Update `package.json` to reflect how users should run the build artifacts builtPackageJson.scripts = { start: 'next start', }; (0, update_package_json_1.updatePackageJson)(builtPackageJson, context); (0, devkit_1.writeJsonFile)(`${options.outputPath}/package.json`, builtPackageJson); if (options.generateLockfile) { const packageManager = (0, devkit_1.detectPackageManager)(context.root); if (packageManager === 'bun') { devkit_1.logger.warn('Bun lockfile generation is not supported. The generated package.json will not include a lockfile. Run "bun install" in the output directory after deployment if needed.'); } else { const lockFile = (0, js_1.createLockFile)(builtPackageJson, context.projectGraph, packageManager); (0, node_fs_1.writeFileSync)(`${options.outputPath}/${(0, js_1.getLockFileName)(packageManager)}`, lockFile, { encoding: 'utf-8', }); } } // If output path is different from source path, then copy over the config and public files. // This is the default behavior when running `nx build <app>`. if (options.outputPath.replace(/\/$/, '') !== projectRoot) { (0, create_next_config_file_1.createNextConfigFile)(options, context); (0, node_fs_1.cpSync)((0, path_1.join)(projectRoot, 'public'), (0, path_1.join)(options.outputPath, 'public'), { dereference: true, recursive: true, }); } return { success: true }; } function runCliBuild(workspaceRoot, projectRoot, options) { const { experimentalAppOnly, experimentalBuildMode, profile, debug, outputPath, turbo, webpack, } = options; // Set output path here since it can also be set via CLI // We can retrieve it inside plugins/with-nx process.env.NX_NEXT_OUTPUT_PATH ??= outputPath; // Check for conflicting flags if (turbo && webpack) { throw new Error('Cannot specify both --turbo and --webpack flags. Please use only one bundler option.'); } // Determine bundler flag based on Next.js version and options const cliOptions = { experimentalAppOnly, experimentalBuildMode, profile, debug, }; const nextJsVersion = (0, runtime_version_utils_1.getInstalledNextVersionRuntime)(); const isNext16Plus = nextJsVersion !== null && nextJsVersion >= 16; if (isNext16Plus) { // Next.js 16+: Turbopack is default, use --webpack to opt-in to webpack if (webpack) { cliOptions.webpack = true; devkit_1.logger.info('Using webpack bundler for build (Next.js 16+ detected)'); } else if (turbo) { devkit_1.logger.warn('The --turbo flag is redundant in Next.js 16+ as Turbopack is now the default bundler. You can remove this flag.'); } } else { // Next.js 15 and below: webpack is default, use --turbo to opt-in to turbopack if (turbo) { cliOptions.turbo = true; } else if (webpack) { devkit_1.logger.warn('The --webpack flag is only applicable in Next.js 16 and above. It will be ignored.'); } } const args = (0, create_cli_options_1.createCliOptions)(cliOptions); return new Promise((resolve, reject) => { childProcess = (0, child_process_1.fork)(require.resolve('next/dist/bin/next'), ['build', ...args], { cwd: (0, path_1.resolve)(workspaceRoot, projectRoot), stdio: ['ignore', 'inherit', 'inherit', 'ipc'], env: process.env, }); // Ensure the child process is killed when the parent exits process.on('exit', () => childProcess.kill()); process.on('SIGTERM', (signal) => { reject({ code: (0, internal_1.signalToCode)(signal), signal }); }); process.on('SIGINT', (signal) => { reject({ code: (0, internal_1.signalToCode)(signal), signal }); }); childProcess.on('error', (err) => { reject({ error: err }); }); childProcess.on('exit', (code, signal) => { if (code === null) code = (0, internal_1.signalToCode)(signal); if (code === 0) { resolve({ code, signal }); } else { reject({ code, signal }); } }); }); }