nx/docs/shared/recipes/plugins/compose-executors.md
Jack Hsu 66eaf2fc74
docs(misc): remove /nx-api pages (#31453)
This PR removes the `/nx-api` pages from `nx-dev`. They are already
redirected from `/nx-api` to either `/technologies` or
`/reference/core-api` URLs.

e.g. `/nx-api/nx` goes to `/reference/core-api/nx` and `/nx-api/react`
goes to `/technologies/react/api`

**Changes**:
- Remove old `nx-api.json` from being generated in
`scripts/documentation/generators/generate-manifests.ts` -- this was
used to generate the sitemap
- Remove `pages/nx-api` from Next.js app since we don't need them
- Remove workaround from link checker
`scripts/documentation/internal-link-checker.ts` -- the angular
rspack/rsbuild and other workarounds are gone now that they are proper
docs in `map.json`
- Update Powerpack/Remote Cache reference docs to exclude API documents
(since they are duplicated in the Intro page) --
`nx-dev/models-document/src/lib/mappings.ts`
- All content in `docs` have been updated with new URL structure

**Note:** Redirects are already handled, and Claude Code was used to
verify the updated `docs/` URLs (see report below). The twelve 404s
links were updated by hand.

## Verification Report

https://gist.github.com/jaysoo/c7863fe7e091cb77929d1976165c357a
2025-06-04 16:57:01 -04:00

3.4 KiB

title description
Compose Executors Learn how to compose and chain Nx executors together, including how to invoke other targets and executors from within your custom executors.

Compose executors

An executor is just a function, so you can import and invoke it directly, as follows:

import printAllCaps from 'print-all-caps';

export default async function (
  options: Schema,
  context: ExecutorContext
): Promise<{ success: true }> {
  // do something before
  await printAllCaps({ message: 'All caps' });
  // do something after
}

This only works when you know what executor you want to invoke. Sometimes, however, you need to invoke a target. For instance, the e2e target is often configured like this:

{
  "e2e": {
    "builder": "@nx/cypress:cypress",
    "options": {
      "cypressConfig": "apps/myapp-e2e/cypress.json",
      "tsConfig": "apps/myapp-e2e/tsconfig.e2e.json",
      "devServerTarget": "myapp:serve"
    }
  }
}

In this case we need to invoke the target configured in devSeverTarget. We can do it as follows:

async function* startDevServer(
  opts: CypressExecutorOptions,
  context: ExecutorContext
) {
  const { project, target, configuration } = parseTargetString(
    opts.devServerTarget
  );
  for await (const output of await runExecutor<{
    success: boolean;
    baseUrl?: string;
  }>(
    { project, target, configuration },
    {
      watch: opts.watch,
    },
    context
  )) {
    if (!output.success && !opts.watch)
      throw new Error('Could not compile application files');
    yield opts.baseUrl || (output.baseUrl as string);
  }
}

The runExecutor utility will find the target in the configuration, find the executor, construct the options (as if you invoked it in the terminal) and invoke the executor. Note that runExecutor always returns an iterable instead of a promise.

Devkit helper functions

Property Description
logger Wraps console to add some formatting
getPackageManagerCommand Returns commands for the package manager used in the workspace
parseTargetString Parses a target string into {project, target, configuration}
readTargetOptions Reads and combines options for a given target
runExecutor Constructs options and invokes an executor

See more helper functions in the Devkit API Docs

Using RxJS observables

The Nx devkit only uses language primitives (promises and async iterables). It doesn't use RxJS observables, but you can use them and convert them to a Promise or an async iterable.

You can convert Observables to a Promise with toPromise.

import { of } from 'rxjs';

export default async function (opts) {
  return of({ success: true }).toPromise();
}

You can use the rxjs-for-await library to convert an Observable into an async iterable.

import { of } from 'rxjs';
import { eachValueFrom } from 'rxjs-for-await';

export default async function (opts) {
  return eachValueFrom(of({ success: true }));
}