or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

executors.mdgenerators.mdindex.mdpackage-management.mdplugin-development.mdproject-graph.mdtree-operations.mdutilities.mdworkspace-configuration.md

plugin-development.mddocs/

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

```