DEV Community

Cover image for ⛔ Target Exclusions in Nx Project Crystal
jogelin
jogelin

Posted on • Updated on • Originally published at jgelin.Medium

⛔ Target Exclusions in Nx Project Crystal

UPDATE 2024–04-03:

An official way now exists to exclude or include plugins for a specific project:

feat(core): add ability to scope plugins #22379

Current Behavior

There is no way to users to filter where plugins are applied. Only by the plugin author.

This means that errors thrown make the plugin unusable and more modifications to the project graph are made than desired.

Expected Behavior

Plugins can be scoped via include and exclude which are both lists of file patterns.

{
  plugins: [
    {
      plugin: '@nx/jest/plugin',
      include: ['packages/**/*'],
      exclude: ['e2e/**/*']
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

This will scope running the plugin to processing only those config files.

Related Issue(s)

Fixes #

If we take the example below, you can now use the approach:

{
  "plugins": [
    {
      "plugin": "@nx/jest/plugin",
      "options": {
        "targetName": "test",
        "exclude": ["my-lib-e2e/**/*"]
      }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Since the release of Nx 18, many repositories have started the adoption of Nx Project Crystal. As it’s still the beginning, the feature to exclude some projects from the dynamic target assignment is not supported yet.

In this article, I want to share workarounds and ideas until the Nx team implements something internally.

If you’re looking to understand how Nx assigns targets to projects dynamically, I first recommend reading my article:

Workarounds

Let’s specify the context by mentioning that you have a project named my-lib-e2e and you don't want that project to have a test target even if it contains a jest.config.ts. However, you still want to get the benefit of the @nx/jest/plugin for your other 1000 Nx projects 😋.

Manually Override the “test” Target with nx:noop

In your my-lib-e2e/project.json:

{
  "name": "my-lib-e2e",
  "targets": {
    "test": {}
  }
}
Enter fullscreen mode Exit fullscreen mode

Given that project.json takes precedence over all plugins, the inferred test target from the @nx/jest/plugin will be overridden.

Dynamically Override Any Target with nx:noop

You can also develop a plugin to automatically assign nx:noop on any target. For instance, I crafted a plugin located at tools/nx-plugins/targets-noopify.ts:

import { CreateNodes, ProjectConfiguration, readJsonFile } from '@nx/devkit';
import { dirname, join } from 'node:path';

interface TargetsNoopifyOptions {
  projectName: string;
  targets: string[];
}

export const createNodes: CreateNodes<TargetsNoopifyOptions> = [
  '**/project.json',
  async (configFilePath, options, { workspaceRoot }) => {
    const projectRoot = dirname(configFilePath);

    // get project name from porject.json
    const projectJson = readJsonFile<ProjectConfiguration>(
      join(workspaceRoot, configFilePath)
    );
    const projectName = projectJson.name;

    // check if the target of the project should be noopified
    const targetsToStub = options[projectName];
    if (!targetsToStub) {
      return {};
    }

    // override the targets
    const targets = targetsToStub.reduce(
      (acc, targetName) => ({
        ...acc,
        [targetName]: {},
      }),
      {}
    );

    return {
      projects: {
        [projectRoot]: {
          root: projectRoot,
          targets: targets,
        },
      },
    };
  },
];
Enter fullscreen mode Exit fullscreen mode

This plugin should then be listed last in nx.json:

{
  "plugins": [
    {
      "plugin": "@nx/jest/plugin",
      "options": {
        "targetName": "test"
      }
    },
    // MAKE SURE THIS COMES AFTER THE PLUGIN YOU WISH TO OVERRIDE
    {
      "plugin": "./tools/nx-plugins/targets-noopify.ts",
      "options": {
        "my-lib-e2e": ["test"]
      }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Create Your Custom @nx/jest/plugin and Skip the Projects

Alternatively, you could implement your custom Jest plugin that filters projects. Here’s an example plugin tools/nx-plugins/skip-jest-plugin.ts:

import {
  CreateNodes,
  joinPathFragments,
  ProjectConfiguration,
  readJsonFile,
} from '@nx/devkit';
import { dirname, join } from 'node:path';
import {
  JestPluginOptions,
  createNodes as createJestNodes,
} from '@nx/jest/plugin';
import { readdirSync } from 'fs';

export const createNodes: CreateNodes<
  JestPluginOptions & { skipProjects: string[] }
> = [
  createJestNodes[0],
  async (configFilePath, options, context) => {
    const projectRoot = dirname(configFilePath);

    const siblingFiles = readdirSync(join(context.workspaceRoot, projectRoot));
    if (!siblingFiles.includes('project.json')) {
      return {};
    }

    const path = joinPathFragments(projectRoot, 'project.json');
    const projectJson = readJsonFile<ProjectConfiguration>(path);
    const projectName = projectJson.name;

    if (options.skipProjects.includes(projectName)) {
      return {};
    }

    return createJestNodes[1](configFilePath, options, context);
  },
];
Enter fullscreen mode Exit fullscreen mode

And then, in the nx.json, it should replace @nx/jest/plugin with:

{
  "plugins": [
    // HERE, REPLACE "@nx/jest/plugin" WITH YOUR CUSTOM PLUGIN
    {
      "plugin": "./tools/nx-plugins/skip-jest-plugin.ts",
      "options": {
        "targetName": "test",
        "skipProjects": ["my-lib-e2e"]
      }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Ideas

As Max Kless commented, they are open to ideas, so I jumped into it 🙂

Skip projects per plugin

This is related to the last workaround described above but integrated into Project Crystal. For example, you would have an nx.json like:

{
  "plugins": [
    {
      "plugin": "@nx/jest/plugin",
      "options": {
        "targetName": "test",
        "skipProjects": ["my-lib-e2e"] // Could be a pattern instead
      }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

✅ Specific per plugin

⛔ Duplication in each plugin

⛔ If the plugin adds multiple targets, it is not possible to filter only one

Add “projects” property on targetDefaults

This strategy takes a leaf from the Nx Release workflow, applies it to targetDefaults for more granular control:

{
  "$schema": "./node_modules/nx/schemas/nx-schema.json",
  "targetDefaults": {
    "test": {
      "cache": true,
      "inputs": ["production", "^production"],
      "projects": ["*", "!my-lib-e2e"]
    },
  }
}
Enter fullscreen mode Exit fullscreen mode

✅ Specific per target

✅ Centralized

✅ Re-use existing from Release

⛔ Cannot be specified by executor because the uses Nx Command

⛔ It does not reflect what you can specify in your Project Configuration

In Conclusion

Got more ideas? Feel free to share, and I’ll eagerly incorporate them. Let’s keep the conversation going and make Nx Project Crystal even more versatile.
🚀 Stay Tuned!


Looking for some help?🤝

Connect with me on TwitterLinkedInGithub


Related

Top comments (0)