实践过程记录
1、安装
# 全局装依赖
npm install -g @angular-devkit/schematics-cli
# 建空项目
schematics blank hello
# 可以增加修改的 watch,在 package.json 文件中
...
"build:watch": "tsc -p tsconfig.json -w"
...
# 项目构建,这一步一定不能少了,否则后面运行 schematics 命令时会提示找不到 index 文件
npm run build
# 在 hello 项目中自执行,可以不依赖 angular 项目
schematics .:hello
# 在其他 angular 脚手架生成的项目中执行该命令
schematics ../hello/src/collection.json:hello
2、文件写入
# index.ts 中增加文件,后面是内容
tree.create('hello.js', `console.log('Hello world!');`);
# build
npm run build
# 执行 schematics, --dry-run=false 或者 --debug=false 可以将文件生成出来,否则是调试模式,不会生成。
schematics .:hello --dry-run=false
3、写入文件传参
export function hello(_options: any): Rule {
return (tree: Tree, _context: SchematicContext) => {
const { name } = _options;
tree.create('hello.js', `console.log('Hello world, ${name}');`);
return tree;
};
}
schematics ../hello/src/collection.json:hello --debug=false --name=Tomas
4、增加 参数校验
# src/hello/ 增加 schema.json 文件
# 如果 name 参数不传,那么会提示错误
{
"$schema": "http://json-schema.org/schema",
"$id": "HelloSchematics",
"title": "Hello Options Schema",
"type": "object",
"description": "say hello to the world",
"properties": {
"name": {
"type": "string",
"description": "name to greet",
"$default": {
"$source": "argv",
"index": 0
}
}
},
"required": ["name"]
}
5、增加 schema.d.ts,增加类型文件
# src/hello/schema.d.ts
export interface Schema {
name: String
}
...
import { Schema } from './schema';
export function hello(_options: Schema): Rule {
...
# 除了上面的手动方式添加,还可以自动添加,在 ./src/hello 目录
npx -p dtsgenerator dtsgen schema.json -o schema.d.ts
6、读取输入参数
# 注意,这里的 x-prompt 可以做到提示并获取输入的参数
"properties": {
"name": {
"type": "string",
"description": "name to greet",
"$default": {
"$source": "argv",
"index": 0
},
"x-prompt": "What is your name?"
}
}
7、使用 schematics template
# src/hello 目录下新建 目录
hello-__name@dasherize__
# src/hello/hello-__name@dasherize__ 目录下新建文件
hello-__name@dasherize__.ts
# 新建文件内容
@Component({
selector: 'hello-<%= dasherize(name) %>',
})
export class Hello<%= classify(name)%>Component {
}
# src/hello/index.js 文件调整
import { mergeWith, Rule, SchematicContext, Tree, apply, template, url } from '@angular-devkit/schematics';
import { strings } from '@angular-devkit/core';
import { Schema } from './schema';
export function hello(_options: Schema): Rule {
return (tree: Tree, _context: SchematicContext) => {
const sourceTemplates = url('./files');
const sourceParametrizedTemplates = apply(sourceTemplates, [
template({
..._options,
...strings,
})
]);
return mergeWith(sourceParametrizedTemplates)(tree, _context);
};
}
# 构建
npm run build
# 实验看效果
schematics .:hello "testFile" --debug=false
8、schematics template 中传入函数
# src/hello/index.ts 增加 reName 函数
export function hello(_options: Schema): Rule {
return (tree: Tree, _context: SchematicContext) => {
function reName(value: string): string {
return value + '?.value';
}
const sourceTemplates = url('./files');
const sourceParametrizedTemplates = apply(sourceTemplates, [
template({
..._options,
...strings,
reName
})
]);
return mergeWith(sourceParametrizedTemplates)(tree, _context);
};
}
# files 中 文件调整,可以直接使用外部传入的函数方便使用
...
export class Hello<%= classify(name)%>Component {
const <%= reName(name)%> = 1;
...
9、常用的名字转换功能函数梳理
- classify 大驼峰
- camelize 小驼峰
findAll(): Observable<<%= classify(name) %>[]>{
<% if(transform) {%>
return this.http.get<<%= classify(name) %>[]>(API_URL).pipe(map(this.transform))
<% } else { %>
return this.http.get<<%= classify(name) %>[]>(API_URL)
<% } %>
}
10、template中 IF 的使用
```shell在src/hello/files/name@dasherize 目录增加 service,注意 前缀 hello 已经都去掉了。
name@dasherize.service.ts
name@dasherize.service.ts 文件内容如下
import { Injectable } from ‘@angular/core’; import { HttpClient } from ‘@angular/common/http’; import { Observable } from ‘rxjs’; import { map } from ‘rxjs/operators’;
@Injectable({ providedIn: ‘root’ }) export class <%= classify(name)%>CrudService { const API_URL = ‘’
constructor(private http: HttpClient) {}
findAll(): Observable<<%= classify(name) %>[]>{
<% if(transform) {%>
return this.http.get<<%= classify(name) %>[]>(API_URL).pipe(map(this.transform))
<% } else { %>
return this.http.get<<%= classify(name) %>[]>(API_URL)
<% } %>
}
<% if (transform) {%>
transform(response: any): <%= classify(name) %>[] {
return {}
}
<% } %>
}
构建
npm run build
执行,注意这里的 transform 传入 boolean 值的写法
schematics .:hello —name “patient” —transform —debug false
<a name="hDCfF"></a>
### 11、template 中 FOR 的使用
```shell
# src/hello/files 目录新建 frameworks.ts 文件
import { Component } from '@angular/core';
@Component({
selector: 'frameworks',
template: `
<h1>Frameworks</h1>
<% if (frameworks && frameworks.length) { %>
<ul>
<% for (let framework of frameworks) { %>
<li><%= framework %></li>
<% } %>
</ul>
<% } %>
`
})
export class FrameworksComponent {
}
# schema.d.ts 中增加定义
export interface Schema {
name: string;
transform?: boolean;
frameworks?: string[];
}
# index.ts 中定义frameworks,此处暂时固定为程序写入,其实可以通过用户输入进行读取
export function hello(_options: Schema): Rule {
return (tree: Tree, _context: SchematicContext) => {
...
_options.frameworks = ['angular', 'react', 'vue'];
...
};
}
12、集成 自定义 schematics 到 angular cli 中
# 安装 @schematics/angular
npm i @schematics/angular
# src/hello/index.ts 代码如下
import { mergeWith, Rule, SchematicContext, Tree, apply, template, url, move } from '@angular-devkit/schematics';
import { strings } from '@angular-devkit/core';
// import { buildDefaultPath } from '@schematics/angular/utility/workspace';
import { parseName } from '@schematics/angular/utility/parse-name';
import { Schema } from './schema';
export function hello(_options: Schema): Rule {
return (tree: Tree, _context: SchematicContext) => {
// 已 buffer 形式读取 angular.json 文件
const workspaceConfigBuffer = tree.read("angular.json");
if (!workspaceConfigBuffer) {
throw new Error("Not a Angular CLI workspace");
}
const workspaceConfig = JSON.parse(workspaceConfigBuffer.toString());
const projectName = _options.project || workspaceConfig.defaultProject;
const project = workspaceConfig.projects[projectName];
// 首先, buildDefaultPath(project) 获取项目的默认路径
// 例如, src/app 或者 projects/some-app/src/app 基于 workspace
// 该方法已经不能使用
// const defaultProjectPath = buildDefaultPath(project);
const defaultProjectPath = `${project.projectType === "application" ? "" : "/"}${project.root}${project.sourceRoot}/${project.prefix}`;
// 然后, _options.name 能够包含路径,例如 some-feature/some-sub-feature/some-service
// parseName 函数能够解析出路径和文件名,路径:some-feature/some-sub-feature 文件名:some-service
// 默认的项目名称将会追加到前面,最终路径是 src/app/some-feature/some-sub-feature
// 或者 projects/some-app/src/app/some-feature/some-sub-feature
const parsedPath = parseName(defaultProjectPath, _options.name);
console.log('parsedPath', parsedPath);
const { name, path } = parsedPath;
function reName(value: string): string {
return value + '?.value';
}
const sourceTemplates = url('./files');
_options.frameworks = ['angular', 'react', 'vue'];
const sourceParametrizedTemplates = apply(sourceTemplates, [
template({
..._options,
...strings,
name,
reName
}),
move(path),
]);
return mergeWith(sourceParametrizedTemplates)(tree, _context);
};
}
# schema.json 代码如下
{
"$schema": "http://json-schema.org/schema",
"$id": "HelloSchematics",
"title": "Hello Options Schema",
"type": "object",
"description": "say hello to the world",
"properties": {
"name": {
"type": "string",
"description": "name to greet",
"$default": {
"$source": "argv",
"index": 0
},
"x-prompt": "What is your name?"
},
"project": {
"type": "string",
"description": "generate in specific Angular CLI workspace project"
},
"transform": {
"type": "boolean",
"default": false
}
},
"required": ["name"]
}
# schema.d.ts 代码如下
export interface Schema {
name: string;
project: string;
transform?: boolean;
frameworks?: string[];
}
# 构建
npm run bulid
# 到另一个 Angular CLI 创建的项目中执行如下命令
# 注意冒号两侧没有空格
ng g ../hello/src/collection.json:hello "feat/test"
# 测试 help 命令
ng g ../hello/src/collection.json:hello "feat/test" --help
参考
- https://tomastrajan.medium.com/total-guide-to-custom-angular-schematics-5c50cf90cdb4
- https://jimmylin212.github.io/post/0015_angular_schematics_intro/ 上一篇文章的实践
- https://blog.kevinyang.net/2018/06/15/angular-schematics-ng-add/ 如何写出一个可以被 ng add 使用的 schematics。
- https://angular.io/guide/schematics 官方对于自定义 schematics 的介绍
- https://github.dev/ng-alain/delon ng-alain 组件库的封装实践