@nx/next
Version:
159 lines (158 loc) • 7.4 kB
JavaScript
;
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 });
}
});
});
}