Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 109 additions & 32 deletions astro-docs/src/content/docs/reference/nx-json.mdoc
Original file line number Diff line number Diff line change
Expand Up @@ -35,22 +35,24 @@ The following is an expanded example showing all options. Your `nx.json` will li
"default": ["{projectRoot}/**/*"],
"production": ["!{projectRoot}/**/*.spec.tsx"]
},
"targetDefaults": {
"@nx/js:tsc": {
"targetDefaults": [
{
"target": "@nx/js:tsc",
"inputs": ["production", "^production"],
"dependsOn": ["^build"],
"options": {
"main": "{projectRoot}/src/index.ts"
},
"cache": true
},
"test": {
{
"target": "test",
"cache": true,
"inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"],
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"executor": "@nx/jest:jest"
}
},
],
"release": {
"version": {
"conventionalCommits": true
Expand Down Expand Up @@ -211,8 +213,38 @@ Additionally, if there is not a match for either of the above, we look for other

Target defaults matching the executor takes precedence over those matching the target name. If we find a target default for a given target, we use it as the base for that target's configuration.

`targetDefaults` is an array of entries. Each entry **must** specify a `target` (name, glob, or executor string). Optional `projects` and `source` filters let you scope a default to a subset of projects or targets inferred by a particular plugin.

| Field | Type | Description |
| ---------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `target` | `string` | Required. Target name (e.g. `test`), glob (e.g. `e2e-ci--*`), or executor string (e.g. `@nx/vite:test`). |
| `projects` | `string` \| `string[]` | Optional. Restricts the default to matching projects. Accepts project names, globs, directory patterns, tags (`tag:foo`), and negation (`!foo`) — anything `findMatchingProjects` understands. |
| `source` | `string` | Optional. Restricts the default to targets originated by a specific plugin (e.g. `@nx/vite`). Useful when two plugins expose a target with the same name (e.g. `@nx/vite:test` and `@nx/jest:test` both as `test`). |
| other | any field from [Target Configuration](/docs/reference/project-configuration) | The defaults applied when the filter matches. |

When multiple entries match a given `(target, project)` tuple, the **most specific** entry wins (`target + projects + source` > `target + projects` > `target + source` > `target` alone). Within the same specificity tier, an exact `target` match beats a glob match, and later entries in the array beat earlier ones.

```json
// nx.json — polyglot example
{
"targetDefaults": [
{ "target": "test", "cache": true },
{
"target": "test",
"source": "@nx/vite",
"inputs": ["default", "^production"]
},
{
"target": "test",
"projects": "tag:dotnet",
"options": { "configuration": "Release" }
}
]
}
```

{% aside type="caution" title="Beware" %}
When using a target name as the key of a target default, make sure all the targets with that name use the same executor or that the target defaults you're setting make sense to all targets regardless of the executor they use. Anything set in a target default will also override the configuration of [tasks inferred by plugins](/docs/concepts/inferred-tasks).
When an entry has no `projects` or `source` filter, Nx matches by target name (or executor) alone. Make sure all targets with that name use the same executor, or that the defaults you're setting make sense to all of them. Anything set in a target default will also override the configuration of [tasks inferred by plugins](/docs/concepts/inferred-tasks).
{% /aside %}

Some common scenarios for this follow.
Expand All @@ -222,18 +254,19 @@ Some common scenarios for this follow.
Named inputs defined in `nx.json` are merged with the named inputs defined in [project level configuration](/docs/reference/project-configuration). In other words, every project has a set of named inputs, and it's defined as: `{...namedInputsFromNxJson, ...namedInputsFromProjectsProjectJson}`.

Defining `inputs` for a given target would replace the set of inputs for that target name defined in `nx.json`.
Using pseudocode `inputs = projectJson.targets.build.inputs || nxJson.targetDefaults.build.inputs`.
Using pseudocode `inputs = projectJson.targets.build.inputs || nxJsonTargetDefaults.build.inputs`.

You can also define and redefine named inputs. This enables one key use case, where your `nx.json` can define things like this (which applies to every project):

```json
// nx.json
{
"targetDefaults": {
"test": {
"targetDefaults": [
{
"target": "test",
"inputs": ["default", "^production"]
}
}
]
}
```

Expand Down Expand Up @@ -267,11 +300,12 @@ defining `targetDefaults` in `nx.json` is helpful.
```json
// nx.json
{
"targetDefaults": {
"build": {
"targetDefaults": [
{
"target": "build",
"dependsOn": ["^build"]
}
}
]
}
```

Expand All @@ -289,11 +323,12 @@ Another target default you can configure is `outputs`:
```json
// nx.json
{
"targetDefaults": {
"build": {
"targetDefaults": [
{
"target": "build",
"outputs": ["{projectRoot}/custom-dist"]
}
}
]
}
```

Expand All @@ -302,8 +337,9 @@ When defining any options or configurations inside of a target default, you may
```json
// nx.json
{
"targetDefaults": {
"@nx/js:tsc": {
"targetDefaults": [
{
"target": "@nx/js:tsc",
"options": {
"main": "{projectRoot}/src/index.ts"
},
Expand All @@ -315,18 +351,19 @@ When defining any options or configurations inside of a target default, you may
"inputs": ["prod"],
"outputs": ["{workspaceRoot}/{projectRoot}"]
},
"build": {
{
"target": "build",
"inputs": ["prod"],
"outputs": ["{workspaceRoot}/{projectRoot}"],
"cache": true
}
}
]
}
```

#### Target default priority

Note that the inputs and outputs are specified on both the `@nx/js:tsc` and `build` default configurations. This is **required**, as when reading target defaults Nx will only ever look at one key. If there is a default configuration based on the executor used, it will be read first. If not, Nx will fall back to looking at the configuration based on target name. For instance, running `nx build project` will read the options from `targetDefaults[@nx/js:tsc]` if the target configuration for `build` uses the `@nx/js:tsc executor`. It **would not** read any of the configuration from the `build` target default configuration unless the executor does not match.
Only one entry wins per `(target, project)` tuple — the most specific one (see the table above). In the example above, both entries apply to targets named `build`, but the `@nx/js:tsc` entry matches when the target uses that executor and beats the plain `build` entry because executor matches rank as specific as an exact target-name match. Specify the full config on the winning entry — less-specific entries do not contribute when a more-specific entry matches.

{% cardgrid %}
{% linkcard title="Configure Outputs for Task Caching" description="This recipe walks you through how to set outputs" href="/docs/guides/tasks--caching/configure-outputs" /%}
Expand All @@ -339,11 +376,12 @@ In Nx 17 and higher, caching is configured by specifying `"cache": true` in a ta
```json
// nx.json
{
"targetDefaults": {
"test": {
"targetDefaults": [
{
"target": "test",
"cache": true
}
}
]
}
```

Expand All @@ -360,13 +398,14 @@ You can configure options specific to a target's executor. As an example, if you
```json
// nx.json
{
"targetDefaults": {
"@nx/js:tsc": {
"targetDefaults": [
{
"target": "@nx/js:tsc",
"options": {
"generateExportsField": true
}
}
}
]
}
```

Expand All @@ -375,14 +414,51 @@ You can also provide defaults for [inferred targets](/docs/concepts/inferred-tas
```json
// nx.json
{
"targetDefaults": {
"build": {
"targetDefaults": [
{
"target": "build",
"options": {
"assetsInlineLimit": 2048,
"assetsDir": "static/assets"
}
}
}
]
}
```

If two plugins expose a target with the same name (e.g. both `@nx/vite` and `@nx/jest` infer `test`), use the `source` filter to scope per-plugin defaults:

```json
// nx.json
{
"targetDefaults": [
{
"target": "test",
"source": "@nx/vite",
"inputs": ["default", "^production"]
},
{
"target": "test",
"source": "@nx/jest",
"inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"]
}
]
}
```

Or use the `projects` filter — which accepts the same patterns as the `--projects` CLI flag (names, globs, `tag:foo`, `!exclude`) — to scope a default to a subset of projects:

```json
// nx.json
{
"targetDefaults": [
{
"target": "build",
"projects": "tag:dotnet",
"options": { "configuration": "Release" }
},
{ "target": "build", "projects": ["apps/*", "!apps/legacy"], "cache": true }
]
}
```

Expand All @@ -399,13 +475,14 @@ Task Atomizer plugins create several targets with a similar pattern. For example
```json
// nx.json
{
"targetDefaults": {
"e2e-ci--**/**": {
"targetDefaults": [
{
"target": "e2e-ci--**/**",
"options": {
"headless": true
}
}
}
]
}
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,63 @@ In the case of an explicit target using an executor, you can specify the executo
}
```

### Spread token

By default, each configuration source overwrites the previous one for every property it defines. The spread token (`"..."`) lets you control merge priority rather than always replacing.

**In arrays**, `"..."` is substituted with the items from the base array at that position:

```json
// project.json
{
"targets": {
"build": {
"inputs": ["my-project-input", "..."]
}
}
}
```

If the inferred target has `inputs: ["default", "^production"]`, the result is `["my-project-input", "default", "^production"]`.

**In objects**, a key of `"..."` set to `true` spreads base properties at that position. Keys defined after `"..."` override base values; keys defined before `"..."` can be overridden by base values.

```json
// project.json
{
"targets": {
"build": {
"options": {
"env": {
"MY_VAR": "my-value",
"...": true
}
}
}
}
}
```

Nx processes target configuration down to `options[x]` and `configurations[x][y]`; values below that are opaque to the merge pipeline. Spread is therefore resolved at these levels:

| Level | Example |
| -------------------------------------------------- | --------------------------------------------------------------------------- |
| Target root | `"build": { "dependsOn": ["lint"], "...": true }` |
| `inputs`, `outputs`, `dependsOn`, `syncGenerators` | `"inputs": ["my-input", "..."]` |
| `options` | `"options": { "...": true, "outputPath": "dist/custom" }` |
| `options[x]` | `"options": { "env": { "MY_VAR": "val", "...": true } }` |
| `configurations` | `"configurations": { "my-config": { ... }, "...": true }` |
| `configurations[x]` | `"configurations": { "prod": { "...": true, "sourceMap": false } }` |
| `configurations[x][y]` | `"configurations": { "prod": { "env": { "MY_VAR": "val", "...": true } } }` |

{% aside type="caution" title="Spread does not apply to deeply nested options" %}
Because `options[x]` and `configurations[x][y]` are the innermost levels Nx inspects, a spread token nested any deeper has no effect. For example, `options.webpack = { "...": true }` works (the spread is at `options[x]`), but `options.webpack.plugins = { "...": true }` is ignored because the spread sits inside the opaque value Nx assigns to `options.webpack`.
{% /aside %}

{% aside type="note" title="Spread does not apply to `tags` and `implicitDependencies`" %}
`tags` and `implicitDependencies` always merge across configuration sources — later sources contribute additional entries rather than replacing earlier ones. The spread token is neither needed nor supported for these properties.
{% /aside %}

### Target metadata

You can add additional metadata to be attached to a target. For example, you can provide a description stating what the
Expand Down
42 changes: 27 additions & 15 deletions e2e/angular/src/projects-buildable-libs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,23 +131,35 @@ describe('Angular Projects - Buildable Libraries', () => {

// update the nx.json
updateJson('nx.json', (config) => {
config.targetDefaults ??= {};
config.targetDefaults['@nx/angular:webpack-browser'] ??= {
const inputs =
config.namedInputs && 'production' in config.namedInputs
? ['production', '^production']
: ['default', '^default'];
const defaults: Record<string, unknown> = {
cache: true,
dependsOn: [`^build`],
inputs:
config.namedInputs && 'production' in config.namedInputs
? ['production', '^production']
: ['default', '^default'],
};
config.targetDefaults['@nx/angular:browser-esbuild'] ??= {
cache: true,
dependsOn: [`^build`],
inputs:
config.namedInputs && 'production' in config.namedInputs
? ['production', '^production']
: ['default', '^default'],
dependsOn: ['^build'],
inputs,
};
const targets = [
'@nx/angular:webpack-browser',
'@nx/angular:browser-esbuild',
];
if (Array.isArray(config.targetDefaults)) {
for (const target of targets) {
if (
!config.targetDefaults.some(
(e: { target?: string }) => e.target === target
)
) {
config.targetDefaults.push({ target, ...defaults });
}
}
} else {
config.targetDefaults ??= {};
for (const target of targets) {
config.targetDefaults[target] ??= defaults;
}
}
return config;
});

Expand Down
Loading
Loading