or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

cli.mddevkit-core.mddevkit-files.mddevkit-tasks.mdgenerators-executors.mdindex.mdplugins.md

plugins.mddocs/

0

# Plugin System

1

2

Comprehensive APIs for creating custom plugins that extend Nx's project detection, dependency analysis, and task configuration capabilities.

3

4

## Capabilities

5

6

### Plugin Interface

7

8

Core plugin interface for extending Nx functionality.

9

10

```typescript { .api }

11

/**

12

* Main plugin interface for extending Nx

13

*/

14

interface NxPlugin {

15

/** Plugin name */

16

name: string;

17

/** Create project configurations from config files */

18

createNodes?: CreateNodes;

19

/** Create additional project dependencies */

20

createDependencies?: CreateDependencies;

21

/** Process and modify the project graph */

22

processProjectGraph?: ProcessProjectGraph;

23

/** Generate workspace files and configurations */

24

createNodesV2?: CreateNodesV2;

25

}

26

27

/**

28

* Function to create project nodes from configuration files

29

*/

30

type CreateNodes = (

31

configFilePath: string,

32

options: any,

33

context: CreateNodesContext

34

) => CreateNodesResult | Promise<CreateNodesResult>;

35

36

/**

37

* Enhanced version of CreateNodes with batching support

38

*/

39

type CreateNodesV2 = (

40

configFiles: string[],

41

options: any,

42

context: CreateNodesContext

43

) => Promise<CreateNodesResultV2>;

44

45

/**

46

* Function to create project dependencies

47

*/

48

type CreateDependencies = (

49

opts: CreateDependenciesContext

50

) => RawProjectGraphDependency[] | Promise<RawProjectGraphDependency[]>;

51

52

/**

53

* Function to process the project graph

54

*/

55

type ProcessProjectGraph = (

56

graph: ProjectGraph,

57

context: CreateDependenciesContext

58

) => ProjectGraph | Promise<ProjectGraph>;

59

```

60

61

### Plugin Context Types

62

63

Context objects provided to plugin functions.

64

65

```typescript { .api }

66

interface CreateNodesContext {

67

/** Nx workspace configuration */

68

nxJsonConfiguration: NxJsonConfiguration;

69

/** Workspace root directory */

70

workspaceRoot: string;

71

/** All configuration files found */

72

configFiles: string[];

73

/** File map for workspace files */

74

fileMap?: ProjectFileMap;

75

}

76

77

interface CreateDependenciesContext {

78

/** All workspace files */

79

projects: Record<string, ProjectConfiguration>;

80

/** Nx workspace configuration */

81

nxJsonConfiguration: NxJsonConfiguration;

82

/** File map for workspace files */

83

fileMap: ProjectFileMap;

84

/** File map for external dependencies */

85

externalNodes?: Record<string, ProjectGraphExternalNode>;

86

}

87

88

interface CreateNodesResult {

89

/** Map of project root to project configuration */

90

projects?: Record<string, CreateNodesProjectConfiguration>;

91

/** External dependencies discovered */

92

externalNodes?: Record<string, ProjectGraphExternalNode>;

93

}

94

95

interface CreateNodesResultV2 {

96

/** Map of config file to create nodes result */

97

[configFile: string]: CreateNodesResult;

98

}

99

100

interface CreateNodesProjectConfiguration {

101

/** Project name */

102

name?: string;

103

/** Build targets */

104

targets?: Record<string, TargetConfiguration>;

105

/** Project metadata */

106

metadata?: ProjectMetadata;

107

/** Project tags */

108

tags?: string[];

109

/** Named inputs */

110

namedInputs?: NamedInputs;

111

}

112

```

113

114

### Plugin Registration

115

116

Functions for loading and registering plugins.

117

118

```typescript { .api }

119

/**

120

* Loads Nx plugins from the workspace configuration

121

* @param nxJson - Nx workspace configuration

122

* @param paths - Additional paths to search for plugins

123

* @returns Array of loaded plugins

124

*/

125

function loadNxPlugins(

126

nxJson: NxJsonConfiguration,

127

paths?: string[]

128

): Promise<LoadedNxPlugin[]>;

129

130

/**

131

* Checks if an object implements the NxPlugin interface

132

* @param plugin - Object to check

133

* @returns True if object is an NxPlugin

134

*/

135

function isNxPlugin(plugin: any): plugin is NxPlugin;

136

137

/**

138

* Reads package.json for a plugin package

139

* @param pluginName - Name of the plugin package

140

* @param paths - Paths to search for the plugin

141

* @returns Plugin package.json content

142

*/

143

function readPluginPackageJson(

144

pluginName: string,

145

paths?: string[]

146

): { packageJson: PackageJson; path: string };

147

148

interface LoadedNxPlugin {

149

/** Plugin name */

150

name: string;

151

/** Plugin implementation */

152

plugin: NxPlugin;

153

/** Plugin options from nx.json */

154

options: any;

155

}

156

```

157

158

### Project Graph Dependencies

159

160

Types for defining project dependencies through plugins.

161

162

```typescript { .api }

163

interface RawProjectGraphDependency {

164

/** Source project name */

165

source: string;

166

/** Target project name */

167

target: string;

168

/** Type of dependency */

169

type: DependencyType;

170

/** Source file that created the dependency */

171

sourceFile?: string;

172

}

173

174

interface ProjectGraphExternalNode {

175

/** Package type (npm, etc.) */

176

type: 'npm' | string;

177

/** Package name */

178

name: string;

179

/** Package data */

180

data: {

181

version: string;

182

packageName: string;

183

hash?: string;

184

};

185

}

186

187

type DependencyType =

188

| 'static' // Import/require statements

189

| 'dynamic' // Dynamic imports

190

| 'implicit' // Configured dependencies

191

| 'direct' // Direct project dependencies

192

| 'indirect'; // Transitive dependencies

193

```

194

195

### Built-in Plugin Utilities

196

197

Utilities for common plugin development tasks.

198

199

```typescript { .api }

200

/**

201

* Creates a project node from package.json

202

* @param packageJsonPath - Path to package.json

203

* @param context - Create nodes context

204

* @returns Project configuration

205

*/

206

function createNodeFromPackageJson(

207

packageJsonPath: string,

208

context: CreateNodesContext

209

): CreateNodesProjectConfiguration | null;

210

211

/**

212

* Parses target configuration from executor strings

213

* @param executor - Executor string (e.g., "@nx/webpack:webpack")

214

* @returns Parsed executor information

215

*/

216

function parseExecutor(executor: string): {

217

package: string;

218

executor: string;

219

};

220

221

/**

222

* Resolves workspace relative paths

223

* @param workspaceRoot - Workspace root directory

224

* @param path - Path to resolve

225

* @returns Resolved absolute path

226

*/

227

function resolveWorkspacePath(workspaceRoot: string, path: string): string;

228

229

/**

230

* Gets project root from configuration file path

231

* @param configFilePath - Path to configuration file

232

* @param workspaceRoot - Workspace root directory

233

* @returns Project root directory

234

*/

235

function getProjectRootFromConfigFile(

236

configFilePath: string,

237

workspaceRoot: string

238

): string;

239

```

240

241

## Usage Examples

242

243

### Basic Plugin Implementation

244

245

```typescript

246

import {

247

NxPlugin,

248

CreateNodes,

249

CreateNodesContext,

250

CreateNodesResult,

251

TargetConfiguration,

252

logger

253

} from "nx/src/devkit-exports";

254

255

const createNodes: CreateNodes = (

256

configFilePath: string,

257

options: any,

258

context: CreateNodesContext

259

): CreateNodesResult => {

260

logger.info(`Processing config file: ${configFilePath}`);

261

262

// Get project root from config file path

263

const projectRoot = path.dirname(configFilePath);

264

const projectName = path.basename(projectRoot);

265

266

// Read configuration file

267

const configContent = readFileSync(configFilePath, 'utf-8');

268

const config = JSON.parse(configContent);

269

270

// Create build target based on configuration

271

const buildTarget: TargetConfiguration = {

272

executor: '@my-plugin/executor:build',

273

options: {

274

outputPath: `dist/${projectRoot}`,

275

...config.buildOptions

276

}

277

};

278

279

// Create test target if tests are configured

280

const targets: Record<string, TargetConfiguration> = { build: buildTarget };

281

282

if (config.testFramework) {

283

targets.test = {

284

executor: '@my-plugin/executor:test',

285

options: {

286

testFramework: config.testFramework,

287

...config.testOptions

288

}

289

};

290

}

291

292

return {

293

projects: {

294

[projectRoot]: {

295

name: projectName,

296

targets,

297

tags: config.tags || [],

298

metadata: {

299

framework: config.framework,

300

version: config.version

301

}

302

}

303

}

304

};

305

};

306

307

const myPlugin: NxPlugin = {

308

name: 'my-custom-plugin',

309

createNodes: [

310

// Glob pattern to match configuration files

311

'**/my-config.json',

312

createNodes

313

]

314

};

315

316

export default myPlugin;

317

```

318

319

### Advanced Plugin with Dependencies

320

321

```typescript

322

import {

323

NxPlugin,

324

CreateNodes,

325

CreateDependencies,

326

CreateDependenciesContext,

327

RawProjectGraphDependency,

328

logger

329

} from "nx/src/devkit-exports";

330

331

const createDependencies: CreateDependencies = (

332

context: CreateDependenciesContext

333

): RawProjectGraphDependency[] => {

334

const dependencies: RawProjectGraphDependency[] = [];

335

336

// Analyze workspace files to find dependencies

337

for (const [projectName, project] of Object.entries(context.projects)) {

338

const projectFiles = context.fileMap.projectFileMap[projectName] || [];

339

340

// Look for custom import patterns

341

for (const file of projectFiles) {

342

if (file.file.endsWith('.ts') || file.file.endsWith('.js')) {

343

const filePath = path.join(context.workspaceRoot, file.file);

344

const content = readFileSync(filePath, 'utf-8');

345

346

// Find custom import statements

347

const importRegex = /import.*from\s+['"]@workspace\/([^'"]+)['"]/g;

348

let match;

349

350

while ((match = importRegex.exec(content)) !== null) {

351

const targetProject = match[1];

352

353

if (context.projects[targetProject]) {

354

dependencies.push({

355

source: projectName,

356

target: targetProject,

357

type: 'static',

358

sourceFile: file.file

359

});

360

}

361

}

362

}

363

}

364

}

365

366

logger.info(`Found ${dependencies.length} custom dependencies`);

367

return dependencies;

368

};

369

370

const advancedPlugin: NxPlugin = {

371

name: 'advanced-plugin',

372

createNodes: ['**/project.config.json', createNodes],

373

createDependencies

374

};

375

376

export default advancedPlugin;

377

```

378

379

### Plugin with Project Graph Processing

380

381

```typescript

382

import {

383

NxPlugin,

384

ProcessProjectGraph,

385

ProjectGraph,

386

CreateDependenciesContext,

387

logger

388

} from "nx/src/devkit-exports";

389

390

const processProjectGraph: ProcessProjectGraph = (

391

graph: ProjectGraph,

392

context: CreateDependenciesContext

393

): ProjectGraph => {

394

logger.info('Processing project graph for optimization');

395

396

// Add virtual nodes for shared resources

397

const modifiedGraph = { ...graph };

398

399

// Find projects that share common patterns

400

const sharedLibraries = new Set<string>();

401

402

for (const [projectName, dependencies] of Object.entries(graph.dependencies)) {

403

dependencies.forEach(dep => {

404

if (dep.target.startsWith('shared-')) {

405

sharedLibraries.add(dep.target);

406

}

407

});

408

}

409

410

// Add metadata to shared library nodes

411

for (const sharedLib of sharedLibraries) {

412

if (modifiedGraph.nodes[sharedLib]) {

413

modifiedGraph.nodes[sharedLib].data = {

414

...modifiedGraph.nodes[sharedLib].data,

415

tags: [...(modifiedGraph.nodes[sharedLib].data.tags || []), 'shared']

416

};

417

}

418

}

419

420

return modifiedGraph;

421

};

422

423

const graphProcessorPlugin: NxPlugin = {

424

name: 'graph-processor-plugin',

425

processProjectGraph

426

};

427

428

export default graphProcessorPlugin;

429

```

430

431

### Multi-Framework Plugin

432

433

```typescript

434

import {

435

NxPlugin,

436

CreateNodes,

437

CreateNodesContext,

438

CreateNodesResult,

439

TargetConfiguration

440

} from "nx/src/devkit-exports";

441

442

interface FrameworkConfig {

443

framework: 'react' | 'vue' | 'angular';

444

buildTool: 'webpack' | 'vite' | 'rollup';

445

features: string[];

446

}

447

448

const createNodes: CreateNodes = (

449

configFilePath: string,

450

options: any,

451

context: CreateNodesContext

452

): CreateNodesResult => {

453

const projectRoot = path.dirname(configFilePath);

454

const config: FrameworkConfig = JSON.parse(

455

readFileSync(configFilePath, 'utf-8')

456

);

457

458

// Create framework-specific targets

459

const targets: Record<string, TargetConfiguration> = {};

460

461

// Build target based on framework and build tool

462

switch (config.framework) {

463

case 'react':

464

targets.build = {

465

executor: config.buildTool === 'vite'

466

? '@nx/vite:build'

467

: '@nx/webpack:webpack',

468

options: {

469

outputPath: `dist/${projectRoot}`,

470

main: `${projectRoot}/src/main.tsx`,

471

index: `${projectRoot}/src/index.html`

472

}

473

};

474

break;

475

476

case 'vue':

477

targets.build = {

478

executor: '@nx/vite:build',

479

options: {

480

outputPath: `dist/${projectRoot}`,

481

configFile: `${projectRoot}/vite.config.ts`

482

}

483

};

484

break;

485

486

case 'angular':

487

targets.build = {

488

executor: '@angular-devkit/build-angular:browser',

489

options: {

490

outputPath: `dist/${projectRoot}`,

491

index: `${projectRoot}/src/index.html`,

492

main: `${projectRoot}/src/main.ts`,

493

polyfills: `${projectRoot}/src/polyfills.ts`,

494

tsConfig: `${projectRoot}/tsconfig.app.json`

495

}

496

};

497

break;

498

}

499

500

// Add feature-specific targets

501

if (config.features.includes('testing')) {

502

targets.test = {

503

executor: '@nx/jest:jest',

504

options: {

505

jestConfig: `${projectRoot}/jest.config.ts`

506

}

507

};

508

}

509

510

if (config.features.includes('e2e')) {

511

targets.e2e = {

512

executor: '@nx/cypress:cypress',

513

options: {

514

cypressConfig: `${projectRoot}/cypress.config.ts`

515

}

516

};

517

}

518

519

if (config.features.includes('storybook')) {

520

targets.storybook = {

521

executor: '@nx/storybook:storybook',

522

options: {

523

port: 4400,

524

configDir: `${projectRoot}/.storybook`

525

}

526

};

527

}

528

529

return {

530

projects: {

531

[projectRoot]: {

532

name: path.basename(projectRoot),

533

targets,

534

tags: [config.framework, config.buildTool, ...config.features],

535

metadata: {

536

framework: config.framework,

537

buildTool: config.buildTool,

538

features: config.features

539

}

540

}

541

}

542

};

543

};

544

545

const multiFrameworkPlugin: NxPlugin = {

546

name: 'multi-framework-plugin',

547

createNodes: ['**/framework.config.json', createNodes]

548

};

549

550

export default multiFrameworkPlugin;

551

```

552

553

### Plugin Configuration in nx.json

554

555

```json

556

{

557

"plugins": [

558

{

559

"plugin": "@my-org/nx-custom-plugin",

560

"options": {

561

"buildCommand": "custom-build",

562

"testPattern": "**/*.test.{js,ts}",

563

"outputDir": "custom-dist"

564

}

565

},

566

{

567

"plugin": "./tools/local-plugin",

568

"options": {

569

"localOption": "value"

570

}

571

}

572

]

573

}

574

```

575

576

### Plugin Development Best Practices

577

578

```typescript

579

import {

580

NxPlugin,

581

CreateNodes,

582

logger,

583

workspaceRoot

584

} from "nx/src/devkit-exports";

585

586

// Cache parsed configurations to improve performance

587

const configCache = new Map<string, any>();

588

589

const createNodes: CreateNodes = (configFilePath, options, context) => {

590

// Use caching for expensive operations

591

if (configCache.has(configFilePath)) {

592

const cachedConfig = configCache.get(configFilePath);

593

return createProjectFromConfig(cachedConfig, configFilePath, options);

594

}

595

596

try {

597

// Read and parse configuration

598

const config = parseConfigFile(configFilePath);

599

configCache.set(configFilePath, config);

600

601

return createProjectFromConfig(config, configFilePath, options);

602

603

} catch (error) {

604

logger.warn(`Failed to parse config file ${configFilePath}: ${error.message}`);

605

return {}; // Return empty result on error

606

}

607

};

608

609

function createProjectFromConfig(config: any, configFilePath: string, options: any) {

610

const projectRoot = path.relative(workspaceRoot, path.dirname(configFilePath));

611

612

// Validate configuration

613

if (!config || typeof config !== 'object') {

614

logger.warn(`Invalid configuration in ${configFilePath}`);

615

return {};

616

}

617

618

// Create targets with error handling

619

const targets: Record<string, TargetConfiguration> = {};

620

621

try {

622

if (config.build) {

623

targets.build = createBuildTarget(config.build, projectRoot, options);

624

}

625

626

if (config.test) {

627

targets.test = createTestTarget(config.test, projectRoot, options);

628

}

629

} catch (error) {

630

logger.error(`Error creating targets for ${projectRoot}: ${error.message}`);

631

return {};

632

}

633

634

return {

635

projects: {

636

[projectRoot]: {

637

name: config.name || path.basename(projectRoot),

638

targets,

639

tags: config.tags || [],

640

metadata: {

641

configFile: configFilePath,

642

pluginVersion: '1.0.0'

643

}

644

}

645

}

646

};

647

}

648

649

const robustPlugin: NxPlugin = {

650

name: 'robust-plugin',

651

createNodes: ['**/plugin.config.{json,js,ts}', createNodes]

652

};

653

654

export default robustPlugin;

655

```