CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-nx--devkit

The Nx Devkit provides utilities for creating custom generators, executors, and plugins to extend the Nx build system for different technologies and use cases.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

plugin-development.mddocs/

Plugin Development

Plugin system for extending Nx with custom project discovery, dependency creation, and build integration capabilities.

Capabilities

Plugin Architecture

Create Nx plugins that extend workspace functionality with custom project discovery and task integration.

/**
 * Nx Plugin interface for extending workspace capabilities
 */
interface NxPlugin {
  /** Plugin name for identification */
  name: string;
  /** Function to discover and create project configurations */
  createNodes?: CreateNodesFunction;
  /** Function to create dependencies between projects */
  createDependencies?: CreateDependencies;
  /** Function to create metadata for projects */
  createMetadata?: CreateMetadata;
}

/**
 * Version 2 plugin interface with enhanced capabilities
 */
interface NxPluginV2<TOptions = any> {
  /** Plugin name for identification */
  name: string;
  /** Enhanced project discovery function */
  createNodesV2?: CreateNodesV2<TOptions>;
  /** Function to create dependencies between projects */
  createDependencies?: CreateDependencies;
  /** Function to create metadata for projects */
  createMetadata?: CreateMetadata;
  /** Pre-task execution hook */
  preTasksExecution?: PreTasksExecution;
  /** Post-task execution hook */
  postTasksExecution?: PostTasksExecution;
}

Project Discovery

Implement custom project discovery logic to automatically detect and configure projects.

/**
 * Function to create project nodes from configuration files
 */
type CreateNodesFunction<T = any> = (
  configFile: string,
  options: T | undefined,
  context: CreateNodesContext
) => CreateNodesResult | null;

/**
 * Enhanced version 2 create nodes function
 */
interface CreateNodesV2<T = any> {
  (
    configFiles: string[],
    options: T | undefined,
    context: CreateNodesContextV2
  ): Promise<CreateNodesResultV2>;
}

/**
 * Context provided to create nodes functions
 */
interface CreateNodesContext {
  /** Workspace root directory */
  workspaceRoot: string;
  /** Nx configuration */
  nxJsonConfiguration: NxJsonConfiguration;
  /** File changes since last run */
  fileChanges?: FileChange[];
}

/**
 * Enhanced context for version 2 plugins
 */
interface CreateNodesContextV2 extends CreateNodesContext {
  /** Configuration hash for caching */
  configurationHash?: string;
}

/**
 * Result of project node creation
 */
interface CreateNodesResult {
  /** Discovered projects */
  projects?: Record<string, Omit<ProjectConfiguration, "root">>;
  /** External dependencies found */
  externalNodes?: Record<string, ProjectGraphExternalNode>;
}

/**
 * Enhanced result for version 2 plugins
 */
interface CreateNodesResultV2 {
  /** Map of configuration files to their discovered projects */
  projects?: Record<string, CreateNodesResult>;
}

Usage Examples:

import { CreateNodesFunction, CreateNodesResult, CreateNodesContext } from "@nx/devkit";

// Example plugin for discovering Vite projects
const createNodes: CreateNodesFunction<{ buildOutputPath?: string }> = (
  configFilePath,
  options,
  context
) => {
  const projectRoot = dirname(configFilePath);
  const packageJsonPath = join(context.workspaceRoot, projectRoot, "package.json");
  
  if (!existsSync(packageJsonPath)) {
    return null;
  }
  
  const packageJson = readJsonFile(packageJsonPath);
  const projectName = basename(projectRoot);
  
  const result: CreateNodesResult = {
    projects: {
      [projectRoot]: {
        name: projectName,
        sourceRoot: join(projectRoot, "src"),
        projectType: packageJson.private ? "application" : "library",
        targets: {
          build: {
            executor: "@nx/vite:build",
            outputs: [
              options?.buildOutputPath ?? join("{workspaceRoot}", "dist", projectRoot)
            ],
            options: {
              configFile: configFilePath,
            },
          },
          serve: {
            executor: "@nx/vite:dev-server",
            options: {
              configFile: configFilePath,
            },
          },
        },
      },
    },
  };
  
  return result;
};

// Plugin definition
export const VitePlugin: NxPlugin = {
  name: "nx-vite-plugin",
  createNodes: ["**/vite.config.{js,ts}", createNodes],
};

Dependency Creation

Create dynamic dependencies between projects based on analysis of source code or configuration.

/**
 * Function to create dependencies between projects
 */
interface CreateDependencies {
  (
    opts: CreateDependenciesContext
  ): RawProjectGraphDependency[] | Promise<RawProjectGraphDependency[]>;
}

/**
 * Context for dependency creation
 */
interface CreateDependenciesContext {
  /** Projects in the workspace */
  projects: Record<string, ProjectConfiguration>;
  /** External nodes (npm packages) */
  externalNodes: Record<string, ProjectGraphExternalNode>;
  /** File map for analyzing file contents */
  fileMap: ProjectFileMap;
  /** Files that have changed */
  filesToProcess: ProjectFileMap;
  /** Workspace root directory */
  workspaceRoot: string;
  /** Nx configuration */
  nxJsonConfiguration: NxJsonConfiguration;
}

Usage Examples:

import { 
  CreateDependencies, 
  CreateDependenciesContext,
  RawProjectGraphDependency,
  DependencyType 
} from "@nx/devkit";

// Example dependency creator for custom import patterns
const createDependencies: CreateDependencies = (context) => {
  const dependencies: RawProjectGraphDependency[] = [];
  
  // Analyze files for custom dependencies
  Object.entries(context.filesToProcess).forEach(([projectName, files]) => {
    files.forEach(file => {
      const content = readFileSync(join(context.workspaceRoot, file.file), "utf-8");
      
      // Look for custom dependency patterns
      const customImportRegex = /@my-org\/([^'"]+)/g;
      let match;
      
      while ((match = customImportRegex.exec(content)) !== null) {
        const targetPackage = match[1];
        
        // Find corresponding project 
        const targetProject = Object.entries(context.projects).find(
          ([_, config]) => config.name === targetPackage
        );
        
        if (targetProject) {
          dependencies.push({
            sourceFile: file.file,
            target: targetProject[0],
            type: DependencyType.static,
          });
        }
      }
    });
  });
  
  return dependencies;
};

// Plugin with dependency creation
export const CustomDependencyPlugin: NxPlugin = {
  name: "custom-dependency-plugin",
  createDependencies,
};

Metadata Creation

Generate additional metadata for projects that can be used by other tools and plugins.

/**
 * Function to create metadata for projects
 */
interface CreateMetadata {
  (
    graph: ProjectGraph,
    context: CreateMetadataContext
  ): ProjectsMetadata | Promise<ProjectsMetadata>;
}

/**
 * Context for metadata creation
 */
interface CreateMetadataContext {
  /** Workspace root directory */
  workspaceRoot: string;
  /** Nx configuration */
  nxJsonConfiguration: NxJsonConfiguration;
}

/**
 * Metadata for projects
 */
interface ProjectsMetadata {
  [projectName: string]: {
    /** Additional project metadata */
    [key: string]: any;
  };
}

Plugin Lifecycle Hooks

Implement hooks that run before and after task execution for custom logic.

/**
 * Hook that runs before task execution
 */
interface PreTasksExecution {
  (context: PreTasksExecutionContext): void | Promise<void>;
}

/**
 * Hook that runs after task execution
 */
interface PostTasksExecution {
  (context: PostTasksExecutionContext): void | Promise<void>;
}

/**
 * Context for pre-task execution
 */
interface PreTasksExecutionContext {
  /** Project graph */
  projectGraph: ProjectGraph;
  /** Task graph to be executed */
  taskGraph: TaskGraph;
  /** Workspace root */
  workspaceRoot: string;
  /** Nx configuration */
  nxJsonConfiguration: NxJsonConfiguration;
}

/**
 * Context for post-task execution
 */
interface PostTasksExecutionContext extends PreTasksExecutionContext {
  /** Results of task execution */
  taskResults: TaskResults;
}

Usage Examples:

import {
  PreTasksExecution,
  PostTasksExecution,
  PreTasksExecutionContext,
  PostTasksExecutionContext
} from "@nx/devkit";

// Pre-task hook for setup
const preTasksExecution: PreTasksExecution = async (context) => {
  console.log(`Preparing to execute ${Object.keys(context.taskGraph.tasks).length} tasks`);
  
  // Custom setup logic
  await setupCustomEnvironment(context.workspaceRoot);
  
  // Validate prerequisites
  const buildTasks = Object.values(context.taskGraph.tasks)
    .filter(task => task.target.target === "build");
    
  if (buildTasks.length > 0) {
    console.log(`Found ${buildTasks.length} build tasks`);
  }
};

// Post-task hook for cleanup and reporting
const postTasksExecution: PostTasksExecution = async (context) => {
  const results = Object.values(context.taskResults);
  const successful = results.filter(r => r.success).length;
  const failed = results.filter(r => !r.success).length;
  
  console.log(`Task execution complete: ${successful} succeeded, ${failed} failed`);
  
  // Custom cleanup logic
  await cleanupCustomEnvironment(context.workspaceRoot);
  
  // Generate custom reports
  if (failed > 0) {
    generateFailureReport(context.taskResults);
  }
};

// Plugin with lifecycle hooks
export const LifecyclePlugin: NxPluginV2 = {
  name: "lifecycle-plugin",
  preTasksExecution,
  postTasksExecution,
};

Plugin Utilities

Utilities for working with plugins and handling errors.

/**
 * Create project nodes from files using registered plugins
 * @param files - Configuration files to process
 * @param options - Plugin options
 * @param context - Creation context
 * @param plugins - Plugins to use
 * @returns Created nodes result
 */
function createNodesFromFiles<T = any>(
  files: string[],
  options: T,
  context: CreateNodesContextV2,
  plugins: CreateNodesV2<T>[]
): Promise<CreateNodesResultV2>;

/**
 * Error thrown when multiple plugins fail to create nodes
 */
class AggregateCreateNodesError extends Error {
  constructor(
    public readonly errors: Array<[string, Error]>,
    public readonly partialResults: CreateNodesResultV2
  ) {
    super("Multiple plugins failed to create nodes");
  }
}

/**
 * Error thrown when project graph cache is stale
 */
class StaleProjectGraphCacheError extends Error {
  constructor(message: string) {
    super(message);
  }
}

/**
 * Create project nodes from a single configuration file
 * @param configFile - Configuration file to process
 * @param options - Plugin options
 * @param context - Creation context
 * @param plugin - Plugin with createNodes function
 * @returns Created nodes result or null
 */
function createNodesFromFile<T = any>(
  configFile: string,
  options: T,
  context: CreateNodesContext,
  plugin: CreateNodesFunction<T>
): CreateNodesResult | null;

/**
 * Plugin configuration entry in nx.json
 */
interface PluginConfiguration {
  /** Plugin package name or path */
  plugin: string;
  /** Plugin options */
  options?: any;
  /** Include patterns for plugin */
  include?: string[];
  /** Exclude patterns for plugin */
  exclude?: string[];
}

/**
 * Expanded plugin configuration with resolved plugin instance
 */
interface ExpandedPluginConfiguration<T = any> {
  /** Plugin instance */
  plugin: NxPluginV2<T>;
  /** Plugin options */
  options?: T;
  /** Include patterns for plugin */
  include?: string[];
  /** Exclude patterns for plugin */
  exclude?: string[];
}

Advanced Plugin Patterns

Multi-framework Support

// Plugin that supports multiple frameworks
export const UniversalPlugin: NxPluginV2<{
  framework: "react" | "vue" | "angular";
  buildTool: "webpack" | "vite" | "rollup";
}> = {
  name: "universal-plugin",
  
  createNodesV2: async (configFiles, options, context) => {
    const projects: Record<string, CreateNodesResult> = {};
    
    for (const configFile of configFiles) {
      const projectRoot = dirname(configFile);
      const framework = detectFramework(configFile, context);
      const buildTool = options?.buildTool ?? detectBuildTool(configFile);
      
      if (framework) {
        projects[configFile] = {
          projects: {
            [projectRoot]: createProjectConfig(framework, buildTool, projectRoot)
          }
        };
      }
    }
    
    return { projects };
  }
};

Conditional Plugin Loading

// Plugin that conditionally loads based on workspace state
export const ConditionalPlugin: NxPluginV2 = {
  name: "conditional-plugin",
  
  createNodesV2: async (configFiles, options, context) => {
    // Only activate if certain conditions are met
    const hasReactProjects = Object.values(context.nxJsonConfiguration.projects ?? {})
      .some(project => project.tags?.includes("react"));
      
    if (!hasReactProjects) {
      return { projects: {} };
    }
    
    // Continue with plugin logic...
    return processReactProjects(configFiles, context);
  }
};

Plugin Composition

// Compose multiple plugin capabilities
export function createCompositePlugin(
  name: string,
  ...plugins: Partial<NxPluginV2>[]
): NxPluginV2 {
  return {
    name,
    
    createNodesV2: async (configFiles, options, context) => {
      const results: CreateNodesResultV2 = { projects: {} };
      
      for (const plugin of plugins) {
        if (plugin.createNodesV2) {
          const pluginResult = await plugin.createNodesV2(configFiles, options, context);
          
          // Merge results
          Object.assign(results.projects!, pluginResult.projects ?? {});
        }
      }
      
      return results;
    },
    
    createDependencies: async (context) => {
      const allDependencies: RawProjectGraphDependency[] = [];
      
      for (const plugin of plugins) {
        if (plugin.createDependencies) {
          const deps = await plugin.createDependencies(context);
          allDependencies.push(...deps);
        }
      }
      
      return allDependencies;
    }
  };
}

Install with Tessl CLI

npx tessl i tessl/npm-nx--devkit

docs

executors.md

generators.md

index.md

package-management.md

plugin-development.md

project-graph.md

tree-operations.md

utilities.md

workspace-configuration.md

tile.json