From e216afc545ee6568f09fcf52142089377ae2956b Mon Sep 17 00:00:00 2001 From: Craigory Coppola Date: Tue, 7 Jan 2025 09:39:10 -0500 Subject: [PATCH 1/3] fix(core): avoid launching default plugins twice --- .../src/project-graph/plugins/get-plugins.ts | 24 +++-- .../src/project-graph/plugins/internal-api.ts | 93 ++++++++++++++++--- 2 files changed, 97 insertions(+), 20 deletions(-) diff --git a/packages/nx/src/project-graph/plugins/get-plugins.ts b/packages/nx/src/project-graph/plugins/get-plugins.ts index 2ad348a2ff28d..20d38c66c3b3a 100644 --- a/packages/nx/src/project-graph/plugins/get-plugins.ts +++ b/packages/nx/src/project-graph/plugins/get-plugins.ts @@ -1,6 +1,10 @@ import { hashObject } from '../../hasher/file-hasher'; import { readNxJson } from '../../config/nx-json'; -import { LoadedNxPlugin, loadNxPlugins } from './internal-api'; +import { + loadDefaultNxPlugins, + LoadedNxPlugin, + loadSpecifiedNxPlugins, +} from './internal-api'; import { workspaceRoot } from '../../utils/workspace-root'; let currentPluginsConfigurationHash: string; @@ -10,7 +14,7 @@ let pendingPluginsPromise: | undefined; let cleanup: () => void; -export async function getPlugins() { +export async function getPlugins(): Promise { const pluginsConfiguration = readNxJson().plugins ?? []; const pluginsConfigurationHash = hashObject(pluginsConfiguration); @@ -28,13 +32,19 @@ export async function getPlugins() { cleanup(); } - pendingPluginsPromise ??= loadNxPlugins(pluginsConfiguration, workspaceRoot); + pendingPluginsPromise ??= loadSpecifiedNxPlugins( + pluginsConfiguration, + workspaceRoot + ); currentPluginsConfigurationHash = pluginsConfigurationHash; - const [result, cleanupFn] = await pendingPluginsPromise; + const [[result, cleanupFn], defaultPlugins] = await Promise.all([ + pendingPluginsPromise, + getOnlyDefaultPlugins(), + ]); cleanup = cleanupFn; - loadedPlugins = result; - return result; + loadedPlugins = result.concat(defaultPlugins); + return loadedPlugins; } let loadedDefaultPlugins: LoadedNxPlugin[]; @@ -55,7 +65,7 @@ export async function getOnlyDefaultPlugins() { cleanupDefaultPlugins(); } - pendingDefaultPluginPromise ??= loadNxPlugins([], workspaceRoot); + pendingDefaultPluginPromise ??= loadDefaultNxPlugins(workspaceRoot); const [result, cleanupFn] = await pendingDefaultPluginPromise; cleanupDefaultPlugins = cleanupFn; diff --git a/packages/nx/src/project-graph/plugins/internal-api.ts b/packages/nx/src/project-graph/plugins/internal-api.ts index ac29ab7792d8e..b3e9eb0baba60 100644 --- a/packages/nx/src/project-graph/plugins/internal-api.ts +++ b/packages/nx/src/project-graph/plugins/internal-api.ts @@ -145,15 +145,67 @@ function isIsolationEnabled() { return true; } +const loadingMethod = isIsolationEnabled() + ? loadNxPluginInIsolation + : loadNxPlugin; + /** - * Use `getPlugins` instead. - * @deprecated Do not use this. Use `getPlugins` instead. + * @deprecated prefer getOnlyDefaultPlugins */ -export async function loadNxPlugins( +export async function loadDefaultNxPlugins(root = workspaceRoot) { + performance.mark('loadDefaultNxPlugins:start'); + + const plugins = await getDefaultPlugins(root); + + const cleanupFunctions: Array<() => void> = []; + const ret = [ + await Promise.all( + plugins.map(async (plugin) => { + performance.mark(`Load Nx Plugin: ${plugin} - start`); + + const [loadedPluginPromise, cleanup] = await loadingMethod( + plugin, + root + ); + + cleanupFunctions.push(cleanup); + const res = await loadedPluginPromise; + performance.mark(`Load Nx Plugin: ${plugin} - end`); + performance.measure( + `Load Nx Plugin: ${plugin}`, + `Load Nx Plugin: ${plugin} - start`, + `Load Nx Plugin: ${plugin} - end` + ); + + return res; + }) + ), + () => { + for (const fn of cleanupFunctions) { + fn(); + } + if (unregisterPluginTSTranspiler) { + unregisterPluginTSTranspiler(); + } + }, + ] as const; + performance.mark('loadDefaultNxPlugins:end'); + performance.measure( + 'loadDefaultNxPlugins', + 'loadDefaultNxPlugins:start', + 'loadDefaultNxPlugins:end' + ); + return ret; +} + +/** + * @deprecated prefer getPlugins + */ +export async function loadSpecifiedNxPlugins( plugins: PluginConfiguration[], root = workspaceRoot ): Promise void]> { - performance.mark('loadNxPlugins:start'); + performance.mark('loadSpecifiedNxPlugins:start'); const loadingMethod = isIsolationEnabled() ? loadNxPluginInIsolation : loadNxPlugin; @@ -193,23 +245,38 @@ export async function loadNxPlugins( } }, ] as const; - performance.mark('loadNxPlugins:end'); + performance.mark('loadSpecifiedNxPlugins:end'); performance.measure( - 'loadNxPlugins', - 'loadNxPlugins:start', - 'loadNxPlugins:end' + 'loadSpecifiedNxPlugins', + 'loadSpecifiedNxPlugins:start', + 'loadSpecifiedNxPlugins:end' ); return ret; } +/** + * Use `getPlugins` instead. + * @deprecated Do not use this. Use `getPlugins` instead. + */ +export async function loadNxPlugins( + plugins: PluginConfiguration[], + root = workspaceRoot +): Promise void]> { + const defaultPlugins = await loadDefaultNxPlugins(root); + const specifiedPlugins = await loadSpecifiedNxPlugins(plugins, root); + + const combinedCleanup = () => { + specifiedPlugins[1](); + defaultPlugins[1](); + }; + + return [specifiedPlugins[0].concat(defaultPlugins[0]), combinedCleanup]; +} + async function normalizePlugins(plugins: PluginConfiguration[], root: string) { plugins ??= []; - return [ - ...plugins, - // Most of the nx core node plugins go on the end, s.t. it overwrites any other plugins - ...(await getDefaultPlugins(root)), - ]; + return [...plugins]; } export async function getDefaultPlugins(root: string) { From 824fc921b643bb02804fbe0dad236bbe9ca95ec4 Mon Sep 17 00:00:00 2001 From: Craigory Coppola Date: Tue, 7 Jan 2025 18:55:08 -0500 Subject: [PATCH 2/3] chore(core): refactor + cleanup for plugin utils --- .../add-e2e-ci-target-defaults.ts | 2 +- .../nx/src/config/to-project-name.spec.ts | 12 +- ...project-graph-incremental-recomputation.ts | 2 +- packages/nx/src/devkit-internals.ts | 2 +- .../explicit-project-dependencies.spec.ts | 9 +- .../src/project-graph/build-project-graph.ts | 2 +- .../src/project-graph/plugins/get-plugins.ts | 185 ++++++++++- .../plugins/in-process-loader.ts | 85 +++++ .../nx/src/project-graph/plugins/index.ts | 3 +- .../src/project-graph/plugins/internal-api.ts | 291 ------------------ .../project-graph/plugins/isolation/index.ts | 4 +- .../plugins/isolation/messaging.ts | 2 +- .../plugins/isolation/plugin-pool.ts | 4 +- .../plugins/isolation/plugin-worker.ts | 5 +- .../plugins/load-resolved-plugin.ts | 2 +- .../project-graph/plugins/loaded-nx-plugin.ts | 116 +++++++ .../plugins/{loader.ts => resolve-plugin.ts} | 285 +++++------------ .../src/project-graph/plugins/transpiler.ts | 54 ++++ .../utils/project-configuration-utils.spec.ts | 2 +- .../utils/project-configuration-utils.ts | 2 +- .../utils/retrieve-workspace-files.ts | 2 +- .../src/utils/plugins/plugin-capabilities.ts | 4 +- .../add-e2e-ci-target-defaults.ts | 2 +- .../use-serve-static-preview-for-command.ts | 2 +- 24 files changed, 540 insertions(+), 539 deletions(-) create mode 100644 packages/nx/src/project-graph/plugins/in-process-loader.ts delete mode 100644 packages/nx/src/project-graph/plugins/internal-api.ts create mode 100644 packages/nx/src/project-graph/plugins/loaded-nx-plugin.ts rename packages/nx/src/project-graph/plugins/{loader.ts => resolve-plugin.ts} (59%) create mode 100644 packages/nx/src/project-graph/plugins/transpiler.ts diff --git a/packages/cypress/src/migrations/update-19-6-0/add-e2e-ci-target-defaults.ts b/packages/cypress/src/migrations/update-19-6-0/add-e2e-ci-target-defaults.ts index cba828d200b8b..b36e85a41a09f 100644 --- a/packages/cypress/src/migrations/update-19-6-0/add-e2e-ci-target-defaults.ts +++ b/packages/cypress/src/migrations/update-19-6-0/add-e2e-ci-target-defaults.ts @@ -7,7 +7,7 @@ import { parseTargetString, } from '@nx/devkit'; import { addE2eCiTargetDefaults as _addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils'; -import { LoadedNxPlugin } from 'nx/src/project-graph/plugins/internal-api'; +import { LoadedNxPlugin } from 'nx/src/project-graph/plugins/loaded-nx-plugin'; import type { ConfigurationResult } from 'nx/src/project-graph/utils/project-configuration-utils'; import { ProjectConfigurationsError, diff --git a/packages/nx/src/config/to-project-name.spec.ts b/packages/nx/src/config/to-project-name.spec.ts index b297b92a37217..2a2a14d8261b4 100644 --- a/packages/nx/src/config/to-project-name.spec.ts +++ b/packages/nx/src/config/to-project-name.spec.ts @@ -3,7 +3,10 @@ import { TempFs } from '../internal-testing-utils/temp-fs'; import { withEnvironmentVariables } from '../internal-testing-utils/with-environment'; import { retrieveProjectConfigurations } from '../project-graph/utils/retrieve-workspace-files'; import { readNxJson } from './configuration'; -import { loadNxPlugins } from '../project-graph/plugins/internal-api'; +import { + cleanupPlugins, + getPlugins, +} from '../project-graph/plugins/get-plugins'; describe('Workspaces', () => { let fs: TempFs; @@ -40,16 +43,13 @@ describe('Workspaces', () => { NX_WORKSPACE_ROOT_PATH: fs.tempDir, }, async () => { - const [plugins, cleanup] = await loadNxPlugins( - readNxJson(fs.tempDir).plugins, - fs.tempDir - ); + const plugins = await getPlugins(fs.tempDir); const res = await retrieveProjectConfigurations( plugins, fs.tempDir, readNxJson(fs.tempDir) ); - cleanup(); + cleanupPlugins(); return res; } ); 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 81f4830135ff7..1f3d1629a3d0a 100644 --- a/packages/nx/src/daemon/server/project-graph-incremental-recomputation.ts +++ b/packages/nx/src/daemon/server/project-graph-incremental-recomputation.ts @@ -30,7 +30,7 @@ import { notifyFileWatcherSockets } from './file-watching/file-watcher-sockets'; 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 type { LoadedNxPlugin } from '../../project-graph/plugins/loaded-nx-plugin'; import { DaemonProjectGraphError, ProjectConfigurationsError, diff --git a/packages/nx/src/devkit-internals.ts b/packages/nx/src/devkit-internals.ts index 606ab674f346a..ceda5e4fc9097 100644 --- a/packages/nx/src/devkit-internals.ts +++ b/packages/nx/src/devkit-internals.ts @@ -26,7 +26,7 @@ export { findProjectForPath, } from './project-graph/utils/find-project-for-path'; export { retrieveProjectConfigurations } from './project-graph/utils/retrieve-workspace-files'; -export { LoadedNxPlugin } from './project-graph/plugins/internal-api'; +export { LoadedNxPlugin } from './project-graph/plugins/loaded-nx-plugin'; export * from './project-graph/error-types'; export { registerTsProject } from './plugins/js/utils/register'; export { interpolate } from './tasks-runner/utils'; diff --git a/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-project-dependencies.spec.ts b/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-project-dependencies.spec.ts index b6ee52b0359b2..95417535c7b94 100644 --- a/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-project-dependencies.spec.ts +++ b/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-project-dependencies.spec.ts @@ -5,7 +5,6 @@ const tempFs = new TempFs('explicit-project-deps'); import { ProjectGraphProjectNode } from '../../../../config/project-graph'; import { ProjectConfiguration } from '../../../../config/workspace-json-project-json'; import { CreateDependenciesContext } from '../../../../project-graph/plugins'; -import { loadNxPlugins } from '../../../../project-graph/plugins/internal-api'; import { ProjectGraphBuilder } from '../../../../project-graph/project-graph-builder'; import { retrieveProjectConfigurations, @@ -14,6 +13,10 @@ import { import { setupWorkspaceContext } from '../../../../utils/workspace-context'; import { buildExplicitTypeScriptDependencies } from './explicit-project-dependencies'; import { TargetProjectLocator } from './target-project-locator'; +import { + cleanupPlugins, + getOnlyDefaultPlugins, +} from '../../../../project-graph/plugins/get-plugins'; // projectName => tsconfig import path const dependencyProjectNamesToImportPaths = { @@ -698,13 +701,13 @@ async function createContext( setupWorkspaceContext(tempFs.tempDir); - const [plugins, cleanup] = await loadNxPlugins([], tempFs.tempDir); + const plugins = await getOnlyDefaultPlugins(tempFs.tempDir); const { projects, projectRootMap } = await retrieveProjectConfigurations( plugins, tempFs.tempDir, nxJson ); - cleanup(); + cleanupPlugins(); const { fileMap } = await retrieveWorkspaceFiles( tempFs.tempDir, diff --git a/packages/nx/src/project-graph/build-project-graph.ts b/packages/nx/src/project-graph/build-project-graph.ts index b4f740a8f394c..fda80af7aa21a 100644 --- a/packages/nx/src/project-graph/build-project-graph.ts +++ b/packages/nx/src/project-graph/build-project-graph.ts @@ -12,7 +12,7 @@ import { } from './nx-deps-cache'; import { applyImplicitDependencies } from './utils/implicit-project-dependencies'; import { normalizeProjectNodes } from './utils/normalize-project-nodes'; -import { LoadedNxPlugin } from './plugins/internal-api'; +import type { LoadedNxPlugin } from './plugins/loaded-nx-plugin'; import { CreateDependenciesContext, CreateMetadataContext, diff --git a/packages/nx/src/project-graph/plugins/get-plugins.ts b/packages/nx/src/project-graph/plugins/get-plugins.ts index 20d38c66c3b3a..c03fe84f034e1 100644 --- a/packages/nx/src/project-graph/plugins/get-plugins.ts +++ b/packages/nx/src/project-graph/plugins/get-plugins.ts @@ -1,21 +1,33 @@ +import { join } from 'node:path'; + +import { shouldMergeAngularProjects } from '../../adapter/angular-json'; +import { PluginConfiguration, readNxJson } from '../../config/nx-json'; import { hashObject } from '../../hasher/file-hasher'; -import { readNxJson } from '../../config/nx-json'; -import { - loadDefaultNxPlugins, - LoadedNxPlugin, - loadSpecifiedNxPlugins, -} from './internal-api'; +import { IS_WASM } from '../../native'; import { workspaceRoot } from '../../utils/workspace-root'; +import { loadNxPluginInIsolation } from './isolation'; +import { loadNxPlugin } from './in-process-loader'; +import type { LoadedNxPlugin } from './loaded-nx-plugin'; +import { + cleanupPluginTSTranspiler, + pluginTranspilerIsRegistered, +} from './transpiler'; + +/** + * Stuff for specified NX Plugins. + */ let currentPluginsConfigurationHash: string; let loadedPlugins: LoadedNxPlugin[]; let pendingPluginsPromise: | Promise void]> | undefined; -let cleanup: () => void; +let cleanup: () => void | undefined; -export async function getPlugins(): Promise { - const pluginsConfiguration = readNxJson().plugins ?? []; +export async function getPlugins( + root = workspaceRoot +): Promise { + const pluginsConfiguration = readNxJson(root).plugins ?? []; const pluginsConfigurationHash = hashObject(pluginsConfiguration); // If the plugins configuration has not changed, reuse the current plugins @@ -32,28 +44,29 @@ export async function getPlugins(): Promise { cleanup(); } - pendingPluginsPromise ??= loadSpecifiedNxPlugins( - pluginsConfiguration, - workspaceRoot - ); + pendingPluginsPromise ??= loadSpecifiedNxPlugins(pluginsConfiguration, root); currentPluginsConfigurationHash = pluginsConfigurationHash; const [[result, cleanupFn], defaultPlugins] = await Promise.all([ pendingPluginsPromise, - getOnlyDefaultPlugins(), + getOnlyDefaultPlugins(root), ]); cleanup = cleanupFn; loadedPlugins = result.concat(defaultPlugins); return loadedPlugins; } +/** + * Stuff for default NX Plugins. + */ + let loadedDefaultPlugins: LoadedNxPlugin[]; let cleanupDefaultPlugins: () => void; let pendingDefaultPluginPromise: | Promise void]> | undefined; -export async function getOnlyDefaultPlugins() { +export async function getOnlyDefaultPlugins(root = workspaceRoot) { // If the plugins configuration has not changed, reuse the current plugins if (loadedDefaultPlugins) { return loadedPlugins; @@ -76,6 +89,144 @@ export async function getOnlyDefaultPlugins() { export function cleanupPlugins() { pendingPluginsPromise = undefined; pendingDefaultPluginPromise = undefined; - cleanup(); - cleanupDefaultPlugins(); + cleanup?.(); + cleanupDefaultPlugins?.(); +} + +/** + * Stuff for generic loading + */ + +function isIsolationEnabled() { + // Explicitly enabled, regardless of further conditions + if (process.env.NX_ISOLATE_PLUGINS === 'true') { + return true; + } + if ( + // Explicitly disabled + process.env.NX_ISOLATE_PLUGINS === 'false' || + // Isolation is disabled on WASM builds currently. + IS_WASM + ) { + return false; + } + // Default value + return true; +} + +const loadingMethod = isIsolationEnabled() + ? loadNxPluginInIsolation + : loadNxPlugin; + +async function loadDefaultNxPlugins(root = workspaceRoot) { + performance.mark('loadDefaultNxPlugins:start'); + + const plugins = getDefaultPlugins(root); + + const cleanupFunctions: Array<() => void> = []; + const ret = [ + await Promise.all( + plugins.map(async (plugin) => { + performance.mark(`Load Nx Plugin: ${plugin} - start`); + + const [loadedPluginPromise, cleanup] = await loadingMethod( + plugin, + root + ); + + cleanupFunctions.push(cleanup); + const res = await loadedPluginPromise; + performance.mark(`Load Nx Plugin: ${plugin} - end`); + performance.measure( + `Load Nx Plugin: ${plugin}`, + `Load Nx Plugin: ${plugin} - start`, + `Load Nx Plugin: ${plugin} - end` + ); + + return res; + }) + ), + () => { + for (const fn of cleanupFunctions) { + fn(); + } + if (pluginTranspilerIsRegistered()) { + cleanupPluginTSTranspiler(); + } + }, + ] as const; + performance.mark('loadDefaultNxPlugins:end'); + performance.measure( + 'loadDefaultNxPlugins', + 'loadDefaultNxPlugins:start', + 'loadDefaultNxPlugins:end' + ); + return ret; +} + +async function loadSpecifiedNxPlugins( + plugins: PluginConfiguration[], + root = workspaceRoot +): Promise void]> { + performance.mark('loadSpecifiedNxPlugins:start'); + + plugins = await normalizePlugins(plugins, root); + + const cleanupFunctions: Array<() => void> = []; + const ret = [ + 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(); + } + if (pluginTranspilerIsRegistered()) { + cleanupPluginTSTranspiler(); + } + }, + ] as const; + performance.mark('loadSpecifiedNxPlugins:end'); + performance.measure( + 'loadSpecifiedNxPlugins', + 'loadSpecifiedNxPlugins:start', + 'loadSpecifiedNxPlugins:end' + ); + return ret; +} + +async function normalizePlugins(plugins: PluginConfiguration[], root: string) { + plugins ??= []; + + return [...plugins]; +} + +function getDefaultPlugins(root: string) { + return [ + join(__dirname, '../../plugins/js'), + ...(shouldMergeAngularProjects(root, false) + ? [join(__dirname, '../../adapter/angular-json')] + : []), + join(__dirname, '../../plugins/package-json'), + join(__dirname, '../../plugins/project-json/build-nodes/project-json'), + ]; } diff --git a/packages/nx/src/project-graph/plugins/in-process-loader.ts b/packages/nx/src/project-graph/plugins/in-process-loader.ts new file mode 100644 index 0000000000000..341525cd3e912 --- /dev/null +++ b/packages/nx/src/project-graph/plugins/in-process-loader.ts @@ -0,0 +1,85 @@ +// This file contains methods and utilities that should **only** be used by the plugin worker. + +import { ProjectConfiguration } from '../../config/workspace-json-project-json'; + +import { getNxRequirePaths } from '../../utils/installation-directory'; +import { + PackageJson, + readModulePackageJsonWithoutFallbacks, +} from '../../utils/package-json'; +import { readJsonFile } from '../../utils/fileutils'; + +import type { PluginConfiguration } from '../../config/nx-json'; +import type { LoadedNxPlugin } from './loaded-nx-plugin'; +import { LoadPluginError } from '../error-types'; +import path = require('node:path/posix'); +import { loadResolvedNxPluginAsync } from './load-resolved-plugin'; +import { resolveLocalNxPlugin, resolveNxPlugin } from './resolve-plugin'; +import { + pluginTranspilerIsRegistered, + registerPluginTSTranspiler, +} from './transpiler'; + +export function readPluginPackageJson( + pluginName: string, + projects: Record, + paths = getNxRequirePaths() +): { + path: string; + json: PackageJson; +} { + try { + const result = readModulePackageJsonWithoutFallbacks(pluginName, paths); + return { + json: result.packageJson, + path: result.path, + }; + } catch (e) { + if (e.code === 'MODULE_NOT_FOUND') { + const localPluginPath = resolveLocalNxPlugin(pluginName, projects); + if (localPluginPath) { + const localPluginPackageJson = path.join( + localPluginPath.path, + 'package.json' + ); + if (pluginTranspilerIsRegistered()) { + registerPluginTSTranspiler(); + } + return { + path: localPluginPackageJson, + json: readJsonFile(localPluginPackageJson), + }; + } + } + throw e; + } +} + +export function loadNxPlugin(plugin: PluginConfiguration, root: string) { + return [ + loadNxPluginAsync(plugin, getNxRequirePaths(root), root), + () => {}, + ] as const; +} + +export async function loadNxPluginAsync( + pluginConfiguration: PluginConfiguration, + paths: string[], + root: string +): Promise { + const moduleName = + typeof pluginConfiguration === 'string' + ? pluginConfiguration + : pluginConfiguration.plugin; + try { + const { pluginPath, name, shouldRegisterTSTranspiler } = + await resolveNxPlugin(moduleName, root, paths); + + if (shouldRegisterTSTranspiler) { + registerPluginTSTranspiler(); + } + return loadResolvedNxPluginAsync(pluginConfiguration, pluginPath, name); + } catch (e) { + throw new LoadPluginError(moduleName, e); + } +} diff --git a/packages/nx/src/project-graph/plugins/index.ts b/packages/nx/src/project-graph/plugins/index.ts index 3bd38dd1dc22b..270855785849d 100644 --- a/packages/nx/src/project-graph/plugins/index.ts +++ b/packages/nx/src/project-graph/plugins/index.ts @@ -2,5 +2,6 @@ export * from './public-api'; // export * from './get-plugins'; -export { readPluginPackageJson, registerPluginTSTranspiler } from './loader'; +export { readPluginPackageJson } from './in-process-loader'; +export { registerPluginTSTranspiler } from './transpiler'; 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 deleted file mode 100644 index b3e9eb0baba60..0000000000000 --- a/packages/nx/src/project-graph/plugins/internal-api.ts +++ /dev/null @@ -1,291 +0,0 @@ -// This file contains the bits and bobs of the internal API for loading and interacting with Nx plugins. -// For the public API, used by plugin authors, see `./public-api.ts`. - -import { join } from 'path'; - -import { workspaceRoot } from '../../utils/workspace-root'; -import { PluginConfiguration } from '../../config/nx-json'; -import { shouldMergeAngularProjects } from '../../adapter/angular-json'; - -import { - CreateDependencies, - CreateDependenciesContext, - CreateMetadata, - CreateMetadataContext, - CreateNodesContextV2, - CreateNodesResult, - NxPluginV2, - ProjectsMetadata, -} from './public-api'; -import { ProjectGraph } from '../../config/project-graph'; -import { loadNxPluginInIsolation } from './isolation'; -import { loadNxPlugin, unregisterPluginTSTranspiler } from './loader'; -import { createNodesFromFiles } from './utils'; -import { - AggregateCreateNodesError, - isAggregateCreateNodesError, -} from '../error-types'; -import { IS_WASM } from '../../native'; -import { RawProjectGraphDependency } from '../project-graph-builder'; - -export class LoadedNxPlugin { - readonly name: string; - readonly createNodes?: [ - filePattern: string, - // The create nodes function takes all matched files instead of just one, and includes - // the result's context. - fn: ( - matchedFiles: string[], - context: CreateNodesContextV2 - ) => Promise< - Array - > - ]; - readonly createDependencies?: ( - context: CreateDependenciesContext - ) => Promise; - readonly createMetadata?: ( - graph: ProjectGraph, - context: CreateMetadataContext - ) => Promise; - - readonly options?: unknown; - readonly include?: string[]; - readonly exclude?: string[]; - - constructor(plugin: NxPluginV2, pluginDefinition: PluginConfiguration) { - this.name = plugin.name; - if (typeof pluginDefinition !== 'string') { - this.options = pluginDefinition.options; - this.include = pluginDefinition.include; - this.exclude = pluginDefinition.exclude; - } - - if (plugin.createNodes && !plugin.createNodesV2) { - this.createNodes = [ - plugin.createNodes[0], - (configFiles, context) => - createNodesFromFiles( - plugin.createNodes[1], - configFiles, - this.options, - context - ).then((results) => results.map((r) => [this.name, r[0], r[1]])), - ]; - } - - if (plugin.createNodesV2) { - this.createNodes = [ - plugin.createNodesV2[0], - async (configFiles, context) => { - const result = await plugin.createNodesV2[1]( - configFiles, - this.options, - context - ); - return result.map((r) => [this.name, r[0], r[1]]); - }, - ]; - } - - if (this.createNodes) { - const inner = this.createNodes[1]; - this.createNodes[1] = async (...args) => { - performance.mark(`${plugin.name}:createNodes - start`); - try { - return await inner(...args); - } catch (e) { - if (isAggregateCreateNodesError(e)) { - throw e; - } - // The underlying plugin errored out. We can't know any partial results. - throw new AggregateCreateNodesError([[null, e]], []); - } finally { - performance.mark(`${plugin.name}:createNodes - end`); - performance.measure( - `${plugin.name}:createNodes`, - `${plugin.name}:createNodes - start`, - `${plugin.name}:createNodes - end` - ); - } - }; - } - - if (plugin.createDependencies) { - this.createDependencies = async (context) => - plugin.createDependencies(this.options, context); - } - - if (plugin.createMetadata) { - this.createMetadata = async (graph, context) => - plugin.createMetadata(graph, this.options, context); - } - } -} - -export type CreateNodesResultWithContext = CreateNodesResult & { - file: string; - pluginName: string; -}; - -function isIsolationEnabled() { - // Explicitly enabled, regardless of further conditions - if (process.env.NX_ISOLATE_PLUGINS === 'true') { - return true; - } - if ( - // Explicitly disabled - process.env.NX_ISOLATE_PLUGINS === 'false' || - // Isolation is disabled on WASM builds currently. - IS_WASM - ) { - return false; - } - // Default value - return true; -} - -const loadingMethod = isIsolationEnabled() - ? loadNxPluginInIsolation - : loadNxPlugin; - -/** - * @deprecated prefer getOnlyDefaultPlugins - */ -export async function loadDefaultNxPlugins(root = workspaceRoot) { - performance.mark('loadDefaultNxPlugins:start'); - - const plugins = await getDefaultPlugins(root); - - const cleanupFunctions: Array<() => void> = []; - const ret = [ - await Promise.all( - plugins.map(async (plugin) => { - performance.mark(`Load Nx Plugin: ${plugin} - start`); - - const [loadedPluginPromise, cleanup] = await loadingMethod( - plugin, - root - ); - - cleanupFunctions.push(cleanup); - const res = await loadedPluginPromise; - performance.mark(`Load Nx Plugin: ${plugin} - end`); - performance.measure( - `Load Nx Plugin: ${plugin}`, - `Load Nx Plugin: ${plugin} - start`, - `Load Nx Plugin: ${plugin} - end` - ); - - return res; - }) - ), - () => { - for (const fn of cleanupFunctions) { - fn(); - } - if (unregisterPluginTSTranspiler) { - unregisterPluginTSTranspiler(); - } - }, - ] as const; - performance.mark('loadDefaultNxPlugins:end'); - performance.measure( - 'loadDefaultNxPlugins', - 'loadDefaultNxPlugins:start', - 'loadDefaultNxPlugins:end' - ); - return ret; -} - -/** - * @deprecated prefer getPlugins - */ -export async function loadSpecifiedNxPlugins( - plugins: PluginConfiguration[], - root = workspaceRoot -): Promise void]> { - performance.mark('loadSpecifiedNxPlugins:start'); - const loadingMethod = isIsolationEnabled() - ? loadNxPluginInIsolation - : loadNxPlugin; - - plugins = await normalizePlugins(plugins, root); - - const cleanupFunctions: Array<() => void> = []; - const ret = [ - 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(); - } - if (unregisterPluginTSTranspiler) { - unregisterPluginTSTranspiler(); - } - }, - ] as const; - performance.mark('loadSpecifiedNxPlugins:end'); - performance.measure( - 'loadSpecifiedNxPlugins', - 'loadSpecifiedNxPlugins:start', - 'loadSpecifiedNxPlugins:end' - ); - return ret; -} - -/** - * Use `getPlugins` instead. - * @deprecated Do not use this. Use `getPlugins` instead. - */ -export async function loadNxPlugins( - plugins: PluginConfiguration[], - root = workspaceRoot -): Promise void]> { - const defaultPlugins = await loadDefaultNxPlugins(root); - const specifiedPlugins = await loadSpecifiedNxPlugins(plugins, root); - - const combinedCleanup = () => { - specifiedPlugins[1](); - defaultPlugins[1](); - }; - - return [specifiedPlugins[0].concat(defaultPlugins[0]), combinedCleanup]; -} - -async function normalizePlugins(plugins: PluginConfiguration[], root: string) { - plugins ??= []; - - return [...plugins]; -} - -export async function getDefaultPlugins(root: string) { - return [ - join(__dirname, '../../plugins/js'), - ...(shouldMergeAngularProjects(root, false) - ? [join(__dirname, '../../adapter/angular-json')] - : []), - join(__dirname, '../../plugins/package-json'), - join(__dirname, '../../plugins/project-json/build-nodes/project-json'), - ]; -} diff --git a/packages/nx/src/project-graph/plugins/isolation/index.ts b/packages/nx/src/project-graph/plugins/isolation/index.ts index 1f77f6be5e7ee..8d757058231a0 100644 --- a/packages/nx/src/project-graph/plugins/isolation/index.ts +++ b/packages/nx/src/project-graph/plugins/isolation/index.ts @@ -1,6 +1,6 @@ import { workspaceRoot } from '../../../utils/workspace-root'; -import { PluginConfiguration } from '../../../config/nx-json'; -import { LoadedNxPlugin } from '../internal-api'; +import type { PluginConfiguration } from '../../../config/nx-json'; +import type { LoadedNxPlugin } from '../loaded-nx-plugin'; import { loadRemoteNxPlugin } from './plugin-pool'; export async function loadNxPluginInIsolation( diff --git a/packages/nx/src/project-graph/plugins/isolation/messaging.ts b/packages/nx/src/project-graph/plugins/isolation/messaging.ts index e0d55f5c8506f..20c3cd18d109b 100644 --- a/packages/nx/src/project-graph/plugins/isolation/messaging.ts +++ b/packages/nx/src/project-graph/plugins/isolation/messaging.ts @@ -5,7 +5,7 @@ import { CreateMetadataContext, CreateNodesContextV2, } from '../public-api'; -import { LoadedNxPlugin } from '../internal-api'; +import type { LoadedNxPlugin } from '../loaded-nx-plugin'; import { Serializable } from 'child_process'; import { Socket } from 'net'; diff --git a/packages/nx/src/project-graph/plugins/isolation/plugin-pool.ts b/packages/nx/src/project-graph/plugins/isolation/plugin-pool.ts index bb912362dd45e..9d7bc29280a2d 100644 --- a/packages/nx/src/project-graph/plugins/isolation/plugin-pool.ts +++ b/packages/nx/src/project-graph/plugins/isolation/plugin-pool.ts @@ -7,7 +7,7 @@ import { PluginConfiguration } from '../../../config/nx-json'; // TODO (@AgentEnder): After scoped verbose logging is implemented, re-add verbose logs here. // import { logger } from '../../utils/logger'; -import { LoadedNxPlugin } from '../internal-api'; +import type { LoadedNxPlugin } from '../loaded-nx-plugin'; import { getPluginOsSocketPath } from '../../../daemon/socket-utils'; import { consumeMessagesFromSocket } from '../../../utils/consume-messages-from-socket'; @@ -17,7 +17,7 @@ import { sendMessageOverSocket, } from './messaging'; import { getNxRequirePaths } from '../../../utils/installation-directory'; -import { resolveNxPlugin } from '../loader'; +import { resolveNxPlugin } from '../resolve-plugin'; const cleanupFunctions = new Set<() => void>(); 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 349ab00347916..52a44f51d60ea 100644 --- a/packages/nx/src/project-graph/plugins/isolation/plugin-worker.ts +++ b/packages/nx/src/project-graph/plugins/isolation/plugin-worker.ts @@ -4,7 +4,6 @@ import { consumeMessagesFromSocket } from '../../../utils/consume-messages-from- import { createServer } from 'net'; import { unlinkSync } from 'fs'; -import { registerPluginTSTranspiler } from '../loader'; if (process.env.NX_PERF_LOGGING === 'true') { require('../../../utils/perf-logging'); @@ -53,7 +52,9 @@ const server = createServer((socket) => { // Register the ts-transpiler if we are pointing to a // plain ts file that's not part of a plugin project if (shouldRegisterTSTranspiler) { - registerPluginTSTranspiler(); + ( + require('../transpiler') as typeof import('../transpiler') + ).registerPluginTSTranspiler(); } plugin = await loadResolvedNxPluginAsync( pluginConfiguration, diff --git a/packages/nx/src/project-graph/plugins/load-resolved-plugin.ts b/packages/nx/src/project-graph/plugins/load-resolved-plugin.ts index a77337581a26f..bfae38b7bcc81 100644 --- a/packages/nx/src/project-graph/plugins/load-resolved-plugin.ts +++ b/packages/nx/src/project-graph/plugins/load-resolved-plugin.ts @@ -1,5 +1,5 @@ import type { PluginConfiguration } from '../../config/nx-json'; -import { LoadedNxPlugin } from './internal-api'; +import { LoadedNxPlugin } from './loaded-nx-plugin'; import { NxPlugin } from './public-api'; export async function loadResolvedNxPluginAsync( diff --git a/packages/nx/src/project-graph/plugins/loaded-nx-plugin.ts b/packages/nx/src/project-graph/plugins/loaded-nx-plugin.ts new file mode 100644 index 0000000000000..d35ca9c456bd1 --- /dev/null +++ b/packages/nx/src/project-graph/plugins/loaded-nx-plugin.ts @@ -0,0 +1,116 @@ +import type { ProjectGraph } from '../../config/project-graph'; +import type { PluginConfiguration } from '../../config/nx-json'; +import { + AggregateCreateNodesError, + isAggregateCreateNodesError, +} from '../error-types'; +import type { RawProjectGraphDependency } from '../project-graph-builder'; +import type { + CreateDependenciesContext, + CreateMetadataContext, + CreateNodesContextV2, + CreateNodesResult, + NxPluginV2, + ProjectsMetadata, +} from './public-api'; +import { createNodesFromFiles } from './utils'; + +export class LoadedNxPlugin { + readonly name: string; + readonly createNodes?: [ + filePattern: string, + // The create nodes function takes all matched files instead of just one, and includes + // the result's context. + fn: ( + matchedFiles: string[], + context: CreateNodesContextV2 + ) => Promise< + Array + > + ]; + readonly createDependencies?: ( + context: CreateDependenciesContext + ) => Promise; + readonly createMetadata?: ( + graph: ProjectGraph, + context: CreateMetadataContext + ) => Promise; + + readonly options?: unknown; + readonly include?: string[]; + readonly exclude?: string[]; + + constructor(plugin: NxPluginV2, pluginDefinition: PluginConfiguration) { + this.name = plugin.name; + if (typeof pluginDefinition !== 'string') { + this.options = pluginDefinition.options; + this.include = pluginDefinition.include; + this.exclude = pluginDefinition.exclude; + } + + if (plugin.createNodes && !plugin.createNodesV2) { + this.createNodes = [ + plugin.createNodes[0], + (configFiles, context) => + createNodesFromFiles( + plugin.createNodes[1], + configFiles, + this.options, + context + ).then((results) => results.map((r) => [this.name, r[0], r[1]])), + ]; + } + + if (plugin.createNodesV2) { + this.createNodes = [ + plugin.createNodesV2[0], + async (configFiles, context) => { + const result = await plugin.createNodesV2[1]( + configFiles, + this.options, + context + ); + return result.map((r) => [this.name, r[0], r[1]]); + }, + ]; + } + + if (this.createNodes) { + const inner = this.createNodes[1]; + this.createNodes[1] = async (...args) => { + performance.mark(`${plugin.name}:createNodes - start`); + try { + return await inner(...args); + } catch (e) { + if (isAggregateCreateNodesError(e)) { + throw e; + } + // The underlying plugin errored out. We can't know any partial results. + throw new AggregateCreateNodesError([[null, e]], []); + } finally { + performance.mark(`${plugin.name}:createNodes - end`); + performance.measure( + `${plugin.name}:createNodes`, + `${plugin.name}:createNodes - start`, + `${plugin.name}:createNodes - end` + ); + } + }; + } + + if (plugin.createDependencies) { + this.createDependencies = async (context) => + plugin.createDependencies(this.options, context); + } + + if (plugin.createMetadata) { + this.createMetadata = async (graph, context) => + plugin.createMetadata(graph, this.options, context); + } + } +} + +export type CreateNodesResultWithContext = CreateNodesResult & { + file: string; + pluginName: string; +}; diff --git a/packages/nx/src/project-graph/plugins/loader.ts b/packages/nx/src/project-graph/plugins/resolve-plugin.ts similarity index 59% rename from packages/nx/src/project-graph/plugins/loader.ts rename to packages/nx/src/project-graph/plugins/resolve-plugin.ts index 8ca7153d81003..81d9e3d96b389 100644 --- a/packages/nx/src/project-graph/plugins/loader.ts +++ b/packages/nx/src/project-graph/plugins/resolve-plugin.ts @@ -1,71 +1,59 @@ -// This file contains methods and utilities that should **only** be used by the plugin worker. - -import { ProjectConfiguration } from '../../config/workspace-json-project-json'; +import * as path from 'node:path'; +import { existsSync } from 'node:fs'; -import { join } from 'node:path/posix'; -import { getNxRequirePaths } from '../../utils/installation-directory'; -import { - PackageJson, - readModulePackageJsonWithoutFallbacks, -} from '../../utils/package-json'; +import { getPackageEntryPointsToProjectMap } from '../../plugins/js/utils/packages'; import { readJsonFile } from '../../utils/fileutils'; +import { logger } from '../../utils/logger'; +import { normalizePath } from '../../utils/path'; import { workspaceRoot } from '../../utils/workspace-root'; -import { existsSync } from 'node:fs'; -import { - registerTranspiler, - registerTsConfigPaths, -} from '../../plugins/js/utils/register'; import { - ProjectRootMappings, findProjectForPath, + ProjectRootMappings, } from '../utils/find-project-for-path'; -import { normalizePath } from '../../utils/path'; -import { logger } from '../../utils/logger'; - -import type * as ts from 'typescript'; -import { extname } from 'node:path'; -import type { PluginConfiguration } from '../../config/nx-json'; import { retrieveProjectConfigurationsWithoutPluginInference } from '../utils/retrieve-workspace-files'; -import { LoadedNxPlugin } from './internal-api'; -import { LoadPluginError } from '../error-types'; -import path = require('node:path/posix'); -import { readTsConfig } from '../../plugins/js/utils/typescript'; -import { loadResolvedNxPluginAsync } from './load-resolved-plugin'; -import { getPackageEntryPointsToProjectMap } from '../../plugins/js/utils/packages'; -export function readPluginPackageJson( - pluginName: string, - projects: Record, - paths = getNxRequirePaths() -): { - path: string; - json: PackageJson; -} { +import type { ProjectConfiguration } from '../../config/workspace-json-project-json'; + +let projectsWithoutInference: Record; + +export async function resolveNxPlugin( + moduleName: string, + root: string, + paths: string[] +) { try { - const result = readModulePackageJsonWithoutFallbacks(pluginName, paths); - return { - json: result.packageJson, - path: result.path, - }; - } catch (e) { - if (e.code === 'MODULE_NOT_FOUND') { - const localPluginPath = resolveLocalNxPlugin(pluginName, projects); - if (localPluginPath) { - const localPluginPackageJson = path.join( - localPluginPath.path, - 'package.json' - ); - if (!unregisterPluginTSTranspiler) { - registerPluginTSTranspiler(); - } - return { - path: localPluginPackageJson, - json: readJsonFile(localPluginPackageJson), - }; - } - } - throw e; + require.resolve(moduleName); + } catch { + // If a plugin cannot be resolved, we will need projects to resolve it + projectsWithoutInference ??= + await retrieveProjectConfigurationsWithoutPluginInference(root); } + const { pluginPath, name, shouldRegisterTSTranspiler } = getPluginPathAndName( + moduleName, + paths, + projectsWithoutInference, + root + ); + return { pluginPath, name, shouldRegisterTSTranspiler }; +} + +function readPluginMainFromProjectConfiguration( + plugin: ProjectConfiguration +): string | null { + const { main } = + Object.values(plugin.targets).find((x) => + [ + '@nx/js:tsc', + '@nrwl/js:tsc', + '@nx/js:swc', + '@nrwl/js:swc', + '@nx/node:package', + '@nrwl/node:package', + ].includes(x.executor) + )?.options || + plugin.targets?.build?.options || + {}; + return main; } export function resolveLocalNxPlugin( @@ -76,40 +64,45 @@ export function resolveLocalNxPlugin( return lookupLocalPlugin(importPath, projects, root); } -export let unregisterPluginTSTranspiler: (() => void) | null = null; - -/** - * Register swc-node or ts-node if they are not currently registered - * with some default settings which work well for Nx plugins. - */ -export function registerPluginTSTranspiler() { - // Get the first tsconfig that matches the allowed set - const tsConfigName = [ - join(workspaceRoot, 'tsconfig.base.json'), - join(workspaceRoot, 'tsconfig.json'), - ].find((x) => existsSync(x)); - - if (!tsConfigName) { - return; +export function getPluginPathAndName( + moduleName: string, + paths: string[], + projects: Record, + root: string +) { + let pluginPath: string; + let shouldRegisterTSTranspiler = false; + try { + pluginPath = require.resolve(moduleName, { + paths, + }); + const extension = path.extname(pluginPath); + shouldRegisterTSTranspiler = extension === '.ts'; + } catch (e) { + if (e.code === 'MODULE_NOT_FOUND') { + const plugin = resolveLocalNxPlugin(moduleName, projects, root); + if (plugin) { + shouldRegisterTSTranspiler = true; + const main = readPluginMainFromProjectConfiguration( + plugin.projectConfig + ); + pluginPath = main ? path.join(root, main) : plugin.path; + } else { + logger.error(`Plugin listed in \`nx.json\` not found: ${moduleName}`); + throw e; + } + } else { + throw e; + } } + const packageJsonPath = path.join(pluginPath, 'package.json'); - const tsConfig: Partial = tsConfigName - ? readTsConfig(tsConfigName) - : {}; - const cleanupFns = [ - registerTsConfigPaths(tsConfigName), - registerTranspiler( - { - experimentalDecorators: true, - emitDecoratorMetadata: true, - ...tsConfig.options, - }, - tsConfig.raw - ), - ]; - unregisterPluginTSTranspiler = () => { - cleanupFns.forEach((fn) => fn?.()); - }; + const { name } = + !['.ts', '.js'].some((x) => path.extname(moduleName) === x) && // Not trying to point to a ts or js file + existsSync(packageJsonPath) // plugin has a package.json + ? readJsonFile(packageJsonPath) // read name from package.json + : { name: moduleName }; + return { pluginPath, name, shouldRegisterTSTranspiler }; } function lookupLocalPlugin( @@ -184,115 +177,3 @@ function readTsConfigPaths(root: string = workspaceRoot) { } return tsconfigPaths ?? {}; } - -function readPluginMainFromProjectConfiguration( - plugin: ProjectConfiguration -): string | null { - const { main } = - Object.values(plugin.targets).find((x) => - [ - '@nx/js:tsc', - '@nrwl/js:tsc', - '@nx/js:swc', - '@nrwl/js:swc', - '@nx/node:package', - '@nrwl/node:package', - ].includes(x.executor) - )?.options || - plugin.targets?.build?.options || - {}; - return main; -} - -export function getPluginPathAndName( - moduleName: string, - paths: string[], - projects: Record, - root: string -) { - let pluginPath: string; - let shouldRegisterTSTranspiler = false; - try { - pluginPath = require.resolve(moduleName, { - paths, - }); - const extension = path.extname(pluginPath); - shouldRegisterTSTranspiler = extension === '.ts'; - } catch (e) { - if (e.code === 'MODULE_NOT_FOUND') { - const plugin = resolveLocalNxPlugin(moduleName, projects, root); - if (plugin) { - shouldRegisterTSTranspiler = true; - const main = readPluginMainFromProjectConfiguration( - plugin.projectConfig - ); - pluginPath = main ? path.join(root, main) : plugin.path; - } else { - logger.error(`Plugin listed in \`nx.json\` not found: ${moduleName}`); - throw e; - } - } else { - throw e; - } - } - const packageJsonPath = path.join(pluginPath, 'package.json'); - - const { name } = - !['.ts', '.js'].some((x) => extname(moduleName) === x) && // Not trying to point to a ts or js file - existsSync(packageJsonPath) // plugin has a package.json - ? readJsonFile(packageJsonPath) // read name from package.json - : { name: moduleName }; - return { pluginPath, name, shouldRegisterTSTranspiler }; -} - -let projectsWithoutInference: Record; - -export function loadNxPlugin(plugin: PluginConfiguration, root: string) { - return [ - loadNxPluginAsync(plugin, getNxRequirePaths(root), root), - () => {}, - ] as const; -} - -export async function resolveNxPlugin( - moduleName: string, - root: string, - paths: string[] -) { - try { - require.resolve(moduleName); - } catch { - // If a plugin cannot be resolved, we will need projects to resolve it - projectsWithoutInference ??= - await retrieveProjectConfigurationsWithoutPluginInference(root); - } - const { pluginPath, name, shouldRegisterTSTranspiler } = getPluginPathAndName( - moduleName, - paths, - projectsWithoutInference, - root - ); - return { pluginPath, name, shouldRegisterTSTranspiler }; -} - -export async function loadNxPluginAsync( - pluginConfiguration: PluginConfiguration, - paths: string[], - root: string -): Promise { - const moduleName = - typeof pluginConfiguration === 'string' - ? pluginConfiguration - : pluginConfiguration.plugin; - try { - const { pluginPath, name, shouldRegisterTSTranspiler } = - await resolveNxPlugin(moduleName, root, paths); - - if (shouldRegisterTSTranspiler) { - registerPluginTSTranspiler(); - } - return loadResolvedNxPluginAsync(pluginConfiguration, pluginPath, name); - } catch (e) { - throw new LoadPluginError(moduleName, e); - } -} diff --git a/packages/nx/src/project-graph/plugins/transpiler.ts b/packages/nx/src/project-graph/plugins/transpiler.ts new file mode 100644 index 0000000000000..c98c382108c51 --- /dev/null +++ b/packages/nx/src/project-graph/plugins/transpiler.ts @@ -0,0 +1,54 @@ +import { existsSync } from 'node:fs'; +import { join } from 'node:path/posix'; +import { workspaceRoot } from '../../utils/workspace-root'; +import { + registerTranspiler, + registerTsConfigPaths, +} from '../../plugins/js/utils/register'; +import { readTsConfig } from '../../plugins/js/utils/typescript'; +import type * as ts from 'typescript'; + +export let unregisterPluginTSTranspiler: (() => void) | null = null; + +/** + * Register swc-node or ts-node if they are not currently registered + * with some default settings which work well for Nx plugins. + */ +export function registerPluginTSTranspiler() { + // Get the first tsconfig that matches the allowed set + const tsConfigName = [ + join(workspaceRoot, 'tsconfig.base.json'), + join(workspaceRoot, 'tsconfig.json'), + ].find((x) => existsSync(x)); + + if (!tsConfigName) { + return; + } + + const tsConfig: Partial = tsConfigName + ? readTsConfig(tsConfigName) + : {}; + const cleanupFns = [ + registerTsConfigPaths(tsConfigName), + registerTranspiler( + { + experimentalDecorators: true, + emitDecoratorMetadata: true, + ...tsConfig.options, + }, + tsConfig.raw + ), + ]; + unregisterPluginTSTranspiler = () => { + cleanupFns.forEach((fn) => fn?.()); + }; +} + +export function pluginTranspilerIsRegistered() { + return unregisterPluginTSTranspiler !== null; +} + +export function cleanupPluginTSTranspiler() { + unregisterPluginTSTranspiler?.(); + unregisterPluginTSTranspiler = null; +} diff --git a/packages/nx/src/project-graph/utils/project-configuration-utils.spec.ts b/packages/nx/src/project-graph/utils/project-configuration-utils.spec.ts index d3ca5ecabca8b..8983e5197a89c 100644 --- a/packages/nx/src/project-graph/utils/project-configuration-utils.spec.ts +++ b/packages/nx/src/project-graph/utils/project-configuration-utils.spec.ts @@ -15,7 +15,7 @@ import { readTargetDefaultsForTarget, } from './project-configuration-utils'; import { NxPluginV2 } from '../plugins'; -import { LoadedNxPlugin } from '../plugins/internal-api'; +import { LoadedNxPlugin } from '../plugins/loaded-nx-plugin'; import { dirname } from 'path'; import { isProjectConfigurationsError } from '../error-types'; 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 320773a1713b2..b83315d691ca4 100644 --- a/packages/nx/src/project-graph/utils/project-configuration-utils.ts +++ b/packages/nx/src/project-graph/utils/project-configuration-utils.ts @@ -14,7 +14,7 @@ import { minimatch } from 'minimatch'; import { join } from 'path'; import { performance } from 'perf_hooks'; -import { LoadedNxPlugin } from '../plugins/internal-api'; +import { LoadedNxPlugin } from '../plugins/loaded-nx-plugin'; import { MergeNodesError, ProjectConfigurationsError, 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 31f76b59ac7c9..d9960ec71c2b5 100644 --- a/packages/nx/src/project-graph/utils/retrieve-workspace-files.ts +++ b/packages/nx/src/project-graph/utils/retrieve-workspace-files.ts @@ -9,7 +9,7 @@ import { ConfigurationResult, createProjectConfigurations, } from './project-configuration-utils'; -import { LoadedNxPlugin } from '../plugins/internal-api'; +import { LoadedNxPlugin } from '../plugins/loaded-nx-plugin'; import { getNxWorkspaceFilesFromContext, globWithWorkspaceContext, diff --git a/packages/nx/src/utils/plugins/plugin-capabilities.ts b/packages/nx/src/utils/plugins/plugin-capabilities.ts index 893e26d828464..a870d1b77d0ab 100644 --- a/packages/nx/src/utils/plugins/plugin-capabilities.ts +++ b/packages/nx/src/utils/plugins/plugin-capabilities.ts @@ -7,9 +7,9 @@ import { ProjectConfiguration } from '../../config/workspace-json-project-json'; import { readJsonFile } from '../fileutils'; import { getNxRequirePaths } from '../installation-directory'; import { readPluginPackageJson } from '../../project-graph/plugins'; -import { loadNxPlugin } from '../../project-graph/plugins/loader'; +import { loadNxPlugin } from '../../project-graph/plugins/in-process-loader'; import { PackageJson } from '../package-json'; -import { LoadedNxPlugin } from '../../project-graph/plugins/internal-api'; +import { LoadedNxPlugin } from '../../project-graph/plugins/loaded-nx-plugin'; export interface PluginCapabilities { name: string; diff --git a/packages/playwright/src/migrations/update-19-6-0/add-e2e-ci-target-defaults.ts b/packages/playwright/src/migrations/update-19-6-0/add-e2e-ci-target-defaults.ts index a66d50491f62f..05f507353b25f 100644 --- a/packages/playwright/src/migrations/update-19-6-0/add-e2e-ci-target-defaults.ts +++ b/packages/playwright/src/migrations/update-19-6-0/add-e2e-ci-target-defaults.ts @@ -6,7 +6,7 @@ import { parseTargetString, } from '@nx/devkit'; import { addE2eCiTargetDefaults as _addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils'; -import { LoadedNxPlugin } from 'nx/src/project-graph/plugins/internal-api'; +import { LoadedNxPlugin } from 'nx/src/project-graph/plugins/loaded-nx-plugin'; import type { ConfigurationResult } from 'nx/src/project-graph/utils/project-configuration-utils'; import { ProjectConfigurationsError, diff --git a/packages/playwright/src/migrations/update-19-6-0/use-serve-static-preview-for-command.ts b/packages/playwright/src/migrations/update-19-6-0/use-serve-static-preview-for-command.ts index 4d3794018405b..cf90f509a0b74 100644 --- a/packages/playwright/src/migrations/update-19-6-0/use-serve-static-preview-for-command.ts +++ b/packages/playwright/src/migrations/update-19-6-0/use-serve-static-preview-for-command.ts @@ -12,7 +12,7 @@ import { import { tsquery } from '@phenomnomnominal/tsquery'; import addE2eCiTargetDefaults from './add-e2e-ci-target-defaults'; import type { ConfigurationResult } from 'nx/src/project-graph/utils/project-configuration-utils'; -import { LoadedNxPlugin } from 'nx/src/project-graph/plugins/internal-api'; +import { LoadedNxPlugin } from 'nx/src/project-graph/plugins/loaded-nx-plugin'; import { retrieveProjectConfigurations } from 'nx/src/project-graph/utils/retrieve-workspace-files'; import { ProjectConfigurationsError } from 'nx/src/project-graph/error-types'; import { createNodesV2 as webpackCreateNodesV2 } from '@nx/webpack/src/plugins/plugin'; From a5b786e2d62b464b091040d70e2f0b8de5422b81 Mon Sep 17 00:00:00 2001 From: FrozenPandaz Date: Wed, 8 Jan 2025 16:03:30 -0500 Subject: [PATCH 3/3] cleanup(core): remove normalize --- packages/nx/src/project-graph/plugins/get-plugins.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/nx/src/project-graph/plugins/get-plugins.ts b/packages/nx/src/project-graph/plugins/get-plugins.ts index c03fe84f034e1..ac4063041ceab 100644 --- a/packages/nx/src/project-graph/plugins/get-plugins.ts +++ b/packages/nx/src/project-graph/plugins/get-plugins.ts @@ -170,7 +170,7 @@ async function loadSpecifiedNxPlugins( ): Promise void]> { performance.mark('loadSpecifiedNxPlugins:start'); - plugins = await normalizePlugins(plugins, root); + plugins ??= []; const cleanupFunctions: Array<() => void> = []; const ret = [ @@ -214,12 +214,6 @@ async function loadSpecifiedNxPlugins( return ret; } -async function normalizePlugins(plugins: PluginConfiguration[], root: string) { - plugins ??= []; - - return [...plugins]; -} - function getDefaultPlugins(root: string) { return [ join(__dirname, '../../plugins/js'),