diff --git a/packages/nx/bin/post-install.ts b/packages/nx/bin/post-install.ts index 714386af0aa0a..9822b4575e861 100644 --- a/packages/nx/bin/post-install.ts +++ b/packages/nx/bin/post-install.ts @@ -17,9 +17,11 @@ import { setupWorkspaceContext } from '../src/utils/workspace-context'; if (isMainNxPackage() && fileExists(join(workspaceRoot, 'nx.json'))) { assertSupportedPlatform(); setupWorkspaceContext(workspaceRoot); - try { - await daemonClient.stop(); - } catch (e) {} + if (daemonClient.enabled()) { + try { + await daemonClient.stop(); + } catch (e) {} + } const tasks: Array> = [ buildProjectGraphAndSourceMapsWithoutDaemon(), ]; diff --git a/packages/nx/plugins/package-json.ts b/packages/nx/plugins/package-json.ts index 502e0da250204..3ea2d5a85e627 100644 --- a/packages/nx/plugins/package-json.ts +++ b/packages/nx/plugins/package-json.ts @@ -1,13 +1,50 @@ -import type { NxPluginV2 } from '../src/project-graph/plugins'; +import { createNodesFromFiles, NxPluginV2 } from '../src/project-graph/plugins'; import { workspaceRoot } from '../src/utils/workspace-root'; import { createNodeFromPackageJson } from '../src/plugins/package-json'; +import { workspaceDataDirectory } from '../src/utils/cache-directory'; +import { join } from 'path'; +import { ProjectConfiguration } from '../src/config/workspace-json-project-json'; +import { readJsonFile, writeJsonFile } from '../src/utils/fileutils'; + +export type PackageJsonConfigurationCache = { + [hash: string]: ProjectConfiguration; +}; + +const cachePath = join(workspaceDataDirectory, 'package-json.hash'); + +export function readPackageJsonConfigurationCache() { + try { + return readJsonFile(cachePath); + } catch (e) { + return {}; + } +} + +function writeCache(cache: PackageJsonConfigurationCache) { + writeJsonFile(cachePath, cache); +} const plugin: NxPluginV2 = { name: 'nx-all-package-jsons-plugin', - createNodes: [ + createNodesV2: [ '*/**/package.json', - (f) => createNodeFromPackageJson(f, workspaceRoot), + (configFiles, options, context) => { + const cache = readPackageJsonConfigurationCache(); + + const result = createNodesFromFiles( + (f) => createNodeFromPackageJson(f, workspaceRoot, cache), + configFiles, + options, + context + ); + + writeCache(cache); + + return result; + }, ], }; module.exports = plugin; +module.exports.readPackageJsonConfigurationCache = + readPackageJsonConfigurationCache; diff --git a/packages/nx/src/command-line/affected/affected.ts b/packages/nx/src/command-line/affected/affected.ts index 2bda67751f240..b915fa2516410 100644 --- a/packages/nx/src/command-line/affected/affected.ts +++ b/packages/nx/src/command-line/affected/affected.ts @@ -53,7 +53,9 @@ export async function affected( await connectToNxCloudIfExplicitlyAsked(nxArgs); - const projectGraph = await createProjectGraphAsync({ exitOnError: true }); + const projectGraph = await createProjectGraphAsync({ + exitOnError: true, + }); const projects = await getAffectedGraphNodes(nxArgs, projectGraph); try { diff --git a/packages/nx/src/command-line/format/format.ts b/packages/nx/src/command-line/format/format.ts index c5a9c028a7ac4..655de5930729a 100644 --- a/packages/nx/src/command-line/format/format.ts +++ b/packages/nx/src/command-line/format/format.ts @@ -1,10 +1,10 @@ import { exec, execSync } from 'node:child_process'; import * as path from 'node:path'; import * as yargs from 'yargs'; -import { FileData, calculateFileChanges } from '../../project-graph/file-utils'; +import { calculateFileChanges, FileData } from '../../project-graph/file-utils'; import { - NxArgs, getProjectRoots, + NxArgs, parseFiles, splitArgsIntoNxArgsAndOverrides, } from '../../utils/command-line-utils'; @@ -52,7 +52,7 @@ export async function format( const patterns = (await getPatterns({ ...args, ...nxArgs } as any)).map( // prettier removes one of the \ // prettier-ignore - (p) => `"${p.replace(/\$/g, "\\\$")}"` + (p) => `"${p.replace(/\$/g, '\\\$')}"` ); // Chunkify the patterns array to prevent crashing the windows terminal @@ -156,7 +156,9 @@ async function getPatternsFromApps( allWorkspaceFiles: FileData[], projectGraph: ProjectGraph ): Promise { - const graph = await createProjectGraphAsync({ exitOnError: true }); + const graph = await createProjectGraphAsync({ + exitOnError: true, + }); const affectedGraph = await filterAffected( graph, calculateFileChanges(affectedFiles, allWorkspaceFiles) @@ -268,6 +270,7 @@ function sortTsConfig() { } let prettierPath: string; + function getPrettierPath() { if (prettierPath) { return prettierPath; diff --git a/packages/nx/src/command-line/run-many/run-many.spec.ts b/packages/nx/src/command-line/run-many/run-many.spec.ts index c2aa94cd1821e..5116e9d47c142 100644 --- a/packages/nx/src/command-line/run-many/run-many.spec.ts +++ b/packages/nx/src/command-line/run-many/run-many.spec.ts @@ -189,8 +189,8 @@ describe('run-many', () => { } }); - it('should be able to select and exclude via patterns', async () => { - performance.mark('start'); + it('should be able to select and exclude via patterns', () => { + const start = performance.now(); projectsToRun( { targets: ['test'], @@ -199,9 +199,8 @@ describe('run-many', () => { }, projectGraph ); - performance.mark('end'); - const measure = performance.measure('projects', 'start', 'end'); - expect(measure.duration).toBeLessThan(10000); + const end = performance.now(); + expect(end - start).toBeLessThan(10000); }); }); }); diff --git a/packages/nx/src/command-line/show/project.ts b/packages/nx/src/command-line/show/project.ts index 5cb1b7bcf211d..ab093399fe273 100644 --- a/packages/nx/src/command-line/show/project.ts +++ b/packages/nx/src/command-line/show/project.ts @@ -6,6 +6,8 @@ import { generateGraph } from '../graph/graph'; export async function showProjectHandler( args: ShowProjectOptions ): Promise { + performance.mark('code-loading:end'); + performance.measure('code-loading', 'init-local', 'code-loading:end'); const graph = await createProjectGraphAsync(); const node = graph.nodes[args.projectName]; if (!node) { @@ -70,5 +72,8 @@ export async function showProjectHandler( } } } + + // TODO: Find a better fix for this + await new Promise((res) => setImmediate(res)); await output.drain(); } diff --git a/packages/nx/src/command-line/show/projects.spec.ts b/packages/nx/src/command-line/show/projects.spec.ts index 137e47a8ab6eb..fbc69a62dc694 100644 --- a/packages/nx/src/command-line/show/projects.spec.ts +++ b/packages/nx/src/command-line/show/projects.spec.ts @@ -20,9 +20,13 @@ jest.mock('../../project-graph/project-graph', () => ({ .mockImplementation(() => Promise.resolve(graph)), })); +performance.mark = jest.fn(); +performance.measure = jest.fn(); + describe('show projects', () => { beforeEach(() => { jest.spyOn(console, 'log').mockImplementation(() => {}); + performance.mark('init-local'); }); afterEach(() => { jest.clearAllMocks(); diff --git a/packages/nx/src/command-line/show/projects.ts b/packages/nx/src/command-line/show/projects.ts index 1da055b751ee3..5661b24d9fa23 100644 --- a/packages/nx/src/command-line/show/projects.ts +++ b/packages/nx/src/command-line/show/projects.ts @@ -23,6 +23,8 @@ import { ShowProjectsOptions } from './command-object'; export async function showProjectsHandler( args: ShowProjectsOptions ): Promise { + performance.mark('code-loading:end'); + performance.measure('code-loading', 'init-local', 'code-loading:end'); let graph = await createProjectGraphAsync(); const nxJson = readNxJson(); const { nxArgs } = splitArgsIntoNxArgsAndOverrides( @@ -82,6 +84,8 @@ export async function showProjectsHandler( } } + // TODO: Find a better fix for this + await new Promise((res) => setImmediate(res)); await output.drain(); } diff --git a/packages/nx/src/daemon/server/handle-request-project-graph.ts b/packages/nx/src/daemon/server/handle-request-project-graph.ts index 1dc4507383c82..449d57a693805 100644 --- a/packages/nx/src/daemon/server/handle-request-project-graph.ts +++ b/packages/nx/src/daemon/server/handle-request-project-graph.ts @@ -3,8 +3,6 @@ import { serializeResult } from '../socket-utils'; import { serverLogger } from './logger'; import { getCachedSerializedProjectGraphPromise } from './project-graph-incremental-recomputation'; import { HandlerResult } from './server'; -import { getPlugins } from './plugins'; -import { readNxJson } from '../../config/nx-json'; export async function handleRequestProjectGraph(): Promise { try { diff --git a/packages/nx/src/daemon/server/project-graph-incremental-recomputation.ts b/packages/nx/src/daemon/server/project-graph-incremental-recomputation.ts index fc3abef69a438..81f4830135ff7 100644 --- a/packages/nx/src/daemon/server/project-graph-incremental-recomputation.ts +++ b/packages/nx/src/daemon/server/project-graph-incremental-recomputation.ts @@ -31,12 +31,12 @@ import { serverLogger } from './logger'; import { NxWorkspaceFilesExternals } from '../../native'; import { ConfigurationResult } from '../../project-graph/utils/project-configuration-utils'; import { LoadedNxPlugin } from '../../project-graph/plugins/internal-api'; -import { getPlugins } from './plugins'; import { DaemonProjectGraphError, ProjectConfigurationsError, isAggregateProjectGraphError, } from '../../project-graph/error-types'; +import { getPlugins } from '../../project-graph/plugins/get-plugins'; interface SerializedProjectGraph { error: Error | null; diff --git a/packages/nx/src/daemon/server/shutdown-utils.ts b/packages/nx/src/daemon/server/shutdown-utils.ts index fde1e65d936f6..d49e74be69875 100644 --- a/packages/nx/src/daemon/server/shutdown-utils.ts +++ b/packages/nx/src/daemon/server/shutdown-utils.ts @@ -4,12 +4,12 @@ import { serverLogger } from './logger'; import { serializeResult } from '../socket-utils'; import { deleteDaemonJsonProcessCache } from '../cache'; import type { Watcher } from '../../native'; -import { cleanupPlugins } from './plugins'; import { DaemonProjectGraphError, ProjectGraphError, } from '../../project-graph/error-types'; import { removeDbConnections } from '../../utils/db-connection'; +import { cleanupPlugins } from '../../project-graph/plugins/get-plugins'; export const SERVER_INACTIVITY_TIMEOUT_MS = 10800000 as const; // 10800000 ms = 3 hours diff --git a/packages/nx/src/executors/utils/convert-nx-executor.ts b/packages/nx/src/executors/utils/convert-nx-executor.ts index 5a43944bfdc0d..961dad287762a 100644 --- a/packages/nx/src/executors/utils/convert-nx-executor.ts +++ b/packages/nx/src/executors/utils/convert-nx-executor.ts @@ -8,7 +8,7 @@ import { Executor, ExecutorContext } from '../../config/misc-interfaces'; import { retrieveProjectConfigurations } from '../../project-graph/utils/retrieve-workspace-files'; import { readProjectConfigurationsFromRootMap } from '../../project-graph/utils/project-configuration-utils'; import { ProjectsConfigurations } from '../../config/workspace-json-project-json'; -import { loadNxPlugins } from '../../project-graph/plugins/internal-api'; +import { getPlugins } from '../../project-graph/plugins/get-plugins'; /** * Convert an Nx Executor into an Angular Devkit Builder @@ -20,10 +20,7 @@ export function convertNxExecutor(executor: Executor) { const promise = async () => { const nxJsonConfiguration = readNxJson(builderContext.workspaceRoot); - const [plugins, cleanup] = await loadNxPlugins( - nxJsonConfiguration.plugins, - builderContext.workspaceRoot - ); + const plugins = await getPlugins(); const projectsConfigurations: ProjectsConfigurations = { version: 2, projects: readProjectConfigurationsFromRootMap( @@ -36,7 +33,6 @@ export function convertNxExecutor(executor: Executor) { ).projects ), }; - cleanup(); const context: ExecutorContext = { root: builderContext.workspaceRoot, projectName: builderContext.target.project, diff --git a/packages/nx/src/plugins/package-json/create-nodes.spec.ts b/packages/nx/src/plugins/package-json/create-nodes.spec.ts index fa6bdda6d70db..a83cf0a183088 100644 --- a/packages/nx/src/plugins/package-json/create-nodes.spec.ts +++ b/packages/nx/src/plugins/package-json/create-nodes.spec.ts @@ -48,7 +48,7 @@ describe('nx package.json workspaces plugin', () => { '/root' ); - expect(createNodeFromPackageJson('package.json', '/root')) + expect(createNodeFromPackageJson('package.json', '/root', {})) .toMatchInlineSnapshot(` { "projects": { @@ -90,8 +90,9 @@ describe('nx package.json workspaces plugin', () => { }, } `); - expect(createNodeFromPackageJson('packages/lib-a/package.json', '/root')) - .toMatchInlineSnapshot(` + expect( + createNodeFromPackageJson('packages/lib-a/package.json', '/root', {}) + ).toMatchInlineSnapshot(` { "projects": { "packages/lib-a": { @@ -132,8 +133,9 @@ describe('nx package.json workspaces plugin', () => { }, } `); - expect(createNodeFromPackageJson('packages/lib-b/package.json', '/root')) - .toMatchInlineSnapshot(` + expect( + createNodeFromPackageJson('packages/lib-b/package.json', '/root', {}) + ).toMatchInlineSnapshot(` { "projects": { "packages/lib-b": { @@ -731,13 +733,12 @@ describe('nx package.json workspaces plugin', () => { ); expect( - createNodeFromPackageJson('apps/myapp/package.json', '/root').projects[ - 'apps/myapp' - ].projectType + createNodeFromPackageJson('apps/myapp/package.json', '/root', {}) + .projects['apps/myapp'].projectType ).toEqual('application'); expect( - createNodeFromPackageJson('packages/mylib/package.json', '/root') + createNodeFromPackageJson('packages/mylib/package.json', '/root', {}) .projects['packages/mylib'].projectType ).toEqual('library'); }); @@ -760,7 +761,7 @@ describe('nx package.json workspaces plugin', () => { ); expect( - createNodeFromPackageJson('package.json', '/root').projects['.'] + createNodeFromPackageJson('package.json', '/root', {}).projects['.'] .projectType ).toEqual('library'); }); @@ -786,11 +787,11 @@ describe('nx package.json workspaces plugin', () => { ); expect( - createNodeFromPackageJson('packages/mylib/package.json', '/root') + createNodeFromPackageJson('packages/mylib/package.json', '/root', {}) .projects['packages/mylib'].projectType ).toEqual('library'); expect( - createNodeFromPackageJson('example/package.json', '/root').projects[ + createNodeFromPackageJson('example/package.json', '/root', {}).projects[ 'example' ].projectType ).toBeUndefined(); diff --git a/packages/nx/src/plugins/package-json/create-nodes.ts b/packages/nx/src/plugins/package-json/create-nodes.ts index 0670dffcd7b13..1a4bdea2e3c59 100644 --- a/packages/nx/src/plugins/package-json/create-nodes.ts +++ b/packages/nx/src/plugins/package-json/create-nodes.ts @@ -21,6 +21,11 @@ import { CreateNodesV2, } from '../../project-graph/plugins'; import { basename } from 'path'; +import { hashObject } from '../../hasher/file-hasher'; +import { + PackageJsonConfigurationCache, + readPackageJsonConfigurationCache, +} from '../../../plugins/package-json'; export const createNodesV2: CreateNodesV2 = [ combineGlobPatterns( @@ -41,6 +46,8 @@ export const createNodesV2: CreateNodesV2 = [ return projectJsonRoots.has(dirname(packageJsonPath)); }; + const cache = readPackageJsonConfigurationCache(); + return createNodesFromFiles( (packageJsonPath, options, context) => { if ( @@ -53,7 +60,8 @@ export const createNodesV2: CreateNodesV2 = [ return createNodeFromPackageJson( packageJsonPath, - context.workspaceRoot + context.workspaceRoot, + cache ); }, packageJsons, @@ -120,15 +128,35 @@ export function buildPackageJsonWorkspacesMatcher( export function createNodeFromPackageJson( pkgJsonPath: string, - workspaceRoot: string + workspaceRoot: string, + cache: PackageJsonConfigurationCache ) { const json: PackageJson = readJsonFile(join(workspaceRoot, pkgJsonPath)); + + const projectRoot = dirname(pkgJsonPath); + + const hash = hashObject({ + ...json, + root: projectRoot, + }); + + const cached = cache[hash]; + if (cached) { + return { + projects: { + [cached.root]: cached, + }, + }; + } + const project = buildProjectConfigurationFromPackageJson( json, workspaceRoot, pkgJsonPath, readNxJson(workspaceRoot) ); + + cache[hash] = project; return { projects: { [project.root]: project, diff --git a/packages/nx/src/project-graph/affected/affected-project-graph.ts b/packages/nx/src/project-graph/affected/affected-project-graph.ts index 52cc5c8f0f218..26cd04dd1a344 100644 --- a/packages/nx/src/project-graph/affected/affected-project-graph.ts +++ b/packages/nx/src/project-graph/affected/affected-project-graph.ts @@ -30,6 +30,7 @@ export async function filterAffected( const touchedProjects = []; for (const locator of touchedProjectLocators) { + performance.mark(locator.name + ':start'); const projects = await locator( touchedFiles, graph.nodes, @@ -37,6 +38,12 @@ export async function filterAffected( packageJson, graph ); + performance.mark(locator.name + ':end'); + performance.measure( + locator.name, + locator.name + ':start', + locator.name + ':end' + ); touchedProjects.push(...projects); } diff --git a/packages/nx/src/project-graph/affected/locators/project-glob-changes.spec.ts b/packages/nx/src/project-graph/affected/locators/project-glob-changes.spec.ts index 31578fd73cb10..7a65e4befdeb4 100644 --- a/packages/nx/src/project-graph/affected/locators/project-glob-changes.spec.ts +++ b/packages/nx/src/project-graph/affected/locators/project-glob-changes.spec.ts @@ -1,55 +1,59 @@ -import { ProjectGraphProjectNode } from '../../../config/project-graph'; -import { ProjectConfiguration } from '../../../config/workspace-json-project-json'; - import * as nxPlugin from '../../../project-graph/plugins'; +import { ProjectGraphProjectNode } from '../../../config/project-graph'; import { DeletedFileChange } from '../../file-utils'; import { getTouchedProjectsFromProjectGlobChanges } from './project-glob-changes'; +jest.mock('../../../project-graph/plugins', () => ({ + ...jest.requireActual('../../../project-graph/plugins'), + getPlugins: async () => { + return [ + { + name: 'test', + createNodes: [ + '**/project.json', + async () => { + return []; + }, + ], + }, + ]; + }, +})); describe('getTouchedProjectsFromProjectGlobChanges', () => { - it('empty', () => {}); + it('should affect all projects if a project is removed', async () => { + const nodes = { + proj1: makeProjectGraphNode('proj1'), + proj2: makeProjectGraphNode('proj2'), + proj3: makeProjectGraphNode('proj3'), + }; + const result = await getTouchedProjectsFromProjectGlobChanges( + [ + { + file: 'libs/proj1/project.json', + hash: 'some-hash', + getChanges: () => [new DeletedFileChange()], + }, + ], + nodes, + { + plugins: [], + }, + {}, + { + nodes: nodes, + dependencies: {}, + } + ); + expect(result).toEqual(['proj1', 'proj2', 'proj3']); + }); }); -// describe('getTouchedProjectsFromProjectGlobChanges', () => { -// beforeEach(() => { -// jest.spyOn(nxPlugin, 'loadNxPlugins').mockResolvedValue([]); -// }); -// -// it('should affect all projects if a project is removed', async () => { -// const result = await getTouchedProjectsFromProjectGlobChanges( -// [ -// { -// file: 'libs/proj1/project.json', -// hash: 'some-hash', -// getChanges: () => [new DeletedFileChange()], -// }, -// ], -// { -// proj2: makeProjectGraphNode('proj2'), -// proj3: makeProjectGraphNode('proj3'), -// }, -// { -// plugins: [], -// } -// ); -// expect(result).toEqual(['proj2', 'proj3']); -// }); -// }); - -// function makeProjectGraphNode( -// name, -// configurationFile = 'project.json' -// ): ProjectGraphProjectNode { -// return { -// data: { -// files: [ -// { -// file: `libs/${name}/${configurationFile}`, -// hash: 'hash' + Math.floor(Math.random() * 10000), -// }, -// ], -// root: `libs/${name}`, -// }, -// name, -// type: 'lib', -// }; -// } +function makeProjectGraphNode(name): ProjectGraphProjectNode { + return { + data: { + root: `libs/${name}`, + }, + name, + type: 'lib', + }; +} diff --git a/packages/nx/src/project-graph/affected/locators/project-glob-changes.ts b/packages/nx/src/project-graph/affected/locators/project-glob-changes.ts index 6c54137366a25..aa1ab25d98a87 100644 --- a/packages/nx/src/project-graph/affected/locators/project-glob-changes.ts +++ b/packages/nx/src/project-graph/affected/locators/project-glob-changes.ts @@ -4,12 +4,12 @@ import { workspaceRoot } from '../../../utils/workspace-root'; import { join } from 'path'; import { existsSync } from 'fs'; import { configurationGlobs } from '../../utils/retrieve-workspace-files'; -import { loadNxPlugins } from '../../plugins/internal-api'; import { combineGlobPatterns } from '../../../utils/globs'; +import { getPlugins } from '../../plugins/get-plugins'; export const getTouchedProjectsFromProjectGlobChanges: TouchedProjectLocator = - async (touchedFiles, projectGraphNodes, nxJson): Promise => { - const [plugins] = await loadNxPlugins(nxJson?.plugins ?? [], workspaceRoot); + async (touchedFiles, projectGraphNodes): Promise => { + const plugins = await getPlugins(); const globPattern = combineGlobPatterns(configurationGlobs(plugins)); const touchedProjects = new Set(); diff --git a/packages/nx/src/project-graph/build-project-graph.ts b/packages/nx/src/project-graph/build-project-graph.ts index 0ef18c56050a7..76b64136772d1 100644 --- a/packages/nx/src/project-graph/build-project-graph.ts +++ b/packages/nx/src/project-graph/build-project-graph.ts @@ -210,8 +210,6 @@ async function buildProjectGraphUsingContext( plugins: LoadedNxPlugin[], sourceMap: ConfigurationSourceMaps ) { - performance.mark('build project graph:start'); - const builder = new ProjectGraphBuilder(null, ctx.fileMap.projectFileMap); builder.setVersion(projectGraphVersion); for (const node in knownExternalNodes) { @@ -262,13 +260,6 @@ async function buildProjectGraphUsingContext( const finalGraph = updatedBuilder.getUpdatedProjectGraph(); - performance.mark('build project graph:end'); - performance.measure( - 'build project graph', - 'build project graph:start', - 'build project graph:end' - ); - if (!error) { return finalGraph; } else { @@ -311,6 +302,7 @@ async function updateProjectGraphWithPlugins( const createDependencyPlugins = plugins.filter( (plugin) => plugin.createDependencies ); + performance.mark('createDependencies:start'); await Promise.all( createDependencyPlugins.map(async (plugin) => { performance.mark(`${plugin.name}:createDependencies - start`); @@ -344,6 +336,12 @@ async function updateProjectGraphWithPlugins( ); }) ); + performance.mark('createDependencies:end'); + performance.measure( + `createDependencies`, + `createDependencies:start`, + `createDependencies:end` + ); const graphWithDeps = builder.getUpdatedProjectGraph(); @@ -387,6 +385,7 @@ export async function applyProjectMetadata( const results: { metadata: ProjectsMetadata; pluginName: string }[] = []; const errors: CreateMetadataError[] = []; + performance.mark('createMetadata:start'); const promises = plugins.map(async (plugin) => { if (plugin.createMetadata) { performance.mark(`${plugin.name}:createMetadata - start`); @@ -424,5 +423,12 @@ export async function applyProjectMetadata( } } + performance.mark('createMetadata:end'); + performance.measure( + `createMetadata`, + `createMetadata:start`, + `createMetadata:end` + ); + return { errors, graph }; } diff --git a/packages/nx/src/project-graph/error-types.ts b/packages/nx/src/project-graph/error-types.ts index dda621c383828..1f61deb951d4b 100644 --- a/packages/nx/src/project-graph/error-types.ts +++ b/packages/nx/src/project-graph/error-types.ts @@ -4,7 +4,7 @@ import { } from './utils/project-configuration-utils'; import { ProjectConfiguration } from '../config/workspace-json-project-json'; import { ProjectGraph } from '../config/project-graph'; -import { CreateNodesFunctionV2 } from './plugins'; +import { CreateNodesFunctionV2 } from './plugins/public-api'; export class ProjectGraphError extends Error { readonly #errors: Array< diff --git a/packages/nx/src/daemon/server/plugins.ts b/packages/nx/src/project-graph/plugins/get-plugins.ts similarity index 61% rename from packages/nx/src/daemon/server/plugins.ts rename to packages/nx/src/project-graph/plugins/get-plugins.ts index 3e6da219c98a8..83489795852a3 100644 --- a/packages/nx/src/daemon/server/plugins.ts +++ b/packages/nx/src/project-graph/plugins/get-plugins.ts @@ -1,9 +1,6 @@ import { hashObject } from '../../hasher/file-hasher'; import { readNxJson } from '../../config/nx-json'; -import { - LoadedNxPlugin, - loadNxPlugins, -} from '../../project-graph/plugins/internal-api'; +import { LoadedNxPlugin, loadNxPlugins } from './internal-api'; import { workspaceRoot } from '../../utils/workspace-root'; let currentPluginsConfigurationHash: string; @@ -37,6 +34,27 @@ export async function getPlugins() { return result; } +let loadedDefaultPlugins: LoadedNxPlugin[]; +let cleanupDefaultPlugins: () => void; + +export async function getOnlyDefaultPlugins() { + // If the plugins configuration has not changed, reuse the current plugins + if (loadedDefaultPlugins) { + return loadedPlugins; + } + + // Cleanup current plugins before loading new ones + if (cleanupDefaultPlugins) { + cleanupDefaultPlugins(); + } + + const [result, cleanupFn] = await loadNxPlugins([], workspaceRoot); + cleanupDefaultPlugins = cleanupFn; + loadedPlugins = result; + return result; +} + export function cleanupPlugins() { cleanup(); + cleanupDefaultPlugins(); } diff --git a/packages/nx/src/project-graph/plugins/index.ts b/packages/nx/src/project-graph/plugins/index.ts index 3739a2e887039..3bd38dd1dc22b 100644 --- a/packages/nx/src/project-graph/plugins/index.ts +++ b/packages/nx/src/project-graph/plugins/index.ts @@ -1,4 +1,6 @@ export * from './public-api'; +// export * from './get-plugins'; + export { readPluginPackageJson, registerPluginTSTranspiler } from './loader'; export { createNodesFromFiles } from './utils'; diff --git a/packages/nx/src/project-graph/plugins/internal-api.ts b/packages/nx/src/project-graph/plugins/internal-api.ts index 0692d6874848a..49a6c85d6c565 100644 --- a/packages/nx/src/project-graph/plugins/internal-api.ts +++ b/packages/nx/src/project-graph/plugins/internal-api.ts @@ -143,31 +143,45 @@ function isIsolationEnabled() { return true; } +/** + * Use `getPlugins` instead. + * @deprecated Do not use this. Use `getPlugins` instead. + */ export async function loadNxPlugins( plugins: PluginConfiguration[], root = workspaceRoot ): Promise void]> { performance.mark('loadNxPlugins:start'); - const loadingMethod = isIsolationEnabled() ? loadNxPluginInIsolation : loadNxPlugin; plugins = await normalizePlugins(plugins, root); - const result: Promise[] = new Array(plugins?.length); - const cleanupFunctions: Array<() => void> = []; - await Promise.all( - plugins.map(async (plugin, idx) => { - const [loadedPluginPromise, cleanup] = await loadingMethod(plugin, root); - result[idx] = loadedPluginPromise; - cleanupFunctions.push(cleanup); - }) - ); - const ret = [ - await Promise.all(result), + await Promise.all( + plugins.map(async (plugin) => { + const pluginPath = typeof plugin === 'string' ? plugin : plugin.plugin; + performance.mark(`Load Nx Plugin: ${pluginPath} - start`); + + const [loadedPluginPromise, cleanup] = await loadingMethod( + plugin, + root + ); + + cleanupFunctions.push(cleanup); + const res = await loadedPluginPromise; + performance.mark(`Load Nx Plugin: ${pluginPath} - end`); + performance.measure( + `Load Nx Plugin: ${pluginPath}`, + `Load Nx Plugin: ${pluginPath} - start`, + `Load Nx Plugin: ${pluginPath} - end` + ); + + return res; + }) + ), () => { for (const fn of cleanupFunctions) { fn(); diff --git a/packages/nx/src/project-graph/plugins/isolation/index.ts b/packages/nx/src/project-graph/plugins/isolation/index.ts index 4bab409265189..1f77f6be5e7ee 100644 --- a/packages/nx/src/project-graph/plugins/isolation/index.ts +++ b/packages/nx/src/project-graph/plugins/isolation/index.ts @@ -7,11 +7,5 @@ export async function loadNxPluginInIsolation( plugin: PluginConfiguration, root = workspaceRoot ): Promise, () => void]> { - const [loadingPlugin, cleanup] = await loadRemoteNxPlugin(plugin, root); - return [ - loadingPlugin, - () => { - cleanup(); - }, - ] as const; + return loadRemoteNxPlugin(plugin, root); } diff --git a/packages/nx/src/project-graph/plugins/isolation/plugin-worker.ts b/packages/nx/src/project-graph/plugins/isolation/plugin-worker.ts index a1d2451ae61ab..5bc4477a3bfb0 100644 --- a/packages/nx/src/project-graph/plugins/isolation/plugin-worker.ts +++ b/packages/nx/src/project-graph/plugins/isolation/plugin-worker.ts @@ -1,6 +1,4 @@ import { consumeMessage, isPluginWorkerMessage } from './messaging'; -import { LoadedNxPlugin } from '../internal-api'; -import { loadNxPlugin } from '../loader'; import { createSerializableError } from '../../../utils/serializable-error'; import { consumeMessagesFromSocket } from '../../../utils/consume-messages-from-socket'; @@ -14,7 +12,7 @@ if (process.env.NX_PERF_LOGGING === 'true') { global.NX_GRAPH_CREATION = true; global.NX_PLUGIN_WORKER = true; let connected = false; -let plugin: LoadedNxPlugin; +let plugin; const socketPath = process.argv[2]; @@ -41,6 +39,7 @@ const server = createServer((socket) => { if (loadTimeout) clearTimeout(loadTimeout); process.chdir(root); try { + const { loadNxPlugin } = await import('../loader'); const [promise] = loadNxPlugin(pluginConfiguration, root); plugin = await promise; return { diff --git a/packages/nx/src/project-graph/plugins/loader.ts b/packages/nx/src/project-graph/plugins/loader.ts index bfa539e4e4107..05661bcc93525 100644 --- a/packages/nx/src/project-graph/plugins/loader.ts +++ b/packages/nx/src/project-graph/plugins/loader.ts @@ -24,8 +24,8 @@ import { logger } from '../../utils/logger'; import type * as ts from 'typescript'; import { extname } from 'node:path'; -import { NxPlugin } from './public-api'; -import { PluginConfiguration } from '../../config/nx-json'; +import type { NxPlugin } from './public-api'; +import type { PluginConfiguration } from '../../config/nx-json'; import { retrieveProjectConfigurationsWithoutPluginInference } from '../utils/retrieve-workspace-files'; import { LoadedNxPlugin } from './internal-api'; import { LoadPluginError } from '../error-types'; @@ -266,8 +266,6 @@ export async function loadNxPluginAsync( projectsWithoutInference ??= await retrieveProjectConfigurationsWithoutPluginInference(root); } - - performance.mark(`Load Nx Plugin: ${moduleName} - start`); const { pluginPath, name } = getPluginPathAndName( moduleName, paths, @@ -276,12 +274,7 @@ export async function loadNxPluginAsync( ); const plugin = await importPluginModule(pluginPath); plugin.name ??= name; - performance.mark(`Load Nx Plugin: ${moduleName} - end`); - performance.measure( - `Load Nx Plugin: ${moduleName}`, - `Load Nx Plugin: ${moduleName} - start`, - `Load Nx Plugin: ${moduleName} - end` - ); + return new LoadedNxPlugin(plugin, pluginConfiguration); } catch (e) { throw new LoadPluginError(moduleName, e); diff --git a/packages/nx/src/project-graph/project-graph.ts b/packages/nx/src/project-graph/project-graph.ts index eb1c101faee7b..30f2a1912db0c 100644 --- a/packages/nx/src/project-graph/project-graph.ts +++ b/packages/nx/src/project-graph/project-graph.ts @@ -24,12 +24,12 @@ import { readProjectGraphCache, writeCache, } from './nx-deps-cache'; -import { loadNxPlugins } from './plugins/internal-api'; import { ConfigurationResult } from './utils/project-configuration-utils'; import { retrieveProjectConfigurations, retrieveWorkspaceFiles, } from './utils/retrieve-workspace-files'; +import { getPlugins } from './plugins/get-plugins'; /** * Synchronously reads the latest cached copy of the workspace's ProjectGraph. @@ -96,7 +96,7 @@ export async function buildProjectGraphAndSourceMapsWithoutDaemon() { performance.mark('retrieve-project-configurations:start'); let configurationResult: ConfigurationResult; let projectConfigurationsError: ProjectConfigurationsError; - const [plugins, cleanup] = await loadNxPlugins(nxJson.plugins); + const plugins = await getPlugins(); try { configurationResult = await retrieveProjectConfigurations( plugins, @@ -147,14 +147,6 @@ export async function buildProjectGraphAndSourceMapsWithoutDaemon() { } else { throw e; } - } finally { - // When plugins are isolated we don't clean them up during - // a single run of the CLI. They are cleaned up when the CLI - // process exits. Cleaning them here could cause issues if pending - // promises are not resolved. - if (process.env.NX_ISOLATE_PLUGINS !== 'true') { - cleanup(); - } } const { projectGraph, projectFileMapCache } = projectGraphResult; diff --git a/packages/nx/src/project-graph/utils/project-configuration-utils.ts b/packages/nx/src/project-graph/utils/project-configuration-utils.ts index 56cbb493eea4f..911199881bfe2 100644 --- a/packages/nx/src/project-graph/utils/project-configuration-utils.ts +++ b/packages/nx/src/project-graph/utils/project-configuration-utils.ts @@ -28,7 +28,7 @@ import { isAggregateCreateNodesError, AggregateCreateNodesError, } from '../error-types'; -import { CreateNodesResult } from '../plugins'; +import { CreateNodesResult } from '../plugins/public-api'; import { isGlobPattern } from '../../utils/globs'; export type SourceInformation = [file: string | null, plugin: string]; diff --git a/packages/nx/src/project-graph/utils/retrieve-workspace-files.spec.ts b/packages/nx/src/project-graph/utils/retrieve-workspace-files.spec.ts index 4499637562e47..b75b214284730 100644 --- a/packages/nx/src/project-graph/utils/retrieve-workspace-files.spec.ts +++ b/packages/nx/src/project-graph/utils/retrieve-workspace-files.spec.ts @@ -27,12 +27,11 @@ describe('retrieveProjectConfigurationPaths', () => { const configPaths = await retrieveProjectConfigurationPaths(fs.tempDir, [ { + name: 'test', createNodes: [ '{project.json,**/project.json}', - () => { - return { - projects: {}, - }; + async () => { + return []; }, ], }, diff --git a/packages/nx/src/project-graph/utils/retrieve-workspace-files.ts b/packages/nx/src/project-graph/utils/retrieve-workspace-files.ts index d87792f1093fc..31f76b59ac7c9 100644 --- a/packages/nx/src/project-graph/utils/retrieve-workspace-files.ts +++ b/packages/nx/src/project-graph/utils/retrieve-workspace-files.ts @@ -9,13 +9,14 @@ import { ConfigurationResult, createProjectConfigurations, } from './project-configuration-utils'; -import { LoadedNxPlugin, loadNxPlugins } from '../plugins/internal-api'; +import { LoadedNxPlugin } from '../plugins/internal-api'; import { getNxWorkspaceFilesFromContext, globWithWorkspaceContext, } from '../../utils/workspace-context'; import { buildAllWorkspaceFiles } from './build-all-workspace-files'; import { join } from 'path'; +import { getOnlyDefaultPlugins, getPlugins } from '../plugins/get-plugins'; /** * Walks the workspace directory to create the `projectFileMap`, `ProjectConfigurations` and `allWorkspaceFiles` @@ -96,23 +97,19 @@ export async function retrieveProjectConfigurationsWithAngularProjects( pluginsToLoad.push(join(__dirname, '../../adapter/angular-json')); } - const [plugins, cleanup] = await loadNxPlugins( - nxJson?.plugins ?? [], - workspaceRoot - ); + const plugins = await getPlugins(); const res = await retrieveProjectConfigurations( plugins, workspaceRoot, nxJson ); - cleanup(); return res; } export function retrieveProjectConfigurationPaths( root: string, - plugins: Array<{ createNodes?: readonly [string, ...unknown[]] } & unknown> + plugins: Array ): Promise { const projectGlobPatterns = configurationGlobs(plugins); return globWithWorkspaceContext(root, projectGlobPatterns); @@ -128,7 +125,7 @@ export async function retrieveProjectConfigurationsWithoutPluginInference( root: string ): Promise> { const nxJson = readNxJson(root); - const [plugins, cleanup] = await loadNxPlugins([]); // only load default plugins + const plugins = await getOnlyDefaultPlugins(); // only load default plugins const projectGlobPatterns = await retrieveProjectConfigurationPaths( root, plugins @@ -150,14 +147,10 @@ export async function retrieveProjectConfigurationsWithoutPluginInference( projectsWithoutPluginCache.set(cacheKey, projects); - cleanup(); - return projects; } -export function configurationGlobs( - plugins: Array<{ createNodes?: readonly [string, ...unknown[]] }> -): string[] { +export function configurationGlobs(plugins: Array): string[] { const globPatterns = []; for (const plugin of plugins) { if ('createNodes' in plugin && plugin.createNodes) {