实践过程记录
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!');`);# buildnpm 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.tsexport 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/angularnpm 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-featureconst 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 组件库的封装实践
