使用schematics简化脚手架开发

一、什么是schematics?

schematics中文译为“原理图”,是angular生态提供的一个基于模板、支持复杂语法逻辑的代码生成器,它与yeoman之类的生成工具相比,最大的区别是使用了“虚拟文件系统”的理念:即把真实的文件抽象成虚拟的文件树,对文件的所有操作都是基于这棵虚拟树进行的,只有在确认有效时才会把虚拟的文件树映射成真实的文件。这意味着当您的schematics在操作某个文件发生错误时,它将回滚至操作前的状态,这样做的目的是确保文件的一致性,确保操作的“原子性”。

schematics另一个显著的特征是管道式接口调用。一个schematic包可以做为另一个schematics包的输入,多个schematics包组合成一个工作流完成复杂的工作。

听起来还不错,但这不是angular生态中的工具吗?其他框架能用吗?答案是可以的,angular框架并没有把它跟ng cli进行强绑定,而是做为一个底层可复用单元发布成了独立的包。

 

二、schematics具体可以干啥?

场景1:批量升级项目

假设我们有多个项目都是使用同一个脚手架开发的,某天这些项目都需要修改一些文件,如果采用人工方式挨个挨个去修改,效率繁琐低下,这时就可以编写一个schematics包去替代人工操作。

场景2:开发一个高度可配置的脚手架

在常规的脚手架开发中,我们需要采用commander.js和inquirer两个库去实现交互式命令行工具,需要编写代码对参数进行解析验证等等,采用schematics的话,只需要按规范去定义一个配置文件就好了,其他的自动帮您完成。

如果您的脚手架像下图中一样,需要根据cli收集到的配置来生成不同的文件后缀、过滤掉无关的模板代码的话,您就更应该使用schematics了。

 

三、schematics核心概念

Tree: 即虚拟的文件树,是schematics需要操作的对象。

Source: Tree的来源,可以是一棵空树,也可以是从硬盘读取的文件。Source由下列函数生成:empty()、source(tree:Tree)、url(url:string),apply(source: Source, rules: Rule[])

Rule: 对Tree进行转换的规则函数,接收一个Tree,返回一个新Tree。Rule是schematics实现文件操作的核心,Rule由RuleFactory生成。

Action: 转换动作,有四种操作: Create、Rename、Overwrite、Delete

 

四、schematics的模板介绍

schematics中的模板分为两种:一种是内容模板,模板语法跟ejs一致,另一种是文件路径模板,文件路径模板可以是一个变量,也可以是一个函数,如下表所示:

编译模板需要在规则函数中调用template(),template函数接收一个对象,模板中用到的所有变量及函数,都需要放入该对象。

 

五、开始体验schematics

1.安装创建schematics库的脚手架: 

npm install -g @angular-devkit/schematics-cli

2. 创建一个空的schematics库:

schematics blank --name=demo-app

运行命名后会创建一个名为demo-app的文件夹,并将项目的package.json中的name、schematics库的名字命名为demo-app。接下来进入demo-app目录,并运行npm install命名

 

3.定义规则和动作

规则由规则工厂函数(RuleFactory)生成,开发者需要在该函数中编写相应的代码,文件位置:./src/demo-app/index.ts文件,默认的工厂函数如下:

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 demoApp(_options: any): Rule {
	return (tree: Tree, _context: SchematicContext) => {
	return tree;
	};
}

第一行导入了@angular-devkit/schematics,对Tree操作的api都在这个库中,例如合并规则、编译模板等,具体的api可查询库的文档

工厂函数有一个_options的参数,这个参数的值是schematics-cli从界面上收集到并自动传入的,因此接下来先定义一个gui界面。

函数内部的tree代表运行schematics库所在的目录,假设在c:\web目录上运行了命令,那么tree的路径就是c:\web。

 

4. 定义一个输入界面

规则可用的选项及其允许的值和默认值是在./src/demo-app/schema.json中定义的,格式如下:

	{
	"$schema": "http://json-schema.org/schema",
	    "properties": {
	        "name": {
	            "type": "string",
	            "minLength": 5,
	            "default": "默认值",
	            "x-prompt": "请输入文件夹名称"
	        },
	        "useTS": {
	            "type": "boolean",
	            "default": false,
	            "x-prompt": "是否使用ts?"
	        },
	        "language": {
	            "type": "string",
	            "default": "less",
	            "enum": [
	                "less",
	                "scss"
	            ],
	            "x-prompt": "请选择一种样式语言"
	        }
	    }
	}

     该json文件可以使用json-schema对属性值进行约束,例如为name属性指定最少需要输入5个字符。schematics-cli会根据属性模式自动生成合适的用户交互界面,支持的交互行为包括:输入、确认、从清单列表中选择一项,总结如下:

定义好schema文件后,规则工厂函数内部就可以使用_option.xxx来获取到这些输入数据了,为了在开发中知道_option各个属性的类型,定义一个schema.ts文件,代码如下:

export default interface Option {
	name: string,
	useTS?: boolean,
	language?: 'less' | 'scss'
}

修改./src/demo-app/index.ts,导入上面定义的对象,并将_option的类型由any改为Option。

最后一步,需要修改./src/collection.json文件,加入schema节点,值为schema文件所在路径:

{
  "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json",
  "schematics": {
    "demo-app": {
      "description": "A blank schematic.",
      "factory": "./demo-app/index#demoApp",
      "schema": "./demo-app/schema.json"
    }
  }
}

5. 编写规则

接下来我们在./src/demo-app/目录下建立一个files文件夹存放样板工程文件,然后编写相应的规则,为了展示常用的api,我们的规则如下:

第1步. 在运行schematics库的目录下生成一个readme.md文件

第2步. 读取样板工程文件夹、编译模板、过滤掉不需要的文件

第3步. 将样板工程子树合并到当前树中

示例代码如下:

import {
	Rule, SchematicContext, Tree,
	url,apply,template,filter, noop,
	mergeWith
} from '@angular-devkit/schematics';
import Option from './schema';
 
export function demoApp(_options: Option): Rule {
	return (tree: Tree, _context: SchematicContext) => {
	//在执行命令的文件夹下生成一个文件
	tree.create('readme.md','演示生成一个文件');
	//样板工程子树处理
	const destSource = apply(url('./files'),[
	//编译模板
	template({
	//传递给模板的数据,如果路径模板需要函数啥的,也在这里定义
	..._options
	}),
	//不需要指定后缀的文件
	_options.useTS ? noop() : filter(fPath=> !fPath.includes('.ts'))
	]);
	// 将样板工程子树合并到当前树
	return mergeWith(destSource)
	};
}

 

6.  编译及调试

运行npm run build编译ts,然后运行npm link

 

7. 运行schematics包

schematics demo-app:demo-app 

命令格式备注:schematics npm包名:schematics库名

posted @ 2022-03-25 10:33  我是格鲁特  阅读(275)  评论(0编辑  收藏  举报