cleanup(misc): misc docs update and small cleanups
This commit is contained in:
parent
4df3b66152
commit
7457b16811
@ -2,9 +2,9 @@
|
||||
|
||||
> Note: this document covers the difference between Nx Devkit and Angular Devkit. See the [Nx Devkit](/{{framework}}/core-concepts/nx-devkit) guide for more in-depth details about Nx Devkit.
|
||||
|
||||
Nx comes with a devkit to write generators and executors, but you can also use Angular devkit (schematics and builders). An Angular schematic is a second way to implement generators. An Angular builder is a second way to implement an executor.
|
||||
Nx comes with a devkit to write generators and executors, but you can also use Angular devkit (schematics and builders). In other words, you can use an Angular schematic to implement a generator, and you can use an Angular builder to implement an executor.
|
||||
|
||||
What is the difference between Nx Devkit and Angular Devkit?
|
||||
**What are the differences between Nx Devkit and Angular Devkit?**
|
||||
|
||||
## Generators
|
||||
|
||||
@ -19,7 +19,7 @@ interface Schema {
|
||||
skipFormat: boolean;
|
||||
}
|
||||
|
||||
export default async function (tree: Tree, optoins: Schema) {
|
||||
export default async function (tree: Tree, options: Schema) {
|
||||
generateFiles(
|
||||
tree,
|
||||
path.join(__dirname, 'files'),
|
||||
@ -69,16 +69,73 @@ export default function (options: Schema): Rule {
|
||||
}
|
||||
```
|
||||
|
||||
**Some notable changes:**
|
||||
### Notable Differences
|
||||
|
||||
- Nx Devkit generators do not use partial application. An Angular Schematic returns a rule that is then invoked with a tree.
|
||||
- Nx Devkit generators do not use RxJS observables. Just invoke the helpers directly. This makes them more debuggable. As you step through the generator you can see the tree being updated.
|
||||
- `chain([mergeWith(apply(url` is replaced with `generateFiles`)
|
||||
- Nx Devkit generators do not use RxJS observables. Instead you invoke the helpers directly, which makes them more debuggable. As you step through the generator you can see the tree being updated.
|
||||
- There are more affordances for commonly used operations. For instance, `chain([mergeWith(apply(url` is replaced with `generateFiles`)
|
||||
- Nx Devkit generators return a function that performs side effects. Angular Schematics have to create a custom task runner and register a task using it.
|
||||
- You don't need any special helpers to compose Nx Devkit generators. You do need to go through a special resolution step to compose Angular Schematics.
|
||||
- Nx Devkit generators are composed as any other JS function. You do need to go through a special resolution step (`externalSchematic`) that is required when using Angular Schematics.
|
||||
- No special utilities are needed to test Nx Devkit generators. Special utilities are needed to test Angular Schematics.
|
||||
|
||||
The schema files for both Nx Devkit generators and Angular Schematics are the same. Nx can run both of them in the same way. You can invoke Angular schematics from within Nx Devkit generators using `wrapAngularDevkitSchematic`.
|
||||
### Conversions
|
||||
|
||||
The Nx CLI can invoke Nx Generator or Angular Schematics directly. When the user runs:
|
||||
|
||||
```bash
|
||||
nx g mygenerator params
|
||||
ng g mygenerator params # will work exactly the same same as the line above
|
||||
```
|
||||
|
||||
The Nx CLI will see what type of generator `mygenerator` is and will invoke it using the right machinery. The user doesn't have to know how the generator is implemented.
|
||||
|
||||
At times, however, it might be useful to use an Nx Devkit generator in an Angular Schematic or vice versa.
|
||||
|
||||
**Making an Angular Schematic out of Nx Devkit Generator:**
|
||||
|
||||
First, you need to
|
||||
|
||||
```typescript
|
||||
export async function mygenerator(tree: Tree, options: Schema) {
|
||||
// ...
|
||||
}
|
||||
export const mygeneratorSchematic = convertNxGenerator(mygenerator);
|
||||
```
|
||||
|
||||
Then, you might need to register it in the `collections.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Nx React",
|
||||
"version": "0.1",
|
||||
"extends": ["@nrwl/workspace"],
|
||||
"schematics": {
|
||||
"mygenerator": {
|
||||
"factory": "./src/generators/mygenerator/mygenerator#mygeneratorSchematic",
|
||||
"schema": "./src/generators/mygenerator/schema.json"
|
||||
}
|
||||
},
|
||||
"generators": {
|
||||
"init": {
|
||||
"factory": "./src/generators/mygenerator/mygenerator#mygenerator",
|
||||
"schema": "./src/generators/mygenerator/schema.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Making an Nx Devkit Generator out of Angular Schematic:**
|
||||
|
||||
```typescript
|
||||
export const libraryGenerator = wrapAngularDevkitSchematic(
|
||||
'@nrwl/angular',
|
||||
'library'
|
||||
);
|
||||
|
||||
export async function mygenerator(tree: Tree, options: Schema) {
|
||||
await libraryGenerator(tree, options);
|
||||
}
|
||||
```
|
||||
|
||||
## Executors
|
||||
|
||||
@ -125,7 +182,7 @@ export default function (
|
||||
export default createBuilder<NextBuildBuilderOptions>(run);
|
||||
```
|
||||
|
||||
Some notable changes:
|
||||
### Notable Differences
|
||||
|
||||
- Nx Devkit executors return a Promise (or async iterable). If you want, you can always convert an observable to a promise or an async iterable. See [Using Rxjs Observables](/{{framework}}/core-concepts/nx-devkit#using-rxjs-observables)
|
||||
- Nx Devkit executors do not have to be wrapped using `createBuilder`.
|
||||
|
||||
@ -4,16 +4,16 @@ Nx is a pluggable build tool, so most of its functionality is provided by plugin
|
||||
|
||||
Plugins have:
|
||||
|
||||
- Generators
|
||||
- Anytime you run `nx generate ...`, you invoke a generator
|
||||
- Generators automate making changes to the file system
|
||||
- **Generators**
|
||||
- Anytime you run `nx generate ...`, you invoke a generator.
|
||||
- Generators automate making changes to the file system.
|
||||
- They are used to create/update applications, libraries, components, etc..
|
||||
- Executors
|
||||
- Anytime you run `nx run ...` (or `nx test`, `nx build`), you invoke an executor
|
||||
- Executors define how to perform an action on a project
|
||||
- **Executors**
|
||||
- Anytime you run `nx run ...` (or `nx test`, `nx build`), you invoke an executor.
|
||||
- Executors define how to perform an action on a project.
|
||||
- They are used to build applications and libraries, test them, lint them, etc..
|
||||
|
||||
All of the core plugins are written using Nx Devkit and you can use the same utilities to write your own generators and executors.
|
||||
All of the core plugins are written using Nx Devkit, and you can use the same utilities to write your own generators and executors.
|
||||
|
||||
## Pay as You Go
|
||||
|
||||
@ -21,14 +21,14 @@ As with most things in Nx, the core of Nx Devkit is very simple. It only uses la
|
||||
|
||||
## Generators
|
||||
|
||||
Generators automate making file changes for you. They can create new files, overwrite existing files, delete existing files, etc. For example, adding a new application may involve creating numerous files and updating configuration. By providing a generator that creates new applications, you can start coding the interesting parts of their application without having to spend hours setting the project up.
|
||||
Generators automate making file changes. They can create new files, overwrite existing files, delete existing files, etc. For example, adding a new application often requires creating numerous files and updating configuration. Adding a new component may require adding a storybook configuration and a suite of e2e tests. Generators can automate all of these and help you focus on actually matters.
|
||||
|
||||
A generator consists of the following:
|
||||
A generator has:
|
||||
|
||||
- a schema that describes what can be input into the generator
|
||||
- the implementation that takes the inputs and makes changes to the file system
|
||||
- a schema describing the inputs (i.e., flags, args, options)
|
||||
- the implementation taking the inputs and making changes to the file system
|
||||
|
||||
Unlike a naive script which makes changes to the file system, generators update the file system atomically at the end. This means that if an error occurs, the file system is not partially updated.
|
||||
Unlike many other tools Nx generators update the file system atomically at the end. This means that if an error occurs, the file system is not partially updated. It also means that you can preview the changes to the file system without actually modifying any files.
|
||||
|
||||
### Schema
|
||||
|
||||
@ -163,13 +163,15 @@ If you want to learn more about the schema language, check out the core plugins
|
||||
The implementation is a function that takes two arguments:
|
||||
|
||||
- `tree`: an implementation of the file system
|
||||
- Allows you to read/write files, list children, etc.
|
||||
- It's recommended to use the tree instead of directly interacting with the file system
|
||||
- This enables the `--dry-run` mode so you can try different sets of options before actually making changes to their files.
|
||||
- `options`: the options that a user passes
|
||||
- This is described by the schema and allows users to customize the result of the generator to their needs.
|
||||
- It allows you to read/write files, list children, etc.
|
||||
- It's recommended to use the tree instead of directly interacting with the file system.
|
||||
- This enables the `--dry-run` mode so you can try different sets of options before actually making changes to the files.
|
||||
- `options`
|
||||
- This is a combination of the options from `workspace.json`, command-line overrides, and schema defaults.
|
||||
- All the options are validated and transformed in accordance with the schema.
|
||||
- You normally don't have to validate anything in the implementation function because it won't be invoked unless the schema validation passes.
|
||||
|
||||
The implementation can return a callback which is invoked _after changes have been made to the file system_. For example, the implementation might add dependencies to `package.json` and install them afterwards. Because installing dependencies requires that the `package.json` has the changes on disk, installing dependencies should be done in the callback returned.
|
||||
The implementation can return a callback which is invoked _after changes have been made to the file system_.
|
||||
|
||||
#### Examples
|
||||
|
||||
@ -208,7 +210,7 @@ The generator is an async function. You could create new projects and generate n
|
||||
|
||||
### Composing Generators
|
||||
|
||||
A generator is just an async function so they can be easily composed together. This is often useful when you want to combine multiple generations. For instance, to write a generator that generates two React libraries:
|
||||
Generators are just async functions so they can be easily composed together. For instance, to write a generator that generates two React libraries:
|
||||
|
||||
```typescript
|
||||
import {
|
||||
@ -303,16 +305,16 @@ export default async function (tree, opts) {
|
||||
|
||||
## Executors
|
||||
|
||||
Executors act on a project commonly producing some resulting artifacts. The canonical example of an executor is one which builds a project for deployment.
|
||||
Executors act on a project. Some examples including: building projects, testing projects, serving projects.
|
||||
|
||||
An executor consists of the following:
|
||||
|
||||
- a schema that describes what options are available
|
||||
- the implementation which defines what is done when performing an action on a project
|
||||
- a schema describing the inputs (i.e., flags, args, options).
|
||||
- the implementation taking the inputs and acting on the project.
|
||||
|
||||
### Schema
|
||||
|
||||
The executor's schema describes the inputs--what you can pass into it.
|
||||
A generator's schema describes the inputs--what you can pass into it. The schema is used to validate inputs, to parse args (e.g., covert strings into numbers), to set defaults, and to power the VSCode plugin. It is written with [JSON Schema](https://json-schema.org/).
|
||||
|
||||
```json
|
||||
{
|
||||
@ -335,11 +337,11 @@ The executor's schema describes the inputs--what you can pass into it.
|
||||
}
|
||||
```
|
||||
|
||||
The schema above defines two fields: `message` and `upperCase`. The `message` field is a string, `upperCase` is a boolean. The schema support for executors and generators is identical, so see the section on generators above for more information.
|
||||
The schema above defines two fields: `message` and `upperCase`. The `message` field is a string, `upperCase` is a boolean. The schema support for executors and generators is identical. See the section on generators above for more information.
|
||||
|
||||
### Implementation
|
||||
|
||||
The implementation function takes two arguments (the options and the target context) and returns a promise (or an async iterable) with the success property. The context params contains information about the workspace and the invoked target.
|
||||
The implementation function takes two arguments (the options and the executor context) and returns a promise (or an async iterable) with the success property. The context params contains information about the workspace and the invoked target.
|
||||
|
||||
Most of the time executors return a promise.
|
||||
|
||||
@ -420,7 +422,9 @@ async function* startDevServer(
|
||||
opts: CypressExecutorOptions,
|
||||
context: ExecutorContext
|
||||
) {
|
||||
const [project, target, configuration] = opts.devServerTarget.split(':');
|
||||
const { project, target, configuration } = parseTargetString(
|
||||
opts.devServerTarget
|
||||
);
|
||||
for await (const output of await runExecutor<{
|
||||
success: boolean;
|
||||
baseUrl?: string;
|
||||
@ -438,12 +442,14 @@ async function* startDevServer(
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
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
|
||||
|
||||
- `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.
|
||||
|
||||
### Simplest Executor
|
||||
|
||||
@ -18,6 +18,8 @@ npx create-nx-plugin my-org --pluginName my-plugin
|
||||
|
||||
This command creates a brand new workspace, and sets up a pre-configured plugin with the specified name.
|
||||
|
||||
> Note, the command above will create a plugin the package name set to `@my-org/my-plugin`. You can pass `--importPath` to provide a different package name.
|
||||
|
||||
## Workspace Structure
|
||||
|
||||
After executing the above command, the following tree structure is created:
|
||||
@ -75,7 +77,7 @@ A new plugin is created with a default generator, executor, and e2e app.
|
||||
|
||||
## Generator
|
||||
|
||||
The generated generator contains boilerplate that will do the following:
|
||||
The created generator contains boilerplate that will do the following:
|
||||
|
||||
- Normalize a schema (the options that the generator accepts)
|
||||
- Update the `workspace.json`
|
||||
|
||||
@ -17,9 +17,11 @@ const nxVersion = 'NX_VERSION';
|
||||
const prettierVersion = 'PRETTIER_VERSION';
|
||||
|
||||
const parsedArgs = yargsParser(process.argv, {
|
||||
string: ['pluginName', 'packageManager'],
|
||||
string: ['pluginName', 'packageManager', 'importPath'],
|
||||
alias: {
|
||||
importPath: 'import-path',
|
||||
pluginName: 'plugin-name',
|
||||
packageManager: 'pm',
|
||||
},
|
||||
boolean: ['help'],
|
||||
});
|
||||
@ -78,8 +80,14 @@ function createWorkspace(
|
||||
});
|
||||
}
|
||||
|
||||
function createNxPlugin(workspaceName, pluginName, packageManager) {
|
||||
const command = `nx generate @nrwl/nx-plugin:plugin ${pluginName} --importPath=${workspaceName}/${pluginName}`;
|
||||
function createNxPlugin(
|
||||
workspaceName,
|
||||
pluginName,
|
||||
packageManager,
|
||||
parsedArgs: any
|
||||
) {
|
||||
const importPath = parsedArgs.importPath ?? `${workspaceName}/${pluginName}`;
|
||||
const command = `nx generate @nrwl/nx-plugin:plugin ${pluginName} --importPath=${importPath}`;
|
||||
console.log(command);
|
||||
|
||||
const pmc = getPackageManagerCommand(packageManager);
|
||||
@ -195,7 +203,7 @@ determineWorkspaceName(parsedArgs).then((workspaceName) => {
|
||||
const tmpDir = createSandbox(packageManager);
|
||||
createWorkspace(tmpDir, packageManager, parsedArgs, workspaceName);
|
||||
updateWorkspace(workspaceName);
|
||||
createNxPlugin(workspaceName, pluginName, packageManager);
|
||||
createNxPlugin(workspaceName, pluginName, packageManager, parsedArgs);
|
||||
commitChanges(workspaceName);
|
||||
showNxWarning(workspaceName);
|
||||
});
|
||||
|
||||
@ -37,6 +37,14 @@ describe('Cypress builder', () => {
|
||||
},
|
||||
]);
|
||||
(devkit as any).stripIndents = (s) => s;
|
||||
(devkit as any).parseTargetString = (s) => {
|
||||
const [project, target, configuration] = s.split(':');
|
||||
return {
|
||||
project,
|
||||
target,
|
||||
configuration,
|
||||
};
|
||||
};
|
||||
cypressRun = spyOn(Cypress, 'run').and.returnValue(Promise.resolve({}));
|
||||
cypressOpen = spyOn(Cypress, 'open').and.returnValue(Promise.resolve({}));
|
||||
});
|
||||
|
||||
@ -3,6 +3,7 @@ import { installedCypressVersion } from '../../utils/cypress-version';
|
||||
import {
|
||||
ExecutorContext,
|
||||
logger,
|
||||
parseTargetString,
|
||||
runExecutor,
|
||||
stripIndents,
|
||||
} from '@nrwl/devkit';
|
||||
@ -110,7 +111,11 @@ async function* startDevServer(
|
||||
return;
|
||||
}
|
||||
|
||||
const [project, target, configuration] = opts.devServerTarget.split(':');
|
||||
console.log('VALUE....', parseTargetString(opts.devServerTarget));
|
||||
|
||||
const { project, target, configuration } = parseTargetString(
|
||||
opts.devServerTarget
|
||||
);
|
||||
for await (const output of await runExecutor<{
|
||||
success: boolean;
|
||||
baseUrl?: string;
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
/**
|
||||
* Parses a target string into {project, target, configuration}
|
||||
*
|
||||
* @param targetString - target reference
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* ```typescript
|
||||
* parseTargetString("proj:test") // returns { project: "proj", target: "test" }
|
||||
* parseTargetString("proj:test:production") // returns { project: "proj", target: "test", configuration: "production" }
|
||||
* ```
|
||||
*/
|
||||
export function parseTargetString(targetString: string) {
|
||||
const [project, target, configuration] = targetString.split(':');
|
||||
if (!project || !target) {
|
||||
|
||||
@ -2,6 +2,11 @@ import { Target } from '@nrwl/tao/src/commands/run';
|
||||
import { ExecutorContext, Workspaces } from '@nrwl/tao/src/shared/workspace';
|
||||
import { combineOptionsForExecutor } from '@nrwl/tao/src/shared/params';
|
||||
|
||||
/**
|
||||
* Reads and combines options for a given target.
|
||||
*
|
||||
* Works as if you invoked the target yourself without passing any command lint overrides.
|
||||
*/
|
||||
export function readTargetOptions<T = any>(
|
||||
{ project, target, configuration }: Target,
|
||||
context: ExecutorContext
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user