Extending an existing NX generator
Tags: nx, angular
Published: 2023-07-10 08:35:04

Nx provides a wide range of built-in tools that automatically generate code for you. These code generation tools are called generators in Nx. However, there may be instances where you need to generate code that is **almost** the same as the existing generator, but with some slight differences.

In Nx, it is easy to reuse a built-in generator and customize it to better suit your company’s needs. Since all generators are JS functions, overriding them becomes a child game.

In this article, I will explain how to generate the boilerplate code for a generator _(thanks to another generator 😇)_. Then, we will extend the built-in **@nx/angular:library** generator and override the generated Jest and lint configurations. Often, we need to modify the library configuration file to better align with our company’s requirements. By creating this custom library generator, we can facilitate our team’s workflow by ensuring that all files are correctly set within the Nx monorepo.

This article provides a step-by-step explanation of the process.

## Create a custom plugin:
First, to create your own generator, you need to install the package `@nx/plugin` by executing the following command:

```bash
npm i @nx/plugin -D
```
Once the package is installed, it provides access to 7 built-in generators.

![@nx/plugin](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kgn3swqle37usqg3g1k2.png)

The next step is to create a custom plugin that will serve as a container for all our future generators or executors.

To do this, open the Nx Console _(if you don’t have it, I strongly recommend installing it as a plugin in VSCode or Webstorm)_. Then, click on the **generate** button and search for **@nx/plugin:plugin**.

![nx console view](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4j2218g325cnn77rzulx.png)


![nx console generator view](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5q1zx8u45deanzzujtik.png)


By selecting this option, a new library called **demo** will be created inside your lib folder.

## Create the boilerplate code of our generator.
Go back to the Nx Console and click on **generate** again. This time, search for **@nx/plugin:generator**.


![nx console generator generator view](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/86mxgk26zx48nbooh8rh.png)

This will create the necessary boilerplate code for your generator inside your **demo/src/generators** folder.

![generated files](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/no2e30fp5rjcu5lob7iu.png)


- `schema.d.ts` contains the interface for your input schema. It defines the structure and types of the properties that can be set for your generator.
- `schema.json` is defines how all the properties of your generator will be set and displayed within the Nx Console. `(You can refer to the list of options available [here](https://nx.dev/plugins/recipes/generator-options#schema) to configure this file.)`
- `generator.ts` is where you will write the code for your generator.

Now that we have all the necessary files set up for our generator, we can proceed to start creating its functionality.

## Creation of our generator:
In order to extend the default **@nx/angular:library** generator, we need to locate the generator function within the Nx GitHub source code _(You can find it [here](https://github.com/nrwl/nx/tree/master/packages/angular/src/generators/library))_. As you can see, the structure of the default generator is identical to ours.

Inside `library.ts` the `libraryGenerator` has been exported as a public API. We can simply use it to replicate what the Nx generator does, as shown below:

```ts
import { libraryGenerator } from '@nx/angular/generators';
import { Tree, formatFiles } from '@nx/devkit';
import { LibraryGeneratorSchema } from './schema';

export async function mylibraryGenerator(tree: Tree, options: LibraryGeneratorSchema) {
  await libraryGenerator(tree, options);

  await formatFiles(tree);
}

export default mylibraryGenerator;
```
**Note:** To simplify things, you can copy/paste the `schema.d.ts` and `schema.json` to your own repository.

Alternatively, you can select the properties you want to customize when invoking your generator, and the rest can be passed to the Nx generator as default properties, as shown below:

```ts
import { libraryGenerator } from '@nx/angular/generators';
import { Tree, formatFiles } from '@nx/devkit';
import { LibraryGeneratorSchema } from './schema';
import { Linter } from '@nx/linter';

export async function mylibraryGenerator(tree: Tree, options: LibraryGeneratorSchema) {
  await libraryGenerator(tree, {
    ...options,
    addTailwind: true,
    buildable: true,
    linter: Linter.EsLint
  });

  await formatFiles(tree);
}

export default mylibraryGenerator;
```
In the above code, `addTailwind`, `buildable` and `linter` will not be editable. If we want to provide default values while still allowing them to be edited, we can:

- use the `default` property inside the `schema.json`
- set a default value inside `nx.json` at the root of our project.

```json
{
  "generators": {
    "@my-workspace/demo:library": {
      "linter": "eslint",
      "buildable": true,
      "addTailwind": true
    }
  }
}
```
Now that we have created the same default set of files that the Nx generator creates, we can customize some files according to our needs.

> _If you want to learn by practicing and challenge yourself, try out Angular Challenges. It’s a library that lists multiples Angular, Nx, Rxjs and more Challenges._

## Customize Jest Configuration File:
If we want to generate a different `jest.contig.ts` file with additional properties, we have two options:

### Option 1:
We can skip the generation of all Jest configuration through the Nx generator by setting the following inside the `libraryGenerator` function.

```ts
import { UnitTestRunner } from '@nx/angular/generators';

unitTestRunner: UnitTestRunner.None,
```
This option is suitable if you have many modifications to apply to the default Jest configuration, but keep in mind that you will have to manually update all the necessary files yourself, such as `project.json` with the new test target, `tsconfig.json` with the a `tsconfig.spec.ts` and create additional required files.

### Option 2:
In most cases, this option is preferred as it allows you to update only what needs to be changed. In our case, we only want to update the `jest.config.ts` file.

To achieve this, follow these steps:

- Add a template file named `jest.config.ts__tmpl__` inside the **files** directory of your generator folder. To avoid syntax compilation errors in our IDE, we append **__tmpl__** at the end of the file name.
The content of the template file can be as follows:

```
/* eslint-disable */
export default {
  displayName: '<%= project %>', // 👈 variable
  preset: '<%= offsetFromRoot %>jest.preset.js', // 👈 variable
  setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
  transform: {
    '^.+\\.(ts|mjs|js|html)$': [
      'jest-preset-angular',
      {
        tsconfig: '<rootDir>/tsconfig.spec.json',
        stringifyContentPathRegex: '\\.(html|svg)$',
      },
    ],
  },
  transformIgnorePatterns: ['node_modules/(?!(.*\\.mjs$|lodash-es))'],
};
```
This template file sets two variables that can be edited based on your configuration.

- Finally, add the following code to your generator:

```ts
const projectConfig = readProjectConfiguration(tree, options.project);

tree.delete(join(projectConfig.root, './jest-config.ts'));

generateFiles(
tree,
joinPathFragments(__dirname, 'file'),
projectConfig.root,
{
  tmpl: '',
  project: options.project,
  offsetFromRoot: offsetFromRoot(projectConfig.root),
}
);
```
Let’s go through the code:

- We read the project configuration.
- We delete the configuration file generated by the built-in Nx generator.
- We generate our new configuration file by providing the path to the template file, the path where we want to create this file, and the definition of each variable used inside our template.
By following these steps, you can customize the any TS file according to your needs.

## Customize eslint configuration file:
For this one, we want to add the rule` @typescript-eslint/member-ordering: ‘off’` to all our eslintrc file.

We could do the same as above, replacing the generated file with our new custom file. But since the eslintrc file is a JSON file, we can easily manipulate it.

We will just add the line at the right position inside the file.

Using the utilities provided by `@nx/devkit`, we can use the `updateJson` function to update our file. This function takes three arguments: the Nx tree, the path to the JSON file, and a callback function that manipulate the JSON object.

Here’s the function to achieve this:

```ts
  updateJson(tree, eslintPath, (json) => {
    json.overrides[0].rules = {
      ...json.overrides[0].rules,
      '@typescript-eslint/member-ordering': 'off',
    };
    return json;
  });
```
In the above code, we update the rulesproperty inside the first override object of the ESLint configuration file. We add the rule `@typescript-eslint/member-ordering` with the value off.

To check out the full working example, it’s here. 👇

{% embed https://github.com/tomalaforge/angular-challenges/pull/70 %}

---

Now we have a fully functional generator. If you go back to your NX Console and navigate to the **generate** command, you should see our custom generator listed there.

**Note:** If you don’t see it, simply restart your IDE to allow Nx to detect your new generator.

---

You did it! You have built a fantastic generator that will save you a lot of errors and time. Enjoy the full power of these generators and create as many as your team needs. 🚀🚀🚀

---

You can find me on [Twitter](https://twitter.com/laforge_toma) or [Github](https://github.com/tomalaforge).Don't hesitate to reach out to me if you have any questions.