|
| 1 | +# Schematics |
| 2 | +> A scaffolding library for the modern web. |
| 3 | +
|
| 4 | +## Description |
| 5 | +Schematics are generators that transform an existing filesystem. It can create files, refactor existing files, or move files around. |
| 6 | + |
| 7 | +What distinguish Schematics from other generators, such as Yeoman or Yarn Create, is that schematics are purely descriptive; no changes are applied to the actual filesystem until everything is ready to be committed. There is no side effect, by design, in Schematics. |
| 8 | + |
| 9 | +# Glossary |
| 10 | + |
| 11 | +| Term | Description | |
| 12 | +|------|-------------| |
| 13 | +| **Schematics** | A generator that execute descriptive code without side effects on an existing file system. | |
| 14 | +| **Collection** | A list of schematics metadata. Schematics can be referred by name inside a collection. | |
| 15 | +| **Tool** | The code using the Schematics library. | |
| 16 | +| **Tree** | A staging area for changes, containing the original file system, and a list of changes to apply to it. | |
| 17 | +| **Rule** | A function that applies actions to a `Tree`. It returns a new Tree that will contain all transformations to be applied. | |
| 18 | +| **Source** | A function that creates an entirely new `Tree` from an empty filesystem. For example, a file source could read files from disk and create a Create Action for each of those. |
| 19 | +| **Action** | A atomic operation to be validated and committed to a filesystem or a `Tree`. Actions are created by schematics. | |
| 20 | +| **Sink** | The final destination of all `Action`s. | |
| 21 | + |
| 22 | +# Tooling |
| 23 | +Schematics is a library, and does not work by itself. A reference CLI is available in [`@angular/schematics-cli`](../schematics_cli/README.md). This document explain the library usage and the tooling API, but does not go into the tool implementation itself. |
| 24 | + |
| 25 | +The tooling is responsible for the following tasks: |
| 26 | + |
| 27 | +1. Create the Schematic Engine, and pass in a Collection and Schematic loader. |
| 28 | +1. Understand and respect the Schematics metadata and dependencies between collections. Schematics can refer to dependencies, and it's the responsibility of the tool to honor those dependencies. The reference CLI uses NPM packages for its collections. |
| 29 | +1. Create the Options object. Options can be anything, but the schematics can specify a JSON Schema that should be respected. The reference CLI, for example, parses the arguments as a JSON object and validate it with the Schema specified by the collection. |
| 30 | +1. Call the schematics with the original Tree. The tree should represent the initial state of the filesystem. The reference CLI uses the current directory for this. |
| 31 | +1. Create a Sink and commit the result of the schematics to the Sink. Many sinks are provided by the library; FileSystemSink and DryRunSink are examples. |
| 32 | +1. Output any logs propagated by the library, including debugging information. |
| 33 | + |
| 34 | +The tooling API is composed of the following pieces: |
| 35 | + |
| 36 | +## EngineHost |
| 37 | +The `SchematicEngine` is responsible for loading and constructing `Collection`s and `Schematics`'. When creating an engine, the tooling provides an `EngineHost` interface that understands how to create a `CollectionDescription` from a name, how to create a `SchematicDescription` from a `CollectionDescription` and a name, as well as how to create the `Rule` factory for that Schematics. Both of which are information necessary for the `Engine` to work properly. |
| 38 | + |
| 39 | +All Description interfaces are generics that take interfaces as type parameters. Those interfaces can be used by the tooling to store additional information in the `CollectionDescription` and the `SchematicDescription`. The descriptions returned by the host are guaranteed to be the same objects when passing them as input. |
| 40 | + |
| 41 | +### CollectionDescription |
| 42 | +A `CollectionDescription` is the minimum amount of information that `Engine` needs to create a collection. It is currently only a `name`, which is used to validate the collection and cache it. |
| 43 | + |
| 44 | +### SchematicDescription |
| 45 | +A `SchematicDescription` is the minimum amount of information that `Engine` needs to create a schematic. It is currently a `name` (which is used to be cached), and a `CollectionDescription`. The collection description is asserted to be the same description as passed in. It is used later on to reference collections when schematics are created by name only. |
| 46 | + |
| 47 | +### Source from URL |
| 48 | +It is possible for schematics to create `Source`s from a URL. These are useful when we want to load a list of template files. There are 3 default URL protocols supported by the Engine: |
| 49 | + |
| 50 | +- `null:` returns a Tree that's invalid and will throw exceptions. |
| 51 | +- `empty:` returns a Tree that's empty. |
| 52 | +- `host:` returns a copy of the host passed to this schematic, from the context. |
| 53 | + |
| 54 | +### RuleFactory |
| 55 | +The other method necessary to resolve a schematics is the `RuleFactory`, a function that takes an option argument and returns a `Rule`. That factory is created from both descriptions by the host and the result will be called by the Engine when necessary. Please note that the engine cache this `RuleFactory` based on both descriptions, so if a schematic is created twice the `getSchematicRuleFactory` host function will only be called once. |
| 56 | + |
| 57 | +### Default MergeStrategy |
| 58 | +The `EngineHost` can have an optional `defaultMergeStrategy` to specify how the tooling wants to set the default `MergeStrategy`. This will be used if schematics don't specify a merge strategy on their own. |
| 59 | + |
| 60 | +## EngineHost Implementations |
| 61 | + |
| 62 | +### NodeModulesEngineHost |
| 63 | +The Schematics library provides an EngineHost that understands NPM node modules, using node modules to define collections and schematics. |
| 64 | + |
| 65 | +This engine host use the following conventions: |
| 66 | + |
| 67 | +1. A node package needs to define a `schematics` key in its `package.json`, which points to a JSON file that contains collection metadata. This metadata is of the follpwing type: |
| 68 | + |
| 69 | + ```typescript |
| 70 | + interface NodeModuleCollectionJson { |
| 71 | + name: string; |
| 72 | + version?: string; |
| 73 | + description: string; |
| 74 | + schematics: { |
| 75 | + [name: string]: { |
| 76 | + factory: string; |
| 77 | + description: string; |
| 78 | + schema?: string; |
| 79 | + } |
| 80 | + }; |
| 81 | + } |
| 82 | + ``` |
| 83 | + |
| 84 | + The name must be the same name as the NPM package. |
| 85 | +1. The `schematics` dictionary is used to resolve schematics information. |
| 86 | + - The `factory` is a string of the form `modulePath#ExportName`. It is resolved relative to the collection JSON file, and the `ExportName` will be `default` if not specified. |
| 87 | + - The `schema` is a string that points to a JSON Schema file (relative to the collection JSON file). |
| 88 | + - The `description` field is a description that can be used by the tooling to show to the user. |
| 89 | +1. The `RuleFactory` is loaded from the `factory` field above by using `require()`. The `SchematicDescription` contains the name and more information necessary for the Host to resolve more. |
| 90 | +1. This EngineHost also registers some URLs: |
| 91 | + - `file:` (or not specifying a protocol) supports loading a file system from the disk. |
| 92 | + |
| 93 | +# Schematics |
| 94 | +A schematics is defined by the `RuleFactory` and its `SchematicDescription`, which contains the name and collection. |
| 95 | + |
| 96 | +## Tree |
| 97 | +By definition, a schematic is a transformation between a `Tree` and another `Tree`. It receives a host `Tree`, and applies a list of actions to it, potentially returning it at the end. |
| 98 | + |
| 99 | +## Action |
| 100 | +A tree is transformed by staging actions, which can write over a file, create new files, rename or delete existing files. |
| 101 | + |
| 102 | +## Branching |
| 103 | +A tree can be branched, keeping its history, then adding actions on top of it. Two trees that are being merged will ignore their common history. |
| 104 | + |
| 105 | +## Merging |
| 106 | +Merging two trees results in a tree containing all actions. If two actions apply on the same path, it is automatically considered a conflict and needs to be resolved. |
| 107 | + |
| 108 | +### Conflicts |
| 109 | +Merge conflicts are resolved using the chosen `MergeStrategy` (with the default set by the tooling): |
| 110 | + |
| 111 | +1. `MergeStrategy.Error`. Throw an exception and stops creating the schematic. |
| 112 | +1. `MergeStrategy.Overwrite`. The action from the last merge argument is preferred. |
| 113 | +1. `MergeStrategy.ContentOnly`. Creation or Renaming the same file will throw an exception, but overwriting its content will resolve as if `MergeStrategy.Overwrite` is chosen. |
| 114 | + |
| 115 | +## Optimize |
| 116 | +Optimizing a tree results in the tree with a smaller staging; actions that overrules each other within the same tree are removed or simplified. The history of the tree is NOT preserved, but only the staged actions are changed. |
| 117 | + |
| 118 | +# Examples |
| 119 | + |
| 120 | +## Simple |
| 121 | +An example of a simple Schematics which creates a "hello world" file, using an option to determine its path: |
| 122 | + |
| 123 | +```typescript |
| 124 | +import {Tree} from '@angular/schematics'; |
| 125 | +
|
| 126 | +export default function MySchematic(options: any) { |
| 127 | + return (tree: Tree) => { |
| 128 | + tree.create(options.path + '/hi', 'Hello world!'); |
| 129 | + return tree; |
| 130 | + }; |
| 131 | +} |
| 132 | +``` |
| 133 | + |
| 134 | +## Templated Source |
| 135 | +An example of a simple Schematics which reads a directory and apply templates to its content and path. |
| 136 | + |
| 137 | +```typescript |
| 138 | +import {apply, mergeWith, template, url} from '@angular/schematics'; |
| 139 | +
|
| 140 | +export default function(options: any) { |
| 141 | + return mergeWith([ |
| 142 | + apply(url('./files'), [ |
| 143 | + template({ utils: stringUtils, ...options }) |
| 144 | + ]) |
| 145 | + ]); |
| 146 | +}; |
| 147 | +``` |
| 148 | + |
| 149 | + |
| 150 | +# Future Work |
| 151 | + |
| 152 | +Schematics is not done yet. Here's a list of things we are considering: |
| 153 | + |
| 154 | +* Smart defaults for Options. Having a JavaScript function for default values based on other default values. |
| 155 | +* Prompt for input options. This should only be prompted for the original schematics, dependencies to other schematics should not trigger another prompting. |
| 156 | +* Tasks for running tooling-specific jobs before and after a schematics has been scaffolded. Such tasks can involve initialize git, or npm install. A specific list of tasks should be provided by the tool, with unsupported tasks generating an error. |
| 157 | +* Better URL support for more consistency. Right now tools define their own URLs without having consistency between two tools, which means that there is still some cohesion between the schematic and the tool. |
| 158 | +* Annotation support. Annotations are being designed right now, but they will be a type-safe way to attach metadata to a file that is updated if the file changes content. Such Annotation could tell if a file is, e.g., a test file, or binary, or the annotation could be the TypeScript AST of the file itself. |
0 commit comments