docs(core): generator guides (#6070)
* docs(nxdev): generator guides * docs(nxdev): cleanup * docs(nxdev): more cleanup * docs(nxdev): formatting * docs(nxdev): code formatting * docs(nxdev): only mention angular devkit in angular docs * docs(nxdev): tidy Co-authored-by: Isaac Mann <isaacplmann+git@gmail.com>
This commit is contained in:
parent
c66324d71d
commit
b3aa5c3d25
39
docs/angular/generators/composing-generators.md
Normal file
39
docs/angular/generators/composing-generators.md
Normal file
@ -0,0 +1,39 @@
|
||||
# Composing Generators
|
||||
|
||||
Generators are useful individually, but reusing and composing generators allows you to build whole workflows out of simpler building blocks.
|
||||
|
||||
## Using Nx Devkit Generators
|
||||
|
||||
Nx Devkit generators can be imported and invoked like any javascript function. They often return a `Promise`, so they can be used with the `await` keyword to mimic synchronous code. Because this is standard javascript, control flow logic can be adjusted with `if` blocks and `for` loops as usual.
|
||||
|
||||
```typescript
|
||||
import { libraryGenerator } from '@nrwl/workspace';
|
||||
|
||||
export default async function (tree: Tree, schema: any) {
|
||||
await libraryGenerator(
|
||||
tree, // virtual file system tree
|
||||
{ name: schema.name } // options for the generator
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Using Angular Devkit Schematics
|
||||
|
||||
If you need to use a generator written with the Angular devkit (also known as a schematic) inside of an Nx devkit generator, you need to wrap the schematic inside the `wrapAngularDevkitSchematic` function.
|
||||
|
||||
Note: All Nrwl maintained schematics have been migrated over to the Nx Devkit, so this should only be necessary for third party schematics.
|
||||
|
||||
```typescript
|
||||
import { wrapAngularDevkitSchematic } from '@nrwl/devkit/ngcli-adapter';
|
||||
|
||||
export default async function (tree: Tree, schema: any) {
|
||||
const libraryGenerator = wrapAngularDevkitSchematic(
|
||||
'@nrwl/angular', // plugin name
|
||||
'lib' // schematic name
|
||||
);
|
||||
await libraryGenerator(
|
||||
tree, // virtual file system tree
|
||||
{ name: schema.name } // options for the generator
|
||||
);
|
||||
}
|
||||
```
|
||||
28
docs/angular/generators/using-generators.md
Normal file
28
docs/angular/generators/using-generators.md
Normal file
@ -0,0 +1,28 @@
|
||||
# Using Generators
|
||||
|
||||
## Overview
|
||||
|
||||
Generators provide a way to automate many tasks you regularly perform as part of your development workflow. Whether it is scaffolding out components, features, ensuring libraries are generated and structured in a certain way, or updating your configuration files, generators help you standardize these tasks in a consistent, and predictable manner.
|
||||
|
||||
Generators can be written using `@nrwl/devkit` or `@angular-devkit`. Generators written with the `@angular-devkit` are called schematics. To read more about the concepts of `@angular-devkit` schematics, and building an example schematic, see the [Schematics Authoring Guide](https://angular.io/guide/schematics-authoring).
|
||||
|
||||
The [Workspace Generators](/{{framework}}/generators/workspace-generators) guide shows you how to create, run, and customize workspace generators within your Nx workspace.
|
||||
|
||||
## Types of Generators
|
||||
|
||||
There are three main types of generators:
|
||||
|
||||
1. **Plugin Generators** are available when an Nx plugin has been installed in your workspace.
|
||||
2. **Workspace Generators** are generators that you can create for your own workspace. [Workspace generators](/{{framework}}/generators/workspace-generators) allow you to codify the processes that are unique to your own organization.
|
||||
3. **Update Generators** are invoked by Nx plugins when you [update Nx](/{{framework}}/core-concepts/updating-nx) to keep your config files in sync with the latest versions of third party tools.
|
||||
|
||||
## Invoking Plugin Generators
|
||||
|
||||
Generators allow you to create or modify your codebase in a simple and repeatable way. Generators are invoked using the [`nx generate`](/{{framework}}/cli/generate) command.
|
||||
|
||||
```bash
|
||||
nx generate [plugin]:[generator-name] [options]
|
||||
nx generate @nrwl/react:component mycmp --project=myapp
|
||||
```
|
||||
|
||||
It is important to have a clean git working directory before invoking a generator so that you can easily revert changes and re-invoke the generator with different inputs.
|
||||
107
docs/angular/generators/workspace-generators.md
Normal file
107
docs/angular/generators/workspace-generators.md
Normal file
@ -0,0 +1,107 @@
|
||||
# Workspace Generators
|
||||
|
||||
Workspace generators provide a way to automate many tasks you regularly perform as part of your development workflow. Whether it is scaffolding out components, features, or ensuring libraries are generated and structured in a certain way, generators help you standardize these tasks in a consistent, and predictable manner. Nx provides tooling around creating, and running custom generators from within your workspace. This guide shows you how to create, run, and customize workspace generators within your Nx workspace.
|
||||
|
||||
## Creating a workspace generator
|
||||
|
||||
Use the Nx CLI to generate the initial files needed for your workspace generator.
|
||||
|
||||
```bash
|
||||
nx generate @nrwl/workspace:workspace-generator my-generator
|
||||
```
|
||||
|
||||
After the command is finished, the workspace generator is created under the `tools/generators` folder.
|
||||
|
||||
```treeview
|
||||
happynrwl/
|
||||
├── apps/
|
||||
├── libs/
|
||||
├── tools/
|
||||
│ ├── generators
|
||||
│ | └── my-generator/
|
||||
│ | | ├── index.ts
|
||||
│ | | └── schema.json
|
||||
├── nx.json
|
||||
├── package.json
|
||||
└── tsconfig.base.json
|
||||
```
|
||||
|
||||
The `index.ts` provides an entry point to the generator. The file contains a function that is called to perform manipulations on a tree that represents the file system.
|
||||
The `schema.json` provides a description of the generator, available options, validation information, and default values.
|
||||
|
||||
The initial generator function creates a library.
|
||||
|
||||
```typescript
|
||||
import { Tree, formatFiles, installPackagesTask } from '@nrwl/devkit';
|
||||
import { libraryGenerator } from '@nrwl/workspace';
|
||||
|
||||
export default async function (tree: Tree, schema: any) {
|
||||
await libraryGenerator(tree, { name: schema.name });
|
||||
await formatFiles(tree);
|
||||
return () => {
|
||||
installPackagesTask(tree);
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
To invoke other generators, import the entry point function and run it against the tree tree. `async/await` can be used to make code with Promises read like procedural code. The generator function may return a callback function that is executed after changes to the file system have been applied.
|
||||
|
||||
In the schema.json file for your generator, the `name` is provided as a default option. The `cli` property is set to `nx` to signal that this is a generator that uses `@nrwl/devkit` and not `@angular-devkit`.
|
||||
|
||||
```json
|
||||
{
|
||||
"cli": "nx",
|
||||
"id": "test",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Library name",
|
||||
"$default": {
|
||||
"$source": "argv",
|
||||
"index": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
```
|
||||
|
||||
The `$default` object is used to read arguments from the command-line that are passed to the generator. The first argument passed to this schematic is used as the `name` property.
|
||||
|
||||
## Running a workspace generator
|
||||
|
||||
To run a generator, invoke the `nx workspace-generator` command with the name of the generator.
|
||||
|
||||
```bash
|
||||
nx workspace-generator my-generator mylib
|
||||
```
|
||||
|
||||
## Running a workspace schematic created with @angular-devkit
|
||||
|
||||
Generators that are created using the `@angular-devkit` are called schematics. Workspace schematics that have been created with the `@angular-devkit` will omit the `"cli": "nx"` property in `schema.json`. Nx will recognize this and correctly run the schematic using the same command as an `@nrwl/devkit` generator.
|
||||
|
||||
```bash
|
||||
nx workspace-generator my-schematic mylib
|
||||
```
|
||||
|
||||
The command is also aliased to the previous `workspace-schematic` command, so this still works:
|
||||
|
||||
```bash
|
||||
nx workspace-schematic my-schematic mylib
|
||||
```
|
||||
|
||||
## Debugging Workspace generators
|
||||
|
||||
### With Visual Studio Code
|
||||
|
||||
1. Open the Command Palette and choose `Debug: Create JavaScript Debug Terminal`.
|
||||
This will open a terminal with debugging enabled.
|
||||
2. Set breakpoints in your code
|
||||
3. Run `nx workspace-generator my-generator` in the debug terminal.
|
||||
|
||||

|
||||
|
||||
## Workspace Generator Utilities
|
||||
|
||||
The `@nrwl/devkit` package provides many utility functions that can be used in schematics to help with modifying files, reading and updating configuration files, and working with an Abstract Syntax Tree (AST).
|
||||
@ -942,12 +942,32 @@
|
||||
{
|
||||
"name": "Using Generators",
|
||||
"id": "using-schematics",
|
||||
"file": "shared/using-generators"
|
||||
"file": "angular/generators/using-generators"
|
||||
},
|
||||
{
|
||||
"name": "Workspace Generators",
|
||||
"id": "workspace-generators",
|
||||
"file": "shared/tools-workspace-generators"
|
||||
"file": "angular/generators/workspace-generators"
|
||||
},
|
||||
{
|
||||
"name": "Composing Generators",
|
||||
"id": "composing-generators",
|
||||
"file": "angular/generators/composing-generators"
|
||||
},
|
||||
{
|
||||
"name": "Generator Options",
|
||||
"id": "generator-options",
|
||||
"file": "shared/generators/generator-options"
|
||||
},
|
||||
{
|
||||
"name": "Creating Files",
|
||||
"id": "creating-files",
|
||||
"file": "shared/generators/creating-files"
|
||||
},
|
||||
{
|
||||
"name": "Modifying Files",
|
||||
"id": "modifying-files",
|
||||
"file": "shared/generators/modifying-files"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -2054,12 +2074,32 @@
|
||||
{
|
||||
"name": "Using Generators",
|
||||
"id": "using-schematics",
|
||||
"file": "shared/using-generators"
|
||||
"file": "shared/generators/using-generators"
|
||||
},
|
||||
{
|
||||
"name": "Workspace Generators",
|
||||
"id": "workspace-generators",
|
||||
"file": "shared/tools-workspace-generators"
|
||||
"file": "shared/generators/workspace-generators"
|
||||
},
|
||||
{
|
||||
"name": "Composing Generators",
|
||||
"id": "composing-generators",
|
||||
"file": "shared/generators/composing-generators"
|
||||
},
|
||||
{
|
||||
"name": "Generator Options",
|
||||
"id": "generator-options",
|
||||
"file": "shared/generators/generator-options"
|
||||
},
|
||||
{
|
||||
"name": "Creating Files",
|
||||
"id": "creating-files",
|
||||
"file": "shared/generators/creating-files"
|
||||
},
|
||||
{
|
||||
"name": "Modifying Files",
|
||||
"id": "modifying-files",
|
||||
"file": "shared/generators/modifying-files"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -3117,12 +3157,32 @@
|
||||
{
|
||||
"name": "Using Generators",
|
||||
"id": "using-schematics",
|
||||
"file": "shared/using-generators"
|
||||
"file": "shared/generators/using-generators"
|
||||
},
|
||||
{
|
||||
"name": "Workspace Generators",
|
||||
"id": "workspace-generators",
|
||||
"file": "shared/tools-workspace-generators"
|
||||
"file": "shared/generators/workspace-generators"
|
||||
},
|
||||
{
|
||||
"name": "Composing Generators",
|
||||
"id": "composing-generators",
|
||||
"file": "shared/generators/composing-generators"
|
||||
},
|
||||
{
|
||||
"name": "Generator Options",
|
||||
"id": "generator-options",
|
||||
"file": "shared/generators/generator-options"
|
||||
},
|
||||
{
|
||||
"name": "Creating Files",
|
||||
"id": "creating-files",
|
||||
"file": "shared/generators/creating-files"
|
||||
},
|
||||
{
|
||||
"name": "Modifying Files",
|
||||
"id": "modifying-files",
|
||||
"file": "shared/generators/modifying-files"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@ -40,7 +40,7 @@ The Nx Devkit is the underlying technology used to customize Nx to support diffe
|
||||
|
||||
### Pay as you go
|
||||
|
||||
As with most things in Nx, the core of Nx Devkit is very simple. It only uses language primitives and immutable objects (the tree being the only exception). See [Simplest Generator](/{{framework}}/generators/using-schematics#simplest-generator) and [Simplest Executor](/{{framework}}/executors/using-builders#simplest-executor) for examples on creating generators and executors. The [Using Executors](/{{framework}}/executors/using-builders) and [Using Generators](/{{framework}}/generators/using-schematics) guides also have additional information on executors and generators.
|
||||
As with most things in Nx, the core of Nx Devkit is very simple. It only uses language primitives and immutable objects (the tree being the only exception). See [Simplest Generator](/{{framework}}/generators/creating-files) and [Simplest Executor](/{{framework}}/executors/using-builders#simplest-executor) for examples on creating generators and executors. The [Using Executors](/{{framework}}/executors/using-builders) and [Using Generators](/{{framework}}/generators/using-schematics) guides also have additional information on executors and generators.
|
||||
|
||||
## Learn more
|
||||
|
||||
|
||||
18
docs/shared/generators/composing-generators.md
Normal file
18
docs/shared/generators/composing-generators.md
Normal file
@ -0,0 +1,18 @@
|
||||
# Composing Generators
|
||||
|
||||
Generators are useful individually, but reusing and composing generators allows you to build whole workflows out of simpler building blocks.
|
||||
|
||||
## Using Nx Devkit Generators
|
||||
|
||||
Nx Devkit generators can be imported and invoked like any javascript function. They often return a `Promise`, so they can be used with the `await` keyword to mimic synchronous code. Because this is standard javascript, control flow logic can be adjusted with `if` blocks and `for` loops as usual.
|
||||
|
||||
```typescript
|
||||
import { libraryGenerator } from '@nrwl/workspace';
|
||||
|
||||
export default async function (tree: Tree, schema: any) {
|
||||
await libraryGenerator(
|
||||
tree, // virtual file system tree
|
||||
{ name: schema.name } // options for the generator
|
||||
);
|
||||
}
|
||||
```
|
||||
146
docs/shared/generators/creating-files.md
Normal file
146
docs/shared/generators/creating-files.md
Normal file
@ -0,0 +1,146 @@
|
||||
# Creating files with a generator
|
||||
|
||||
Generators provide an API for managing files within your workspace. You can use generators to do things such as create, update, move, and delete files. Files with static or dynamic content can also be created.
|
||||
|
||||
The generator below shows you how to generate a library, and then scaffold out additional files with the newly created library.
|
||||
|
||||
First, you define a folder to store your static or dynamic templates used to generated files. This is commonly done in a `files` folder.
|
||||
|
||||
```treeview
|
||||
happynrwl/
|
||||
├── apps/
|
||||
├── libs/
|
||||
├── tools/
|
||||
│ ├── generators
|
||||
│ | └── my-generator/
|
||||
│ | | └── files
|
||||
│ | | └── NOTES.md
|
||||
│ | | ├── index.ts
|
||||
│ | | └── schema.json
|
||||
├── nx.json
|
||||
├── package.json
|
||||
└── tsconfig.base.json
|
||||
```
|
||||
|
||||
The files can use EJS syntax to substitute variables and logic. See the [EJS Docs](https://ejs.co/) to see more information about how to write these template files.
|
||||
|
||||
Example NOTES.md:
|
||||
|
||||
```markdown
|
||||
Hello, my name is <%= name %>!
|
||||
```
|
||||
|
||||
Next, update the `index.ts` file for the generator, and generate the new files.
|
||||
|
||||
```typescript
|
||||
import {
|
||||
Tree,
|
||||
formatFiles,
|
||||
installPackagesTask,
|
||||
generateFiles,
|
||||
joinPathFragments,
|
||||
readProjectConfiguration,
|
||||
} from '@nrwl/devkit';
|
||||
import { libraryGenerator } from '@nrwl/workspace';
|
||||
|
||||
export default async function (tree: Tree, schema: any) {
|
||||
await libraryGenerator(tree, { name: schema.name });
|
||||
const libraryRoot = readProjectConfiguration(tree, schema.name).root;
|
||||
generateFiles(
|
||||
tree, // the virtual file system
|
||||
joinPathFragments(__dirname, './files'), // path to the file templates
|
||||
libraryRoot, // destination path of the files
|
||||
schema // config object to replace variable in file templates
|
||||
);
|
||||
await formatFiles(tree);
|
||||
return () => {
|
||||
installPackagesTask(tree);
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
The exported function first creates the library, then creates the additional files in the new library's folder.
|
||||
|
||||
Next, run the generator:
|
||||
|
||||
> Use the `-d` or `--dry-run` flag to see your changes without applying them.
|
||||
|
||||
```bash
|
||||
nx workspace-generator my-generator mylib
|
||||
```
|
||||
|
||||
The following information will be displayed.
|
||||
|
||||
```bash
|
||||
CREATE libs/mylib/README.md
|
||||
CREATE libs/mylib/.babelrc
|
||||
CREATE libs/mylib/src/index.ts
|
||||
CREATE libs/mylib/src/lib/mylib.spec.ts
|
||||
CREATE libs/mylib/src/lib/mylib.ts
|
||||
CREATE libs/mylib/tsconfig.json
|
||||
CREATE libs/mylib/tsconfig.lib.json
|
||||
UPDATE tsconfig.base.json
|
||||
UPDATE workspace.json
|
||||
UPDATE nx.json
|
||||
CREATE libs/mylib/.eslintrc.json
|
||||
CREATE libs/mylib/jest.config.js
|
||||
CREATE libs/mylib/tsconfig.spec.json
|
||||
UPDATE jest.config.js
|
||||
CREATE libs/mylib/NOTES.md
|
||||
```
|
||||
|
||||
`libs/mylib/NOTES.md` will contain the content with substituted variables:
|
||||
|
||||
```markdown
|
||||
Hello, my name is mylib!
|
||||
```
|
||||
|
||||
## Dynamic File Names
|
||||
|
||||
If you want the generated file or folder name to contain variable values, use `__variable__`. So `NOTES-for-__name__.md` would be resolved to `NOTES_for_mylib.md` in the above example.
|
||||
|
||||
## EJS Syntax Quickstart
|
||||
|
||||
The EJS syntax can do much more than replace variable names with values. Here are some common techniques.
|
||||
|
||||
1. Pass a function into the template:
|
||||
|
||||
```typescript
|
||||
// template file
|
||||
This is my <%= uppercase(name) %>
|
||||
```
|
||||
|
||||
```typescript
|
||||
// typescript file
|
||||
function uppercase(val) {
|
||||
val.toUppercase();
|
||||
}
|
||||
|
||||
// later
|
||||
|
||||
generateFiles(tree, joinPathFragments(__dirname, './files'), libraryRoot, {
|
||||
uppercase,
|
||||
name: schema.name,
|
||||
});
|
||||
```
|
||||
|
||||
2. Use javascript for control flow in the template:
|
||||
|
||||
```typescript
|
||||
<% if(shortVersion) { %>
|
||||
This is the short version.
|
||||
<% } else {
|
||||
for(let x=0; x<numRepetitions; x++) {
|
||||
%>
|
||||
This text will be repeated <%= numRepetitions %> times.
|
||||
<% } // end for loop
|
||||
} // end else block %>
|
||||
```
|
||||
|
||||
```typescript
|
||||
// typescript file
|
||||
generateFiles(tree, joinPathFragments(__dirname, './files'), libraryRoot, {
|
||||
shortVerstion: false,
|
||||
numRepetitions: 3,
|
||||
});
|
||||
```
|
||||
103
docs/shared/generators/generator-options.md
Normal file
103
docs/shared/generators/generator-options.md
Normal file
@ -0,0 +1,103 @@
|
||||
# Customizing generator options
|
||||
|
||||
## Adding a TypeScript schema
|
||||
|
||||
To create a TypeScript schema to use in your generator function, define a TypeScript file next to your schema.json named `schema.ts`. Inside the `schema.ts`, define an interface to match the properties in your schema.json file, and whether they are required.
|
||||
|
||||
```typescript
|
||||
export interface GeneratorOptions {
|
||||
name: string;
|
||||
type?: string;
|
||||
}
|
||||
```
|
||||
|
||||
Import the TypeScript schema into your generator file and replace the any in your generator function with the interface.
|
||||
|
||||
```typescript
|
||||
import { Tree, formatFiles, installPackagesTask } from '@nrwl/devkit';
|
||||
import { libraryGenerator } from '@nrwl/workspace';
|
||||
|
||||
export default async function (tree: Tree, schema: GeneratorOptions) {
|
||||
await libraryGenerator(tree, { name: `${schema.name}-${schema.type || ''}` });
|
||||
await formatFiles(tree);
|
||||
return () => {
|
||||
installPackagesTask(tree);
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Adding static options
|
||||
|
||||
Static options for a generator don't prompt the user for input. To add a static option, define a key in the schema.json file with the option name, and define an object with its type, description, and optional default value.
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"id": "my-generator",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Library name",
|
||||
"$default": {
|
||||
"$source": "argv",
|
||||
"index": 0
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "Provide the library type, such as 'data-access' or 'state'"
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
```
|
||||
|
||||
If you run the generator without providing a value for the type, it is not included in the generated name of the library.
|
||||
|
||||
## Adding dynamic prompts
|
||||
|
||||
Dynamic options can prompt the user to select from a list of options. To define a prompt, add an `x-prompt` property to the option object, set the type to list, and define an items array for the choices.
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"id": "my-generator",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Library name",
|
||||
"$default": {
|
||||
"$source": "argv",
|
||||
"index": 0
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "Provide the library type",
|
||||
"x-prompt": {
|
||||
"message": "Which type of library would you like to generate?",
|
||||
"type": "list",
|
||||
"items": [
|
||||
{
|
||||
"value": "data-access",
|
||||
"label": "Data Access"
|
||||
},
|
||||
{
|
||||
"value": "feature",
|
||||
"label": "Feature"
|
||||
},
|
||||
{
|
||||
"value": "state",
|
||||
"label": "State Management"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
```
|
||||
|
||||
Running the generator without providing a value for the type will prompt the user to make a selection.
|
||||
132
docs/shared/generators/modifying-files.md
Normal file
132
docs/shared/generators/modifying-files.md
Normal file
@ -0,0 +1,132 @@
|
||||
# Modifying Files with a Generator
|
||||
|
||||
Modifying existing files is an order of magnitude harder than creating new files, so care should be taken when trying to automate this process. When the situation merits it, automating a process can lead to tremendous benefits across the organization. Here are some approaches listed from simplest to most complex.
|
||||
|
||||
## Compose Existing Generators
|
||||
|
||||
If you can compose together existing generators to modify the files you need, you should take that approach. See [Composing Generators](./composing-generators) for more information.
|
||||
|
||||
## Modify JSON Files
|
||||
|
||||
JSON files are fairly simple to modify, given their predictable structure.
|
||||
|
||||
The following example adds a `package.json` script that issues a friendly greeting.
|
||||
|
||||
```typescript
|
||||
import { updateJson } from '@nrwl/devkit';
|
||||
|
||||
export default async function (tree: Tree, schema: any) {
|
||||
updateJson(tree, 'package.json', (pkgJson) => {
|
||||
// if scripts is undefined, set it to an empty object
|
||||
pkgJson.scripts = pkgJson.scripts ?? {};
|
||||
// add greet script
|
||||
pkgJson.scripts.greet = 'echo "Hello!"';
|
||||
// return modified JSON object
|
||||
return pkgJson;
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## String Replace
|
||||
|
||||
For files that are not as predictable as JSON files (like `.ts`, `.md` or `.css` files), modifying the contents can get tricky. One approach is to do a find and replace on the string contents of the file.
|
||||
|
||||
Let's say we want to replace any instance of `thomasEdison` with `nikolaTesla` in the `index.ts` file.
|
||||
|
||||
```typescript
|
||||
export default async function (tree: Tree, schema: any) {
|
||||
const filePath = `path/to/index.ts`;
|
||||
const contents = tree.read(filePath);
|
||||
contents.replace('thomasEdison', 'nikolaTesla');
|
||||
tree.write(filePath, contents);
|
||||
}
|
||||
```
|
||||
|
||||
This works, but only replaces the first instance of `thomasEdison`. To replace them all, you need to use regular expressions. (Regular expressions also give you a lot more flexibility in how you search for a string.)
|
||||
|
||||
```typescript
|
||||
export default async function (tree: Tree, schema: any) {
|
||||
const filePath = `path/to/index.ts`;
|
||||
const contents = tree.read(filePath);
|
||||
contents.replace(/thomasEdison/g, 'nikolaTesla');
|
||||
tree.write(filePath, contents);
|
||||
}
|
||||
```
|
||||
|
||||
## AST Manipulation
|
||||
|
||||
ASTs (Abstract Syntax Trees) allow you to understand exactly the code you're modifying. Replacing a string value can accidentally modify text found in a comment rather than changing the name of a variable.
|
||||
|
||||
We'll write a generator that replaces all instances of the type `Array<something>` with `something[]`. To help accomplish this, we'll use the `@phenomnominal/tsquery` npm package and the [AST Explorer](https://astexplorer.net) site. TSQuery allows you to query and modify ASTs with a syntax similar to CSS selectors. The AST Explorer tool allows you to easily examine the AST for a given snippet of code.
|
||||
|
||||
First, go to [AST Explorer](https://astexplorer.net) and paste in a snippet of code that contains the input and desired output of our generator.
|
||||
|
||||
```typescript
|
||||
// input
|
||||
const arr: Array<string> = [];
|
||||
|
||||
// desired output
|
||||
const arr: string[] = [];
|
||||
```
|
||||
|
||||
Make sure the parser is set to `typescript`. When you place the cursor on the `Array` text, the right hand panel highlights the corresponding node of the AST. The AST node we're looking for looks like this:
|
||||
|
||||
```typescript
|
||||
{ // TypeReference
|
||||
typeName: { // Identifier
|
||||
escapedText: "Array"
|
||||
},
|
||||
typeArguments: [/* this is where the generic type parameter is specified */]
|
||||
}
|
||||
```
|
||||
|
||||
Second, we need to choose a selector to target this node. Just like with CSS selectors, there is an art to choosing a selector that is specific enough to target the correct nodes, but not overly tied to a certain structure. For our simple example, we can use `TypeReference` to select the parent node and check to see if it has a `typeName` of `Array` before we perform the replacement. We'll then use the `typeArguments` to get the text inside the `<>` characters.
|
||||
|
||||
The finished code looks like this:
|
||||
|
||||
```typescript
|
||||
import { readProjectConfiguration, Tree } from '@nrwl/devkit';
|
||||
import { tsquery } from '@phenomnomnominal/tsquery';
|
||||
import { TypeReferenceNode } from 'typescript';
|
||||
|
||||
/**
|
||||
* Run the callback on all files inside the specified path
|
||||
*/
|
||||
function visitAllFiles(
|
||||
tree: Tree,
|
||||
path: string,
|
||||
callback: (filePath: string) => void
|
||||
) {
|
||||
tree.children(path).forEach((fileName) => {
|
||||
const filePath = `${path}/${fileName}`;
|
||||
if (!tree.isFile(filePath)) {
|
||||
visitAllFiles(tree, filePath, callback);
|
||||
} else {
|
||||
callback(filePath);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default function (tree: Tree, schema: any) {
|
||||
const sourceRoot = readProjectConfiguration(tree, schema.name).sourceRoot;
|
||||
visitAllFiles(tree, sourceRoot, (filePath) => {
|
||||
const fileEntry = tree.read(filePath);
|
||||
const contents = fileEntry.toString();
|
||||
|
||||
// Check each `TypeReference` node to see if we need to replace it
|
||||
const newContents = tsquery.replace(contents, 'TypeReference', (node) => {
|
||||
const trNode = node as TypeReferenceNode;
|
||||
if (trNode.typeName.getText() === 'Array') {
|
||||
const typeArgument = trNode.typeArguments[0];
|
||||
return `${typeArgument.getText()}[]`;
|
||||
}
|
||||
// return undefined does not replace anything
|
||||
});
|
||||
|
||||
// only write the file if something has changed
|
||||
if (newContents !== contents) {
|
||||
tree.write(filePath, newContents);
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
26
docs/shared/generators/using-generators.md
Normal file
26
docs/shared/generators/using-generators.md
Normal file
@ -0,0 +1,26 @@
|
||||
# Using Generators
|
||||
|
||||
## Overview
|
||||
|
||||
Generators provide a way to automate many tasks you regularly perform as part of your development workflow. Whether it is scaffolding out components, features, ensuring libraries are generated and structured in a certain way, or updating your configuration files, generators help you standardize these tasks in a consistent, and predictable manner.
|
||||
|
||||
The [Workspace Generators](/{{framework}}/generators/workspace-generators) guide shows you how to create, run, and customize workspace generators within your Nx workspace.
|
||||
|
||||
## Types of Generators
|
||||
|
||||
There are three main types of generators:
|
||||
|
||||
1. **Plugin Generators** are available when an Nx plugin has been installed in your workspace.
|
||||
2. **Workspace Generators** are generators that you can create for your own workspace. [Workspace generators](/{{framework}}/generators/workspace-generators) allow you to codify the processes that are unique to your own organization.
|
||||
3. **Update Generators** are invoked by Nx plugins when you [update Nx](/{{framework}}/core-concepts/updating-nx) to keep your config files in sync with the latest versions of third party tools.
|
||||
|
||||
## Invoking Plugin Generators
|
||||
|
||||
Generators allow you to create or modify your codebase in a simple and repeatable way. Generators are invoked using the [`nx generate`](/{{framework}}/cli/generate) command.
|
||||
|
||||
```bash
|
||||
nx generate [plugin]:[generator-name] [options]
|
||||
nx generate @nrwl/react:component mycmp --project=myapp
|
||||
```
|
||||
|
||||
It is important to have a clean git working directory before invoking a generator so that you can easily revert changes and re-invoke the generator with different inputs.
|
||||
93
docs/shared/generators/workspace-generators.md
Normal file
93
docs/shared/generators/workspace-generators.md
Normal file
@ -0,0 +1,93 @@
|
||||
# Workspace Generators
|
||||
|
||||
Workspace generators provide a way to automate many tasks you regularly perform as part of your development workflow. Whether it is scaffolding out components, features, or ensuring libraries are generated and structured in a certain way, generators help you standardize these tasks in a consistent, and predictable manner. Nx provides tooling around creating, and running custom generators from within your workspace. This guide shows you how to create, run, and customize workspace generators within your Nx workspace.
|
||||
|
||||
## Creating a workspace generator
|
||||
|
||||
Use the Nx CLI to generate the initial files needed for your workspace generator.
|
||||
|
||||
```bash
|
||||
nx generate @nrwl/workspace:workspace-generator my-generator
|
||||
```
|
||||
|
||||
After the command is finished, the workspace generator is created under the `tools/generators` folder.
|
||||
|
||||
```treeview
|
||||
happynrwl/
|
||||
├── apps/
|
||||
├── libs/
|
||||
├── tools/
|
||||
│ ├── generators
|
||||
│ | └── my-generator/
|
||||
│ | | ├── index.ts
|
||||
│ | | └── schema.json
|
||||
├── nx.json
|
||||
├── package.json
|
||||
└── tsconfig.base.json
|
||||
```
|
||||
|
||||
The `index.ts` provides an entry point to the generator. The file contains a function that is called to perform manipulations on a tree that represents the file system.
|
||||
The `schema.json` provides a description of the generator, available options, validation information, and default values.
|
||||
|
||||
The initial generator function creates a library.
|
||||
|
||||
```typescript
|
||||
import { Tree, formatFiles, installPackagesTask } from '@nrwl/devkit';
|
||||
import { libraryGenerator } from '@nrwl/workspace';
|
||||
|
||||
export default async function (tree: Tree, schema: any) {
|
||||
await libraryGenerator(tree, { name: schema.name });
|
||||
await formatFiles(tree);
|
||||
return () => {
|
||||
installPackagesTask(tree);
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
To invoke other generators, import the entry point function and run it against the tree tree. `async/await` can be used to make code with Promises read like procedural code. The generator function may return a callback function that is executed after changes to the file system have been applied.
|
||||
|
||||
In the schema.json file for your generator, the `name` is provided as a default option. The `cli` property is set to `nx` to signal that this is a generator that uses `@nrwl/devkit` and not `@angular-devkit`.
|
||||
|
||||
```json
|
||||
{
|
||||
"cli": "nx",
|
||||
"id": "test",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Library name",
|
||||
"$default": {
|
||||
"$source": "argv",
|
||||
"index": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
```
|
||||
|
||||
The `$default` object is used to read arguments from the command-line that are passed to the generator. The first argument passed to this schematic is used as the `name` property.
|
||||
|
||||
## Running a workspace generator
|
||||
|
||||
To run a generator, invoke the `nx workspace-generator` command with the name of the generator.
|
||||
|
||||
```bash
|
||||
nx workspace-generator my-generator mylib
|
||||
```
|
||||
|
||||
## Debugging Workspace generators
|
||||
|
||||
### With Visual Studio Code
|
||||
|
||||
1. Open the Command Palette and choose `Debug: Create JavaScript Debug Terminal`.
|
||||
This will open a terminal with debugging enabled.
|
||||
2. Set breakpoints in your code
|
||||
3. Run `nx workspace-generator my-generator` in the debug terminal.
|
||||
|
||||

|
||||
|
||||
## Workspace Generator Utilities
|
||||
|
||||
The `@nrwl/devkit` package provides many utility functions that can be used in schematics to help with modifying files, reading and updating configuration files, and working with an Abstract Syntax Tree (AST).
|
||||
@ -1,308 +0,0 @@
|
||||
# Workspace Generators
|
||||
|
||||
Workspace generators provide a way to automate many tasks you regularly perform as part of your development workflow. Whether it is scaffolding out components, features, or ensuring libraries are generated and structured in a certain way, generators help you standardize these tasks in a consistent, and predictable manner. Nx provides tooling around creating, and running custom generators from within your workspace. This guide shows you how to create, run, and customize workspace generators within your Nx workspace.
|
||||
|
||||
## Creating a workspace generator
|
||||
|
||||
Use the Nx CLI to generate the initial files needed for your workspace generator.
|
||||
|
||||
```bash
|
||||
nx generate @nrwl/workspace:workspace-generator my-generator
|
||||
```
|
||||
|
||||
After the command is finished, the workspace generator is created under the `tools/generators` folder.
|
||||
|
||||
```treeview
|
||||
happynrwl/
|
||||
├── apps/
|
||||
├── libs/
|
||||
├── tools/
|
||||
│ ├── generators
|
||||
│ | └── my-generator/
|
||||
│ | | ├── index.ts
|
||||
│ | | └── schema.json
|
||||
├── nx.json
|
||||
├── package.json
|
||||
└── tsconfig.base.json
|
||||
```
|
||||
|
||||
The `index.ts` provides an entry point to the generator. The file contains a function that is called to perform manipulations on a tree that represents the file system.
|
||||
The `schema.json` provides a description of the generator, available options, validation information, and default values.
|
||||
|
||||
The initial generator function creates a library.
|
||||
|
||||
```typescript
|
||||
import { Tree, formatFiles, installPackagesTask } from '@nrwl/devkit';
|
||||
import { libraryGenerator } from '@nrwl/workspace';
|
||||
|
||||
export default async function (tree: Tree, schema: any) {
|
||||
await libraryGenerator(tree, { name: schema.name });
|
||||
await formatFiles(tree);
|
||||
return () => {
|
||||
installPackagesTask(tree);
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
To invoke other generators, import the entry point function and run it against the tree tree. `async/await` can be used to make code with Promises read like procedural code. The generator function may return a callback function that is executed after changes to the file system have been applied.
|
||||
|
||||
In the schema.json file for your generator, the `name` is provided as a default option. The `cli` property is set to `nx` to signal that this is a generator that uses `@nrwl/devkit` and not `@angular-devkit`.
|
||||
|
||||
```json
|
||||
{
|
||||
"cli": "nx",
|
||||
"id": "test",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Library name",
|
||||
"$default": {
|
||||
"$source": "argv",
|
||||
"index": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
```
|
||||
|
||||
The `$default` object is used to read arguments from the command-line that are passed to the generator. The first argument passed to this schematic is used as the `name` property.
|
||||
|
||||
## Running a workspace generator
|
||||
|
||||
To run a generator, invoke the `nx workspace-generator` command with the name of the generator.
|
||||
|
||||
```bash
|
||||
nx workspace-generator my-generator mylib
|
||||
```
|
||||
|
||||
## Running a workspace schematic created with @angular-devkit
|
||||
|
||||
Generators that are created using the `@angular-devkit` are called schematics. Workspace schematics that have been created with the `@angular-devkit` will omit the `"cli": "nx"` property in `schema.json`. Nx will recognize this and correctly run the schematic using the same command as an `@nrwl/devkit` generator.
|
||||
|
||||
```bash
|
||||
nx workspace-generator my-schematic mylib
|
||||
```
|
||||
|
||||
The command is also aliased to the previous `workspace-schematic` command, so this still works:
|
||||
|
||||
```bash
|
||||
nx workspace-schematic my-schematic mylib
|
||||
```
|
||||
|
||||
## Creating files with a generator
|
||||
|
||||
Generators provide an API for managing files within your workspace. You can use generators to do things such as create, update, move, and delete files. Files with static or dynamic content can also be created.
|
||||
|
||||
The generator below shows you how to generate a library, and then scaffold out additional files with the newly created library.
|
||||
|
||||
First, you define a folder to store your static or dynamic templates used to generated files. This is commonly done in a `files` folder.
|
||||
|
||||
```treeview
|
||||
happynrwl/
|
||||
├── apps/
|
||||
├── libs/
|
||||
├── tools/
|
||||
│ ├── generators
|
||||
│ | └── my-generator/
|
||||
│ | | └── files
|
||||
│ | | └── NOTES.md
|
||||
│ | | ├── index.ts
|
||||
│ | | └── schema.json
|
||||
├── nx.json
|
||||
├── package.json
|
||||
└── tsconfig.base.json
|
||||
```
|
||||
|
||||
The files can use EJS syntax to substitute variables and logic. See the [EJS Docs](https://ejs.co/) to see more information about how to write these template files.
|
||||
|
||||
Example NOTES.md:
|
||||
|
||||
```md
|
||||
Hello, my name is <%= name %>!
|
||||
```
|
||||
|
||||
Next, update the `index.ts` file for the generator, and generate the new files.
|
||||
|
||||
```typescript
|
||||
import {
|
||||
Tree,
|
||||
formatFiles,
|
||||
installPackagesTask,
|
||||
generateFiles,
|
||||
joinPathFragments,
|
||||
readProjectConfiguration,
|
||||
} from '@nrwl/devkit';
|
||||
import { libraryGenerator } from '@nrwl/workspace';
|
||||
|
||||
export default async function (tree: Tree, schema: any) {
|
||||
await libraryGenerator(tree, { name: schema.name });
|
||||
const libraryRoot = readProjectConfiguration(tree, schema.name).root;
|
||||
generateFiles(
|
||||
tree, // the virtual file system
|
||||
joinPathFragments(__dirname, './files'), // path to the file templates
|
||||
libraryRoot, // destination path of the files
|
||||
schema // config object to replace variable in file templates
|
||||
);
|
||||
await formatFiles(tree);
|
||||
return () => {
|
||||
installPackagesTask(tree);
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
The exported function first creates the library, then creates the additional files in the new library's folder.
|
||||
|
||||
Next, run the generator:
|
||||
|
||||
> Use the `-d` or `--dry-run` flag to see your changes without applying them.
|
||||
|
||||
```bash
|
||||
nx workspace-generator my-generator mylib
|
||||
```
|
||||
|
||||
The following information will be displayed.
|
||||
|
||||
```bash
|
||||
CREATE libs/mylib/README.md
|
||||
CREATE libs/mylib/.babelrc
|
||||
CREATE libs/mylib/src/index.ts
|
||||
CREATE libs/mylib/src/lib/mylib.spec.ts
|
||||
CREATE libs/mylib/src/lib/mylib.ts
|
||||
CREATE libs/mylib/tsconfig.json
|
||||
CREATE libs/mylib/tsconfig.lib.json
|
||||
UPDATE tsconfig.base.json
|
||||
UPDATE workspace.json
|
||||
UPDATE nx.json
|
||||
CREATE libs/mylib/.eslintrc.json
|
||||
CREATE libs/mylib/jest.config.js
|
||||
CREATE libs/mylib/tsconfig.spec.json
|
||||
UPDATE jest.config.js
|
||||
CREATE libs/mylib/NOTES.md
|
||||
```
|
||||
|
||||
`libs/mylib/NOTES.md` will contain the content with substituted variables:
|
||||
|
||||
```md
|
||||
Hello, my name is mylib!
|
||||
```
|
||||
|
||||
## Customizing generator options
|
||||
|
||||
### Adding a TypeScript schema
|
||||
|
||||
To create a TypeScript schema to use in your generator function, define a TypeScript file next to your schema.json named `schema.ts`. Inside the `schema.ts`, define an interface to match the properties in your schema.json file, and whether they are required.
|
||||
|
||||
```typescript
|
||||
export interface SchematicOptions {
|
||||
name: string;
|
||||
type?: string;
|
||||
}
|
||||
```
|
||||
|
||||
Import the TypeScript schema into your generator file and replace the any in your generator function with the interface.
|
||||
|
||||
```typescript
|
||||
import { Tree, formatFiles, installPackagesTask } from '@nrwl/devkit';
|
||||
import { libraryGenerator } from '@nrwl/workspace';
|
||||
|
||||
export default async function (tree: Tree, schema: SchematicOptions) {
|
||||
await libraryGenerator(tree, { name: `${schema.name}-${schema.type || ''}` });
|
||||
await formatFiles(tree);
|
||||
return () => {
|
||||
installPackagesTask(tree);
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Adding static options
|
||||
|
||||
Static options for a generator don't prompt the user for input. To add a static option, define a key in the schema.json file with the option name, and define an object with its type, description, and optional default value.
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"id": "my-generator",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Library name",
|
||||
"$default": {
|
||||
"$source": "argv",
|
||||
"index": 0
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "Provide the library type, such as 'data-access' or 'state'"
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
```
|
||||
|
||||
If you run the generator without providing a value for the type, it is not included in the generated name of the library.
|
||||
|
||||
### Adding dynamic prompts
|
||||
|
||||
Dynamic options can prompt the user to select from a list of options. To define a prompt, add an `x-prompt` property to the option object, set the type to list, and define an items array for the choices.
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"id": "my-generator",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Library name",
|
||||
"$default": {
|
||||
"$source": "argv",
|
||||
"index": 0
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "Provide the library type",
|
||||
"x-prompt": {
|
||||
"message": "Which type of library would you like to generate?",
|
||||
"type": "list",
|
||||
"items": [
|
||||
{
|
||||
"value": "data-access",
|
||||
"label": "Data Access"
|
||||
},
|
||||
{
|
||||
"value": "feature",
|
||||
"label": "Feature"
|
||||
},
|
||||
{
|
||||
"value": "state",
|
||||
"label": "State Management"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
```
|
||||
|
||||
Running the generator without providing a value for the type will prompt the user to make a selection.
|
||||
|
||||
## Debugging Workspace generators
|
||||
|
||||
### With Visual Studio Code
|
||||
|
||||
1. Open the Command Palette and choose `Debug: Create JavaScript Debug Terminal`.
|
||||
This will open a terminal with debugging enabled.
|
||||
2. Set breakpoints in your code
|
||||
3. Run `nx workspace-generator my-generator` in the debug terminal.
|
||||
|
||||

|
||||
|
||||
## Workspace Generator Utilities
|
||||
|
||||
The `@nrwl/devkit` package provides many utility functions that can be used in schematics to help with modifying files, reading and updating configuration files, and working with an Abstract Syntax Tree (AST).
|
||||
@ -1,301 +0,0 @@
|
||||
# Using Generators
|
||||
|
||||
## Overview
|
||||
|
||||
Generators provide a way to automate many tasks you regularly perform as part of your development workflow. Whether it is scaffolding out components, features, ensuring libraries are generated and structured in a certain way, or updating your configuration files, generators help you standardize these tasks in a consistent, and predictable manner.
|
||||
|
||||
Generators can be written using `@nrwl/devkit` or `@angular-devkit`. Generators written with the `@angular-devkit` are called schematics. To read more about the concepts of `@angular-devkit` schematics, and building an example schematic, see the [Schematics Authoring Guide](https://angular.io/guide/schematics-authoring).
|
||||
|
||||
The [Workspace Generators](/{{framework}}/generators/workspace-generators) guide shows you how to create, run, and customize workspace generators within your Nx workspace.
|
||||
|
||||
## Types of generators
|
||||
|
||||
There are three main types of generators:
|
||||
|
||||
1. **Plugin Generators** are available when an Nx plugin has been installed in your workspace.
|
||||
2. **Workspace Generators** are generators that you can create for your own workspace. [Workspace generators](/{{framework}}/generators/workspace-generators) allow you to codify the processes that are unique to your own organization.
|
||||
3. **Update Generators** are invoked by Nx plugins when you [update Nx](/{{framework}}/core-concepts/updating-nx) to keep your config files in sync with the latest versions of third party tools.
|
||||
|
||||
## Invoking plugin generators
|
||||
|
||||
Generators allow you to create or modify your codebase in a simple and repeatable way. Generators are invoked using the [`nx generate`](/{{framework}}/cli/generate) command.
|
||||
|
||||
```bash
|
||||
nx generate [plugin]:[generator-name] [options]
|
||||
nx generate @nrwl/react:component mycmp --project=myapp
|
||||
```
|
||||
|
||||
It is important to have a clean git working directory before invoking a generator so that you can easily revert changes and re-invoke the generator with different inputs.
|
||||
|
||||
### Simplest Generator
|
||||
|
||||
```json
|
||||
{
|
||||
"cli": "nx",
|
||||
"id": "CustomGenerator",
|
||||
"description": "Create a custom generator",
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": true
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
export default async function (tree, opts) {
|
||||
console.log('options', opts);
|
||||
}
|
||||
```
|
||||
|
||||
### Defining a generator schema
|
||||
|
||||
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/).
|
||||
|
||||
#### Examples
|
||||
|
||||
```json
|
||||
{
|
||||
"cli": "nx",
|
||||
"id": "CustomGenerator",
|
||||
"description": "Create a custom generator",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Generator name",
|
||||
"x-prompt": "What name would you like to use for the workspace generator?"
|
||||
},
|
||||
"skipFormat": {
|
||||
"description": "Skip formatting files",
|
||||
"type": "boolean",
|
||||
"alias": "sf",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
```
|
||||
|
||||
The schema above defines two fields: `name` and `skipFormat`. The `name` field is a string, `skipFormat` is a boolean. The `x-prompt` property tells Nx to ask for the `name` value if one isn't given. The `skipFormat` field has the default value set to `false`. The schema language is rich and lets you use lists, enums, references, etc.. A few more examples:
|
||||
|
||||
```json
|
||||
{
|
||||
"cli": "nx",
|
||||
"id": "CustomGenerator",
|
||||
"description": "Create a custom generator",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"stringOrBoolean": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"default": "mystring!"
|
||||
},
|
||||
{
|
||||
"type": "boolean"
|
||||
}
|
||||
]
|
||||
},
|
||||
"innerObject": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"array": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"complexXPrompt": {
|
||||
"type": "string",
|
||||
"default": "css",
|
||||
"x-prompt": {
|
||||
"message": "Which stylesheet format would you like to use?",
|
||||
"type": "list",
|
||||
"items": [
|
||||
{
|
||||
"value": "css",
|
||||
"label": "CSS"
|
||||
},
|
||||
{
|
||||
"value": "scss",
|
||||
"label": "SASS(.scss)"
|
||||
},
|
||||
{
|
||||
"value": "styl",
|
||||
"label": "Stylus(.styl)"
|
||||
},
|
||||
{
|
||||
"value": "none",
|
||||
"label": "None"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"positionalArg": {
|
||||
"type": "string",
|
||||
"$default": {
|
||||
"$source": "argv",
|
||||
"index": 0
|
||||
}
|
||||
},
|
||||
"currentProject": {
|
||||
"type": "string",
|
||||
"$default": {
|
||||
"$source": "projectName"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Sometimes, you may not know the schema or may not care, in this case, you can set the following:
|
||||
|
||||
```json
|
||||
{
|
||||
"cli": "nx",
|
||||
"id": "CustomGenerator",
|
||||
"description": "Create a custom generator",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["name"],
|
||||
"additionalProperties": true
|
||||
}
|
||||
```
|
||||
|
||||
Because `"additionalProperties"` is `true`, the generator above will accept any extra parameters you pass. They, of course, won't be validated or transformed, but sometimes that's good enough.
|
||||
|
||||
If you want to learn more about the schema language, check out the core plugins at [https://github.com/nrwl/nx](https://github.com/nrwl/nx) for more examples.
|
||||
|
||||
### Implementing a generator
|
||||
|
||||
The implementation is a function that takes two arguments:
|
||||
|
||||
- `tree`: an implementation of the file system
|
||||
- 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_.
|
||||
|
||||
#### Examples
|
||||
|
||||
```typescript
|
||||
import {
|
||||
Tree,
|
||||
generateFiles,
|
||||
formatFiles,
|
||||
installPackagesTask,
|
||||
} from '@nrwl/devkit';
|
||||
|
||||
interface Schema {
|
||||
name: string;
|
||||
skipFormat: boolean;
|
||||
}
|
||||
|
||||
export default async function (tree: Tree, options: Schema) {
|
||||
generateFiles(
|
||||
tree,
|
||||
path.join(__dirname, 'files'),
|
||||
path.join('tools/generators', options.name),
|
||||
options
|
||||
);
|
||||
|
||||
if (!options.skipFormat) {
|
||||
await formatFiles(tree);
|
||||
}
|
||||
|
||||
return () => {
|
||||
installPackagesTask(tree);
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
The generator is an async function. You could create new projects and generate new files, but you could also update existing files and refactor things. It's recommended to limit all the side-effects to interacting with the tree and printing to the console. Sometimes generators perform other side-effects such as installing npm packages. Perform them in the function returned from the generator. Nx won't run the returned function in the dry run mode.
|
||||
|
||||
### Devkit helper functions
|
||||
|
||||
Nx provides helpers several functions for writing generators:
|
||||
|
||||
- `readProjectConfiguration` -- Read the project configuration stored in workspace.json and nx.json.
|
||||
- `addProjectConfiguration` -- Add the project configuration stored in workspace.json and nx.json.
|
||||
- `removeProjectConfiguration` -- Remove the project configuration stored in workspace.json and nx.json.
|
||||
- `updateProjectConfiguration` -- Update the project configuration stored in workspace.json and nx.json.
|
||||
- `readWorkspaceConfiguration` -- Read general workspace configuration such as the default project or cli settings.
|
||||
- `updateWorkspaceConfiguration` -- Update general workspace configuration such as the default project or cli settings.
|
||||
- `getProjects` -- Returns the list of projects.
|
||||
- `generateFiles` -- Generate a folder of files based on provided templates.
|
||||
- `formatFiles` -- Format all the created or updated files using Prettier.
|
||||
- `readJson` -- Read a json file.
|
||||
- `writeJson` -- Write a json file.
|
||||
- `updateJson` -- Update a json file.
|
||||
- `addDependenciesToPackageJson` -- Add dependencies and dev dependencies to package.json
|
||||
- `installPackagesTask` -- Runs `npm install`/`yarn install`/`pnpm install` depending on what is used by the workspaces.
|
||||
- `names` -- Util function to generate different strings based off the provided name.
|
||||
- `getWorkspaceLayout` -- Tells where new libs and apps should be generated.
|
||||
- `offestFromRoot` -- Calculates an offset from the root of the workspace, which is useful for constructing relative URLs.
|
||||
- `stripIndents` -- Strips indents from a multiline string.
|
||||
- `normalizePath` -- Coverts an os specific path to a unix style path.
|
||||
- `joinPathFragments` -- Normalize fragments and joins them with a /.
|
||||
- `toJS` -- Coverts a TypeScript file to JavaScript. Useful for generators that support both.
|
||||
- `visitNotIgnoredFiles` -- Utility to act on all files in a tree that are not ignored by git.
|
||||
- `applyChangesToString`-- Applies a list of changes to a string's original value. This is useful when working with ASTs
|
||||
|
||||
Each of those have detailed API docs. Check the [API Docs](/{{framework}}/nx-devkit/index#functions) for more information.
|
||||
|
||||
It's also important to stress that those are just utility functions. You can use them but you don't have to. You can instead write your own functions that take the tree and do whatever you want to do with it.
|
||||
|
||||
### Composing generators
|
||||
|
||||
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 {
|
||||
Tree,
|
||||
generateFiles,
|
||||
formatFiles,
|
||||
installPackagesTask,
|
||||
} from '@nrwl/devkit';
|
||||
import { libraryGenerator } from '@nrwl/react';
|
||||
|
||||
export default async function (tree: Tree, options: Schema) {
|
||||
const libSideEffects1 = libraryGenerator(tree, { name: options.name1 });
|
||||
const libSideEffects2 = libraryGenerator(tree, { name: options.name2 });
|
||||
await performOperationsOnTheTree(tree);
|
||||
return () => {
|
||||
libSideEffects1();
|
||||
libSideEffects2();
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Testing generators
|
||||
|
||||
The Nx Devkit provides the `createTreeWithEmptyWorkspace` utility to create a tree with an empty workspace that can be used in tests. Other than that, the tests simply invoke the generator and check the changes are made in the tree.
|
||||
|
||||
```typescript
|
||||
import { readProjectConfiguration } from '@nrwl/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
|
||||
import createLib from './lib';
|
||||
|
||||
describe('lib', () => {
|
||||
it('should create a lib', async () => {
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
// update tree before invoking the generator
|
||||
await createLib(tree, { name: 'lib' });
|
||||
|
||||
expect(readProjectConfiguration(tree, 'lib')).toBeDefined();
|
||||
});
|
||||
});
|
||||
```
|
||||
Loading…
x
Reference in New Issue
Block a user