🚡 Nx Targets Elevated (Part One)
Unlocking the Full Potential of Nx: A Comprehensive Guide to Centralizing Targets for Enhanced Efficiency
That version of the Nx plugin will be deprecated in v18. Please check the new way of doing in my article 💎 Discovering Nx Project Crystal’s Magic
If you’ve ever worked with Nx, you know how invaluable it can be for maintaining large-scale codebases within an organization. From applications and libraries to executors, generators, and plugins, Nx offers a powerful set of tools to streamline development.
However, as projects grow, maintaining hundreds of libraries and managing thousands of lines of duplicated configurations can become cumbersome.
In this article, we’ll explore a lesser-known aspect of the Nx architecture that addresses this issue — centralizing target configurations. By leveraging this approach, we can eliminate duplication and simplify our development process significantly.
How does it work?
When initiating a target build on the “my-app” project, the run command will not only consider targets specified in the related project.json but also check for targets provided by registered Nx plugins. By merging all relevant targets, it efficiently calls the build target.
This means that we can define all reusable targets within a local plugin instead of cluttering our project.json.
How to do it?
Create an Nx plugin
To begin centralizing targets, the first step is to create an Nx plugin. Execute the following command:
$ nx g @nx/plugin:plugin my-plugin
Don’t forget to register your plugin in your nx.json configuration file:
{
"$schema": "./node_modules/nx/schemas/nx-schema.json",
...
"plugins": ["@my-org/my-plugin"]
}
Export your targets from the plugin
To enable dynamic targets, we need to implement and export the registerProjectTargets
function in the plugin's index.ts:
import { TargetConfiguration } from '@nx/devkit';
export const projectFilePatterns = ['project.json'];
export function registerProjectTargets(projectFilePath: string): Record<string, TargetConfiguration> {
return {
build: {
"executor": "@nx/vite:build",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "production",
"options": {
"outputPath": "dist/packages/my-app"
},
"configurations": {
"development": {
"mode": "development"
},
"production": {
"mode": "production"
}
}
},
};
}
As you can see, the configuration mirrors that of your project.json target configuration.
Don’t forget to export the
projectFilePatterns
function, which is essential for project inference.
Customize your configuration
Since this target will be invoked for all projects, customization may be necessary. To do this, modify the registerProjectTargets
function:
export function registerProjectTargets(projectPath: string): Record<string, TargetConfiguration> {
const normalizedPath = normalizePath(projectPath);
const projectRoot = normalizedPath.replace('/project.json','');
const projectName = projectRoot.split('/').pop();
return {
build: {
"executor": "@nx/vite:build",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "production",
"options": {
"outputPath": `dist/packages/${projectName}`
},
"configurations": {
"development": {
"mode": "development"
},
"production": {
"mode": "production"
}
}
},
};
}
In this example, we’ve customized the output path, but any other configurations can be tailored to your specific needs.
Clean up your project.json
With your targets centralized, you can now remove the build target from all application configurations:
{
"name": "my-app",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"sourceRoot": "packages/my-app/src",
"targets": {
// build target can be removed here
"serve": {
...
},
"preview": {
...
},
"test": {
...
},
"lint": {
...
}
}
}
Iterate again for the other targets
Apply the same principles to other targets such as lint, test, serve, and so on.
Conclusion
Incorporating Nx’s centralized target approach offers more than just code generation and execution capabilities. It provides a powerful way to streamline your configuration management.
By adopting this method, you can effortlessly maintain your architecture without duplicating configurations. Any modifications made to configurations will only need to be applied once, benefiting all projects using them.
Additionally, this streamlined approach simplifies generators, as they’ll generate fewer configurations.
Example
You can find an example on my repository here: https://github.com/jogelin/register-nx-targets