Skip to content

Commit e31b168

Browse files
authored
feat(js): add createNodesV2 for typescript plugin (#26788)
<!-- 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 --> There is no implementation for the `CreateNodesV2` API. ## Expected Behavior <!-- This is the behavior we should expect with the changes in this PR --> There should be an implementation for the `CreateNodesV2` API. ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> <!-- Fixes NXC-790 --> <!-- Fixes NXC-791 --> Fixes #
1 parent 2b7b523 commit e31b168

File tree

3 files changed

+117
-72
lines changed

3 files changed

+117
-72
lines changed

packages/js/src/plugins/typescript/plugin.spec.ts

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { type CreateNodesContext } from '@nx/devkit';
2+
import { TempFs } from '@nx/devkit/internal-testing-utils';
23
import { minimatch } from 'minimatch';
3-
import { TempFs } from 'nx/src/internal-testing-utils/temp-fs';
4-
import { PLUGIN_NAME, TscPluginOptions, createNodes } from './plugin';
54
import { setupWorkspaceContext } from 'nx/src/utils/workspace-context';
5+
import { PLUGIN_NAME, createNodesV2, type TscPluginOptions } from './plugin';
66

77
describe(`Plugin: ${PLUGIN_NAME}`, () => {
88
let context: CreateNodesContext;
@@ -2170,7 +2170,7 @@ async function applyFilesToTempFsAndContext(
21702170
await tempFs.createFiles(fileSys);
21712171
// @ts-expect-error update otherwise readonly property for testing
21722172
context.configFiles = Object.keys(fileSys).filter((file) =>
2173-
minimatch(file, createNodes[0], { dot: true })
2173+
minimatch(file, createNodesV2[0], { dot: true })
21742174
);
21752175
setupWorkspaceContext(tempFs.tempDir);
21762176
}
@@ -2181,15 +2181,19 @@ async function invokeCreateNodesOnMatchingFiles(
21812181
) {
21822182
const aggregateProjects: Record<string, any> = {};
21832183
for (const file of context.configFiles) {
2184-
const nodes = await createNodes[1](file, pluginOptions, context);
2185-
for (const [projectName, project] of Object.entries(nodes.projects ?? {})) {
2186-
if (aggregateProjects[projectName]) {
2187-
aggregateProjects[projectName].targets = {
2188-
...aggregateProjects[projectName].targets,
2189-
...project.targets,
2190-
};
2191-
} else {
2192-
aggregateProjects[projectName] = project;
2184+
const results = await createNodesV2[1]([file], pluginOptions, context);
2185+
for (const [, nodes] of results) {
2186+
for (const [projectName, project] of Object.entries(
2187+
nodes.projects ?? {}
2188+
)) {
2189+
if (aggregateProjects[projectName]) {
2190+
aggregateProjects[projectName].targets = {
2191+
...aggregateProjects[projectName].targets,
2192+
...project.targets,
2193+
};
2194+
} else {
2195+
aggregateProjects[projectName] = project;
2196+
}
21932197
}
21942198
}
21952199
}

packages/js/src/plugins/typescript/plugin.ts

Lines changed: 100 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,26 @@
11
import {
2+
createNodesFromFiles,
23
detectPackageManager,
34
joinPathFragments,
5+
logger,
46
normalizePath,
57
readJsonFile,
68
writeJsonFile,
79
type CreateDependencies,
810
type CreateNodes,
911
type CreateNodesContext,
12+
type CreateNodesResult,
13+
type CreateNodesV2,
1014
type NxJsonConfiguration,
15+
type ProjectConfiguration,
1116
type TargetConfiguration,
1217
} from '@nx/devkit';
1318
import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash-for-create-nodes';
1419
import { getNamedInputs } from '@nx/devkit/src/utils/get-named-inputs';
20+
import { minimatch } from 'minimatch';
1521
import { existsSync, readdirSync, statSync } from 'node:fs';
1622
import { basename, dirname, join, relative } from 'node:path';
17-
import { minimatch } from 'minimatch';
23+
import { hashObject } from 'nx/src/hasher/file-hasher';
1824
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
1925
import { getLockFileName } from 'nx/src/plugins/js/lock-file/lock-file';
2026
import { workspaceDataDirectory } from 'nx/src/utils/cache-directory';
@@ -49,84 +55,118 @@ interface NormalizedPluginOptions {
4955
};
5056
}
5157

52-
const cachePath = join(workspaceDataDirectory, 'tsc.hash');
53-
const targetsCache = readTargetsCache();
58+
type TscProjectResult = Pick<ProjectConfiguration, 'targets'>;
5459

55-
function readTargetsCache(): Record<
56-
string,
57-
Record<string, TargetConfiguration<unknown>>
58-
> {
60+
function readTargetsCache(cachePath: string): Record<string, TscProjectResult> {
5961
return existsSync(cachePath) ? readJsonFile(cachePath) : {};
6062
}
6163

62-
function writeTargetsToCache() {
63-
const oldCache = readTargetsCache();
64-
writeJsonFile(cachePath, {
65-
...oldCache,
66-
...targetsCache,
67-
});
64+
function writeTargetsToCache(
65+
cachePath: string,
66+
results?: Record<string, TscProjectResult>
67+
) {
68+
writeJsonFile(cachePath, results);
6869
}
6970

71+
/**
72+
* @deprecated The 'createDependencies' function is now a no-op. This functionality is included in 'createNodesV2'.
73+
*/
7074
export const createDependencies: CreateDependencies = () => {
71-
writeTargetsToCache();
7275
return [];
7376
};
7477

7578
export const PLUGIN_NAME = '@nx/js/typescript';
7679

80+
const tsConfigGlob = '**/tsconfig*.json';
81+
82+
export const createNodesV2: CreateNodesV2<TscPluginOptions> = [
83+
tsConfigGlob,
84+
async (configFilePaths, options, context) => {
85+
const optionsHash = hashObject(options);
86+
const cachePath = join(workspaceDataDirectory, `tsc-${optionsHash}.hash`);
87+
const targetsCache = readTargetsCache(cachePath);
88+
const normalizedOptions = normalizePluginOptions(options);
89+
try {
90+
return await createNodesFromFiles(
91+
(configFile, options, context) =>
92+
createNodesInternal(configFile, options, context, targetsCache),
93+
configFilePaths,
94+
normalizedOptions,
95+
context
96+
);
97+
} finally {
98+
writeTargetsToCache(cachePath, targetsCache);
99+
}
100+
},
101+
];
102+
77103
export const createNodes: CreateNodes<TscPluginOptions> = [
78-
'**/tsconfig*.json',
104+
tsConfigGlob,
79105
async (configFilePath, options, context) => {
80-
const pluginOptions = normalizePluginOptions(options);
81-
const projectRoot = dirname(configFilePath);
82-
const fullConfigPath = joinPathFragments(
83-
context.workspaceRoot,
84-
configFilePath
106+
logger.warn(
107+
'`createNodes` is deprecated. Update your plugin to utilize createNodesV2 instead. In Nx 20, this will change to the createNodesV2 API.'
85108
);
109+
const normalizedOptions = normalizePluginOptions(options);
110+
return createNodesInternal(configFilePath, normalizedOptions, context, {});
111+
},
112+
];
86113

87-
// Do not create a project if package.json and project.json isn't there.
88-
const siblingFiles = readdirSync(join(context.workspaceRoot, projectRoot));
89-
if (
90-
!siblingFiles.includes('package.json') &&
91-
!siblingFiles.includes('project.json')
92-
) {
93-
return {};
94-
}
114+
async function createNodesInternal(
115+
configFilePath: string,
116+
options: NormalizedPluginOptions,
117+
context: CreateNodesContext,
118+
targetsCache: Record<string, TscProjectResult>
119+
): Promise<CreateNodesResult> {
120+
const projectRoot = dirname(configFilePath);
121+
const fullConfigPath = joinPathFragments(
122+
context.workspaceRoot,
123+
configFilePath
124+
);
125+
126+
// Do not create a project if package.json and project.json isn't there.
127+
const siblingFiles = readdirSync(join(context.workspaceRoot, projectRoot));
128+
if (
129+
!siblingFiles.includes('package.json') &&
130+
!siblingFiles.includes('project.json')
131+
) {
132+
return {};
133+
}
95134

96-
// Do not create a project if it's not a tsconfig.json and there is no tsconfig.json in the same directory
97-
if (
98-
basename(configFilePath) !== 'tsconfig.json' &&
99-
!siblingFiles.includes('tsconfig.json')
100-
) {
101-
return {};
102-
}
135+
// Do not create a project if it's not a tsconfig.json and there is no tsconfig.json in the same directory
136+
if (
137+
basename(configFilePath) !== 'tsconfig.json' &&
138+
!siblingFiles.includes('tsconfig.json')
139+
) {
140+
return {};
141+
}
103142

104-
const nodeHash = await calculateHashForCreateNodes(
105-
projectRoot,
106-
pluginOptions,
107-
context,
108-
[getLockFileName(detectPackageManager(context.workspaceRoot))]
109-
);
110-
// The hash is calculated at the node/project level, so we add the config file path to avoid conflicts when caching
111-
const cacheKey = `${nodeHash}_${configFilePath}`;
112-
113-
targetsCache[cacheKey] ??= buildTscTargets(
114-
fullConfigPath,
115-
projectRoot,
116-
pluginOptions,
117-
context
118-
);
143+
const nodeHash = await calculateHashForCreateNodes(
144+
projectRoot,
145+
options,
146+
context,
147+
[getLockFileName(detectPackageManager(context.workspaceRoot))]
148+
);
149+
// The hash is calculated at the node/project level, so we add the config file path to avoid conflicts when caching
150+
const cacheKey = `${nodeHash}_${configFilePath}`;
151+
152+
targetsCache[cacheKey] ??= buildTscTargets(
153+
fullConfigPath,
154+
projectRoot,
155+
options,
156+
context
157+
);
119158

120-
return {
121-
projects: {
122-
[projectRoot]: {
123-
projectType: 'library',
124-
targets: targetsCache[cacheKey],
125-
},
159+
const { targets } = targetsCache[cacheKey];
160+
161+
return {
162+
projects: {
163+
[projectRoot]: {
164+
projectType: 'library',
165+
targets,
126166
},
127-
};
128-
},
129-
];
167+
},
168+
};
169+
}
130170

131171
function buildTscTargets(
132172
configFilePath: string,
@@ -220,7 +260,7 @@ function buildTscTargets(
220260
};
221261
}
222262

223-
return targets;
263+
return { targets };
224264
}
225265

226266
function getInputs(

packages/js/typescript.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export {
22
createDependencies,
33
createNodes,
4+
createNodesV2,
45
} from './src/plugins/typescript/plugin';

0 commit comments

Comments
 (0)