Skip to content

Commit

Permalink
fix(core): reduce time it takes to require nx commands (#28884)
Browse files Browse the repository at this point in the history
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

`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
<!-- This is the behavior we should expect with the changes in this PR
-->

`nx/src/command-line/nx-commands` takes ~30-33ms on my machine to
require.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
  • Loading branch information
FrozenPandaz authored Nov 11, 2024
1 parent 19b0828 commit c21b039
Show file tree
Hide file tree
Showing 9 changed files with 149 additions and 151 deletions.
6 changes: 2 additions & 4 deletions packages/nx/src/command-line/generate/command-object.ts
Original file line number Diff line number Diff line change
@@ -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 = {
Expand All @@ -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));
},
};

Expand Down
5 changes: 4 additions & 1 deletion packages/nx/src/command-line/generate/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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,
Expand Down
17 changes: 11 additions & 6 deletions packages/nx/src/command-line/init/command-object.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
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',
describe:
'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 {
Expand All @@ -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', {
Expand Down
115 changes: 2 additions & 113 deletions packages/nx/src/command-line/migrate/command-object.ts
Original file line number Diff line number Diff line change
@@ -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 = {
Expand All @@ -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);
},
};
Expand Down Expand Up @@ -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);
}
105 changes: 104 additions & 1 deletion packages/nx/src/command-line/migrate/migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
readNxMigrateConfig,
} from '../../utils/package-json';
import {
copyPackageManagerConfigurationFiles,
createTempNpmDirectory,
detectPackageManager,
getPackageManagerCommand,
Expand All @@ -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';
Expand All @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
7 changes: 3 additions & 4 deletions packages/nx/src/command-line/release/command-object.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -11,6 +9,7 @@ import {
withOverrides,
withRunManyOptions,
withVerbose,
readParallelFromArgsAndEnv,
} from '../yargs-utils/shared-options';
import { VersionData } from './utils/shared';

Expand Down Expand Up @@ -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]) {
Expand Down
Loading

0 comments on commit c21b039

Please sign in to comment.