Skip to content

Commit

Permalink
fix(core): move resolving plugins back to main thread
Browse files Browse the repository at this point in the history
  • Loading branch information
FrozenPandaz committed Dec 3, 2024
1 parent 3cc321d commit f5ed589
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 50 deletions.
19 changes: 14 additions & 5 deletions packages/nx/src/project-graph/plugins/get-plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { workspaceRoot } from '../../utils/workspace-root';

let currentPluginsConfigurationHash: string;
let loadedPlugins: LoadedNxPlugin[];
let pendingPluginsPromise:
| Promise<readonly [LoadedNxPlugin[], () => void]>
| undefined;
let cleanup: () => void;

export async function getPlugins() {
Expand All @@ -24,18 +27,20 @@ export async function getPlugins() {
cleanup();
}

pendingPluginsPromise ??= loadNxPlugins(pluginsConfiguration, workspaceRoot);

currentPluginsConfigurationHash = pluginsConfigurationHash;
const [result, cleanupFn] = await loadNxPlugins(
pluginsConfiguration,
workspaceRoot
);
const [result, cleanupFn] = await pendingPluginsPromise;
cleanup = cleanupFn;
loadedPlugins = result;
return result;
}

let loadedDefaultPlugins: LoadedNxPlugin[];
let cleanupDefaultPlugins: () => void;
let pendingDefaultPluginPromise:
| Promise<readonly [LoadedNxPlugin[], () => void]>
| undefined;

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

const [result, cleanupFn] = await loadNxPlugins([], workspaceRoot);
pendingDefaultPluginPromise ??= loadNxPlugins([], workspaceRoot);

const [result, cleanupFn] = await pendingDefaultPluginPromise;
cleanupDefaultPlugins = cleanupFn;
loadedPlugins = result;
return result;
}

export function cleanupPlugins() {
pendingPluginsPromise = undefined;
pendingDefaultPluginPromise = undefined;
cleanup();
cleanupDefaultPlugins();
}
3 changes: 3 additions & 0 deletions packages/nx/src/project-graph/plugins/isolation/messaging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ export interface PluginWorkerLoadMessage {
payload: {
plugin: PluginConfiguration;
root: string;
name: string;
pluginPath: string;
shouldRegisterTSTranspiler: boolean;
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {
isPluginWorkerResult,
sendMessageOverSocket,
} from './messaging';
import { getNxRequirePaths } from '../../../utils/installation-directory';
import { resolveNxPlugin } from '../loader';

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

Expand Down Expand Up @@ -59,6 +61,10 @@ export async function loadRemoteNxPlugin(
if (nxPluginWorkerCache.has(cacheKey)) {
return [nxPluginWorkerCache.get(cacheKey), () => {}];
}
const moduleName = typeof plugin === 'string' ? plugin : plugin.plugin;

const { name, pluginPath, shouldRegisterTSTranspiler } =
await resolveNxPlugin(moduleName, root, getNxRequirePaths(root));

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

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

Expand Down
26 changes: 22 additions & 4 deletions packages/nx/src/project-graph/plugins/isolation/plugin-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { consumeMessagesFromSocket } from '../../../utils/consume-messages-from-

import { createServer } from 'net';
import { unlinkSync } from 'fs';
import { registerPluginTSTranspiler } from 'nx/src/project-graph/plugins';

if (process.env.NX_PERF_LOGGING === 'true') {
require('../../../utils/perf-logging');
Expand Down Expand Up @@ -35,13 +36,30 @@ const server = createServer((socket) => {
return;
}
return consumeMessage(socket, message, {
load: async ({ plugin: pluginConfiguration, root }) => {
load: async ({
plugin: pluginConfiguration,
root,
name,
pluginPath,
shouldRegisterTSTranspiler,
}) => {
if (loadTimeout) clearTimeout(loadTimeout);
process.chdir(root);
try {
const { loadNxPlugin } = await import('../loader');
const [promise] = loadNxPlugin(pluginConfiguration, root);
plugin = await promise;
const { loadResolvedNxPluginAsync } = await import(
'../load-resolved-plugin'
);

// Register the ts-transpiler if we are pointing to a
// plain ts file that's not part of a plugin project
if (shouldRegisterTSTranspiler) {
registerPluginTSTranspiler();
}
plugin = await loadResolvedNxPluginAsync(
pluginConfiguration,
pluginPath,
name
);
return {
type: 'load-result',
payload: {
Expand Down
26 changes: 26 additions & 0 deletions packages/nx/src/project-graph/plugins/load-resolved-plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { PluginConfiguration } from '../../config/nx-json';
import { LoadedNxPlugin } from './internal-api';
import type { NxPlugin } from 'nx/src/project-graph/plugins/public-api';

export async function loadResolvedNxPluginAsync(
pluginConfiguration: PluginConfiguration,
pluginPath: string,
name: string
) {
const plugin = await importPluginModule(pluginPath);
plugin.name ??= name;
return new LoadedNxPlugin(plugin, pluginConfiguration);
}

async function importPluginModule(pluginPath: string): Promise<NxPlugin> {
const m = await import(pluginPath);
if (
m.default &&
('createNodes' in m.default ||
'createNodesV2' in m.default ||
'createDependencies' in m.default)
) {
return m.default;
}
return m;
}
72 changes: 32 additions & 40 deletions packages/nx/src/project-graph/plugins/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ import { logger } from '../../utils/logger';

import type * as ts from 'typescript';
import { extname } from 'node:path';
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';
import path = require('node:path/posix');
import { readTsConfig } from '../../plugins/js/utils/typescript';
import { loadResolvedNxPluginAsync } from './load-resolved-plugin';

export function readPluginPackageJson(
pluginName: string,
Expand Down Expand Up @@ -200,18 +200,18 @@ export function getPluginPathAndName(
root: string
) {
let pluginPath: string;
let registerTSTranspiler = false;
let shouldRegisterTSTranspiler = false;
try {
pluginPath = require.resolve(moduleName, {
paths,
});
const extension = path.extname(pluginPath);
registerTSTranspiler = extension === '.ts';
shouldRegisterTSTranspiler = extension === '.ts';
} catch (e) {
if (e.code === 'MODULE_NOT_FOUND') {
const plugin = resolveLocalNxPlugin(moduleName, projects, root);
if (plugin) {
registerTSTranspiler = true;
shouldRegisterTSTranspiler = true;
const main = readPluginMainFromProjectConfiguration(
plugin.projectConfig
);
Expand All @@ -226,18 +226,12 @@ export function getPluginPathAndName(
}
const packageJsonPath = path.join(pluginPath, 'package.json');

// Register the ts-transpiler if we are pointing to a
// plain ts file that's not part of a plugin project
if (registerTSTranspiler) {
registerPluginTSTranspiler();
}

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 };
return { pluginPath, name, shouldRegisterTSTranspiler };
}

let projectsWithoutInference: Record<string, ProjectConfiguration>;
Expand All @@ -249,6 +243,27 @@ export function loadNxPlugin(plugin: PluginConfiguration, root: string) {
] 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[],
Expand All @@ -259,37 +274,14 @@ export async function loadNxPluginAsync(
? pluginConfiguration
: pluginConfiguration.plugin;
try {
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 } = getPluginPathAndName(
moduleName,
paths,
projectsWithoutInference,
root
);
const plugin = await importPluginModule(pluginPath);
plugin.name ??= name;
const { pluginPath, name, shouldRegisterTSTranspiler } =
await resolveNxPlugin(moduleName, root, paths);

return new LoadedNxPlugin(plugin, pluginConfiguration);
if (shouldRegisterTSTranspiler) {
registerPluginTSTranspiler();
}
return loadResolvedNxPluginAsync(pluginConfiguration, pluginPath, name);
} catch (e) {
throw new LoadPluginError(moduleName, e);
}
}

async function importPluginModule(pluginPath: string): Promise<NxPlugin> {
const m = await import(pluginPath);
if (
m.default &&
('createNodes' in m.default ||
'createNodesV2' in m.default ||
'createDependencies' in m.default)
) {
return m.default;
}
return m;
}

0 comments on commit f5ed589

Please sign in to comment.