custom schematics 自定义脚手架

实践过程记录

1、安装

  1. # 全局装依赖
  2. npm install -g @angular-devkit/schematics-cli
  3. # 建空项目
  4. schematics blank hello
  5. # 可以增加修改的 watch,在 package.json 文件中
  6. ...
  7. "build:watch": "tsc -p tsconfig.json -w"
  8. ...
  9. # 项目构建,这一步一定不能少了,否则后面运行 schematics 命令时会提示找不到 index 文件
  10. npm run build
  11. # 在 hello 项目中自执行,可以不依赖 angular 项目
  12. schematics .:hello
  13. # 在其他 angular 脚手架生成的项目中执行该命令
  14. schematics ../hello/src/collection.json:hello

2、文件写入

  1. # index.ts 中增加文件,后面是内容
  2. tree.create('hello.js', `console.log('Hello world!');`);
  3. # build
  4. npm run build
  5. # 执行 schematics, --dry-run=false 或者 --debug=false 可以将文件生成出来,否则是调试模式,不会生成。
  6. schematics .:hello --dry-run=false

3、写入文件传参

  1. export function hello(_options: any): Rule {
  2. return (tree: Tree, _context: SchematicContext) => {
  3. const { name } = _options;
  4. tree.create('hello.js', `console.log('Hello world, ${name}');`);
  5. return tree;
  6. };
  7. }
  8. schematics ../hello/src/collection.json:hello --debug=false --name=Tomas

4、增加 参数校验

  1. # src/hello/ 增加 schema.json 文件
  2. # 如果 name 参数不传,那么会提示错误
  3. {
  4. "$schema": "http://json-schema.org/schema",
  5. "$id": "HelloSchematics",
  6. "title": "Hello Options Schema",
  7. "type": "object",
  8. "description": "say hello to the world",
  9. "properties": {
  10. "name": {
  11. "type": "string",
  12. "description": "name to greet",
  13. "$default": {
  14. "$source": "argv",
  15. "index": 0
  16. }
  17. }
  18. },
  19. "required": ["name"]
  20. }

5、增加 schema.d.ts,增加类型文件

  1. # src/hello/schema.d.ts
  2. export interface Schema {
  3. name: String
  4. }
  5. ...
  6. import { Schema } from './schema';
  7. export function hello(_options: Schema): Rule {
  8. ...
  9. # 除了上面的手动方式添加,还可以自动添加,在 ./src/hello 目录
  10. npx -p dtsgenerator dtsgen schema.json -o schema.d.ts

6、读取输入参数

  1. # 注意,这里的 x-prompt 可以做到提示并获取输入的参数
  2. "properties": {
  3. "name": {
  4. "type": "string",
  5. "description": "name to greet",
  6. "$default": {
  7. "$source": "argv",
  8. "index": 0
  9. },
  10. "x-prompt": "What is your name?"
  11. }
  12. }

7、使用 schematics template

  1. # src/hello 目录下新建 目录
  2. hello-__name@dasherize__
  3. # src/hello/hello-__name@dasherize__ 目录下新建文件
  4. hello-__name@dasherize__.ts
  5. # 新建文件内容
  6. @Component({
  7. selector: 'hello-<%= dasherize(name) %>',
  8. })
  9. export class Hello<%= classify(name)%>Component {
  10. }
  11. # src/hello/index.js 文件调整
  12. import { mergeWith, Rule, SchematicContext, Tree, apply, template, url } from '@angular-devkit/schematics';
  13. import { strings } from '@angular-devkit/core';
  14. import { Schema } from './schema';
  15. export function hello(_options: Schema): Rule {
  16. return (tree: Tree, _context: SchematicContext) => {
  17. const sourceTemplates = url('./files');
  18. const sourceParametrizedTemplates = apply(sourceTemplates, [
  19. template({
  20. ..._options,
  21. ...strings,
  22. })
  23. ]);
  24. return mergeWith(sourceParametrizedTemplates)(tree, _context);
  25. };
  26. }
  27. # 构建
  28. npm run build
  29. # 实验看效果
  30. schematics .:hello "testFile" --debug=false

8、schematics template 中传入函数

  1. # src/hello/index.ts 增加 reName 函数
  2. export function hello(_options: Schema): Rule {
  3. return (tree: Tree, _context: SchematicContext) => {
  4. function reName(value: string): string {
  5. return value + '?.value';
  6. }
  7. const sourceTemplates = url('./files');
  8. const sourceParametrizedTemplates = apply(sourceTemplates, [
  9. template({
  10. ..._options,
  11. ...strings,
  12. reName
  13. })
  14. ]);
  15. return mergeWith(sourceParametrizedTemplates)(tree, _context);
  16. };
  17. }
  18. # files 中 文件调整,可以直接使用外部传入的函数方便使用
  19. ...
  20. export class Hello<%= classify(name)%>Component {
  21. const <%= reName(name)%> = 1;
  22. ...

9、常用的名字转换功能函数梳理

  • classify 大驼峰
  • camelize 小驼峰
    1. findAll(): Observable<<%= classify(name) %>[]>{
    2. <% if(transform) {%>
    3. return this.http.get<<%= classify(name) %>[]>(API_URL).pipe(map(this.transform))
    4. <% } else { %>
    5. return this.http.get<<%= classify(name) %>[]>(API_URL)
    6. <% } %>
    7. }

    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 = ‘’

  1. constructor(private http: HttpClient) {}
  2. findAll(): Observable<<%= classify(name) %>[]>{
  3. <% if(transform) {%>
  4. return this.http.get<<%= classify(name) %>[]>(API_URL).pipe(map(this.transform))
  5. <% } else { %>
  6. return this.http.get<<%= classify(name) %>[]>(API_URL)
  7. <% } %>
  8. }
  9. <% if (transform) {%>
  10. transform(response: any): <%= classify(name) %>[] {
  11. return {}
  12. }
  13. <% } %>

}

构建

npm run build

执行,注意这里的 transform 传入 boolean 值的写法

schematics .:hello —name “patient” —transform —debug false

  1. <a name="hDCfF"></a>
  2. ### 11、template 中 FOR 的使用
  3. ```shell
  4. # src/hello/files 目录新建 frameworks.ts 文件
  5. import { Component } from '@angular/core';
  6. @Component({
  7. selector: 'frameworks',
  8. template: `
  9. <h1>Frameworks</h1>
  10. <% if (frameworks && frameworks.length) { %>
  11. <ul>
  12. <% for (let framework of frameworks) { %>
  13. <li><%= framework %></li>
  14. <% } %>
  15. </ul>
  16. <% } %>
  17. `
  18. })
  19. export class FrameworksComponent {
  20. }
  21. # schema.d.ts 中增加定义
  22. export interface Schema {
  23. name: string;
  24. transform?: boolean;
  25. frameworks?: string[];
  26. }
  27. # index.ts 中定义frameworks,此处暂时固定为程序写入,其实可以通过用户输入进行读取
  28. export function hello(_options: Schema): Rule {
  29. return (tree: Tree, _context: SchematicContext) => {
  30. ...
  31. _options.frameworks = ['angular', 'react', 'vue'];
  32. ...
  33. };
  34. }

12、集成 自定义 schematics 到 angular cli 中

  1. # 安装 @schematics/angular
  2. npm i @schematics/angular
  3. # src/hello/index.ts 代码如下
  4. import { mergeWith, Rule, SchematicContext, Tree, apply, template, url, move } from '@angular-devkit/schematics';
  5. import { strings } from '@angular-devkit/core';
  6. // import { buildDefaultPath } from '@schematics/angular/utility/workspace';
  7. import { parseName } from '@schematics/angular/utility/parse-name';
  8. import { Schema } from './schema';
  9. export function hello(_options: Schema): Rule {
  10. return (tree: Tree, _context: SchematicContext) => {
  11. // 已 buffer 形式读取 angular.json 文件
  12. const workspaceConfigBuffer = tree.read("angular.json");
  13. if (!workspaceConfigBuffer) {
  14. throw new Error("Not a Angular CLI workspace");
  15. }
  16. const workspaceConfig = JSON.parse(workspaceConfigBuffer.toString());
  17. const projectName = _options.project || workspaceConfig.defaultProject;
  18. const project = workspaceConfig.projects[projectName];
  19. // 首先, buildDefaultPath(project) 获取项目的默认路径
  20. // 例如, src/app 或者 projects/some-app/src/app 基于 workspace
  21. // 该方法已经不能使用
  22. // const defaultProjectPath = buildDefaultPath(project);
  23. const defaultProjectPath = `${project.projectType === "application" ? "" : "/"}${project.root}${project.sourceRoot}/${project.prefix}`;
  24. // 然后, _options.name 能够包含路径,例如 some-feature/some-sub-feature/some-service
  25. // parseName 函数能够解析出路径和文件名,路径:some-feature/some-sub-feature 文件名:some-service
  26. // 默认的项目名称将会追加到前面,最终路径是 src/app/some-feature/some-sub-feature
  27. // 或者 projects/some-app/src/app/some-feature/some-sub-feature
  28. const parsedPath = parseName(defaultProjectPath, _options.name);
  29. console.log('parsedPath', parsedPath);
  30. const { name, path } = parsedPath;
  31. function reName(value: string): string {
  32. return value + '?.value';
  33. }
  34. const sourceTemplates = url('./files');
  35. _options.frameworks = ['angular', 'react', 'vue'];
  36. const sourceParametrizedTemplates = apply(sourceTemplates, [
  37. template({
  38. ..._options,
  39. ...strings,
  40. name,
  41. reName
  42. }),
  43. move(path),
  44. ]);
  45. return mergeWith(sourceParametrizedTemplates)(tree, _context);
  46. };
  47. }
  48. # schema.json 代码如下
  49. {
  50. "$schema": "http://json-schema.org/schema",
  51. "$id": "HelloSchematics",
  52. "title": "Hello Options Schema",
  53. "type": "object",
  54. "description": "say hello to the world",
  55. "properties": {
  56. "name": {
  57. "type": "string",
  58. "description": "name to greet",
  59. "$default": {
  60. "$source": "argv",
  61. "index": 0
  62. },
  63. "x-prompt": "What is your name?"
  64. },
  65. "project": {
  66. "type": "string",
  67. "description": "generate in specific Angular CLI workspace project"
  68. },
  69. "transform": {
  70. "type": "boolean",
  71. "default": false
  72. }
  73. },
  74. "required": ["name"]
  75. }
  76. # schema.d.ts 代码如下
  77. export interface Schema {
  78. name: string;
  79. project: string;
  80. transform?: boolean;
  81. frameworks?: string[];
  82. }
  83. # 构建
  84. npm run bulid
  85. # 到另一个 Angular CLI 创建的项目中执行如下命令
  86. # 注意冒号两侧没有空格
  87. ng g ../hello/src/collection.json:hello "feat/test"
  88. # 测试 help 命令
  89. ng g ../hello/src/collection.json:hello "feat/test" --help

参考