From c21b039dd05ded4ba3588c88b1dea1c602807dc2 Mon Sep 17 00:00:00 2001 From: Jason Jean Date: Mon, 11 Nov 2024 17:18:24 -0500 Subject: [PATCH] fix(core): reduce time it takes to require nx commands (#28884) ## Current Behavior `nx/src/command-line/nx-commands` takes ~88-91ms on my machine to require which needs to happen for every single Nx command. ## Expected Behavior `nx/src/command-line/nx-commands` takes ~30-33ms on my machine to require. ## Related Issue(s) Fixes # --- .../command-line/generate/command-object.ts | 6 +- .../nx/src/command-line/generate/generate.ts | 5 +- .../src/command-line/init/command-object.ts | 17 ++- .../command-line/migrate/command-object.ts | 115 +----------------- .../nx/src/command-line/migrate/migrate.ts | 105 +++++++++++++++- .../command-line/release/command-object.ts | 7 +- .../yargs-utils/shared-options.ts | 21 ++++ packages/nx/src/utils/command-line-utils.ts | 22 +--- packages/nx/src/utils/handle-errors.ts | 2 +- 9 files changed, 149 insertions(+), 151 deletions(-) diff --git a/packages/nx/src/command-line/generate/command-object.ts b/packages/nx/src/command-line/generate/command-object.ts index cc3bcc72edc6f..d426f33ff251d 100644 --- a/packages/nx/src/command-line/generate/command-object.ts +++ b/packages/nx/src/command-line/generate/command-object.ts @@ -1,6 +1,4 @@ -import { CommandModule, Argv } from 'yargs'; -import { getCwd } from '../../utils/path'; -import { linkToNxDevAndExamples } from '../yargs-utils/documentation'; +import { Argv, CommandModule } from 'yargs'; import { withVerbose } from '../yargs-utils/shared-options'; export const yargsGenerateCommand: CommandModule = { @@ -13,7 +11,7 @@ export const yargsGenerateCommand: CommandModule = { // Remove the command from the args args._ = args._.slice(1); - process.exit(await (await import('./generate')).generate(getCwd(), args)); + process.exit(await (await import('./generate')).generate(args)); }, }; diff --git a/packages/nx/src/command-line/generate/generate.ts b/packages/nx/src/command-line/generate/generate.ts index 77d1777327e74..7e18807c0faaf 100644 --- a/packages/nx/src/command-line/generate/generate.ts +++ b/packages/nx/src/command-line/generate/generate.ts @@ -22,6 +22,7 @@ import { workspaceRoot } from '../../utils/workspace-root'; import { calculateDefaultProjectName } from '../../config/calculate-default-project-name'; import { findInstalledPlugins } from '../../utils/plugins/installed-plugins'; import { getGeneratorInformation } from './generator-utils'; +import { getCwd } from '../../utils/path'; export interface GenerateOptions { collectionName: string; @@ -300,7 +301,7 @@ export function printGenHelp( ); } -export async function generate(cwd: string, args: { [k: string]: any }) { +export async function generate(args: { [k: string]: any }) { return handleErrors(args.verbose, async () => { const nxJsonConfiguration = readNxJson(); const projectGraph = await createProjectGraphAsync(); @@ -348,6 +349,8 @@ export async function generate(cwd: string, args: { [k: string]: any }) { return 0; } + const cwd = getCwd(); + const combinedOpts = await combineOptionsForGenerator( opts.generatorOptions, opts.collectionName, diff --git a/packages/nx/src/command-line/init/command-object.ts b/packages/nx/src/command-line/init/command-object.ts index edfef2d999bab..bc7e38995d72e 100644 --- a/packages/nx/src/command-line/init/command-object.ts +++ b/packages/nx/src/command-line/init/command-object.ts @@ -1,10 +1,5 @@ import { Argv, CommandModule } from 'yargs'; import { parseCSV } from '../yargs-utils/shared-options'; -import { readNxJson } from '../../config/nx-json'; - -const useV2 = - process.env['NX_ADD_PLUGINS'] !== 'false' && - readNxJson().useInferencePlugins !== false; export const yargsInitCommand: CommandModule = { command: 'init', @@ -12,6 +7,7 @@ export const yargsInitCommand: CommandModule = { 'Adds Nx to any type of workspace. It installs nx, creates an nx.json configuration file and optionally sets up remote caching. For more info, check https://nx.dev/recipes/adopting-nx.', builder: (yargs) => withInitOptions(yargs), handler: async (args: any) => { + const useV2 = await isInitV2(); if (useV2) { await require('./init-v2').initHandler(args); } else { @@ -21,7 +17,16 @@ export const yargsInitCommand: CommandModule = { }, }; -function withInitOptions(yargs: Argv) { +async function isInitV2() { + return ( + process.env['NX_ADD_PLUGINS'] !== 'false' && + (await import('../../config/nx-json')).readNxJson().useInferencePlugins !== + false + ); +} + +async function withInitOptions(yargs: Argv) { + const useV2 = await isInitV2(); if (useV2) { return yargs .option('nxCloud', { diff --git a/packages/nx/src/command-line/migrate/command-object.ts b/packages/nx/src/command-line/migrate/command-object.ts index 3ec7f78eee23b..213f6d107da25 100644 --- a/packages/nx/src/command-line/migrate/command-object.ts +++ b/packages/nx/src/command-line/migrate/command-object.ts @@ -1,15 +1,5 @@ import { Argv, CommandModule } from 'yargs'; -import * as path from 'path'; -import { runNxSync } from '../../utils/child-process'; import { linkToNxDevAndExamples } from '../yargs-utils/documentation'; -import { execSync } from 'child_process'; -import { - copyPackageManagerConfigurationFiles, - detectPackageManager, - getPackageManagerCommand, -} from '../../utils/package-manager'; -import { writeJsonFile } from '../../utils/fileutils'; -import { workspaceRoot } from '../../utils/workspace-root'; import { withVerbose } from '../yargs-utils/shared-options'; export const yargsMigrateCommand: CommandModule = { @@ -19,8 +9,8 @@ export const yargsMigrateCommand: CommandModule = { - Run migrations (e.g., nx migrate --run-migrations=migrations.json). Use flag --if-exists to run migrations only if the migrations file exists.`, builder: (yargs) => linkToNxDevAndExamples(withMigrationOptions(yargs), 'migrate'), - handler: () => { - runMigration(); + handler: async () => { + (await import('./migrate')).runMigration(); process.exit(0); }, }; @@ -104,104 +94,3 @@ function withMigrationOptions(yargs: Argv) { } ); } - -function runMigration() { - const runLocalMigrate = () => { - runNxSync(`_migrate ${process.argv.slice(3).join(' ')}`, { - stdio: ['inherit', 'inherit', 'inherit'], - }); - }; - if (process.env.NX_MIGRATE_USE_LOCAL === undefined) { - const p = nxCliPath(); - if (p === null) { - runLocalMigrate(); - } else { - // ensure local registry from process is not interfering with the install - // when we start the process from temp folder the local registry would override the custom registry - if ( - process.env.npm_config_registry && - process.env.npm_config_registry.match( - /^https:\/\/registry\.(npmjs\.org|yarnpkg\.com)/ - ) - ) { - delete process.env.npm_config_registry; - } - execSync(`${p} _migrate ${process.argv.slice(3).join(' ')}`, { - stdio: ['inherit', 'inherit', 'inherit'], - windowsHide: false, - }); - } - } else { - runLocalMigrate(); - } -} - -function nxCliPath() { - const version = process.env.NX_MIGRATE_CLI_VERSION || 'latest'; - try { - const packageManager = detectPackageManager(); - const pmc = getPackageManagerCommand(packageManager); - - const { dirSync } = require('tmp'); - const tmpDir = dirSync().name; - writeJsonFile(path.join(tmpDir, 'package.json'), { - dependencies: { - nx: version, - }, - license: 'MIT', - }); - copyPackageManagerConfigurationFiles(workspaceRoot, tmpDir); - if (pmc.preInstall) { - // ensure package.json and repo in tmp folder is set to a proper package manager state - execSync(pmc.preInstall, { - cwd: tmpDir, - stdio: ['ignore', 'ignore', 'ignore'], - windowsHide: false, - }); - // if it's berry ensure we set the node_linker to node-modules - if (packageManager === 'yarn' && pmc.ciInstall.includes('immutable')) { - execSync('yarn config set nodeLinker node-modules', { - cwd: tmpDir, - stdio: ['ignore', 'ignore', 'ignore'], - windowsHide: false, - }); - } - } - - execSync(pmc.install, { - cwd: tmpDir, - stdio: ['ignore', 'ignore', 'ignore'], - windowsHide: false, - }); - - // Set NODE_PATH so that these modules can be used for module resolution - addToNodePath(path.join(tmpDir, 'node_modules')); - addToNodePath(path.join(workspaceRoot, 'node_modules')); - - return path.join(tmpDir, `node_modules`, '.bin', 'nx'); - } catch (e) { - console.error( - `Failed to install the ${version} version of the migration script. Using the current version.` - ); - if (process.env.NX_VERBOSE_LOGGING === 'true') { - console.error(e); - } - return null; - } -} - -function addToNodePath(dir: string) { - // NODE_PATH is a delimited list of paths. - // The delimiter is different for windows. - const delimiter = require('os').platform() === 'win32' ? ';' : ':'; - - const paths = process.env.NODE_PATH - ? process.env.NODE_PATH.split(delimiter) - : []; - - // Add the tmp path - paths.push(dir); - - // Update the env variable. - process.env.NODE_PATH = paths.join(delimiter); -} diff --git a/packages/nx/src/command-line/migrate/migrate.ts b/packages/nx/src/command-line/migrate/migrate.ts index b7729c93de883..78e6718f426d2 100644 --- a/packages/nx/src/command-line/migrate/migrate.ts +++ b/packages/nx/src/command-line/migrate/migrate.ts @@ -42,6 +42,7 @@ import { readNxMigrateConfig, } from '../../utils/package-json'; import { + copyPackageManagerConfigurationFiles, createTempNpmDirectory, detectPackageManager, getPackageManagerCommand, @@ -55,7 +56,7 @@ import { onlyDefaultRunnerIsUsed, } from '../connect/connect-to-nx-cloud'; import { output } from '../../utils/output'; -import { existsSync, readFileSync, writeFileSync } from 'fs'; +import { existsSync, writeFileSync } from 'fs'; import { workspaceRoot } from '../../utils/workspace-root'; import { isCI } from '../../utils/is-ci'; import { getNxRequirePaths } from '../../utils/installation-directory'; @@ -68,6 +69,7 @@ import { readProjectsConfigurationFromProjectGraph, } from '../../project-graph/project-graph'; import { formatFilesWithPrettierIfAvailable } from '../../generators/internal-utils/format-changed-files-with-prettier-if-available'; +import { dirSync } from 'tmp'; export interface ResolvedMigrationConfiguration extends MigrationsJson { packageGroup?: ArrayPackageGroup; @@ -1655,6 +1657,37 @@ export async function migrate( }); } +export function runMigration() { + const runLocalMigrate = () => { + runNxSync(`_migrate ${process.argv.slice(3).join(' ')}`, { + stdio: ['inherit', 'inherit', 'inherit'], + }); + }; + if (process.env.NX_MIGRATE_USE_LOCAL === undefined) { + const p = nxCliPath(); + if (p === null) { + runLocalMigrate(); + } else { + // ensure local registry from process is not interfering with the install + // when we start the process from temp folder the local registry would override the custom registry + if ( + process.env.npm_config_registry && + process.env.npm_config_registry.match( + /^https:\/\/registry\.(npmjs\.org|yarnpkg\.com)/ + ) + ) { + delete process.env.npm_config_registry; + } + execSync(`${p} _migrate ${process.argv.slice(3).join(' ')}`, { + stdio: ['inherit', 'inherit', 'inherit'], + windowsHide: false, + }); + } + } else { + runLocalMigrate(); + } +} + function readMigrationCollection(packageName: string, root: string) { const collectionPath = readPackageMigrationConfig( packageName, @@ -1699,6 +1732,76 @@ function getImplementationPath( return { path: implPath, fnSymbol }; } +function nxCliPath() { + const version = process.env.NX_MIGRATE_CLI_VERSION || 'latest'; + try { + const packageManager = detectPackageManager(); + const pmc = getPackageManagerCommand(packageManager); + + const { dirSync } = require('tmp'); + const tmpDir = dirSync().name; + writeJsonFile(join(tmpDir, 'package.json'), { + dependencies: { + nx: version, + }, + license: 'MIT', + }); + copyPackageManagerConfigurationFiles(workspaceRoot, tmpDir); + if (pmc.preInstall) { + // ensure package.json and repo in tmp folder is set to a proper package manager state + execSync(pmc.preInstall, { + cwd: tmpDir, + stdio: ['ignore', 'ignore', 'ignore'], + windowsHide: false, + }); + // if it's berry ensure we set the node_linker to node-modules + if (packageManager === 'yarn' && pmc.ciInstall.includes('immutable')) { + execSync('yarn config set nodeLinker node-modules', { + cwd: tmpDir, + stdio: ['ignore', 'ignore', 'ignore'], + windowsHide: false, + }); + } + } + + execSync(pmc.install, { + cwd: tmpDir, + stdio: ['ignore', 'ignore', 'ignore'], + windowsHide: false, + }); + + // Set NODE_PATH so that these modules can be used for module resolution + addToNodePath(join(tmpDir, 'node_modules')); + addToNodePath(join(workspaceRoot, 'node_modules')); + + return join(tmpDir, `node_modules`, '.bin', 'nx'); + } catch (e) { + console.error( + `Failed to install the ${version} version of the migration script. Using the current version.` + ); + if (process.env.NX_VERBOSE_LOGGING === 'true') { + console.error(e); + } + return null; + } +} + +function addToNodePath(dir: string) { + // NODE_PATH is a delimited list of paths. + // The delimiter is different for windows. + const delimiter = require('os').platform() === 'win32' ? ';' : ':'; + + const paths = process.env.NODE_PATH + ? process.env.NODE_PATH.split(delimiter) + : []; + + // Add the tmp path + paths.push(dir); + + // Update the env variable. + process.env.NODE_PATH = paths.join(delimiter); +} + // TODO (v21): Remove CLI determination of Angular Migration function isAngularMigration( collection: MigrationsJson, diff --git a/packages/nx/src/command-line/release/command-object.ts b/packages/nx/src/command-line/release/command-object.ts index a9f048710db07..f142fdab854c6 100644 --- a/packages/nx/src/command-line/release/command-object.ts +++ b/packages/nx/src/command-line/release/command-object.ts @@ -1,6 +1,4 @@ import { Argv, CommandModule, showHelp } from 'yargs'; -import { readNxJson } from '../../config/nx-json'; -import { readParallelFromArgsAndEnv } from '../../utils/command-line-utils'; import { logger } from '../../utils/logger'; import { OutputStyle, @@ -11,6 +9,7 @@ import { withOverrides, withRunManyOptions, withVerbose, + readParallelFromArgsAndEnv, } from '../yargs-utils/shared-options'; import { VersionData } from './utils/shared'; @@ -151,13 +150,13 @@ export const yargsReleaseCommand: CommandModule< return val; }, }) - .check((argv) => { + .check(async (argv) => { if (argv.groups && argv.projects) { throw new Error( 'The --projects and --groups options are mutually exclusive, please use one or the other.' ); } - const nxJson = readNxJson(); + const nxJson = (await import('../../config/nx-json')).readNxJson(); if (argv.groups?.length) { for (const group of argv.groups) { if (!nxJson.release?.groups?.[group]) { diff --git a/packages/nx/src/command-line/yargs-utils/shared-options.ts b/packages/nx/src/command-line/yargs-utils/shared-options.ts index 0dcb210724298..b6936498da9e5 100644 --- a/packages/nx/src/command-line/yargs-utils/shared-options.ts +++ b/packages/nx/src/command-line/yargs-utils/shared-options.ts @@ -320,3 +320,24 @@ export function parseCSV(args: string[] | string): string[] { i.startsWith('"') && i.endsWith('"') ? i.slice(1, -1) : i ); } + +export function readParallelFromArgsAndEnv(args: { [k: string]: any }) { + if (args['parallel'] === 'false' || args['parallel'] === false) { + return 1; + } else if ( + args['parallel'] === 'true' || + args['parallel'] === true || + args['parallel'] === '' || + // dont require passing --parallel if NX_PARALLEL is set, but allow overriding it + (process.env.NX_PARALLEL && args['parallel'] === undefined) + ) { + return Number( + args['maxParallel'] || + args['max-parallel'] || + process.env.NX_PARALLEL || + 3 + ); + } else if (args['parallel'] !== undefined) { + return Number(args['parallel']); + } +} diff --git a/packages/nx/src/utils/command-line-utils.ts b/packages/nx/src/utils/command-line-utils.ts index 629ccb08aa2c4..6b0756640eb1c 100644 --- a/packages/nx/src/utils/command-line-utils.ts +++ b/packages/nx/src/utils/command-line-utils.ts @@ -6,6 +6,7 @@ import { NxJsonConfiguration } from '../config/nx-json'; import { execSync } from 'child_process'; import { ProjectGraph } from '../config/project-graph'; import { workspaceRoot } from './workspace-root'; +import { readParallelFromArgsAndEnv } from '../command-line/yargs-utils/shared-options'; export interface RawNxArgs extends NxArgs { prod?: boolean; @@ -191,27 +192,6 @@ export function splitArgsIntoNxArgsAndOverrides( return { nxArgs, overrides } as any; } -export function readParallelFromArgsAndEnv(args: { [k: string]: any }) { - if (args['parallel'] === 'false' || args['parallel'] === false) { - return 1; - } else if ( - args['parallel'] === 'true' || - args['parallel'] === true || - args['parallel'] === '' || - // dont require passing --parallel if NX_PARALLEL is set, but allow overriding it - (process.env.NX_PARALLEL && args['parallel'] === undefined) - ) { - return Number( - args['maxParallel'] || - args['max-parallel'] || - process.env.NX_PARALLEL || - 3 - ); - } else if (args['parallel'] !== undefined) { - return Number(args['parallel']); - } -} - function normalizeNxArgsRunner( nxArgs: RawNxArgs, nxJson: NxJsonConfiguration, diff --git a/packages/nx/src/utils/handle-errors.ts b/packages/nx/src/utils/handle-errors.ts index 77de737eea592..2b15d9bce71fb 100644 --- a/packages/nx/src/utils/handle-errors.ts +++ b/packages/nx/src/utils/handle-errors.ts @@ -1,4 +1,3 @@ -import { daemonClient } from '../daemon/client/client'; import { ProjectGraphError } from '../project-graph/error-types'; import { logger } from './logger'; import { output } from './output'; @@ -52,6 +51,7 @@ export async function handleErrors( bodyLines, }); } + const { daemonClient } = await import('../daemon/client/client'); if (daemonClient.enabled()) { daemonClient.reset(); }