docs(core): add v2 plugin documentation (#18890)
This commit is contained in:
parent
c46f9e4f97
commit
5623e2c32d
@ -216,16 +216,6 @@
|
||||
"isExternal": false,
|
||||
"path": "/extending-nx/recipes/project-graph-plugins",
|
||||
"tags": ["create-your-own-plugin", "explore-graph"]
|
||||
},
|
||||
{
|
||||
"id": "project-inference-plugins",
|
||||
"name": "Identify Custom Projects",
|
||||
"description": "",
|
||||
"file": "shared/recipes/plugins/project-inference-plugins",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/extending-nx/recipes/project-inference-plugins",
|
||||
"tags": ["create-your-own-plugin"]
|
||||
}
|
||||
],
|
||||
"isExternal": false,
|
||||
@ -341,15 +331,5 @@
|
||||
"isExternal": false,
|
||||
"path": "/extending-nx/recipes/project-graph-plugins",
|
||||
"tags": ["create-your-own-plugin", "explore-graph"]
|
||||
},
|
||||
"/extending-nx/recipes/project-inference-plugins": {
|
||||
"id": "project-inference-plugins",
|
||||
"name": "Identify Custom Projects",
|
||||
"description": "",
|
||||
"file": "shared/recipes/plugins/project-inference-plugins",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/extending-nx/recipes/project-inference-plugins",
|
||||
"tags": ["create-your-own-plugin"]
|
||||
}
|
||||
}
|
||||
|
||||
@ -4953,6 +4953,14 @@
|
||||
}
|
||||
],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "v1 Nx Plugin API",
|
||||
"path": "/deprecated/v1-nx-plugin-api",
|
||||
"id": "v1-nx-plugin-api",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
}
|
||||
],
|
||||
"disableCollapsible": false
|
||||
@ -5118,6 +5126,14 @@
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "v1 Nx Plugin API",
|
||||
"path": "/deprecated/v1-nx-plugin-api",
|
||||
"id": "v1-nx-plugin-api",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "See Also",
|
||||
"path": "/see-also",
|
||||
@ -5443,14 +5459,6 @@
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Identify Custom Projects",
|
||||
"path": "/extending-nx/recipes/project-inference-plugins",
|
||||
"id": "project-inference-plugins",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
}
|
||||
],
|
||||
"disableCollapsible": false
|
||||
@ -5542,14 +5550,6 @@
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Identify Custom Projects",
|
||||
"path": "/extending-nx/recipes/project-inference-plugins",
|
||||
"id": "project-inference-plugins",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@ -6177,6 +6177,16 @@
|
||||
"isExternal": false,
|
||||
"path": "/deprecated/storybook",
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
"id": "v1-nx-plugin-api",
|
||||
"name": "v1 Nx Plugin API",
|
||||
"description": "",
|
||||
"file": "shared/deprecated/v1-nx-plugin-api",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/deprecated/v1-nx-plugin-api",
|
||||
"tags": []
|
||||
}
|
||||
],
|
||||
"isExternal": false,
|
||||
@ -6384,6 +6394,16 @@
|
||||
"path": "/deprecated/storybook/upgrade-storybook-v6-react",
|
||||
"tags": []
|
||||
},
|
||||
"/deprecated/v1-nx-plugin-api": {
|
||||
"id": "v1-nx-plugin-api",
|
||||
"name": "v1 Nx Plugin API",
|
||||
"description": "",
|
||||
"file": "shared/deprecated/v1-nx-plugin-api",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/deprecated/v1-nx-plugin-api",
|
||||
"tags": []
|
||||
},
|
||||
"/see-also": {
|
||||
"id": "see-also",
|
||||
"name": "See Also",
|
||||
|
||||
@ -667,13 +667,6 @@
|
||||
"id": "project-graph-plugins",
|
||||
"name": "Modify the Project Graph",
|
||||
"path": "/extending-nx/recipes/project-graph-plugins"
|
||||
},
|
||||
{
|
||||
"description": "",
|
||||
"file": "shared/recipes/plugins/project-inference-plugins",
|
||||
"id": "project-inference-plugins",
|
||||
"name": "Identify Custom Projects",
|
||||
"path": "/extending-nx/recipes/project-inference-plugins"
|
||||
}
|
||||
],
|
||||
"module-federation": [
|
||||
|
||||
@ -1454,6 +1454,11 @@
|
||||
"file": "shared/deprecated/storybook/storybook-v6-react"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "v1 Nx Plugin API",
|
||||
"id": "v1-nx-plugin-api",
|
||||
"file": "shared/deprecated/v1-nx-plugin-api"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -1649,12 +1654,6 @@
|
||||
"id": "project-graph-plugins",
|
||||
"tags": ["create-your-own-plugin", "explore-graph"],
|
||||
"file": "shared/recipes/plugins/project-graph-plugins"
|
||||
},
|
||||
{
|
||||
"name": "Identify Custom Projects",
|
||||
"id": "project-inference-plugins",
|
||||
"tags": ["create-your-own-plugin"],
|
||||
"file": "shared/recipes/plugins/project-inference-plugins"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
245
docs/shared/deprecated/v1-nx-plugin-api.md
Normal file
245
docs/shared/deprecated/v1-nx-plugin-api.md
Normal file
@ -0,0 +1,245 @@
|
||||
# Extending the Project Graph of Nx (v1 API)
|
||||
|
||||
{% callout type="caution" title="Experimental" %}
|
||||
This API has been superceded by the [v2 API](/extending-nx/recipes/project-graph-plugins) and will be removed in Nx 18. If targeting Nx version 16.7 or higher, please use the v2 API instead.
|
||||
{% /callout %}
|
||||
|
||||
The v1 plugin API for modifying the project graph was split into two parts:
|
||||
|
||||
- [Project Inference Plugins](#project-inference-plugins)
|
||||
- [Project Graph Plugins](#project-graph-plugins)
|
||||
|
||||
Project inference plugins are used to infer projects from the file system. Project graph plugins are used to modify the project graph after projects have been identified. In the v2 API, there are still two parts but they are divided differently:
|
||||
|
||||
- [createNodes](/extending-nx/recipes/project-graph-plugins#adding-new-nodes-to-the-project-graph)
|
||||
- [createDependencies](/extending-nx/recipes/project-graph-plugins#adding-new-dependencies-to-the-project-graph)
|
||||
|
||||
These are much clearer in terms of responsibility, and are more flexible. The v1 API is still documented below for reference.
|
||||
|
||||
## Adding Plugins to Workspace
|
||||
|
||||
You can register a plugin by adding it to the plugins array in `nx.json`:
|
||||
|
||||
```jsonc {% fileName="nx.json" %}
|
||||
{
|
||||
...,
|
||||
"plugins": [
|
||||
"awesome-plugin"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Project Inference Plugins
|
||||
|
||||
Project inference describes the ability of Nx to discover and work with projects based on source code and configuration files in your repo. Out of the box, Nx identifies projects based on the presence of `package.json` and `project.json` files. It also identifies targets in the `package.json` scripts and the `project.json` `target`s.
|
||||
|
||||
Project inference plugins allow you to extend this functionality of Nx to other languages and file structures.
|
||||
|
||||
### Project File Patterns
|
||||
|
||||
Project file patterns are used in two scenarios:
|
||||
|
||||
- Inferring projects
|
||||
- Determining which files should be passed into `registerProjectTargets`.
|
||||
|
||||
Let's use the below plugin and workspace layout as an example:
|
||||
|
||||
```typescript {% fileName="libs/awesome-plugin/index.ts" %}
|
||||
export const projectFilePatterns = ['project.json', 'my-other-project-file'];
|
||||
export function registerProjectTargets(projectFilePath) {
|
||||
console.log(projectFilePath);
|
||||
}
|
||||
```
|
||||
|
||||
> workspace layout
|
||||
|
||||
```text
|
||||
my-workspace/
|
||||
├─ node_modules/
|
||||
├─ libs/
|
||||
│ ├─ my-project/
|
||||
│ │ ├─ my-other-project-file
|
||||
│ ├─ nx-project/
|
||||
│ │ ├─ my-other-project-file
|
||||
│ │ ├─ project.json
|
||||
├─ nx.json
|
||||
└─ package.json
|
||||
|
||||
```
|
||||
|
||||
During initialization, we would expect to see "libs/my-project/my-other-project-file", "libs/nx-project/my-other-project-file", "libs/nx-project/project.json" all logged out to the console. Nx was able to infer `my-project` from the layout.
|
||||
|
||||
### Implementing a Project Target Configurator
|
||||
|
||||
A project target configurator is a function that takes a path to a project file, and returns the targets inferred from that file.
|
||||
|
||||
Plugins should export a function named `registerProjectTargets` that infers the targets from each matching project file. This function receives the path to the project file as its sole parameter.
|
||||
|
||||
The `registerProjectTargets` function should return a `Record<string, TargetConfiguration>`, which describes the targets inferred for that specific project file.
|
||||
|
||||
```typescript
|
||||
import { TargetConfiguration } from '@nx/devkit';
|
||||
|
||||
export const projectFilePatterns = ['project.json', 'my-other-project-file'];
|
||||
|
||||
export function registerProjectTargets(
|
||||
projectFilePath: string
|
||||
): Record<string, TargetConfiguration> {
|
||||
return {
|
||||
build: {
|
||||
/**
|
||||
* This object should look exactly like a target
|
||||
* configured inside `project.json`
|
||||
*/
|
||||
},
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Multiple Matches
|
||||
|
||||
It is possible that the registerProjectTargets function may be called multiple times for one project. This could occur in a few cases, one of which is demonstrated above.
|
||||
|
||||
- One plugin may list multiple file patterns, and a project may match more than one of them.
|
||||
- Multiple plugins may list similar patterns, and pick up the project separately.
|
||||
|
||||
**In the first case**, the plugin that you are writing will be called into multiple times. If you return the same target (e.g. `build`) on each call, whichever is ran last would be the target that Nx calls into.
|
||||
|
||||
The order that the function would be called is **NOT** guaranteed, so you should try to avoid this when possible. If specifying multiple patterns, they should either be mutually exclusive (e.g. one match per project) or the plugin should conditionally add targets based on the file passed in.
|
||||
|
||||
**In the second case**, different plugins may attempt to register the same target on a project. If this occurs, whichever target was registered by the plugin listed latest in `nx.json` would be the one called into by Nx. As an example, assume `plugin-a`, `plugin-b`, and `plugin-c` all match a file and register `build` as a target. If `nx.json` included `"plugins": ["plugin-a", "plugin-b", "plugin-c"]`, running `nx build my-project` would run the target as defined by `"plugin-c"`.
|
||||
|
||||
Alternatively, if `nx.json` included `"plugins": ["plugin-c", "plugin-b", "plugin-a"]`, running `nx build my-project` would run the target as defined by `"plugin-a"`.
|
||||
|
||||
### Development Tips
|
||||
|
||||
There is a cache that Nx uses to avoid recalculating the project graph as much as possible, but it may need to be skipped during plugin development. You can set the following environment variable to disable the project graph cache: `NX_CACHE_PROJECT_GRAPH=false`.
|
||||
|
||||
It might also be a good idea to ensure that the dep graph is not running on the nx daemon by setting `NX_DAEMON=false`, as this will ensure you will be able to see any `console.log` statements you add as you're developing. You can also leave the daemon active, but `console.log` statements would only appear in its log file.
|
||||
|
||||
## Project Graph Plugins
|
||||
|
||||
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.
|
||||
|
||||
### Implementing a Project Graph Processor
|
||||
|
||||
A Project Graph Processor takes a project graph and returns a new project graph. It can add/remove nodes and edges.
|
||||
|
||||
Plugins should export a function named `processProjectGraph` that handles updating the project graph with new nodes and edges. This function receives two things:
|
||||
|
||||
- A `ProjectGraph`
|
||||
|
||||
- `graph.nodes` lists all the projects currently known to Nx.
|
||||
- `graph.dependencies` lists the dependencies between projects.
|
||||
|
||||
- A `Context`
|
||||
- `context.workspace` contains the combined configuration for the workspace.
|
||||
- `files` contains all the files found in the workspace.
|
||||
- `filesToProcess` contains all the files that have changed since the last invocation and need to be reanalyzed.
|
||||
|
||||
The `processProjectGraph` function should return an updated `ProjectGraph`. This is most easily done using `ProjectGraphBuilder`. The builder is there for convenience, so you don't have to use it.
|
||||
|
||||
```typescript
|
||||
import {
|
||||
ProjectGraph,
|
||||
ProjectGraphBuilder,
|
||||
ProjectGraphProcessorContext,
|
||||
DependencyType,
|
||||
} from '@nx/devkit';
|
||||
|
||||
export function processProjectGraph(
|
||||
graph: ProjectGraph,
|
||||
context: ProjectGraphProcessorContext
|
||||
): ProjectGraph {
|
||||
const builder = new ProjectGraphBuilder(graph);
|
||||
// We will see how this is used below.
|
||||
return builder.getUpdatedProjectGraph();
|
||||
}
|
||||
```
|
||||
|
||||
### Adding New Nodes to the Project Graph
|
||||
|
||||
You can add nodes to the project graph. Since first-party code is added to the graph automatically, this is most commonly used for third-party packages.
|
||||
|
||||
A Project Graph Plugin can add them to the project graph. After these packages are added as nodes to the project graph, dependencies can then be drawn from the workspace projects to the third party packages as well as between the third party packages.
|
||||
|
||||
```typescript
|
||||
// Add a new node
|
||||
builder.addNode({
|
||||
name: 'new-project',
|
||||
type: 'npm',
|
||||
data: {
|
||||
files: [],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
{% callout type="note" title="More details" %}
|
||||
You can designate any type for the node. This differentiates third party projects from projects in the workspace. If you are writing a plugin for a different language, it's common to use IPC to get the list of nodes which you can then add using the builder.
|
||||
{% /callout %}
|
||||
|
||||
### Adding New Dependencies to the Project Graph
|
||||
|
||||
It's more common for plugins to create new dependencies. First-party code contained in the workspace is added to the project graph automatically. Whether your project contains TypeScript or say Java, both projects will be created in the same way. However, Nx does not know how to analyze Java sources, and that's what plugins can do.
|
||||
|
||||
You can create 2 types of dependencies.
|
||||
|
||||
#### Implicit Dependencies
|
||||
|
||||
An implicit dependency is not associated with any file, and can be created as follows:
|
||||
|
||||
```typescript
|
||||
// Add a new edge
|
||||
builder.addImplicitDependency('existing-project', 'new-project');
|
||||
```
|
||||
|
||||
{% callout type="note" title="More details" %}
|
||||
Even though the plugin is written in JavaScript, resolving dependencies of different languages will probably be more easily written in their native language. Therefore, a common approach is to spawn a new process and communicate via IPC or `stdout`.
|
||||
{% /callout %}
|
||||
|
||||
Because an implicit dependency is not associated with any file, Nx doesn't know when it might change, so it will be recomputed every time.
|
||||
|
||||
#### Static Dependencies
|
||||
|
||||
Nx knows what files have changed since the last invocation. Only those files will be present in the provided `filesToProcess`. You can associate a dependency with a particular file (e.g., if that file contains an import).
|
||||
|
||||
```typescript
|
||||
// Add a new edge
|
||||
builder.addStaticDependency(
|
||||
'existing-project',
|
||||
'new-project',
|
||||
'libs/existing-project/src/index.ts'
|
||||
);
|
||||
```
|
||||
|
||||
If a file hasn't changed since the last invocation, it doesn't need to be reanalyzed. Nx knows what dependencies are associated with what files, so it will reuse this information for the files that haven't changed.
|
||||
|
||||
#### Dynamic Dependencies
|
||||
|
||||
Dynamic dependencies are a special type of explicit dependencies. In contrast to standard `explicit` dependencies, they are only imported in the runtime under specific conditions.
|
||||
A typical example would be lazy-loaded routes. Having separation between these two allows us to identify situations where static import breaks the lazy-loading.
|
||||
|
||||
```typescript
|
||||
import { DependencyType } from '@nx/devkit';
|
||||
|
||||
// Add a new edge
|
||||
builder.addDynamicDependency(
|
||||
'existing-project',
|
||||
'lazy-route',
|
||||
'libs/existing-project/src/router-setup.ts'
|
||||
);
|
||||
```
|
||||
|
||||
### Visualizing the Project Graph
|
||||
|
||||
You can then visualize the project graph as described [here](/core-features/explore-graph). However, there is a cache that Nx uses to avoid recalculating the project graph as much as possible. As you develop your project graph plugin, it might be a good idea to set the following environment variable to disable the project graph cache: `NX_CACHE_PROJECT_GRAPH=false`.
|
||||
|
||||
It might also be a good idea to ensure that the dep graph is not running on the nx daemon by setting `NX_DAEMON=false`, as this will ensure you will be able to see any `console.log` statements you add as you're developing.
|
||||
|
||||
### Example Project Graph Plugin
|
||||
|
||||
The [nrwl/nx-go-project-graph-plugin](https://github.com/nrwl/nx-go-project-graph-plugin) repo contains an example project graph plugin which adds [Go](https://golang.org/) dependencies to the Nx Project Graph! A similar approach can be used for other languages.
|
||||
|
||||
{% github-repository url="https://github.com/nrwl/nx-go-project-graph-plugin" /%}
|
||||
@ -5,7 +5,7 @@ Nx used to have a `workspace.json` file at the root of the repo that at various
|
||||
1. Identified the locations of all project in the repo
|
||||
2. Contained the target configuration for all projects
|
||||
|
||||
Identifying the locations of projects is now done automatically through project inference. You can even customize how projects are inferred with a [project inference plugin](/extending-nx/recipes/project-inference-plugins).
|
||||
Identifying the locations of projects is now done automatically through project inference. You can even customize how projects are inferred with a [project inference plugin](/extending-nx/recipes/project-graph-plugins).
|
||||
|
||||
The target configuration for each project is now stored in individual `project.json` files or `package.json` files.
|
||||
|
||||
|
||||
@ -25890,7 +25890,7 @@
|
||||
"hash": "f038fe0b5e4a2b942c3b46519b847306125f0127"
|
||||
},
|
||||
{
|
||||
"file": "docs/shared/extending-nx/project-inference-plugins.md",
|
||||
"file": "docs/shared/extending-nx/project-graph-plugins.md",
|
||||
"hash": "309aa493244edc1907fb5dcd1bbc953bdcc47020"
|
||||
},
|
||||
{
|
||||
|
||||
@ -50,10 +50,9 @@ If your plugin has functionality that would be useful in more than just your rep
|
||||
|
||||
You can also hook into the way Nx works and modify it to suit your needs
|
||||
|
||||
{% cards cols="3" %}
|
||||
{% cards cols="2" %}
|
||||
|
||||
{% card title="Scaffold a New Workspace" description="Set up a new repo" url="/extending-nx/recipes/create-preset" /%}
|
||||
{% card title="Project Inference Plugins" description="Modify how Nx identifies projects" url="/extending-nx/recipes/project-inference-plugins" /%}
|
||||
{% card title="Project Graph Plugins" description="Modify the Nx graph" url="/extending-nx/recipes/project-graph-plugins" /%}
|
||||
|
||||
{% /cards %}
|
||||
|
||||
@ -1,5 +1,11 @@
|
||||
# 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 %}
|
||||
@ -8,6 +14,11 @@ The Project Graph is the representation of the source code in your repo. Project
|
||||
|
||||
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.
|
||||
|
||||
Project graph plugins are able to add new nodes or dependencies to the project graph. This allows you to extend the project graph with new projects and dependencies. The API is defined by two exported members, which are described below:
|
||||
|
||||
- [createNodes](#adding-new-nodes-to-the-project-graph): This tuple allows a plugin to tell Nx information about projects that are identified by a given file.
|
||||
- [createDependencies](#adding-new-dependencies-to-the-project-graph): This function allows a plugin to tell Nx about dependencies between projects.
|
||||
|
||||
## Adding Plugins to Workspace
|
||||
|
||||
You can register a plugin by adding it to the plugins array in `nx.json`:
|
||||
@ -21,123 +32,187 @@ You can register a plugin by adding it to the plugins array in `nx.json`:
|
||||
}
|
||||
```
|
||||
|
||||
## Implementing a Project Graph Processor
|
||||
|
||||
A Project Graph Processor takes a project graph and returns a new project graph. It can add/remove nodes and edges.
|
||||
|
||||
Plugins should export a function named `processProjectGraph` that handles updating the project graph with new nodes and edges. This function receives two things:
|
||||
|
||||
- A `ProjectGraph`
|
||||
|
||||
- `graph.nodes` lists all the projects currently known to Nx.
|
||||
- `graph.dependencies` lists the dependencies between projects.
|
||||
|
||||
- A `Context`
|
||||
- `context.workspace` contains the combined configuration for the workspace.
|
||||
- `files` contains all the files found in the workspace.
|
||||
- `filesToProcess` contains all the files that have changed since the last invocation and need to be reanalyzed.
|
||||
|
||||
The `processProjectGraph` function should return an updated `ProjectGraph`. This is most easily done using `ProjectGraphBuilder`. The builder is there for convenience, so you don't have to use it.
|
||||
|
||||
```typescript
|
||||
import {
|
||||
ProjectGraph,
|
||||
ProjectGraphBuilder,
|
||||
ProjectGraphProcessorContext,
|
||||
DependencyType,
|
||||
} from '@nx/devkit';
|
||||
|
||||
export function processProjectGraph(
|
||||
graph: ProjectGraph,
|
||||
context: ProjectGraphProcessorContext
|
||||
): ProjectGraph {
|
||||
const builder = new ProjectGraphBuilder(graph);
|
||||
// We will see how this is used below.
|
||||
return builder.getUpdatedProjectGraph();
|
||||
}
|
||||
```
|
||||
|
||||
## Adding New Nodes to the Project Graph
|
||||
|
||||
You can add nodes to the project graph. Since first-party code is added to the graph automatically, this is most commonly used for third-party packages.
|
||||
You can add nodes to the project graph with [`createNodes`](/packages/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.
|
||||
|
||||
A Project Graph Plugin can add them to the project graph. After these packages are added as nodes to the project graph, dependencies can then be drawn from the workspace projects to the third party packages as well as between the third party packages.
|
||||
### Identifying Projects
|
||||
|
||||
```typescript
|
||||
// Add a new node
|
||||
builder.addNode({
|
||||
name: 'new-project',
|
||||
type: 'npm',
|
||||
data: {
|
||||
files: [],
|
||||
Looking at the tuple, you can see that the first element is a file pattern. This is a glob pattern that Nx will use to find files in your workspace. The second element is a function that will be called for each file that matches the pattern. The function will be called with the path to the file and a context object. Your plugin can then return a set of projects and external nodes.
|
||||
|
||||
If a plugin identifies a project that is already in the project graph, it will be merged with the information that is already present. The builtin plugins that identify projects from `package.json` files and `project.json` files are ran after any plugins listed in `nx.json`, and as such will overwrite any configuration that was identified by them. In practice, this means that if a project has both a `project.json`, and a file that your plugin identified, the settings the plugin idenfied will be overwritten by the `project.json`'s contents.
|
||||
|
||||
Project nodes in the graph are considered to be the same if the project has the same root. If multiple plugins identify a project with the same root, the project will be merged. In doing so, the name that is already present in the graph is kept, and the properties below are shallowly merged. Any other properties are overwritten.
|
||||
|
||||
- `targets`
|
||||
- `tags`
|
||||
- `implicitDependencies`
|
||||
- `generators`
|
||||
|
||||
Note: This is a shallow merge, so if you have a target with the same name in both plugins, the target from the second plugin will overwrite the target from the first plugin. Options, configurations, or any other properties within the target will be overwritten **_not_** merged.
|
||||
|
||||
#### Example
|
||||
|
||||
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, context: CreateNodesContext) => {
|
||||
const projectConfiguration = readJson(projectConfigurationFile);
|
||||
const projectRoot = dirname(projectConfigurationFile);
|
||||
const projectName = projectConfiguration.name;
|
||||
|
||||
return {
|
||||
projects: {
|
||||
[projectName]: {
|
||||
...projectConfiguration,
|
||||
root: projectRoot,
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
{% callout type="note" title="More details" %}
|
||||
You can designate any type for the node. This differentiates third party projects from projects in the workspace. If you are writing a plugin for a different language, it's common to use IPC to get the list of nodes which you can then add using the builder.
|
||||
{% /callout %}
|
||||
### Adding External Nodes
|
||||
|
||||
Additionally, plugins can add external nodes to the project graph. External nodes are nodes that are not part of the workspace, but are still part of the project graph. This is useful for things like npm packages, or other external dependencies that are not part of the workspace.
|
||||
|
||||
External nodes are identified by a unique name, and if plugins identify an external node with the same name, the external node will be **_overwritten_**. This is different from projects, where the properties are merged, but is handled this way as it should not be as common and there are less useful properties to merge.
|
||||
|
||||
## Adding New Dependencies to the Project Graph
|
||||
|
||||
It's more common for plugins to create new dependencies. First-party code contained in the workspace is added to the project graph automatically. Whether your project contains TypeScript or say Java, both projects will be created in the same way. However, Nx does not know how to analyze Java sources, and that's what plugins can do.
|
||||
|
||||
You can create 2 types of dependencies.
|
||||
The shape of the [`createDependencies`](/packages/devkit/documents/CreateDependencies) function follows:
|
||||
|
||||
```typescript
|
||||
export type CreateDependencies = (
|
||||
context: CreateDependenciesContext
|
||||
) =>
|
||||
| ProjectGraphDependencyWithFile[]
|
||||
| Promise<ProjectGraphDependencyWithFile[]>;
|
||||
```
|
||||
|
||||
In the `createDependencies` function, you can analyze the files in the workspace and return a list of dependencies. It's up to the plugin to determine how to analyze the files. This should also be exported from the plugin's entry point, as listed in `nx.json`.
|
||||
|
||||
Within the `CreateDependenciesContext`, you have access to the current project graph, the configuration of each project in the workspace, the `nx.json` configuration from the workspace, all files in the workspace, and files that have changed since the last invocation. It's important to utilize the `filesToProcess` parameter, as this will allow Nx to only reanalyze files that have changed since the last invocation, and reuse the information from the previous invocation for files that haven't changed.
|
||||
|
||||
`@nx/devkit` exports a function called `validateDependency` which can be used to validate a dependency. This function takes in a `ProjectGraphDependencyWithFile` and a `ProjectGraph` and throws an error if the dependency is invalid. This function is called when the returned dependencies are merged with the existing project graph, but may be useful to call within your plugin to validate dependencies before returning them when debugging.
|
||||
|
||||
The dependencies can be of three types:
|
||||
|
||||
- Implicit
|
||||
- Static
|
||||
- Dynamic
|
||||
|
||||
### Implicit Dependencies
|
||||
|
||||
An implicit dependency is not associated with any file, and can be created as follows:
|
||||
|
||||
```typescript
|
||||
// Add a new edge
|
||||
builder.addImplicitDependency('existing-project', 'new-project');
|
||||
{
|
||||
source: 'existing-project',
|
||||
target: 'new-project',
|
||||
dependencyType: DependencyType.implicit,
|
||||
}
|
||||
```
|
||||
|
||||
{% callout type="note" title="More details" %}
|
||||
Even though the plugin is written in JavaScript, resolving dependencies of different languages will probably be more easily written in their native language. Therefore, a common approach is to spawn a new process and communicate via IPC or `stdout`.
|
||||
{% /callout %}
|
||||
|
||||
Because an implicit dependency is not associated with any file, Nx doesn't know when it might change, so it will be recomputed every time.
|
||||
|
||||
## Static Dependencies
|
||||
### Static Dependencies
|
||||
|
||||
Nx knows what files have changed since the last invocation. Only those files will be present in the provided `filesToProcess`. You can associate a dependency with a particular file (e.g., if that file contains an import).
|
||||
|
||||
```typescript
|
||||
// Add a new edge
|
||||
builder.addStaticDependency(
|
||||
'existing-project',
|
||||
'new-project',
|
||||
'libs/existing-project/src/index.ts'
|
||||
);
|
||||
{
|
||||
source: 'existing-project',
|
||||
target: 'new-project',
|
||||
sourceFile: 'libs/existing-project/src/index.ts',
|
||||
dependencyType: DependencyType.static,
|
||||
}
|
||||
```
|
||||
|
||||
If a file hasn't changed since the last invocation, it doesn't need to be reanalyzed. Nx knows what dependencies are associated with what files, so it will reuse this information for the files that haven't changed.
|
||||
|
||||
## Dynamic Dependencies
|
||||
### Dynamic Dependencies
|
||||
|
||||
Dynamic dependencies are a special type of explicit dependencies. In contrast to standard `explicit` dependencies, they are only imported in the runtime under specific conditions.
|
||||
A typical example would be lazy-loaded routes. Having separation between these two allows us to identify situations where static import breaks the lazy-loading.
|
||||
|
||||
```typescript
|
||||
import { DependencyType } from '@nx/devkit';
|
||||
|
||||
// Add a new edge
|
||||
builder.addDynamicDependency(
|
||||
'existing-project',
|
||||
'lazy-route',
|
||||
'libs/existing-project/src/router-setup.ts'
|
||||
);
|
||||
{
|
||||
source: 'existing-project',
|
||||
target: 'new-project',
|
||||
sourceFile: 'libs/existing-project/src/index.ts',
|
||||
dependencyType: DependencyType.dynamic,
|
||||
}
|
||||
```
|
||||
|
||||
### Example
|
||||
|
||||
{% callout type="note" title="More details" %}
|
||||
Even though the plugin is written in JavaScript, resolving dependencies of different languages will probably be more easily written in their native language. Therefore, a common approach is to spawn a new process and communicate via IPC or `stdout`.
|
||||
{% /callout %}
|
||||
|
||||
A small plugin that recognizes dependencies to projects in the current workspace which a referenced in another project's `package.json` file may look like so:
|
||||
|
||||
```typescript {% fileName="/my-plugin/index.ts" %}
|
||||
export const createNodes: CreateNodes = (ctx) => {
|
||||
const packageJsonProjectMap = new Map();
|
||||
const nxProjects = Object.values(ctx.projectsConfigurations);
|
||||
const results = [];
|
||||
for (const project of nxProjects) {
|
||||
const maybePackageJsonPath = join(project.root, 'package.json');
|
||||
if (existsSync(maybePackageJsonPath)) {
|
||||
const json = JSON.parse(maybePackageJsonPath);
|
||||
packageJsonProjectMap.set(json.name, project.name);
|
||||
}
|
||||
}
|
||||
for (const project of nxProjects) {
|
||||
const maybePackageJsonPath = join(project.root, 'package.json');
|
||||
if (existsSync(maybePackageJsonPath)) {
|
||||
const json = JSON.parse(maybePackageJsonPath);
|
||||
const deps = [...Object.keys(json.dependencies)];
|
||||
for (const dep of deps) {
|
||||
if (packageJsonProjectMap.has(dep)) {
|
||||
const newDependency = {
|
||||
source: project,
|
||||
target: packageJsonProjectMap.get(dep),
|
||||
sourceFile: maybePackageJsonPath,
|
||||
dependencyType: DependencyType.static,
|
||||
};
|
||||
}
|
||||
validateDependency(ctx.graph, newDependency);
|
||||
results.push(newDependency);
|
||||
}
|
||||
}
|
||||
}
|
||||
return results;
|
||||
};
|
||||
```
|
||||
|
||||
Breaking down this example, we can see that it follows this flow:
|
||||
|
||||
1. Initializes an array to hold dependencies it locates
|
||||
2. Builds a map of all projects in the workspace, mapping the name inside their package.json to their Nx project name.
|
||||
3. Looks at the package.json files within the workspace and:
|
||||
4. Checks if the dependency is another project
|
||||
5. Builds a dependency from this information
|
||||
6. Validates the dependency
|
||||
7. Pushes it into the located dependency array
|
||||
8. Returns the located dependencies
|
||||
|
||||
## Visualizing the Project Graph
|
||||
|
||||
You can then visualize the project graph as described [here](/core-features/explore-graph). However, there is a cache that Nx uses to avoid recalculating the project graph as much as possible. As you develop your project graph plugin, it might be a good idea to set the following environment variable to disable the project graph cache: `NX_CACHE_PROJECT_GRAPH=false`.
|
||||
|
||||
It might also be a good idea to ensure that the dep graph is not running on the nx daemon by setting `NX_DAEMON=false`, as this will ensure you will be able to see any `console.log` statements you add as you're developing.
|
||||
|
||||
## Example Project Graph Plugin
|
||||
<!-- TODO (@AgentEnder): update the nx-go-project-graph-plugin to v2 API and re-add this section -->
|
||||
<!-- ## Example Project Graph Plugin
|
||||
|
||||
The [nrwl/nx-go-project-graph-plugin](https://github.com/nrwl/nx-go-project-graph-plugin) repo contains an example project graph plugin which adds [Go](https://golang.org/) dependencies to the Nx Project Graph! A similar approach can be used for other languages.
|
||||
|
||||
{% github-repository url="https://github.com/nrwl/nx-go-project-graph-plugin" /%}
|
||||
{% github-repository url="https://github.com/nrwl/nx-go-project-graph-plugin" /%} -->
|
||||
|
||||
@ -1,104 +0,0 @@
|
||||
# Project Inference
|
||||
|
||||
{% callout type="caution" title="Experimental" %}
|
||||
This API is experimental and might change.
|
||||
{% /callout %}
|
||||
|
||||
Project inference describes the ability of Nx to discover and work with projects based on source code and configuration files in your repo. Out of the box, Nx identifies projects based on the presence of `package.json` and `project.json` files. It also identifies targets in the `package.json` scripts and the `project.json` `target`s.
|
||||
|
||||
Project inference plugins allow you to extend this functionality of Nx to other languages and file structures.
|
||||
|
||||
## Adding Plugins to Workspace
|
||||
|
||||
You can register a plugin by adding it to the plugins array in `nx.json`:
|
||||
|
||||
```jsonc {% fileName="nx.json" %}
|
||||
{
|
||||
...,
|
||||
"plugins": [
|
||||
"awesome-plugin"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Project File Patterns
|
||||
|
||||
Project file patterns are used in two scenarios:
|
||||
|
||||
- Inferring projects
|
||||
- Determining which files should be passed into `registerProjectTargets`.
|
||||
|
||||
Let's use the below plugin and workspace layout as an example:
|
||||
|
||||
```typescript {% fileName="libs/awesome-plugin/index.ts" %}
|
||||
export const projectFilePatterns = ['project.json', 'my-other-project-file'];
|
||||
export function registerProjectTargets(projectFilePath) {
|
||||
console.log(projectFilePath);
|
||||
}
|
||||
```
|
||||
|
||||
> workspace layout
|
||||
|
||||
```text
|
||||
my-workspace/
|
||||
├─ node_modules/
|
||||
├─ libs/
|
||||
│ ├─ my-project/
|
||||
│ │ ├─ my-other-project-file
|
||||
│ ├─ nx-project/
|
||||
│ │ ├─ my-other-project-file
|
||||
│ │ ├─ project.json
|
||||
├─ nx.json
|
||||
└─ package.json
|
||||
|
||||
```
|
||||
|
||||
During initialization, we would expect to see "libs/my-project/my-other-project-file", "libs/nx-project/my-other-project-file", "libs/nx-project/project.json" all logged out to the console. Nx was able to infer `my-project` from the layout.
|
||||
|
||||
## Implementing a Project Target Configurator
|
||||
|
||||
A project target configurator is a function that takes a path to a project file, and returns the targets inferred from that file.
|
||||
|
||||
Plugins should export a function named `registerProjectTargets` that infers the targets from each matching project file. This function receives the path to the project file as its sole parameter.
|
||||
|
||||
The `registerProjectTargets` function should return a `Record<string, TargetConfiguration>`, which describes the targets inferred for that specific project file.
|
||||
|
||||
```typescript
|
||||
import { TargetConfiguration } from '@nx/devkit';
|
||||
|
||||
export const projectFilePatterns = ['project.json', 'my-other-project-file'];
|
||||
|
||||
export function registerProjectTargets(
|
||||
projectFilePath: string
|
||||
): Record<string, TargetConfiguration> {
|
||||
return {
|
||||
build: {
|
||||
/**
|
||||
* This object should look exactly like a target
|
||||
* configured inside `project.json`
|
||||
*/
|
||||
},
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Multiple Matches
|
||||
|
||||
It is possible that the registerProjectTargets function may be called multiple times for one project. This could occur in a few cases, one of which is demonstrated above.
|
||||
|
||||
- One plugin may list multiple file patterns, and a project may match more than one of them.
|
||||
- Multiple plugins may list similar patterns, and pick up the project separately.
|
||||
|
||||
**In the first case**, the plugin that you are writing will be called into multiple times. If you return the same target (e.g. `build`) on each call, whichever is ran last would be the target that Nx calls into.
|
||||
|
||||
The order that the function would be called is **NOT** guaranteed, so you should try to avoid this when possible. If specifying multiple patterns, they should either be mutually exclusive (e.g. one match per project) or the plugin should conditionally add targets based on the file passed in.
|
||||
|
||||
**In the second case**, different plugins may attempt to register the same target on a project. If this occurs, whichever target was registered by the plugin listed latest in `nx.json` would be the one called into by Nx. As an example, assume `plugin-a`, `plugin-b`, and `plugin-c` all match a file and register `build` as a target. If `nx.json` included `"plugins": ["plugin-a", "plugin-b", "plugin-c"]`, running `nx build my-project` would run the target as defined by `"plugin-c"`.
|
||||
|
||||
Alternatively, if `nx.json` included `"plugins": ["plugin-c", "plugin-b", "plugin-a"]`, running `nx build my-project` would run the target as defined by `"plugin-a"`.
|
||||
|
||||
## Development Tips
|
||||
|
||||
There is a cache that Nx uses to avoid recalculating the project graph as much as possible, but it may need to be skipped during plugin development. You can set the following environment variable to disable the project graph cache: `NX_CACHE_PROJECT_GRAPH=false`.
|
||||
|
||||
It might also be a good idea to ensure that the dep graph is not running on the nx daemon by setting `NX_DAEMON=false`, as this will ensure you will be able to see any `console.log` statements you add as you're developing. You can also leave the daemon active, but `console.log` statements would only appear in its log file.
|
||||
@ -246,6 +246,7 @@
|
||||
- [Angular: Upgrading to Storybook 6](/deprecated/storybook/upgrade-storybook-v6-angular)
|
||||
- [React: Storybook Migration to webpackFinal and the Nx Addon](/deprecated/storybook/migrate-webpack-final-react)
|
||||
- [React: Upgrading to Storybook 6](/deprecated/storybook/upgrade-storybook-v6-react)
|
||||
- [v1 Nx Plugin API](/deprecated/v1-nx-plugin-api)
|
||||
- [See Also](/see-also)
|
||||
- [Site Map](/see-also/sitemap)
|
||||
|
||||
@ -269,7 +270,6 @@
|
||||
- [Create a Preset](/extending-nx/recipes/create-preset)
|
||||
- [Create an Install Package](/extending-nx/recipes/create-install-package)
|
||||
- [Modify the Project Graph](/extending-nx/recipes/project-graph-plugins)
|
||||
- [Identify Custom Projects](/extending-nx/recipes/project-inference-plugins)
|
||||
|
||||
- Cloud
|
||||
|
||||
|
||||
@ -692,8 +692,11 @@ const pluginUrls = {
|
||||
'/extending-nx/recipes/migration-generators',
|
||||
'/recipes/advanced-plugins/project-graph-plugins':
|
||||
'/extending-nx/recipes/project-graph-plugins',
|
||||
// Removed inference doc when updating for v2 API
|
||||
'/extending-nx/recipes/project-inference-plugins':
|
||||
'/extending-nx/recipes/project-graph-plugins',
|
||||
'/recipes/advanced-plugins/project-inference-plugins':
|
||||
'/extending-nx/recipes/project-inference-plugins',
|
||||
'/extending-nx/recipes/project-graph-plugins',
|
||||
'/recipes/advanced-plugins/share-your-plugin':
|
||||
'/extending-nx/tutorials/maintain-published-plugin',
|
||||
'/recipes/executors/compose-executors':
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user