Skip to content
Draft
Show file tree
Hide file tree
Changes from 7 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
121 changes: 89 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,30 @@ 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 +246,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 +292,12 @@ defining `targetDefaults` in `nx.json` is helpful.
```json
// nx.json
{
"targetDefaults": {
"build": {
"targetDefaults": [
{
"target": "build",
"dependsOn": ["^build"]
}
}
]
}
```

Expand All @@ -289,11 +315,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 +329,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 +343,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 +368,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 +390,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 +406,39 @@ 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 +455,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
45 changes: 32 additions & 13 deletions e2e/nx/src/run.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -488,13 +488,23 @@ describe('Nx Running Tests', () => {
const lib = uniq('lib');

updateJson('nx.json', (nxJson) => {
nxJson.targetDefaults ??= {};
nxJson.targetDefaults[target] = {
executor: 'nx:run-commands',
options: {
command: `echo Hello from ${target}`,
},
};
if (Array.isArray(nxJson.targetDefaults)) {
nxJson.targetDefaults.push({
target,
executor: 'nx:run-commands',
options: {
command: `echo Hello from ${target}`,
},
});
} else {
nxJson.targetDefaults ??= {};
nxJson.targetDefaults[target] = {
executor: 'nx:run-commands',
options: {
command: `echo Hello from ${target}`,
},
};
}
return nxJson;
});

Expand All @@ -518,12 +528,21 @@ describe('Nx Running Tests', () => {
const lib = uniq('lib');

updateJson('nx.json', (nxJson) => {
nxJson.targetDefaults ??= {};
nxJson.targetDefaults[`nx:run-commands`] = {
options: {
command: `echo Hello from ${target}`,
},
};
if (Array.isArray(nxJson.targetDefaults)) {
nxJson.targetDefaults.push({
target: `nx:run-commands`,
options: {
command: `echo Hello from ${target}`,
},
});
} else {
nxJson.targetDefaults ??= {};
nxJson.targetDefaults[`nx:run-commands`] = {
options: {
command: `echo Hello from ${target}`,
},
};
}
return nxJson;
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import type {
BrowserBuilderOptions,
ServerBuilderOptions,
} from '@angular-devkit/build-angular';
import type { Tree } from '@nx/devkit';
import type {
NxJsonConfiguration,
TargetConfiguration,
Tree,
} from '@nx/devkit';
import {
joinPathFragments,
logger,
Expand All @@ -11,6 +15,7 @@ import {
updateNxJson,
updateProjectConfiguration,
} from '@nx/devkit';
import { upsertTargetDefault } from '@nx/devkit/src/generators/target-defaults-utils';
import { getProjectSourceRoot } from '@nx/js/src/utils/typescript/ts-solution-setup';
import type { NormalizedGeneratorOptions } from '../schema';
import {
Expand Down Expand Up @@ -156,10 +161,25 @@ export function updateProjectConfigForBrowserBuilder(
'server'
);
}
nxJson.targetDefaults ??= {};
nxJson.targetDefaults.server ??= {};
nxJson.targetDefaults.server.cache ??= true;
updateNxJson(tree, nxJson);
const existing = findServerDefault(nxJson.targetDefaults);
if (!existing || existing.cache === undefined) {
upsertTargetDefault(tree, { target: 'server', cache: true });
}
}

function findServerDefault(
td: NxJsonConfiguration['targetDefaults']
): Partial<TargetConfiguration> | undefined {
if (!td) return undefined;
if (Array.isArray(td)) {
return td.find(
(e) =>
e.target === 'server' &&
e.projects === undefined &&
e.source === undefined
);
}
return td['server'];
}

function getServerOptions(
Expand Down
Loading
Loading