使用 Angular Schematics 快速生成模板代码

准备

  1. 全局安装 @angular-devkit/schematics-cli 以使用 schematics 命令
$ npm install -g @angular-devkit/schematics-cli
复制代码
  1. 创建新的Schematics项目
$ schematics blank <schematic-name>
复制代码

在已生成的Schematics工程根目录下调用该命令会快速添加一个以填写的<schematic-name>命名的schematic空白模板,并在collection.json中添加相应基础信息。

步骤

创建一个Schematics初始工程

$ schematics blank my-schematics
$ cd my-schematics
$ npm i
复制代码

1. Schematics文件构成

/src/collection.json

collection.json 文件是整个Schematics的主要定义文件,并且包含该库中所有可用schematic模板的定义。

如果你查看过 Angular CLI 生成的项目中默认提供的 @schematics/angular 包,会发现它的 collection.json 中包含了常用的生成componentservice的命令。换句话说,我们可以用Schematics生成任何东西。

{
  "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json",
  "schematics": {
    "my-schematics": {
      "description": "A blank schematic.",
      "factory": "./my-schematics/index#mySchematics"
       
    }
  }
}
复制代码
  • description:描述该Schematic模板
  • factory:指明该Schematic模板入口。从该文件可以看出,生成的Schematics初始工程默认提供了一个同名的my-schematics模板,指向./my-schematics/index文件并特别指向mySchematics函数。也可以仅使用 "factory": "./my-schematics/index", 需要将index.tsexport function mySchematics(){}更改为export default function(){}

附加属性:

  • aliases(可选):指定该Schematic模板的一个或多个别名,以string数组表示。
  • schema(可选):指明每个Schematic模板单独的schema和所有的可用的命令行选项参数。

另外,注意Schematics工程中package.json有一个schematic属性指向 collection.json 文件。

index.ts

import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
// 可以不用将本函数设置为默认导出,同一文件中可以有多个 rule factory
// 这是一个 schematic 工厂函数,最终返回一个 rule
export function mySchematics(_options: any): Rule {
  return (tree: Tree, _context: SchematicContext) => {
    // schematic rule 会被 Tree 和 Schematic context 调用
    // rule 会把对文件的变换处理添加到 Tree 上,返回处理后的 Tree 给下一条 rule,这就是说,schematic rule 是可组合的
    return tree;
  };
}
复制代码

schema.json (需要时手动创建)

{
  "$schema": "http://json-schema.org/schema",
  "$id": "MyFirstSchematic",
  "title": "Hello Option Schema",
  "type": "object",
  "description": "My First Schematic",
  "properties": {
    "name": {                               // 参数名
      "type": "string",                     // 参数类型
      "description": "The name of file",    // 参数描述
      "$default": {                         // 设为默认参数
        "$source": "argv",                  // 从命令行命令传入
        "index": 0                          // 在命令中的默认位置。
        // 例如:schematics .:my-schematics Mirai (默认位置) 
        // 对比 schematics .:my-schematics --name=Mirai (标准传入形式)
      },
      "x-prompt": "Who do we want to greet?" // 未传入该位置参数时的交互提示
    }
  },
  "required": [
    "name"
  ]
}
复制代码

2. Schematics 相关基本概念

factory

工厂函数将接受一些自定义的参数 _options 并返回一条规则。

为什么需要工厂函数而不是直接实施一条规则:我们希望 schematic 模板能灵活调整规则以适应一定范围内的需求,所以需要一个函数接收自定义参数 _options

Rule

规则是通过Tree和SchematicContext调用的,并且可以调用其他已经实现的规则。一旦被调用,规则将对树进行调整,并将其返回以供进一步处理。

Tree

Schematics都是关于代码生成和现有文件的更改。

Tree 是工作空间中每个文件的虚拟表示。使用虚拟树而不是直接操作文件有以下一些优势:1)只有在每个 schematic 都成功运行的情况下,我们才会提交对树的更改。 2)在 debug 模式下(使用 --dry-run 标识),可以预览文件的改动而不影响实际的文件系统。 3)I/O 操作仅发生在整个处理过程结束后。


3. 从一个简单的例子体验Schematics

让我们从默认生成的 /src/my-schematics 模板开始体验。

//index.ts
export function mySchematics(_options: any): Rule {
  return (tree: Tree, _context: SchematicContext) => {
    tree.create('hello.js', `console.log('Hello Schematics');`);
    return tree;
  };
}
复制代码

构建并在当前工程根目录下运行:

$ npm run build
$ schematics .:my-schematics
复制代码

运行结果:显示 CREATE hello.js (32 bytes) 但没有实际文件生成。

原因:在使用相对路径调用schematics时默认为调试模式,每次使用时需要添加 --debug=false--dry-run=false 关闭调试模式。

再次调用 schematics .:my-schematics --debug=false,至此,成功生成一个 hello.js 文件,其内容为 console.log('Hello Schematics');


4. 向schematics传入自定义选项

/src/my-schematics下创建schema.json,添加以下代码。这里我们以自定义一个name参数为例。

{
  "$schema": "http://json-schema.org/schema",
  "$id": "MyFirstSchematic",
  "title": "Hello Option Schema",
  "type": "object",
  "description": "My First Schematic",
  "properties": {
    "name": {
      "type": "string",
      "description": "The name of file",
      "$default": {
        "$source": "argv",
        "index": 0
      },
      "x-prompt": "Who do we want to greet?"
    }
  },
  "required": [
    "name"
  ]
}
复制代码

collection.json 中引入创建的 schema.json

{
  "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json",
  "schematics": {
    "my-schematics": {
      "description": "A blank schematic.",
      "factory": "./my-schematics/index#mySchematics",
      "schema": "./my-schematics/schema.json"           // add this
    }
  }
}
复制代码

同时,也可以创建一个 schema.d.ts 来为我们的原理图提供代码及类型的检查。

export interface Schema{
  name: string;
}
复制代码

随后于 index.ts 中引入并使用定义的Schema类型(function mySchematics(_options: Schema): Rule)。

import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
import { Schema } from './schema';

export function mySchematics(_options: Schema): Rule {
  return (tree: Tree, _context: SchematicContext) => {
    const { name } = _options;
    tree.create('hello.js', `console.log('Hello ${name}');`);
    return tree;
  };
}
复制代码

调试(每次调试前将上次生成的hello.js删除):

$ npm run build
$ schematics .:my-schematics Mirai --debug=false
or
$ schematics .:my-schematics --name=Mirai --debug=false
or
$ schematics .:my-schematics --debug=false 
未传入name参数会提示: ? Who do we want to greet? 
复制代码

显示生成hello.js文件(CREATE hello.js (27 bytes)),内容:console.log('Hello Mirai');,参数传入成功!


5. 生成更复杂的schematic模板

创建一个新的模板component来体验类似ng g c <component-name>命令生成的<component-name>.component.ts文件。

在根目录下调用schematics blank component,在生成/src/component/文件夹下创建files文件夹,并在里面创建__name@dasherize__.component.ts.template文件。(另外需要在/src/component/文件夹下创建schema.json schema.d.ts,在collection.json中添加schema属性等,见上一步)

__(双下划线)是分隔符,将name变量与其他普通字符串分隔开。dasherize是一个辅助函数,它将接收name变量的值并将其转换为kebab case字符串,@是将变量应用到辅助函数的方法。

// __name@dasherize__.component.ts.template
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'my-<%= dasherize(name) %>',
  templateUrl: './<%= dasherize(name) %>.component.html',
  styleUrls: ['./<%= dasherize(name) %>.component.css']
})
export class <%= classify(name) %>Component implements OnInit {

  constructor() { }

  ngOnInit() {
  }
}
复制代码

模板文件书写:

  • 模板语言使用 EJS
  • @angular-devkit/core 中引入 strings ,使用其中的 dasherize / classify / underscore 等方法对 name 进行处理。

EJS标签含义:

  • <% ‘脚本’ 标签,用于流程控制,无输出。
  • <%_ 删除其前面的空格符
  • <%= 输出数据到模板(输出是转义 HTML 标签)
  • <%- 输出非转义的数据到模板
  • <%# 注释标签,不执行、不输出内容
  • <%% 输出字符串 ‘<%’
  • %> 一般结束标签
  • -%> 删除紧随其后的换行符
  • _%> 将结束标签后面的空格符删除
// index.ts
import { Rule, SchematicContext, Tree, mergeWith, applyTemplates, apply, url } from '@angular-devkit/schematics';
import { Schema } from './schema';
import { strings } from '@angular-devkit/core';

export function component(_options: Schema): Rule {
  return (tree: Tree, _context: SchematicContext) => {
    return mergeWith(apply(url('./files'), [
      applyTemplates({
        ...strings,
        name: _options.name
      })
    ]))(tree, _context);      //(tree, _context)防止TS编译器报警 "tree 未使用"
  };
}
复制代码

修改index.ts中的转换规则:

  • url() 指定模板文件路径。
  • applyTemplate() 接受向模板文件嵌入的参数及处理函数,转换文件同时删去 .template 后缀。
  • apply() 对模板源应用多个规则,并返回转换后的模板。
  • @angular-devkit/schematics中引入以上方法。

在当前工程根路径下执行:

$ npm run build
$ schematics .:component --name=UserList --debug=false
复制代码

成功生成user-list.component文件,可以看到,内容中name参数被修改为传入的UserList的对应格式。

最后发布至NPM,就可以在项目中快速生成自定义的模板代码啦。

总结

以上是一个基本的模板生成例子,Schematics 运行在一个 Node 容器内,所以其可玩性非常强,可以操作文件并修改文件内容,绝大多数文件类型都可以找到相对应的现有类库来支持。

参考@schematics/angular包,可以封装一些类似其utility下的公用方法,来指定生成文件的路径或验证所填参数等。


参考

Total Guide To Custom Angular Schematics

Schematicsを作ってみよう

angular-cli/packages/angular_devkit/schematics

Use Angular Schematics to Simplify Your Life

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享