0
# Plugin Development
1
2
Plugin system for extending Nx with custom project discovery, dependency creation, and build integration capabilities.
3
4
## Capabilities
5
6
### Plugin Architecture
7
8
Create Nx plugins that extend workspace functionality with custom project discovery and task integration.
9
10
```typescript { .api }
11
/**
12
* Nx Plugin interface for extending workspace capabilities
13
*/
14
interface NxPlugin {
15
/** Plugin name for identification */
16
name: string;
17
/** Function to discover and create project configurations */
18
createNodes?: CreateNodesFunction;
19
/** Function to create dependencies between projects */
20
createDependencies?: CreateDependencies;
21
/** Function to create metadata for projects */
22
createMetadata?: CreateMetadata;
23
}
24
25
/**
26
* Version 2 plugin interface with enhanced capabilities
27
*/
28
interface NxPluginV2<TOptions = any> {
29
/** Plugin name for identification */
30
name: string;
31
/** Enhanced project discovery function */
32
createNodesV2?: CreateNodesV2<TOptions>;
33
/** Function to create dependencies between projects */
34
createDependencies?: CreateDependencies;
35
/** Function to create metadata for projects */
36
createMetadata?: CreateMetadata;
37
/** Pre-task execution hook */
38
preTasksExecution?: PreTasksExecution;
39
/** Post-task execution hook */
40
postTasksExecution?: PostTasksExecution;
41
}
42
```
43
44
### Project Discovery
45
46
Implement custom project discovery logic to automatically detect and configure projects.
47
48
```typescript { .api }
49
/**
50
* Function to create project nodes from configuration files
51
*/
52
type CreateNodesFunction<T = any> = (
53
configFile: string,
54
options: T | undefined,
55
context: CreateNodesContext
56
) => CreateNodesResult | null;
57
58
/**
59
* Enhanced version 2 create nodes function
60
*/
61
interface CreateNodesV2<T = any> {
62
(
63
configFiles: string[],
64
options: T | undefined,
65
context: CreateNodesContextV2
66
): Promise<CreateNodesResultV2>;
67
}
68
69
/**
70
* Context provided to create nodes functions
71
*/
72
interface CreateNodesContext {
73
/** Workspace root directory */
74
workspaceRoot: string;
75
/** Nx configuration */
76
nxJsonConfiguration: NxJsonConfiguration;
77
/** File changes since last run */
78
fileChanges?: FileChange[];
79
}
80
81
/**
82
* Enhanced context for version 2 plugins
83
*/
84
interface CreateNodesContextV2 extends CreateNodesContext {
85
/** Configuration hash for caching */
86
configurationHash?: string;
87
}
88
89
/**
90
* Result of project node creation
91
*/
92
interface CreateNodesResult {
93
/** Discovered projects */
94
projects?: Record<string, Omit<ProjectConfiguration, "root">>;
95
/** External dependencies found */
96
externalNodes?: Record<string, ProjectGraphExternalNode>;
97
}
98
99
/**
100
* Enhanced result for version 2 plugins
101
*/
102
interface CreateNodesResultV2 {
103
/** Map of configuration files to their discovered projects */
104
projects?: Record<string, CreateNodesResult>;
105
}
106
```
107
108
**Usage Examples:**
109
110
```typescript
111
import { CreateNodesFunction, CreateNodesResult, CreateNodesContext } from "@nx/devkit";
112
113
// Example plugin for discovering Vite projects
114
const createNodes: CreateNodesFunction<{ buildOutputPath?: string }> = (
115
configFilePath,
116
options,
117
context
118
) => {
119
const projectRoot = dirname(configFilePath);
120
const packageJsonPath = join(context.workspaceRoot, projectRoot, "package.json");
121
122
if (!existsSync(packageJsonPath)) {
123
return null;
124
}
125
126
const packageJson = readJsonFile(packageJsonPath);
127
const projectName = basename(projectRoot);
128
129
const result: CreateNodesResult = {
130
projects: {
131
[projectRoot]: {
132
name: projectName,
133
sourceRoot: join(projectRoot, "src"),
134
projectType: packageJson.private ? "application" : "library",
135
targets: {
136
build: {
137
executor: "@nx/vite:build",
138
outputs: [
139
options?.buildOutputPath ?? join("{workspaceRoot}", "dist", projectRoot)
140
],
141
options: {
142
configFile: configFilePath,
143
},
144
},
145
serve: {
146
executor: "@nx/vite:dev-server",
147
options: {
148
configFile: configFilePath,
149
},
150
},
151
},
152
},
153
},
154
};
155
156
return result;
157
};
158
159
// Plugin definition
160
export const VitePlugin: NxPlugin = {
161
name: "nx-vite-plugin",
162
createNodes: ["**/vite.config.{js,ts}", createNodes],
163
};
164
```
165
166
### Dependency Creation
167
168
Create dynamic dependencies between projects based on analysis of source code or configuration.
169
170
```typescript { .api }
171
/**
172
* Function to create dependencies between projects
173
*/
174
interface CreateDependencies {
175
(
176
opts: CreateDependenciesContext
177
): RawProjectGraphDependency[] | Promise<RawProjectGraphDependency[]>;
178
}
179
180
/**
181
* Context for dependency creation
182
*/
183
interface CreateDependenciesContext {
184
/** Projects in the workspace */
185
projects: Record<string, ProjectConfiguration>;
186
/** External nodes (npm packages) */
187
externalNodes: Record<string, ProjectGraphExternalNode>;
188
/** File map for analyzing file contents */
189
fileMap: ProjectFileMap;
190
/** Files that have changed */
191
filesToProcess: ProjectFileMap;
192
/** Workspace root directory */
193
workspaceRoot: string;
194
/** Nx configuration */
195
nxJsonConfiguration: NxJsonConfiguration;
196
}
197
```
198
199
**Usage Examples:**
200
201
```typescript
202
import {
203
CreateDependencies,
204
CreateDependenciesContext,
205
RawProjectGraphDependency,
206
DependencyType
207
} from "@nx/devkit";
208
209
// Example dependency creator for custom import patterns
210
const createDependencies: CreateDependencies = (context) => {
211
const dependencies: RawProjectGraphDependency[] = [];
212
213
// Analyze files for custom dependencies
214
Object.entries(context.filesToProcess).forEach(([projectName, files]) => {
215
files.forEach(file => {
216
const content = readFileSync(join(context.workspaceRoot, file.file), "utf-8");
217
218
// Look for custom dependency patterns
219
const customImportRegex = /@my-org\/([^'"]+)/g;
220
let match;
221
222
while ((match = customImportRegex.exec(content)) !== null) {
223
const targetPackage = match[1];
224
225
// Find corresponding project
226
const targetProject = Object.entries(context.projects).find(
227
([_, config]) => config.name === targetPackage
228
);
229
230
if (targetProject) {
231
dependencies.push({
232
sourceFile: file.file,
233
target: targetProject[0],
234
type: DependencyType.static,
235
});
236
}
237
}
238
});
239
});
240
241
return dependencies;
242
};
243
244
// Plugin with dependency creation
245
export const CustomDependencyPlugin: NxPlugin = {
246
name: "custom-dependency-plugin",
247
createDependencies,
248
};
249
```
250
251
### Metadata Creation
252
253
Generate additional metadata for projects that can be used by other tools and plugins.
254
255
```typescript { .api }
256
/**
257
* Function to create metadata for projects
258
*/
259
interface CreateMetadata {
260
(
261
graph: ProjectGraph,
262
context: CreateMetadataContext
263
): ProjectsMetadata | Promise<ProjectsMetadata>;
264
}
265
266
/**
267
* Context for metadata creation
268
*/
269
interface CreateMetadataContext {
270
/** Workspace root directory */
271
workspaceRoot: string;
272
/** Nx configuration */
273
nxJsonConfiguration: NxJsonConfiguration;
274
}
275
276
/**
277
* Metadata for projects
278
*/
279
interface ProjectsMetadata {
280
[projectName: string]: {
281
/** Additional project metadata */
282
[key: string]: any;
283
};
284
}
285
```
286
287
### Plugin Lifecycle Hooks
288
289
Implement hooks that run before and after task execution for custom logic.
290
291
```typescript { .api }
292
/**
293
* Hook that runs before task execution
294
*/
295
interface PreTasksExecution {
296
(context: PreTasksExecutionContext): void | Promise<void>;
297
}
298
299
/**
300
* Hook that runs after task execution
301
*/
302
interface PostTasksExecution {
303
(context: PostTasksExecutionContext): void | Promise<void>;
304
}
305
306
/**
307
* Context for pre-task execution
308
*/
309
interface PreTasksExecutionContext {
310
/** Project graph */
311
projectGraph: ProjectGraph;
312
/** Task graph to be executed */
313
taskGraph: TaskGraph;
314
/** Workspace root */
315
workspaceRoot: string;
316
/** Nx configuration */
317
nxJsonConfiguration: NxJsonConfiguration;
318
}
319
320
/**
321
* Context for post-task execution
322
*/
323
interface PostTasksExecutionContext extends PreTasksExecutionContext {
324
/** Results of task execution */
325
taskResults: TaskResults;
326
}
327
```
328
329
**Usage Examples:**
330
331
```typescript
332
import {
333
PreTasksExecution,
334
PostTasksExecution,
335
PreTasksExecutionContext,
336
PostTasksExecutionContext
337
} from "@nx/devkit";
338
339
// Pre-task hook for setup
340
const preTasksExecution: PreTasksExecution = async (context) => {
341
console.log(`Preparing to execute ${Object.keys(context.taskGraph.tasks).length} tasks`);
342
343
// Custom setup logic
344
await setupCustomEnvironment(context.workspaceRoot);
345
346
// Validate prerequisites
347
const buildTasks = Object.values(context.taskGraph.tasks)
348
.filter(task => task.target.target === "build");
349
350
if (buildTasks.length > 0) {
351
console.log(`Found ${buildTasks.length} build tasks`);
352
}
353
};
354
355
// Post-task hook for cleanup and reporting
356
const postTasksExecution: PostTasksExecution = async (context) => {
357
const results = Object.values(context.taskResults);
358
const successful = results.filter(r => r.success).length;
359
const failed = results.filter(r => !r.success).length;
360
361
console.log(`Task execution complete: ${successful} succeeded, ${failed} failed`);
362
363
// Custom cleanup logic
364
await cleanupCustomEnvironment(context.workspaceRoot);
365
366
// Generate custom reports
367
if (failed > 0) {
368
generateFailureReport(context.taskResults);
369
}
370
};
371
372
// Plugin with lifecycle hooks
373
export const LifecyclePlugin: NxPluginV2 = {
374
name: "lifecycle-plugin",
375
preTasksExecution,
376
postTasksExecution,
377
};
378
```
379
380
### Plugin Utilities
381
382
Utilities for working with plugins and handling errors.
383
384
```typescript { .api }
385
/**
386
* Create project nodes from files using registered plugins
387
* @param files - Configuration files to process
388
* @param options - Plugin options
389
* @param context - Creation context
390
* @param plugins - Plugins to use
391
* @returns Created nodes result
392
*/
393
function createNodesFromFiles<T = any>(
394
files: string[],
395
options: T,
396
context: CreateNodesContextV2,
397
plugins: CreateNodesV2<T>[]
398
): Promise<CreateNodesResultV2>;
399
400
/**
401
* Error thrown when multiple plugins fail to create nodes
402
*/
403
class AggregateCreateNodesError extends Error {
404
constructor(
405
public readonly errors: Array<[string, Error]>,
406
public readonly partialResults: CreateNodesResultV2
407
) {
408
super("Multiple plugins failed to create nodes");
409
}
410
}
411
412
/**
413
* Error thrown when project graph cache is stale
414
*/
415
class StaleProjectGraphCacheError extends Error {
416
constructor(message: string) {
417
super(message);
418
}
419
}
420
421
/**
422
* Create project nodes from a single configuration file
423
* @param configFile - Configuration file to process
424
* @param options - Plugin options
425
* @param context - Creation context
426
* @param plugin - Plugin with createNodes function
427
* @returns Created nodes result or null
428
*/
429
function createNodesFromFile<T = any>(
430
configFile: string,
431
options: T,
432
context: CreateNodesContext,
433
plugin: CreateNodesFunction<T>
434
): CreateNodesResult | null;
435
436
/**
437
* Plugin configuration entry in nx.json
438
*/
439
interface PluginConfiguration {
440
/** Plugin package name or path */
441
plugin: string;
442
/** Plugin options */
443
options?: any;
444
/** Include patterns for plugin */
445
include?: string[];
446
/** Exclude patterns for plugin */
447
exclude?: string[];
448
}
449
450
/**
451
* Expanded plugin configuration with resolved plugin instance
452
*/
453
interface ExpandedPluginConfiguration<T = any> {
454
/** Plugin instance */
455
plugin: NxPluginV2<T>;
456
/** Plugin options */
457
options?: T;
458
/** Include patterns for plugin */
459
include?: string[];
460
/** Exclude patterns for plugin */
461
exclude?: string[];
462
}
463
```
464
465
## Advanced Plugin Patterns
466
467
### Multi-framework Support
468
469
```typescript
470
// Plugin that supports multiple frameworks
471
export const UniversalPlugin: NxPluginV2<{
472
framework: "react" | "vue" | "angular";
473
buildTool: "webpack" | "vite" | "rollup";
474
}> = {
475
name: "universal-plugin",
476
477
createNodesV2: async (configFiles, options, context) => {
478
const projects: Record<string, CreateNodesResult> = {};
479
480
for (const configFile of configFiles) {
481
const projectRoot = dirname(configFile);
482
const framework = detectFramework(configFile, context);
483
const buildTool = options?.buildTool ?? detectBuildTool(configFile);
484
485
if (framework) {
486
projects[configFile] = {
487
projects: {
488
[projectRoot]: createProjectConfig(framework, buildTool, projectRoot)
489
}
490
};
491
}
492
}
493
494
return { projects };
495
}
496
};
497
```
498
499
### Conditional Plugin Loading
500
501
```typescript
502
// Plugin that conditionally loads based on workspace state
503
export const ConditionalPlugin: NxPluginV2 = {
504
name: "conditional-plugin",
505
506
createNodesV2: async (configFiles, options, context) => {
507
// Only activate if certain conditions are met
508
const hasReactProjects = Object.values(context.nxJsonConfiguration.projects ?? {})
509
.some(project => project.tags?.includes("react"));
510
511
if (!hasReactProjects) {
512
return { projects: {} };
513
}
514
515
// Continue with plugin logic...
516
return processReactProjects(configFiles, context);
517
}
518
};
519
```
520
521
### Plugin Composition
522
523
```typescript
524
// Compose multiple plugin capabilities
525
export function createCompositePlugin(
526
name: string,
527
...plugins: Partial<NxPluginV2>[]
528
): NxPluginV2 {
529
return {
530
name,
531
532
createNodesV2: async (configFiles, options, context) => {
533
const results: CreateNodesResultV2 = { projects: {} };
534
535
for (const plugin of plugins) {
536
if (plugin.createNodesV2) {
537
const pluginResult = await plugin.createNodesV2(configFiles, options, context);
538
539
// Merge results
540
Object.assign(results.projects!, pluginResult.projects ?? {});
541
}
542
}
543
544
return results;
545
},
546
547
createDependencies: async (context) => {
548
const allDependencies: RawProjectGraphDependency[] = [];
549
550
for (const plugin of plugins) {
551
if (plugin.createDependencies) {
552
const deps = await plugin.createDependencies(context);
553
allDependencies.push(...deps);
554
}
555
}
556
557
return allDependencies;
558
}
559
};
560
}
561
```