Skip to content

Commit

Permalink
docs(core): createNodesV2 in plugin docs (#29346)
Browse files Browse the repository at this point in the history
Update the project graph plugin recipe to use createNodesV2
  • Loading branch information
isaacplmann authored Dec 13, 2024
1 parent 48cd50a commit 012d4e9
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 43 deletions.
2 changes: 1 addition & 1 deletion docs/shared/deprecated/v1-nx-plugin-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export function registerProjectTargets(
}
```

For guidance on implementing a similar function in the v2 API, see the documentation on [createNodes](/extending-nx/recipes/project-graph-plugins#example-extending-projects-adding-targets).
For guidance on implementing a similar function in the v2 API, see the documentation on [createNodes](/extending-nx/recipes/project-graph-plugins#example-extending-projects-adding-inferred-targets).

### Multiple Matches

Expand Down
114 changes: 72 additions & 42 deletions docs/shared/recipes/plugins/project-graph-plugins.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,5 @@
# Extending the Project Graph of Nx

{% callout type="info" title="Looking for `processProjectGraph`?" %}

Prior to Nx 16.7, a plugin could use `processProjectGraph`, `projectFilePatterns`, and `registerProjectTargets` to modify the project graph. This interface is still supported, but it is deprecated. We recommend using the new interface described below. For documentation on the v1 plugin api, see [here](/deprecated/v1-nx-plugin-api)

{% /callout %}

{% callout type="caution" title="Experimental" %}
This API is experimental and might change.
{% /callout %}

The Project Graph is the representation of the source code in your repo. Projects can have files associated with them. Projects can have dependencies on each other.

One of the best features of Nx the ability to construct the project graph automatically by analyzing your source code. Currently, this works best within the JavaScript ecosystem, but it can be extended to other languages and technologies using plugins.
Expand All @@ -34,7 +24,7 @@ You can register a plugin by adding it to the plugins array in `nx.json`:

## Adding New Nodes to the Project Graph

You can add nodes to the project graph with [`createNodes`](/nx-api/devkit/documents/CreateNodes). This is the API that Nx uses under the hood to identify Nx projects coming from a `project.json` file or a `package.json` that's listed in a package manager's workspaces section.
You can add nodes to the project graph with [`createNodesV2`](/nx-api/devkit/documents/CreateNodesV2). This is the API that Nx uses under the hood to identify Nx projects coming from a `project.json` file or a `package.json` that's listed in a package manager's workspaces section.

### Identifying Projects

Expand All @@ -56,19 +46,39 @@ Note: This is a shallow merge, so if you have a target with the same name in bot
A simplified version of Nx's built-in `project.json` plugin is shown below, which adds a new project to the project graph for each `project.json` file it finds. This should be exported from the entry point of your plugin, which is listed in `nx.json`

```typescript {% fileName="/my-plugin/index.ts" %}
export const createNodes: CreateNodes = [
'**/project.json',
(projectConfigurationFile: string, opts, context: CreateNodesContext) => {
const projectConfiguration = readJsonFile(projectConfigurationFile);
const root = dirname(projectConfigurationFile);
import { createNodesFromFiles, readJsonFile } from '@nx/devkit';
import { dirname } from 'path';

return {
projects: {
[root]: projectConfiguration,
},
};
export interface MyPluginOptions {}

export const createNodesV2: CreateNodesV2<MyPluginOptions> = [
'**/project.json',
async (configFiles, options, context) => {
return await createNodesFromFiles(
(configFile, options, context) =>
createNodesInternal(configFile, options, context),
configFiles,
options,
context
);
},
];

async function createNodesInternal(
configFilePath: string,
options: MyPluginOptions,
context: CreateNodesContextV2
) {
const projectConfiguration = readJsonFile(configFilePath);
const root = dirname(configFilePath);

// Project configuration to be merged into the rest of the Nx configuration
return {
projects: {
[root]: projectConfiguration,
},
};
}
```

{% callout type="warning" title="Dynamic target configurations can't be migrated" %}
Expand All @@ -80,43 +90,63 @@ If you create targets for a project within a plugin's code, the Nx migration gen

{% /callout %}

#### Example (extending projects / adding targets)
#### Example (extending projects / adding inferred targets)

When writing a plugin to add support for some tooling, it may need to add a target to an existing project. For example, our @nx/jest plugin adds a target to the project for running Jest tests. This is done by checking for the presence of a jest configuration file, and if it is present, adding a target to the project.

Most of Nx's first party plugins are written to add a target to a given project based on the configuration files present for that project. The below example shows how a plugin could add a target to a project based on the presence of a `tsconfig.json` file.

```typescript {% fileName="/my-plugin/index.ts" %}
export const createNodes: CreateNodes = [
import { createNodesFromFiles, readJsonFile } from '@nx/devkit';
import { dirname } from 'path';

export interface MyPluginOptions {}

export const createNodesV2: CreateNodesV2<MyPluginOptions> = [
'**/tsconfig.json',
(fileName: string, opts, context: CreateNodesContext) => {
const root = dirname(fileName);
async (configFiles, options, context) => {
return await createNodesFromFiles(
(configFile, options, context) =>
createNodesInternal(configFile, options, context),
configFiles,
options,
context
);
},
];

const isProject =
existsSync(join(root, 'project.json')) ||
existsSync(join(root, 'package.json'));
if (!isProject) {
return {};
}
async function createNodesInternal(
configFilePath: string,
options: MyPluginOptions,
context: CreateNodesContextV2
) {
const projectConfiguration = readJsonFile(configFilePath);
const projectRoot = dirname(configFilePath);

const isProject =
existsSync(join(projectRoot, 'project.json')) ||
existsSync(join(projectRoot, 'package.json'));
if (!isProject) {
return {};
}

return {
projects: {
[root]: {
targets: {
build: {
command: `tsc -p ${fileName}`,
},
return {
projects: {
[projectRoot]: {
targets: {
build: {
command: `tsc -p ${fileName}`,
},
},
},
};
},
];
},
};
}
```

By checking for the presence of a `project.json` or `package.json` file, the plugin can be more confident that the project it is modifying is an existing Nx project.

When extending an existing project, its important to consider how Nx will merge the returned project configurations. In general, plugins are run in the order they are listed in `nx.json`, abd then Nx's built-in plugins are run last. Plugins overwrite information that was identified by plugins that run before them if a merge is not possible.
When extending an existing project, its important to consider how Nx will merge the returned project configurations. In general, plugins are run in the order they are listed in `nx.json`, and then Nx's built-in plugins are run last. Plugins overwrite information that was identified by plugins that run before them if a merge is not possible.

Nx considers two identified projects to be the same if and only if they have the same root. If two projects are identified with the same name, but different roots, there will be an error.

Expand Down

0 comments on commit 012d4e9

Please sign in to comment.