Skip to content

Commit bba941a

Browse files
authored
fix(core): move resolving plugins back to main thread (#29176)
<!-- 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 --> Resolving custom plugins to their paths to be loaded involves loading our default plugins. When plugin isolation, this loads the default plugins for each and every plugin worker. ## Expected Behavior <!-- This is the behavior we should expect with the changes in this PR --> Resolution of the plugins is the same and easily serializable to send to the worker. Resolving plugins on the main thread allows Nx to reuse the default plugins and decrease the memory usage greatly. ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> Fixes #
1 parent 1dbddb1 commit bba941a

File tree

6 files changed

+104
-50
lines changed

6 files changed

+104
-50
lines changed

packages/nx/src/project-graph/plugins/get-plugins.ts

+14-5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import { workspaceRoot } from '../../utils/workspace-root';
55

66
let currentPluginsConfigurationHash: string;
77
let loadedPlugins: LoadedNxPlugin[];
8+
let pendingPluginsPromise:
9+
| Promise<readonly [LoadedNxPlugin[], () => void]>
10+
| undefined;
811
let cleanup: () => void;
912

1013
export async function getPlugins() {
@@ -24,18 +27,20 @@ export async function getPlugins() {
2427
cleanup();
2528
}
2629

30+
pendingPluginsPromise ??= loadNxPlugins(pluginsConfiguration, workspaceRoot);
31+
2732
currentPluginsConfigurationHash = pluginsConfigurationHash;
28-
const [result, cleanupFn] = await loadNxPlugins(
29-
pluginsConfiguration,
30-
workspaceRoot
31-
);
33+
const [result, cleanupFn] = await pendingPluginsPromise;
3234
cleanup = cleanupFn;
3335
loadedPlugins = result;
3436
return result;
3537
}
3638

3739
let loadedDefaultPlugins: LoadedNxPlugin[];
3840
let cleanupDefaultPlugins: () => void;
41+
let pendingDefaultPluginPromise:
42+
| Promise<readonly [LoadedNxPlugin[], () => void]>
43+
| undefined;
3944

4045
export async function getOnlyDefaultPlugins() {
4146
// If the plugins configuration has not changed, reuse the current plugins
@@ -48,13 +53,17 @@ export async function getOnlyDefaultPlugins() {
4853
cleanupDefaultPlugins();
4954
}
5055

51-
const [result, cleanupFn] = await loadNxPlugins([], workspaceRoot);
56+
pendingDefaultPluginPromise ??= loadNxPlugins([], workspaceRoot);
57+
58+
const [result, cleanupFn] = await pendingDefaultPluginPromise;
5259
cleanupDefaultPlugins = cleanupFn;
5360
loadedPlugins = result;
5461
return result;
5562
}
5663

5764
export function cleanupPlugins() {
65+
pendingPluginsPromise = undefined;
66+
pendingDefaultPluginPromise = undefined;
5867
cleanup();
5968
cleanupDefaultPlugins();
6069
}

packages/nx/src/project-graph/plugins/isolation/messaging.ts

+3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ export interface PluginWorkerLoadMessage {
1414
payload: {
1515
plugin: PluginConfiguration;
1616
root: string;
17+
name: string;
18+
pluginPath: string;
19+
shouldRegisterTSTranspiler: boolean;
1720
};
1821
}
1922

packages/nx/src/project-graph/plugins/isolation/plugin-pool.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import {
1616
isPluginWorkerResult,
1717
sendMessageOverSocket,
1818
} from './messaging';
19+
import { getNxRequirePaths } from '../../../utils/installation-directory';
20+
import { resolveNxPlugin } from '../loader';
1921

2022
const cleanupFunctions = new Set<() => void>();
2123

@@ -59,6 +61,10 @@ export async function loadRemoteNxPlugin(
5961
if (nxPluginWorkerCache.has(cacheKey)) {
6062
return [nxPluginWorkerCache.get(cacheKey), () => {}];
6163
}
64+
const moduleName = typeof plugin === 'string' ? plugin : plugin.plugin;
65+
66+
const { name, pluginPath, shouldRegisterTSTranspiler } =
67+
await resolveNxPlugin(moduleName, root, getNxRequirePaths(root));
6268

6369
const { worker, socket } = await startPluginWorker();
6470

@@ -77,7 +83,7 @@ export async function loadRemoteNxPlugin(
7783
const pluginPromise = new Promise<LoadedNxPlugin>((res, rej) => {
7884
sendMessageOverSocket(socket, {
7985
type: 'load',
80-
payload: { plugin, root },
86+
payload: { plugin, root, name, pluginPath, shouldRegisterTSTranspiler },
8187
});
8288
// logger.verbose(`[plugin-worker] started worker: ${worker.pid}`);
8389

packages/nx/src/project-graph/plugins/isolation/plugin-worker.ts

+22-4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { consumeMessagesFromSocket } from '../../../utils/consume-messages-from-
44

55
import { createServer } from 'net';
66
import { unlinkSync } from 'fs';
7+
import { registerPluginTSTranspiler } from '../loader';
78

89
if (process.env.NX_PERF_LOGGING === 'true') {
910
require('../../../utils/perf-logging');
@@ -35,13 +36,30 @@ const server = createServer((socket) => {
3536
return;
3637
}
3738
return consumeMessage(socket, message, {
38-
load: async ({ plugin: pluginConfiguration, root }) => {
39+
load: async ({
40+
plugin: pluginConfiguration,
41+
root,
42+
name,
43+
pluginPath,
44+
shouldRegisterTSTranspiler,
45+
}) => {
3946
if (loadTimeout) clearTimeout(loadTimeout);
4047
process.chdir(root);
4148
try {
42-
const { loadNxPlugin } = await import('../loader');
43-
const [promise] = loadNxPlugin(pluginConfiguration, root);
44-
plugin = await promise;
49+
const { loadResolvedNxPluginAsync } = await import(
50+
'../load-resolved-plugin'
51+
);
52+
53+
// Register the ts-transpiler if we are pointing to a
54+
// plain ts file that's not part of a plugin project
55+
if (shouldRegisterTSTranspiler) {
56+
registerPluginTSTranspiler();
57+
}
58+
plugin = await loadResolvedNxPluginAsync(
59+
pluginConfiguration,
60+
pluginPath,
61+
name
62+
);
4563
return {
4664
type: 'load-result',
4765
payload: {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import type { PluginConfiguration } from '../../config/nx-json';
2+
import { LoadedNxPlugin } from './internal-api';
3+
import { NxPlugin } from './public-api';
4+
5+
export async function loadResolvedNxPluginAsync(
6+
pluginConfiguration: PluginConfiguration,
7+
pluginPath: string,
8+
name: string
9+
) {
10+
const plugin = await importPluginModule(pluginPath);
11+
plugin.name ??= name;
12+
return new LoadedNxPlugin(plugin, pluginConfiguration);
13+
}
14+
15+
async function importPluginModule(pluginPath: string): Promise<NxPlugin> {
16+
const m = await import(pluginPath);
17+
if (
18+
m.default &&
19+
('createNodes' in m.default ||
20+
'createNodesV2' in m.default ||
21+
'createDependencies' in m.default)
22+
) {
23+
return m.default;
24+
}
25+
return m;
26+
}

packages/nx/src/project-graph/plugins/loader.ts

+32-40
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@ import { logger } from '../../utils/logger';
2424

2525
import type * as ts from 'typescript';
2626
import { extname } from 'node:path';
27-
import type { NxPlugin } from './public-api';
2827
import type { PluginConfiguration } from '../../config/nx-json';
2928
import { retrieveProjectConfigurationsWithoutPluginInference } from '../utils/retrieve-workspace-files';
3029
import { LoadedNxPlugin } from './internal-api';
3130
import { LoadPluginError } from '../error-types';
3231
import path = require('node:path/posix');
3332
import { readTsConfig } from '../../plugins/js/utils/typescript';
33+
import { loadResolvedNxPluginAsync } from './load-resolved-plugin';
3434

3535
export function readPluginPackageJson(
3636
pluginName: string,
@@ -200,18 +200,18 @@ export function getPluginPathAndName(
200200
root: string
201201
) {
202202
let pluginPath: string;
203-
let registerTSTranspiler = false;
203+
let shouldRegisterTSTranspiler = false;
204204
try {
205205
pluginPath = require.resolve(moduleName, {
206206
paths,
207207
});
208208
const extension = path.extname(pluginPath);
209-
registerTSTranspiler = extension === '.ts';
209+
shouldRegisterTSTranspiler = extension === '.ts';
210210
} catch (e) {
211211
if (e.code === 'MODULE_NOT_FOUND') {
212212
const plugin = resolveLocalNxPlugin(moduleName, projects, root);
213213
if (plugin) {
214-
registerTSTranspiler = true;
214+
shouldRegisterTSTranspiler = true;
215215
const main = readPluginMainFromProjectConfiguration(
216216
plugin.projectConfig
217217
);
@@ -226,18 +226,12 @@ export function getPluginPathAndName(
226226
}
227227
const packageJsonPath = path.join(pluginPath, 'package.json');
228228

229-
// Register the ts-transpiler if we are pointing to a
230-
// plain ts file that's not part of a plugin project
231-
if (registerTSTranspiler) {
232-
registerPluginTSTranspiler();
233-
}
234-
235229
const { name } =
236230
!['.ts', '.js'].some((x) => extname(moduleName) === x) && // Not trying to point to a ts or js file
237231
existsSync(packageJsonPath) // plugin has a package.json
238232
? readJsonFile(packageJsonPath) // read name from package.json
239233
: { name: moduleName };
240-
return { pluginPath, name };
234+
return { pluginPath, name, shouldRegisterTSTranspiler };
241235
}
242236

243237
let projectsWithoutInference: Record<string, ProjectConfiguration>;
@@ -249,6 +243,27 @@ export function loadNxPlugin(plugin: PluginConfiguration, root: string) {
249243
] as const;
250244
}
251245

246+
export async function resolveNxPlugin(
247+
moduleName: string,
248+
root: string,
249+
paths: string[]
250+
) {
251+
try {
252+
require.resolve(moduleName);
253+
} catch {
254+
// If a plugin cannot be resolved, we will need projects to resolve it
255+
projectsWithoutInference ??=
256+
await retrieveProjectConfigurationsWithoutPluginInference(root);
257+
}
258+
const { pluginPath, name, shouldRegisterTSTranspiler } = getPluginPathAndName(
259+
moduleName,
260+
paths,
261+
projectsWithoutInference,
262+
root
263+
);
264+
return { pluginPath, name, shouldRegisterTSTranspiler };
265+
}
266+
252267
export async function loadNxPluginAsync(
253268
pluginConfiguration: PluginConfiguration,
254269
paths: string[],
@@ -259,37 +274,14 @@ export async function loadNxPluginAsync(
259274
? pluginConfiguration
260275
: pluginConfiguration.plugin;
261276
try {
262-
try {
263-
require.resolve(moduleName);
264-
} catch {
265-
// If a plugin cannot be resolved, we will need projects to resolve it
266-
projectsWithoutInference ??=
267-
await retrieveProjectConfigurationsWithoutPluginInference(root);
268-
}
269-
const { pluginPath, name } = getPluginPathAndName(
270-
moduleName,
271-
paths,
272-
projectsWithoutInference,
273-
root
274-
);
275-
const plugin = await importPluginModule(pluginPath);
276-
plugin.name ??= name;
277+
const { pluginPath, name, shouldRegisterTSTranspiler } =
278+
await resolveNxPlugin(moduleName, root, paths);
277279

278-
return new LoadedNxPlugin(plugin, pluginConfiguration);
280+
if (shouldRegisterTSTranspiler) {
281+
registerPluginTSTranspiler();
282+
}
283+
return loadResolvedNxPluginAsync(pluginConfiguration, pluginPath, name);
279284
} catch (e) {
280285
throw new LoadPluginError(moduleName, e);
281286
}
282287
}
283-
284-
async function importPluginModule(pluginPath: string): Promise<NxPlugin> {
285-
const m = await import(pluginPath);
286-
if (
287-
m.default &&
288-
('createNodes' in m.default ||
289-
'createNodesV2' in m.default ||
290-
'createDependencies' in m.default)
291-
) {
292-
return m.default;
293-
}
294-
return m;
295-
}

0 commit comments

Comments
 (0)