docs: add schematics guide (#28343)

PR Close #28343
This commit is contained in:
Judy Bogart 2019-01-24 08:45:34 -08:00 committed by Matias Niemelä
parent a3ec058f6b
commit bc99b774ba
37 changed files with 1295 additions and 18 deletions

View File

@ -92,3 +92,6 @@ upgrade-phonecat-3-final/tsconfig-aot.json
upgrade-phonecat-3-final/rollup-config.js
!upgrade-phonecat-*/**/karma.conf.js
!upgrade-phonecat-*/**/karma-test-shim.js
# schematics
!schematics-for-libraries/projects/my-lib/package.json

View File

@ -0,0 +1,3 @@
{
"projectType": "schematics"
}

View File

@ -0,0 +1,7 @@
{
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/my-lib",
"lib": {
"entryFile": "src/public_api.ts"
}
}

View File

@ -0,0 +1,20 @@
// #docplaster
// #docregion collection
{
"name": "my-lib",
"version": "0.0.1",
// #enddocregion collection
"scripts": {
"build": "../../node_modules/.bin/tsc -p tsconfig.schematics.json",
"copy:schemas": "cp --parents schematics/*/schema.json ../../dist/my-lib/",
"copy:files": "cp --parents -p schematics/*/files/** ../../dist/my-lib/",
"copy:collection": "cp schematics/collection.json ../../dist/my-lib/schematics/collection.json",
"postbuild": "npm run copy:schemas && npm run copy:files && npm run copy:collection"
},
"peerDependencies": {
"@angular/common": "^7.2.0",
"@angular/core": "^7.2.0"
},
// #docregion collection
"schematics": "./schematics/collection.json"
}

View File

@ -0,0 +1,9 @@
{
"$schema": "../../../node_modules/@angular-devkit/schematics/collection-schema.json",
"schematics": {
"ng-add": {
"description": "Add my library to the project.",
"factory": "./ng-add/index#ngAdd"
}
}
}

View File

@ -0,0 +1,14 @@
{
"$schema": "../../../node_modules/@angular-devkit/schematics/collection-schema.json",
"schematics": {
"ng-add": {
"description": "Add my library to the project.",
"factory": "./ng-add/index#ngAdd"
},
"my-service": {
"description": "Generate a service in the project.",
"factory": "./my-service/index#myService",
"schema": "./my-service/schema.json"
}
}
}

View File

@ -0,0 +1,10 @@
// #docregion template
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root'
})
export class <%= classify(name) %>Service {
constructor(private http: HttpClient) { }
}

View File

@ -0,0 +1,11 @@
import { Rule, Tree } from '@angular-devkit/schematics';
import { Schema as MyServiceSchema } from './schema';
// #docregion factory
export function myService(options: MyServiceSchema): Rule {
return (tree: Tree) => {
return tree;
};
}
// #enddocregion factory

View File

@ -0,0 +1,66 @@
// #docplaster
// #docregion schematics-imports, schema-imports, workspace
import {
Rule, Tree, SchematicsException,
apply, url, applyTemplates, move,
chain, mergeWith
} from '@angular-devkit/schematics';
import { strings, normalize, experimental } from '@angular-devkit/core';
// #enddocregion schematics-imports
import { Schema as MyServiceSchema } from './schema';
// #enddocregion schema-imports
export function myService(options: MyServiceSchema): Rule {
return (tree: Tree) => {
const workspaceConfig = tree.read('/angular.json');
if (!workspaceConfig) {
throw new SchematicsException('Could not find Angular workspace configuration');
}
// convert workspace to string
const workspaceContent = workspaceConfig.toString();
// parse workspace string into JSON object
const workspace: experimental.workspace.WorkspaceSchema = JSON.parse(workspaceContent);
// #enddocregion workspace
// #docregion project-fallback
if (!options.project) {
options.project = workspace.defaultProject;
}
// #enddocregion project-fallback
// #docregion project-info
const projectName = options.project as string;
const project = workspace.projects[projectName];
const projectType = project.projectType === 'application' ? 'app' : 'lib';
// #enddocregion project-info
// #docregion path
if (options.path === undefined) {
options.path = `${project.sourceRoot}/${projectType}`;
}
// #enddocregion path
// #docregion template
const templateSource = apply(url('./files'), [
applyTemplates({
classify: strings.classify,
dasherize: strings.dasherize,
name: options.name
}),
move(normalize(options.path as string))
]);
// #enddocregion template
// #docregion chain
return chain([
mergeWith(templateSource)
]);
// #enddocregion chain
// #docregion workspace
};
}

View File

@ -0,0 +1,28 @@
{
"$schema": "http://json-schema.org/schema",
"id": "SchematicsMyService",
"title": "My Service Schema",
"type": "object",
"properties": {
"name": {
"description": "The name of the service.",
"type": "string"
},
"path": {
"type": "string",
"format": "path",
"description": "The path to create the service.",
"visible": false
},
"project": {
"type": "string",
"description": "The name of the project.",
"$default": {
"$source": "projectName"
}
}
},
"required": [
"name"
]
}

View File

@ -0,0 +1,10 @@
export interface Schema {
// The name of the service.
name: string;
// The path to create the service.
path?: string;
// The name of the project.
project?: string;
}

View File

@ -0,0 +1,10 @@
import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks';
// Just return the tree
export function ngAdd(_options: any): Rule {
return (tree: Tree, _context: SchematicContext) => {
_context.addTask(new NodePackageInstallTask());
return tree;
};
}

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MyLibComponent } from './my-lib.component';
describe('MyLibComponent', () => {
let component: MyLibComponent;
let fixture: ComponentFixture<MyLibComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ MyLibComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MyLibComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,19 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'lib-my-lib',
template: `
<p>
my-lib works!
</p>
`,
styles: []
})
export class MyLibComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}

View File

@ -0,0 +1,10 @@
import { NgModule } from '@angular/core';
import { MyLibComponent } from './my-lib.component';
@NgModule({
declarations: [MyLibComponent],
imports: [
],
exports: [MyLibComponent]
})
export class MyLibModule { }

View File

@ -0,0 +1,12 @@
import { TestBed } from '@angular/core/testing';
import { MyLibService } from './my-lib.service';
describe('MyLibService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: MyLibService = TestBed.get(MyLibService);
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,9 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class MyLibService {
constructor() { }
}

View File

@ -0,0 +1,7 @@
/*
* Public API Surface of my-lib
*/
export * from './lib/my-lib.service';
export * from './lib/my-lib.component';
export * from './lib/my-lib.module';

View File

@ -0,0 +1,32 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../out-tsc/lib",
"target": "es2015",
"module": "es2015",
"moduleResolution": "node",
"declaration": true,
"sourceMap": true,
"inlineSources": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"importHelpers": true,
"types": [],
"lib": [
"dom",
"es2018"
]
},
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"skipTemplateCodegen": true,
"strictMetadataEmit": true,
"fullTemplateTypeCheck": true,
"strictInjectionParameters": true,
"enableResourceInlining": true
},
"exclude": [
"src/test.ts",
"**/*.spec.ts"
]
}

View File

@ -0,0 +1,35 @@
{
"compilerOptions": {
"baseUrl": ".",
"lib": [
"es2018",
"dom"
],
"declaration": true,
"module": "commonjs",
"moduleResolution": "node",
"noEmitOnError": true,
"noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"noImplicitThis": true,
"noUnusedParameters": true,
"noUnusedLocals": true,
"rootDir": "schematics",
"outDir": "../../dist/my-lib/schematics",
"skipDefaultLibCheck": true,
"skipLibCheck": true,
"sourceMap": true,
"strictNullChecks": true,
"target": "es6",
"types": [
"jasmine",
"node"
]
},
"include": [
"schematics/**/*"
],
"exclude": [
"schematics/*/files/**/*"
]
}

View File

@ -0,0 +1,17 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../out-tsc/spec",
"types": [
"jasmine",
"node"
]
},
"files": [
"src/test.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}

View File

@ -0,0 +1,12 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<h2>Library Schematics</h2>
`,
styles: []
})
export class AppComponent {
title = 'schematics-for-libraries';
}

View File

@ -0,0 +1,16 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

View File

@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>SchematicsForLibraries</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>

View File

@ -0,0 +1,12 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));

View File

@ -0,0 +1,10 @@
{
"description": "Schematics For Libraries",
"files": [
"!**/*.d.ts",
"!**/*.js",
"!**/*.[1].*",
"**/*.template"
],
"tags": ["Angular", "Libraries", "Schematics"]
}

View File

@ -0,0 +1,194 @@
# Authoring Schematics
You can create your own schematics to operate on Angular projects.
Library developers typically package schematics with their libraries in order to integrate them with the Angular CLI.
You can also create stand-alone schematics to manipulate the files and constructs in Angular applications as a way of customizing them for your development environment and making them conform to your standards and constraints.
Schematics can be chained, running other schematics to perform complex operations.
Manipulating the code in an application has the potential to be both very powerful and correspondingly dangerous.
For example, creating a file that already exists would be an error, and if it was applied immediately, it would discard all the other changes applied so far.
The Angular Schematics tooling guards against side effects and errors by creating a virtual file system.
A schematic describes a pipeline of transformations that can be applied to the virtual file system.
When a schematic runs, the transformations are recorded in memory, and only applied in the real file system once they're confirmed to be valid.
## Schematics concepts
The public API for schematics defines classes that represent the basic concepts.
* The virtual file system is represented by a `Tree`. The `Tree` data structure contains a *base* (a set of files that already exists) and a *staging area* (a list of changes to be applied to the base).
When making modifications, you don't actually change the base, but add those modifications to the staging area.
* A `Rule` object defines a function that takes a `Tree`, applies transformations, and returns a new `Tree`. The main file for a schematic, `index.ts`, defines a set of rules that implement the schematic's logic.
* A transformation is represented by an `Action`. There are four action types: `Create`, `Rename`, `Overwrite`, and `Delete`.
* Each schematic runs in a context, represented by a `SchematicContext` object.
The context object passed into a rule provides access to utility functions and metadata that the schematic may need to work with, including a logging API to help with debugging.
The context also defines a *merge strategy* that determines how changes are merged from the staged tree into the base tree. A change can be accepted or ignored, or throw an exception.
### Defining rules and actions
When you create a new blank schematic with the [Schematics CLI](#cli), the generated entry function is a *rule factory*.
A `RuleFactory`object defines a higher-order function that creates a `Rule`.
<code-example language="TypeScript" linenums="false">
import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
// You don't have to export the function as default.
// You can also have more than one rule factory per file.
export function helloWorld(_options: any): Rule {
return (tree: Tree, _context: SchematicContext) => {
return tree;
};
}
</code-example>
Your rules can make changes to your projects by calling external tools and implementing logic.
You need a rule, for example, to define how a template in the schematic is to be merged into the hosting project.
Rules can make use of utilities provided with the `@schematics/angular` package. Look for helper functions for working with modules, dependencies, TypeScript, AST, JSON, Angular CLI workspaces and projects, and more.
<code-example language="none" linenums="false">
import {
JsonAstObject,
JsonObject,
JsonValue,
Path,
normalize,
parseJsonAst,
strings,
} from '&#64;angular-devkit/core';
</code-example>
### Defining input options with a schema and interfaces
Rules can collect option values from the caller and inject them into templates.
The options available to your rules, with their allowed values and defaults, are defined in the schematic's JSON schema file, `<schematic>/schema.json`.
You can define variable or enumerated data types for the schema using TypeScript interfaces.
You can see examples of schema files for the Angular CLI command schematics in [`@schematics/angular`](https://github.com/angular/angular-cli/blob/7.0.x/packages/schematics/angular/application/schema.json).
{@a cli}
## Schematics CLI
Schematics come with their own command-line tool.
Using Node 6.9 or above, install the Schematics command line tool globally:
<code-example language="bash" linenums="false">
npm install -g @angular-devkit/schematics-cli
</code-example>
This installs the `schematics` executable, which you can use to create a new schematics collection in its own project folder, add a new schematic to an existing collection, or extend an existing schematic.
In the following sections, we will create a new schematics collection using the CLI in order to introduce the files and file structure, and some of the basic concepts.
The most common use of schematics, however, is to integrate an Angular library with the Angular CLI.
You can do this by creating the schematic files directly within the library project in an Angular workspace, without using the Schematics CLI.
See [Schematics for Libraries](guide/schematics-for-libraries).
### Creating a schematics collection
The following command creates a new schematic named `hello-world` in a new project folder of the same name.
<code-example language="bash" linenums="false">
schematics blank --name=hello-world
</code-example>
The `blank` schematic is provided by the Schematics CLI. The command creates a new project folder (the root folder for the collection) and an initial named schematic in the collection.
Go to the collection folder, install your npm dependencies, and open your new collection in your favorite editor to see the generated files. For example, if you are using VSCode:
<code-example language="bash" linenums="false">
cd hello-world
npm install
npm run build
code .
</code-example>
The initial schematic gets the same name as the project folder, and is generated in `src/hello-world`.
You can add related schematics to this collection, and modify the generated skeleton code to define your schematic's functionality.
Each schematic name must be unique within the collection.
### Running a schematic
Use the `schematics` command to run a named schematic.
Provide the path to the project folder, the schematic name, and any mandatory options, in the following format.
<code-example language="bash" linenums="false">
schematics &lt;path-to-schematics-project&gt;:&lt;schematics-name&gt; --&lt;required-option&gt;=&lt;value&gt;
</code-example>
The path can be absolute or relative to the current working directory where the command is executed.
For example, to run the schematic we just generated (which has no required options), use the following command.
<code-example language="bash" linenums="false">
schematics .:hello-world
</code-example>
### Adding a schematic to a collection
To add a schematic to an existing collection, use the same command you use to start a new schematics project, but run the command inside the project folder.
<code-example language="bash" linenums="false">
cd hello-world
schematics blank --name=goodbye-world
</code-example>
The command generates the new named schematic inside your collection, with a main `index.ts` file and its associated test spec.
It also adds the name, description, and factory function for the new schematic to the collection's schema in the `collection.json` file.
## Collection contents
The top level of the root project folder for a collection contains configuration files, a `node_modules` folder, and a `src/` folder.
The `src/` folder contains subfolders for named schematics in the collection, and a schema, `collection.json`, which describes the collected schematics.
Each schematic is created with a name, description, and factory function.
<code-example language="none" linenums="false">
{
"$schema":
"../node_modules/@angular-devkit/schematics/collection-schema.json",
"schematics": {
"hello-world": {
"description": "A blank schematic.",
"factory": "./hello-world/index#helloWorld"
}
}
}
</code-example>
* The `$schema` property specifies the schema that the CLI uses for validation.
* The `schematics` property lists named schematics that belong to this collection.
Each schematic has a plain-text description, and points to the generated entry function in the main file.
* The `factory` property points to the generated entry function. In this example, you invoke the `hello-world` schematic by calling the `helloWorld()` factory function.
* The optional `schema` property points to a JSON schema file that defines the command-line options available to the schematic.
* The optional `aliases` array specifies one or more strings that can be used to invoke the schematic.
For example, the schematic for the Angular CLI “generate” command has an alias “g”, allowing you to use the command `ng g`.
### Named schematics
When you use the Schematics CLI to create a blank schematics project, the new blank schematic is the first member of the collection, and has the same name as the collection.
When you add a new named schematic to this collection, it is automatically added to the `collection.json` schema.
In addition to the name and description, each schematic has a `factory` property that identifies the schematics entry point.
In the example, you invoke the schematic's defined functionality by calling the `helloWorld()` function in the main file, `hello-world/index.ts`.
<figure>
<img src="generated/images/guide/schematics/collection-files.gif" alt="overview">
</figure>
Each named schematic in the collection has the following main parts.
| | |
| :------------- | :-------------------------------------------|
| `index.ts` | Code that defines the transformation logic for a named schematic. |
| `schema.json` | Schematic variable definition. |
| `schema.d.ts` | Schematic variables. |
| `files/` | Optional component/template files to replicate. |
It is possible for a schematic to provide all of its logic in the `index.ts` file, without additional templates.
You can create dynamic schematics for Angular, however, by providing components and templates in the `files/` folder, like those in standalone Angular projects.
The logic in the index file configures these templates by defining rules that inject data and modify variables.

View File

@ -0,0 +1,320 @@
# Schematics for Libraries
When you create an Angular library, you can provide and package it with schematics that integrate it with the Angular CLI.
With your schematics, your users can use `ng add` to install an initial version of your library,
`ng generate` to create artifacts defined in your library, and `ng update` to adjust their project for a new version of your library that introduces breaking changes.
All three types of schematics can be part of a collection that you package with your library.
Download the <live-example downloadOnly>library schematics project</live-example> for a completed example of the steps below.
## Creating a schematics collection
To start a collection, you need to create the schematic files.
The following steps show you how to add initial support without modifying any project files.
1. In your library's root folder, create a `schematics/` folder.
1. In the `schematics/` folder, create an `ng-add/` folder for your first schematic.
1. At the root level of the `schematics/` folder, create a `collection.json` file.
1. Edit the `collection.json` file to define the initial schema for your collection.
<code-example header="projects/my-lib/schematics/collection.json (Schematics Collection)" path="schematics-for-libraries/projects/my-lib/schematics/collection.1.json">
</code-example>
* The `$schema` path is relative to the Angular Devkit collection schema.
* The `schematics` object describes the named schematics that are part of this collection.
* The first entry is for a schematic named `ng-add`. It contains the description, and points to the factory function that is called when your schematic is executed.
1. In your library project's `package.json` file, add a "schematics" entry with the path to your schema file.
The Angular CLI uses this entry to find named schematics in your collection when it runs commands.
<code-example header="projects/my-lib/package.json (Schematics Collection Reference)" path="schematics-for-libraries/projects/my-lib/package.json" region="collection">
</code-example>
The initial schema that you have created tells the CLI where to find the schematic that supports the `ng add` command.
Now you are ready to create that schematic.
## Providing installation support
A schematic for the `ng add` command can enhance the initial installation process for your users.
The following steps will define this type of schematic.
1. Go to the <lib-root>/schematics/ng-add/ folder.
1. Create the main file, `index.ts`.
1. Open `index.ts` and add the source code for your schematic factory function.
<code-example header="projects/my-lib/schematics/ng-add/index.ts (ng-add Rule Factory)" path="schematics-for-libraries/projects/my-lib/schematics/ng-add/index.ts">
</code-example>
The only step needed to provide initial `ng add` support is to trigger an installation task using the `SchematicContext`.
The task uses the user's preferred package manager to add the library to the project's `package.json` configuration file, and install it in the projects `node_modules` directory.
In this example, the function receives the current `Tree` and returns it without any modifications.
If you need to, you can do additional setup when your package is installed, such as generating files, updating configuration, or any other initial setup your library requires.
## Building your schematics
To bundle your schematics together with your library, you must configure the library to build the schematics separately, then add them to the bundle.
You must build your schematics *after* you build your library, so they are placed in the correct directory.
* Your library needs a custom Typescript configuration file with instructions on how to compile your schematics into your distributed library.
* To add the schematics to the library bundle, add scripts to the library's `package.json` file.
Assume you have a library project `my-lib` in your Angular workspace.
To tell the library how to build the schematics, add a `tsconfig.schematics.json` file next to the generated `tsconfig.lib.json` file that configures the library build.
1. Edit the `tsconfig.schematics.json` file to add the following content.
<code-example header="projects/my-lib/tsconfig.schematics.json (TypeScript Config)" path="schematics-for-libraries/projects/my-lib/tsconfig.schematics.json">
</code-example>
* The `rootDir` specifies that your `schematics/` folder contains the input files to be compiled.
* The `outDir` maps to the library's output folder. By default, this is the `dist/my-lib` folder at the root of your workspace.
1. To make sure your schematics source files get compiled into the library bundle, add the following scripts to the `package.json` file in your library project's root folder (`projects/my-lib`).
<code-example header="projects/my-lib/package.json (Build Scripts)" path="schematics-for-libraries/projects/my-lib/package.json">
</code-example>
* The `build` script compiles your schematic using the custom `tsconfig.schematics.json` file.
* The `copy:*` statements copy compiled schematic files into the proper locations in the library output folder in order to preserve the file structure.
* The `postbuild` script copies the schematic files after the `build` script completes.
## Providing generation support
You can add a named schematic to your collection that lets your users use the `ng generate` command to create an artifact that is defined in your library.
We'll assume that your library defines a service, `my-service`, that requires some setup. You want your users to be able to generate it using the following CLI command.
<code-example language="bash" linenums="false">
ng generate my-lib:my-service
</code-example>
To begin, create a new subfolder, `my-service`, in the `schematics` folder.
### Configure the new schematic
When you add a schematic to the collection, you have to point to it in the collection's schema, and provide configuration files to define options that a user can pass to the command.
1. Edit the `schematics/collection.json` file to point to the new schematic subfolder, and include a pointer to a schema file that will specify inputs for the new schematic.
<code-example header="projects/my-lib/schematics/collection.json (Schematics Collection)" path="schematics-for-libraries/projects/my-lib/schematics/collection.json">
</code-example>
1. Go to the `<lib-root>/schematics/my-service/` folder.
1. Create a `schema.json` file and define the available options for the schematic.
<code-example header="projects/my-lib/schematics/my-service/schema.json (Schematic JSON Schema)" path="schematics-for-libraries/projects/my-lib/schematics/my-service/schema.json">
</code-example>
* *id* : A unique id for the schema in the collection.
* *title* : A human-readable description of the schema.
* *type* : A descriptor for the type provided by the properties.
* *properties* : An object that defines the available options for the schematic.
Each option associates key with a type, description, and optional alias.
The type defines the shape of the value you expect, and the description is displayed when the user requests usage help for your schematic.
See the workspace schema for additional customizations for schematic options.
1. Create a `schema.ts` file and define an interface that stores the values of the options defined in the `schema.json` file.
<code-example header="projects/my-lib/schematics/my-service/schema.ts (Schematic Interface)" path="schematics-for-libraries/projects/my-lib/schematics/my-service/schema.ts">
</code-example>
* *name* : The name you want to provide for the created service.
* *path* : Overrides the path provided to the schematic. The default path value is based on the current working directory.
* *project* : Provides a specific project to run the schematic on. In the schematic, you can provide a default if the option is not provided by the user.
### Add template files
To add artifacts to a project, your schematic needs its own template files.
Schematic templates support special syntax to execute code and variable substitution.
1. Create a `files/` folder inside the `schematics/my-service/` folder.
1. Create a file named `__name@dasherize__.service.ts.template` that defines a template you can use for generating files. This template will generate a service that already has Angular's `HttpClient` injected into its constructor.
<code-example lang="ts" header="projects/my-lib/schematics/my-service/files/__name@dasherize__.service.ts.template (Schematic Template)">
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root'
})
export class <%= classify(name) %>Service {
constructor(private http: HttpClient) { }
}
</code-example>
* The `classify` and `dasherize` methods are utility functions you schematic will use to transform your source template and filename.
* The `name` is provided as a property from your factory function. It is the same `name` you defined in the schema.
### Add the factory function
Now that you have the infrastructure in place, you can define the main function that performs the modifications you need in the user's project.
The Schematics framework provides a file templating system, which supports both path and content templates.
The system operates on placeholders defined inside files or paths that loaded in the input `Tree`.
It fills these in using values passed into the `Rule`.
For details of these data structure and syntax, see the [Schematics README](https://github.com/angular/angular-cli/blob/master/packages/angular_devkit/schematics/README.md).
1. Create the main file, `index.ts` and add the source code for your schematic factory function.
1. First, import the schematics definitions you will need. The Schematics framework offers many utility functions to create and use rules when running a schematic.
<code-example header="projects/my-lib/schematics/my-service/index.ts (Imports)" path="schematics-for-libraries/projects/my-lib/schematics/my-service/index.ts" region="schematics-imports">
</code-example>
1. Import the defined schema interface that provides the type information for your schematic's options.
<code-example header="projects/my-lib/schematics/my-service/index.ts (Schema Import)" path="schematics-for-libraries/projects/my-lib/schematics/my-service/index.ts" region="schema-imports">
</code-example>
1. To build up the generation schematic, start with an empty rule factory.
<code-example header="projects/my-lib/schematics/my-service/index.ts (Initial Rule)" path="schematics-for-libraries/projects/my-lib/schematics/my-service/index.1.ts" region="factory">
</code-example>
This simple rule factory returns the tree without modification.
The options are the option values passed through from the `ng generate` command.
## Define a generation rule
We now have the framework in place for creating the code that actually modifies the user's application to set it up for the service defined in your library.
The Angular workspace where the user has installed your library contains multiple projects (applications and libraries).
The user can specify the project on the command line, or allow it to default.
In either case, your code needs to identify the specific project to which this schematic is being applied, so that you can retrieve information from the project configuration.
You can do this using the `Tree` object that is passed in to the factory function.
The `Tree` methods give you access to the complete file tree in your workspace, allowing you to read and write files during the execution of the schematic.
### Get the project configuration
1. To determine the destination project, use the `Tree.read()` method to read the contents of the workspace configuration file, `angular.json`, at the root of the workspace.
Add the following code to your factory function.
<code-example header="projects/my-lib/schematics/my-service/index.ts (Schema Import)" path="schematics-for-libraries/projects/my-lib/schematics/my-service/index.ts" region="workspace">
</code-example>
* Be sure to check that the context exists and throw the appropriate error.
* After reading the contents into a string, parse the configuration into a JSON object, typed to the `WorkspaceSchema`.
1. The `WorkspaceSchema` contains all the properties of the workspace configuration, including a `defaultProject` value for determining which project to use if not provided.
We will use that value as a fallback, if no project is explicitly specified in the `ng generate` command.
<code-example header="projects/my-lib/schematics/my-service/index.ts (Default Project)" path="schematics-for-libraries/projects/my-lib/schematics/my-service/index.ts" region="project-fallback">
</code-example>
1. Now that you have the project name, use it to retrieve the project-specific configuration information.
<code-example header="projects/my-lib/schematics/my-service/index.ts (Project)" path="schematics-for-libraries/projects/my-lib/schematics/my-service/index.ts" region="project-info">
</code-example>
The `workspace projects` object contains all the project-specific configuration information.
1. The `options.path` determines where the schematic template files are moved to once the schematic is applied.
The `path` option in the schematic's schema is substituted by default with the current working directory.
If the `path` is not defined, use the `sourceRoot` from the project configuration along with the `projectType`.
<code-example header="projects/my-lib/schematics/my-service/index.ts (Project Info)" path="schematics-for-libraries/projects/my-lib/schematics/my-service/index.ts" region="path">
</code-example>
### Define the rule
A `Rule` can use external template files, transform them, and return another `Rule` object with the transformed template. You can use the templating to generate any custom files required for your schematic.
1. Add the following code to your factory function.
<code-example header="projects/my-lib/schematics/my-service/index.ts (Template transform)" path="schematics-for-libraries/projects/my-lib/schematics/my-service/index.ts" region="template">
</code-example>
* The `apply()` method applies multiple rules to a source and returns the transformed source. It takes 2 arguments, a source and an array of rules.
* The `url()` method reads source files from your filesystem, relative to the schematic.
* The `applyTemplates()` method receives an argument of methods and properties you want make available to the schematic template and the schematic filenames. It returns a `Rule`. This is where you define the `classify()` and `dasherize()` methods, and the `name` property.
* The `classify()` method takes a value and returns the value in title case. For example, if the provided name is `my service`, it is returned as `MyService`
* The `dasherize()` method takes a value and returns the value in dashed and lowercase. For example, if the provided name is MyService, it is returned as `my-service.
* The `move` method moves the provided source files to their destination when the schematic is applied.
1. Finally, the rule factory must return a rule.
<code-example header="projects/my-lib/schematics/my-service/index.ts (Chain Rule)" path="schematics-for-libraries/projects/my-lib/schematics/my-service/index.ts" region="chain">
</code-example>
The `chain()` method allows you to combine multiple rules into a single rule, so that you can perform multiple operations in a single schematic.
Here you are only merging the template rules with any code executed by the schematic.
See a complete exampled of the schematic rule function.
<code-example header="projects/my-lib/schematics/my-service/index.ts" path="schematics-for-libraries/projects/my-lib/schematics/my-service/index.ts">
</code-example>
For more information about rules and utility methods, see [Provided Rules](https://github.com/angular/angular-cli/tree/master/packages/angular_devkit/schematics#provided-rules).
## Running your library schematic
After you build your library and schematics, you can install the schematics collection to run against your project. The steps below show you how to generate a service using the schematic you created above.
### Build your library and schematics
From the root of your workspace, run the `ng build` command for your library.
<code-example language="bash" linenums="false">
ng build my-lib
</code-example>
Then, you change into your library directory to build the schematic
<code-example language="bash" linenums="false">
cd projects/my-lib
npm run build
</code-example>
### Link the library
Your library and schematics are packaged and placed in the `dist/my-lib` folder at the root of your workspace. For running the schematic, you need to link the library into your `node_modules` folder. From the root of your workspace, run the `npm link` command with the path to your distributable library.
<code-example language="bash" linenums="false">
npm link dist/my-lib
</code-example>
### Run the schematic
Now that your library is installed, you can run the schematic using the `ng generate` command.
<code-example language="bash" linenums="false">
ng generate my-lib:my-service --name my-data
</code-example>
In the console, you will see that the schematic was run and the `my-data.service.ts` file was created in your app folder.
<code-example language="bash" linenums="false" hideCopy="true">
CREATE src/app/my-data.service.ts (208 bytes)
</code-example>

View File

@ -0,0 +1,121 @@
# Schematics
A schematic is a template-based code generator that supports complex logic.
It is a set of instructions for transforming a software project by generating or modifying code.
Schematics are packaged into [collections](guide/glossary#collection) and installed with npm.
The schematic collection can be a powerful tool for creating, modifying, and maintaining any software project, but is particularly useful for customizing Angular projects to suit the particular needs of your own organization.
You might use schematics, for example, to generate commonly-used UI patterns or specific components, using predefined templates or layouts.
You can use schematics to enforce architectural rules and conventions, making your projects consistent and inter-operative.
## Schematics for the Angular CLI
Schematics are part of the Angular ecosystem. The [Angular CLI](guide/glossary#cli) uses schematics to apply transforms to a web-app project.
You can modify these schematics, and define new ones to do things like update your code to fix breaking changes in a dependency, for example, or to add a new configuration option or framework to an existing project.
Schematics that are included in the `@schematics/angular` collection are run by default by the commands `ng generate` and `ng add`.
The package contains named schematics that configure the options that are available to the CLI for `ng generate` sub-commands, such as `ng generate component` and `ng generate service`.
The subcommands for `ng generate` are shorthand for the corresponding schematic. You can specify a particular schematic (or collection of schematics) to generate, using the long form:
<code-example language="bash" linenums="false">
ng generate my-schematic-collection:my-schematic-name
</code-example>
&mdash;or&mdash;
<code-example language="bash" linenums="false">
ng generate my-schematic-name --collection collection-name
</code-example>
### Configuring CLI schematics
A JSON schema associated with a schematic tells the Angular CLI what options are available to commands and subcommands, and determines the defaults.
These defaults can be overridden by providing a different value for an option on the command line.
See [Workspace Configuration](guide/workspace-config) for information about how you can change the generation option defaults for your workspace.
The JSON schemas for the default schematics used by the CLI to generate projects and parts of projects are collected in the package [`@schematics/angular`](https://raw.githubusercontent.com/angular/angular-cli/v7.0.0/packages/schematics/angular/application/schema.json).
The schema describes the options available to the CLI for each of the `ng generate` sub-commands, as shown in the `--help` output.
## Developing schematics for libraries
As a library developer, you can create your own collections of custom schematics to integrate your library with the Angular CLI.
* An *add schematic* allows developers to install your library in an Angular workspace using `ng add`.
* *Generation schematics* can tell the `ng generate` subcommands how to modify projects, add configurations and scripts, and scaffold artifacts that are defined in your library.
* An *update schematic* can tell the `ng update` command how to update your library's dependencies and adjust for breaking changes when you release a new version.
For more details of what these look like and how to create them, see:
* [Authoring Schematics](guide/schematics-authoring)
* [Schematics for Libraries](guide/schematics-for-libraries)
### Add schematics
An add schematic is typically supplied with a library, so that the library can be added to an existing project with `ng add`.
The `add` command uses your package manager to download new dependencies, and invokes an installation script that is implemented as a schematic.
For example, the [`@angular/material`](https://material.angular.io/guide/schematics) schematic tells the `add` command to install and set up Angular Material and theming, and register new starter components that can be created with `ng generate`.
You can look at this one as an example and model for your own add schematic.
Partner and third party libraries also support the Angular CLI with add schematics.
For example, `@ng-bootstrap/schematics` adds [ng-bootstrap](https://ng-bootstrap.github.io/) to an app, and `@clr/angular` installs and sets up [Clarity from VMWare](https://vmware.github.io/clarity/documentation/v1.0/get-started).
An add schematic can also update a project with configuration changes, add additional dependencies (such as polyfills), or scaffold package-specific initialization code.
For example, the `@angular/pwa` schematic turns your application into a PWA by adding an app manifest and service worker, and the `@angular/elements` schematic adds the `document-register-element.js` polyfill and dependencies for Angular Elements.
### Generation schematics
Generation schematics are instructions for the `ng generate` command.
The documented sub-commands use the default Angular generation schematics, but you can specify a different schematic (in place of a sub-command) to generate an artifact defined in your library.
Angular Material, for example, supplies generation schematics for the UI components that it defines.
The following command uses one of these schematics to render an Angular Material `<mat-table>` that is pre-configured with a datasource for sorting and pagination.
<code-example language="bash" linenums="false">
ng generate @angular/material:table <component-name>
</code-example>
### Update schematics
The `ng update` command can be used to update your workspace's library dependencies. If you supply no options or use the help option, the command examines your workspace and suggests libraries to update.
<code-example language="bash" linenums="false">
ng update
We analyzed your package.json, there are some packages to update:
Name Version Command to update
--------------------------------------------------------------------------------
@angular/cdk 7.2.2 -> 7.3.1 ng update @angular/cdk
@angular/cli 7.2.3 -> 7.3.0 ng update @angular/cli
@angular/core 7.2.2 -> 7.2.3 ng update @angular/core
@angular/material 7.2.2 -> 7.3.1 ng update @angular/material
rxjs 6.3.3 -> 6.4.0 ng update rxjs
There might be additional packages that are outdated.
Run "ng update --all" to try to update all at the same time.
</code-example>
If you pass the command a set of libraries to update (or the `--all` flag), it updates those libraries, their peer dependencies, and the peer dependencies that depend on them.
<div class="alert is-helpful">
If there are inconsistencies (for example, if peer dependencies cannot be matched by a simple [semver](https://semver.io/) range), the command generates an error and does not change anything in the workspace.
We recommend that you do not force an update of all dependencies by default. Try updating specific dependencies first.
For more about how the `ng update` command works, see [Update Command](https://github.com/angular/angular-cli/blob/master/docs/specifications/update.md).
</div>
If you create a new version of your library that introduces potential breaking changes, you can provide an *update schematic* to enable the `ng update` command to automatically resolve any such changes in the project being updated.
For example, suppose you want to update the Angular Material library.
<code-example language="bash" linenums="false">
ng update @angular/material
</code-example>
This command updates both `@angular/material` and its dependency `@angular/cdk` in your workspace's `package.json`.
If either package contains an update schematic that covers migration from the existing version to a new version, the command runs that schematic on your workspace.

View File

@ -56,7 +56,7 @@ The following top-level configuration properties are available for each project,
| `sourceRoot` | The root folder for this project's source files. |
| `projectType` | One of "application" or "library". An application can run independently in a browser, while a library cannot. Both an app and its e2e test app are of type "application".|
| `prefix` | A string that Angular prepends to generated selectors. Can be customized to identify an app or feature area. |
| `schematics` | An object containing schematics that customize CLI commands for this project. |
| `schematics` | An object containing configuration defaults that customize the CLI command behavior for this project. See [Schematics Overview](guide/schematics). |
| `architect` | An object containing configuration defaults for Architect builder targets for this project. |
## Project tool configuration options

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -503,6 +503,27 @@
}
]
},
{
"title": "Schematics",
"tooltip": "Understanding schematics.",
"children": [
{
"url": "guide/schematics",
"title": "Schematics Overview",
"tooltip": "Understand how schematics are used in Angular."
},
{
"url": "guide/schematics-authoring",
"title": "Authoring Schematics",
"tooltip": "Understand the structure of a schematic."
},
{
"url": "guide/schematics-for-libraries",
"title": "Schematics for Libraries",
"tooltip": "Use schematics to integrate your library with the Angular CLI."
}
]
},
{
"url": "guide/ivy",
"title": "Angular Ivy",

View File

@ -93,7 +93,6 @@
"classlist.js": "^1.1.20150312",
"core-js": "^2.4.1",
"rxjs": "^6.3.0",
"tslib": "^1.9.0",
"zone.js": "^0.8.26"
},
"devDependencies": {
@ -156,6 +155,7 @@
"shelljs": "^0.7.7",
"tree-kill": "^1.1.0",
"ts-node": "^3.3.0",
"tslib": "^1.9.0",
"tslint": "~5.9.1",
"typescript": "~3.3.3333",
"uglify-js": "^3.0.15",

View File

@ -0,0 +1,24 @@
{
"scripts": [
{ "name": "ng", "command": "ng" },
{ "name": "build", "command": "ng build --prod" },
{ "name": "build:lib", "command": "ng build my-lib" },
{ "name": "start", "command": "ng serve" },
{ "name": "test", "command": "ng test" },
{ "name": "lint", "command": "ng lint" },
{ "name": "e2e", "command": "ng e2e" }
],
"dependencies": [],
"devDependencies": [
"@angular-devkit/build-angular",
"@angular-devkit/build-ng-packagr",
"@angular/cli",
"@types/jasminewd2",
"jasmine-spec-reporter",
"karma-coverage-istanbul-reporter",
"ng-packagr",
"tsickle",
"tslib",
"ts-node"
]
}

View File

@ -102,7 +102,9 @@ class ExampleZipper {
'src/typings.d.ts',
'src/environments/**/*',
'src/tsconfig.*',
'src/tslint.*'
'src/tslint.*',
// Only ignore root package.json
'!package.json'
];
var alwaysExcludes = [
'!**/bs-config.e2e.json',
@ -110,7 +112,6 @@ class ExampleZipper {
'!**/*zipper.*',
'!**/systemjs.config.js',
'!**/npm-debug.log',
'!**/package.json',
'!**/example-config.json',
'!**/wallaby.js',
// AoT related files

View File

@ -88,6 +88,11 @@ BOILERPLATE_PATHS.ivy = {
]
};
BOILERPLATE_PATHS.schematics = [
...cliRelativePath,
'angular.json'
];
const EXAMPLE_CONFIG_FILENAME = 'example-config.json';
class ExampleBoilerPlate {

View File

@ -0,0 +1,170 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"angular.io-example": {
"root": "",
"sourceRoot": "src",
"projectType": "application",
"prefix": "app",
"schematics": {},
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.app.json",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.css"
],
"scripts": []
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
}
]
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "angular.io-example:build"
},
"configurations": {
"production": {
"browserTarget": "angular.io-example:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "angular.io-example:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.spec.json",
"karmaConfig": "src/karma.conf.js",
"styles": [
"src/styles.css"
],
"scripts": [],
"assets": [
"src/favicon.ico",
"src/assets"
]
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"src/tsconfig.app.json",
"src/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
},
"angular.io-example-e2e": {
"root": "e2e/",
"projectType": "application",
"prefix": "",
"architect": {
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "angular.io-example:serve"
},
"configurations": {
"production": {
"devServerTarget": "angular.io-example:serve:production"
}
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": "e2e/tsconfig.e2e.json",
"exclude": [
"**/node_modules/**"
]
}
}
}
},
"my-lib": {
"root": "projects/my-lib",
"sourceRoot": "projects/my-lib/src",
"projectType": "library",
"prefix": "lib",
"architect": {
"build": {
"builder": "@angular-devkit/build-ng-packagr:build",
"options": {
"tsConfig": "projects/my-lib/tsconfig.lib.json",
"project": "projects/my-lib/ng-package.json"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "projects/my-lib/src/test.ts",
"tsConfig": "projects/my-lib/tsconfig.spec.json",
"karmaConfig": "projects/my-lib/karma.conf.js"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"projects/my-lib/tsconfig.lib.json",
"projects/my-lib/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
}
},
"defaultProject": "angular.io-example"
}