AgentSkillsCN

extending-nx-plugins

全面指南:创建并管理Nx插件,包括生成器、推断任务、迁移操作,以及扩展Nx工作空间的最佳实践。

SKILL.md
--- frontmatter
name: extending-nx-plugins
description: Comprehensive guide for creating and managing Nx plugins including generators, inferred tasks, migrations, and best practices for extending Nx workspaces

Extending Nx with Plugins

Source: Nx Documentation - Extending Nx
Last Updated: December 2024
Nx Version: 22

Overview

Nx plugins extend the core functionality of Nx workspaces by leveraging Nx's task running and project graph capabilities. While Nx core handles task execution and workspace understanding, plugins enforce best practices, integrate tooling, and accelerate development workflows.

Core Nx Functionality:

  • Task running and orchestration
  • Project and task graph understanding
  • Caching and optimization

Plugin Capabilities:

  • Enforce organizational best practices
  • Provide code generators
  • Integrate tools seamlessly
  • Configure inferred tasks
  • Share solutions across teams

When to Create a Plugin

As your repository grows, you may need custom plugins for:

  1. Consistency Enforcement: Help coworkers follow best practices with custom code generators
  2. Configuration Management: Remove duplicate configuration with inferred tasks
  3. Multi-Repository Standards: Share presets and migrations across multiple monorepos
  4. Tool Integration: Integrate new tools or frameworks into Nx
  5. Community Contribution: Share your plugin with the broader Nx ecosystem

Creating a Plugin

New Workspace with Plugin

Create a new workspace with a plugin:

bash
npx create-nx-plugin my-plugin

This scaffolds a complete workspace with:

  • Plugin package structure
  • Generator templates
  • Testing setup
  • Build configuration

Add Plugin to Existing Workspace

Add a plugin to an existing Nx workspace:

bash
# Install plugin capability
npx nx add @nx/plugin

# Generate a new plugin
npx nx g plugin tools/my-plugin

This creates:

  • Plugin package in tools/my-plugin
  • Generator scaffolding
  • Plugin registration files

Plugin Components

Code Generators

Generators automate code scaffolding and enforce patterns:

typescript
// Generator function signature
export async function libraryWithReadmeGenerator(
  tree: Tree,
  options: LibraryWithReadmeGeneratorSchema
) {
  // Generator implementation
}

Key Operations:

  • Create new projects and files
  • Modify existing code
  • Update configuration
  • Enforce naming conventions

Inferred Tasks

Inferred tasks automatically configure build, test, and other targets based on project structure:

  • Reduce manual configuration
  • Ensure accurate caching
  • Standardize task execution

Migrations

Migrations help keep projects in sync across updates:

  • Update dependencies
  • Refactor code patterns
  • Modify configuration
  • Maintain consistency

Presets

Presets provide starting points for new repositories:

  • Organization-specific templates
  • Pre-configured tooling
  • Standard project structures

Creating Your First Generator

Generate a Generator

Create a new generator with the Nx CLI:

bash
npx nx g generator my-plugin/src/generators/library-with-readme

This creates:

  • Generator implementation file
  • Schema definition
  • Template files folder
  • Registration in generators.json

Generator Structure

Basic Generator Implementation:

typescript
import { Tree, addProjectConfiguration, generateFiles, formatFiles } from '@nx/devkit';
import * as path from 'path';

export async function libraryWithReadmeGenerator(
  tree: Tree,
  options: LibraryWithReadmeGeneratorSchema
) {
  const projectRoot = `libs/${options.name}`;
  
  // Create project configuration
  addProjectConfiguration(tree, options.name, {
    root: projectRoot,
    projectType: 'library',
    sourceRoot: `${projectRoot}/src`,
    targets: {},
  });
  
  // Generate files from templates
  generateFiles(
    tree,
    path.join(__dirname, 'files'),
    projectRoot,
    options
  );
  
  // Format generated files
  await formatFiles(tree);
}

Key Functions:

  • addProjectConfiguration - Register new project
  • generateFiles - Create files from templates
  • formatFiles - Apply code formatting

Template Files

Templates use EJS syntax for variable injection:

Example README Template:

markdown
<!-- files/README.md.template -->
# <%= name %>

This was generated by the `library-with-readme` generator!

Template Variables:

  • <%= name %> - Inject generator options
  • <% if (condition) { %> - Conditional logic
  • <% for (item of items) { %> - Iteration

File Naming:

  • .template suffix is removed during generation
  • __name__ is replaced with actual values

Running Generators

Dry Run Mode:

bash
# Test generator without creating files
npx nx g my-plugin:library-with-readme mylib --dry-run

Actual Execution:

bash
# Create files
npx nx g my-plugin:library-with-readme mylib

With Options:

bash
# Pass custom options
npx nx g my-plugin:library-with-readme mylib --directory=shared --tags=utils

Helper Functions

The Nx Devkit provides utilities for common plugin operations:

File Operations

typescript
import {
  generateFiles,
  readProjectConfiguration,
  updateProjectConfiguration,
  joinPathFragments
} from '@nx/devkit';

// Generate files from templates
generateFiles(tree, templatePath, targetPath, variables);

// Read project config
const config = readProjectConfiguration(tree, projectName);

// Update project config
updateProjectConfiguration(tree, projectName, updatedConfig);

Project Management

typescript
import {
  addProjectConfiguration,
  removeProjectConfiguration,
  getProjects
} from '@nx/devkit';

// Add new project
addProjectConfiguration(tree, name, config);

// Remove project
removeProjectConfiguration(tree, name);

// Get all projects
const projects = getProjects(tree);

Dependency Management

typescript
import {
  addDependenciesToPackageJson,
  removeDependenciesFromPackageJson
} from '@nx/devkit';

// Add dependencies
addDependenciesToPackageJson(
  tree,
  { 'lodash': '^4.17.21' },  // dependencies
  { '@types/lodash': '^4.14.0' }  // devDependencies
);

String Utilities

typescript
import { names } from '@nx/devkit';

const result = names('my-awesome-lib');
// {
//   name: 'my-awesome-lib',
//   className: 'MyAwesomeLib',
//   propertyName: 'myAwesomeLib',
//   constantName: 'MY_AWESOME_LIB',
//   fileName: 'my-awesome-lib'
// }

Prerequisites for Plugin Development

To effectively develop Nx plugins, you should understand:

  1. Nx Task Execution: How to run tasks and configure inferred tasks
  2. Code Generation: How to use Nx generators
  3. Project Graph: Understanding workspace dependencies
  4. TypeScript: Writing type-safe code

Plugin Use Cases

Organizational Best Practices

Problem: Teams need consistent project structure and standards.

Solution: Create generators that enforce:

  • Naming conventions
  • File organization
  • Required documentation
  • Testing setup

Tool Integration

Problem: New tool needs to work seamlessly with Nx.

Solution: Create plugin that provides:

  • Configuration inference
  • Task executors
  • Code generators
  • Project templates

Multi-Repository Consistency

Problem: Multiple monorepos need to stay in sync.

Solution: Provide:

  • Shared presets
  • Migration generators
  • Common configuration
  • Standardized workflows

Learning Resources

Official Tutorials

Enforce Best Practices Tutorial:

  • Create organization-specific generators
  • Enforce coding standards
  • Implement custom linting rules
  • View Tutorial

Tool Integration Tutorial:

  • Integrate new build tools
  • Create custom executors
  • Configure task inference
  • View Tutorial

Reference Documentation

Best Practices

1. Use Nx Devkit Utilities

Leverage built-in functions instead of manual file operations:

typescript
// Good: Use Nx utilities
import { updateJson } from '@nx/devkit';
updateJson(tree, 'package.json', (json) => {
  json.scripts = { ...json.scripts, test: 'jest' };
  return json;
});

// Bad: Manual file manipulation
const content = tree.read('package.json').toString();
const json = JSON.parse(content);
json.scripts.test = 'jest';
tree.write('package.json', JSON.stringify(json));

2. Provide Clear Schema Definitions

Define generator options with clear descriptions:

json
{
  "cli": "nx",
  "id": "library-with-readme",
  "description": "Generate a library with README",
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "description": "Library name",
      "$default": {
        "$source": "argv",
        "index": 0
      }
    },
    "directory": {
      "type": "string",
      "description": "Directory where library is created"
    }
  },
  "required": ["name"]
}

3. Test Your Generators

Write tests to ensure generator reliability:

typescript
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { libraryWithReadmeGenerator } from './generator';

describe('library-with-readme', () => {
  it('should create README file', async () => {
    const tree = createTreeWithEmptyWorkspace();
    await libraryWithReadmeGenerator(tree, { name: 'test' });
    
    expect(tree.exists('libs/test/README.md')).toBeTruthy();
  });
});

4. Format Generated Code

Always format generated files for consistency:

typescript
import { formatFiles } from '@nx/devkit';

export async function myGenerator(tree: Tree, options: Schema) {
  // ... generator logic
  
  // Format all modified files
  await formatFiles(tree);
}

Common Patterns

Composing Generators

Generators can call other generators:

typescript
import { libraryGenerator } from '@nx/js';

export async function enhancedLibraryGenerator(
  tree: Tree,
  options: Schema
) {
  // Generate base library
  await libraryGenerator(tree, {
    name: options.name,
    directory: 'libs'
  });
  
  // Add custom files
  generateFiles(
    tree,
    path.join(__dirname, 'files'),
    `libs/${options.name}`,
    options
  );
  
  await formatFiles(tree);
}

Conditional File Generation

Generate files based on options:

typescript
export async function myGenerator(tree: Tree, options: Schema) {
  // Always generate base files
  generateFiles(tree, baseFilesPath, projectRoot, options);
  
  // Conditionally generate test files
  if (options.includeTests) {
    generateFiles(tree, testFilesPath, projectRoot, options);
  }
  
  await formatFiles(tree);
}

Updating Existing Projects

Modify existing project configuration:

typescript
import { updateProjectConfiguration, readProjectConfiguration } from '@nx/devkit';

export async function myGenerator(tree: Tree, options: Schema) {
  const projectConfig = readProjectConfiguration(tree, options.project);
  
  // Add new target
  projectConfig.targets.myTarget = {
    executor: 'nx:run-commands',
    options: {
      command: 'echo "Hello"'
    }
  };
  
  updateProjectConfiguration(tree, options.project, projectConfig);
}

Community and Support

Get Help

Contribute

Next Steps

  1. Browse Plugin Registry: Find existing plugins that meet your needs
  2. Follow Tutorials: Complete the step-by-step guides for specific use cases
  3. Join Community: Collaborate with other plugin authors on Discord
  4. Consider Enterprise: Get dedicated support for custom plugin development

Summary

Nx plugins extend workspace capabilities by:

  • Enforcing Standards: Custom generators ensure consistency
  • Reducing Configuration: Inferred tasks eliminate boilerplate
  • Integrating Tools: Seamless tool integration
  • Enabling Reuse: Share solutions across projects and teams

Start with the built-in generator scaffold, leverage Nx Devkit utilities, and follow the official tutorials to build powerful workspace extensions.