The Nx Devkit provides utilities for creating custom generators, executors, and plugins to extend the Nx build system for different technologies and use cases.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Plugin system for extending Nx with custom project discovery, dependency creation, and build integration capabilities.
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;
}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],
};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,
};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;
};
}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,
};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[];
}// 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 };
}
};// 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);
}
};// 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