Compose executors

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

1import printAllCaps from 'print-all-caps';
2
3export default async function (
4  options: Schema,
5  context: ExecutorContext
6): Promise<{ success: true }> {
7  // do something before
8  await printAllCaps({ message: 'All caps' });
9  // do something after
10}
11

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:

1{
2  "e2e": {
3    "builder": "@nrwl/cypress:cypress",
4    "options": {
5      "cypressConfig": "apps/myapp-e2e/cypress.json",
6      "tsConfig": "apps/myapp-e2e/tsconfig.e2e.json",
7      "devServerTarget": "myapp:serve"
8    }
9  }
10}
11

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

1async function* startDevServer(
2  opts: CypressExecutorOptions,
3  context: ExecutorContext
4) {
5  const { project, target, configuration } = parseTargetString(
6    opts.devServerTarget
7  );
8  for await (const output of await runExecutor<{
9    success: boolean;
10    baseUrl?: string;
11  }>(
12    { project, target, configuration },
13    {
14      watch: opts.watch,
15    },
16    context
17  )) {
18    if (!output.success && !opts.watch)
19      throw new Error('Could not compile application files');
20    yield opts.baseUrl || (output.baseUrl as string);
21  }
22}
23

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

PropertyDescription
loggerWraps console to add some formatting
getPackageManagerCommandReturns commands for the package manager used in the workspace
parseTargetStringParses a target string into {project, target, configuration}
readTargetOptionsReads and combines options for a given target
runExecutorConstructs 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.

1import { of } from 'rxjs';
2
3export default async function (opts) {
4  return of({ success: true }).toPromise();
5}
6

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

1import { of } from 'rxjs';
2import { eachValueFrom } from 'rxjs-for-await';
3
4export default async function (opts) {
5  return eachValueFrom(of({ success: true }));
6}
7