Angular的一个强大的功能是丰富而有效的Angular Cli
,它可以自动生成脚手架程序,也可以生成各种各样的组件,甚至我们可以自己定义原理图来生成我们特定的东西。功能比较强大,所以也比较难一点,我这里分为三篇博客,也是我的一个学习历程,这是第一篇,学习基本的原理图配置和使用。
创建原理图项目 首先安装angular/schematics
:
1 npm install -g @angular-devkit/schematics-cli
然后我们创建自己的shema项目:
1 schematics blank --name=tcs-schema
可以看到,cli帮我们生成了好些文件:
1 2 3 4 5 6 7 8 CREATE /tcs-schema/README.md (639 bytes) CREATE /tcs-schema/.gitignore (191 bytes) CREATE /tcs-schema/.npmignore (64 bytes) CREATE /tcs-schema/package.json (537 bytes) CREATE /tcs-schema/tsconfig.json (656 bytes) CREATE /tcs-schema/src/collection.json (225 bytes) CREATE /tcs-schema/src/tcs-schema/index.ts (316 bytes) CREATE /tcs-schema/src/tcs-schema/index_spec.ts (470 bytes)
我们这个生成的是ts项目,所以运行的时候需要转为js文件来运行,我们加上一个npm运行命令,可以使它动态热更新:
1 2 3 4 5 "scripts" : { "build" : "tsc -p tsconfig.json" , "build:watch" : "tsc -p tsconfig.json --watch" , "test" : "npm run build && jasmine src/**/*_spec.js" } ,
我们加上了buid:watch
命令,运行这个命令:
这个时候可以看到,我们项目文件中将ts文件转换为了js文件。这时候我们可以来通过schematics
来运行我们的原理图:
可以看到输出:
因为目前我们什么都没做,只是建了一个schema的项目而已。
也可以在外部的其他项目中,通过相对路径来使用我们的原理图,比如:
1 2 schematics ../tcs-schema/src/collection.json:tcs-schema Nothing to be done.
来给我们的schema加点料。调整我们的./src/tcs-schema/index.ts
中的规则(rule),使它创建一个hello.js
文件,并写入一条语句:console.log('hello schema')
。我们只需要使用tree
对象的create()
方法即可:
1 2 3 4 5 6 export function tcsSchema (_options: schemaOptions ): Rule { return (tree: Tree, _context: SchematicContext ) => { tree.create ('hello.js' , `console.log('hello schema')` ); return tree; }; }
再次使用tcs-schema
:
发现有输出:
1 CREATE /hello.js (27 bytes)
可以看到成功创建了hello.js
,但是找死也找不到这个文件啊,这是为啥?
是应为我们通过相对路径来使用这个原理图的包,所以在这种情况下原理图运行于debug
模式。调试模式产生的行为与使用--dry-run
标志运行原理图的行为相同,因此只是输出了操作的结果,但是没有实质的文件,不会对文件系统提交任何更改。
要想看到真实创建的文件,我们只需要加上--debug=false
或者--dry-run=false
参数即可。比如:
1 schematics .:tcs-schema --debug=false
可以看到在当前目录确实生成了hello.js
文件,我们可以使用node来运行它:
可以看到正确的输出了我们的hello schema
。
原理图的参数 默认情况下,原理图将每个指定的标志参数传递给_options
对象,比如我们希望传递一个name给我们的`tcs-schema。
我们只需要在index.ts
中从_options
中获取这个参数即可:
1 2 3 4 5 6 7 export function tcsSchema (_options: schemaOptions ): Rule { return (tree: Tree, _context: SchematicContext ) => { const {name} = _options; tree.create ('hello.js' , `console.log('hello ${name} ')` ); return tree; }; }
然后我们先删除之前创建的hello.js
文件,然后运行:
1 schematice .:tcs-schema --name=tony
创建成功后,运行node ./hello.js
,可以看到正确的输出了hello tony
。
我们成功的传值给了我们的schema。我们还可以做得更好。
我们可以创建schema.json
文件来描述我们原理图需要的参数,这样,原理图将对传递的选项进行验证,并提示我们应该传递什么必须的参数。
在./src/tcs-schema
文件夹里面建schema.json
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 { "$schema" : "http://json-schema.org/schema" , "id" : "tcsSchema" , "title" : "" , "type" : "object" , "description" : "custom schema" , "properties" : { "name" : { "type" : "string" , "description" : "name" , "$default" : { "$source" : "argv" , "index" : 0 } } } , "required" : [ "name" ] }
这个json里面定义了可以传递给原理图的所有参数,并且限定了这个参数的类型,以及这个参数是否必须的验证。同时我们给name
属性加上了$defualt
限制,这使得name
属性变成了一个位置参数,即原理图会获取第一个参数作为name,我们无需加上--name
这个标志也可以创建,也就是说,下面两种形式都是可以的:
1 2 schematics .:tcs-schema tony schematics .:tcs-schema --name=tony
现在我们只是创建了schema.json
文件,还没有和我们的原理图进行关联,还是无效的。
我们需要将schema.json
关联到collection.json
文件中去,修改./src/collection.json
文件:
1 2 3 4 5 6 7 8 9 10 { "$schema" : "../node_modules/@angular-devkit/schematics/collection-schema.json" , "schematics" : { "tcs-schema" : { "description" : "A blank schematic." , "factory" : "./tcs-schema/index#tcsSchema" , "schema" : "./tcs-schema/schema.json" } } }
现在,这个schemo.json
中限定的参数就会起作用,让我们跑起来测试下:
像这样不带name
参数执行的时候,会抛出一个错误:
1 2 3 4 5 An error occured: Error: Schematic input does not validate against the Schema: {} Errors: Data path "" should have required property 'name'.
嗯,运行良好,当我们带着name参数的时候,会正常创建。这说明我们的参数验证起作用了。
现在我们需要创建一个interface
,用来告诉我们的index.ts
在创建原理图的时候会包含这些参数,创建文件./src/tcs-schema/schema.ts
:
1 2 3 export interface schemaOptions{ name : string ; }
然后在index.ts
中引入:
1 2 3 4 5 6 7 8 9 10 import { Rule , SchematicContext , Tree } from '@angular-devkit/schematics' ;import { schemaOptions } from './schema' ;export function tcsSchema (_options: schemaOptions ): Rule { return (tree: Tree, _context: SchematicContext ) => { const {name} = _options; tree.create ('hello.js' , `console.log('hello ${name} ')` ); return tree; }; }
通过提示增强参数 指定的选项可以提高开发者的体验,我们可以提供有用的、良好的提示,这样可以更好的帮助他们理解这些参数对原理图的作用。比如前面我们的name参数,可以加个提示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 { "$schema" : "http://json-schema.org/schema" , "id" : "tcsSchema" , "title" : "" , "type" : "object" , "description" : "custom schema" , "properties" : { "name" : { "type" : "string" , "description" : "name" , "$default" : { "$source" : "argv" , "index" : 0 } , "x-prompt" : "你想生成个啥?" } } , "required" : [ "name" ] }
这样,在当执行这个原理图的时候忘记输入了name,那么并不会报错,而是出现个提示项:
1 2 schematics .:tcs-schema ? 你想生成个啥?
这样开发者可以在提示的后面输入忘记的name,然后回车,即可正确的执行原理图。
使用原理图的模板 到现在为止,我们只是使用基本的JavaScript字符串模板生成我们的目标文件,这是基本操作,但是我们需要的是在我们的项目中生成文件,我们需要Angular Schematics
。
我们需要先建立一个files
文件夹:./src/tcs-schemo/files
。(为什么名字叫叫files
?因为我们在创建项目的时候,自动生成的脚手架的tsconfig.json
文件中有 "exclude": [ "src/*/files/**/*"]
,你想换其他名字也行,需要把tsconfig.json
这里也同步改一下。)
在./src/tcs-schema/files
下面再创建一个文件hello-__name@dasherize__.ts
。
__
双下滑线是一个分界符,用于将_options
中传递过来的name
和普通字符串区分开。dasherize
是一个辅助函数,它将接收的name
变量的值转换为kebab
大小写字符串,而@
是将变量应用于辅助函数的一种方式。
举个例子,假如我们提供的name
变量的值为TonyBest
,那么生成的文件为hello-tony-best.ts
。就是这这么神奇。
现在,我们修改hello-__name@dasherize__.ts
里面的内容:
1 console .log ('hello, <%= name %>' );
Angular Schematics
的模板语法由<%=
和%>
标记组成,用于输出变量的值。它还支持诸如dasherize
或者classify
之类的函数调用,用以在模板内部调整变量的值。例如:
1 2 3 4 5 @Component ({ selector : 'hello-<%= dasherize(name) %>' , }) export class Hello <%= classify (name) %>Component {}
ok,现在模板文件已准备好,我们需要再原理图规则内部进行关联。
调整./src/tcs-schema/index.ts
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import { Rule , url, apply, template, mergeWith } from '@angular-devkit/schematics' ;import { schemaOptions } from './schema' ;import { strings } from '@angular-devkit/core' ;export function tcsSchema (_options: schemaOptions ): Rule { const sourceTemplates = url ('./files' ); const sourceParametrizedTemplates = apply (sourceTemplates, [ template ({ ..._options, ...strings, }) ]); return mergeWith (sourceParametrizedTemplates); }
先不去理会这一堆代码的意思,我们先跑一下结果:
1 schematics .:tcs-schema 'tony best' --debug=false
注意:这里name
参数使用引号是因为我们要输入空格隔开的单词,如果我们的name
是一个单词,那么没必要使用引号。
确实生成了文件hello-tony-best.ts
,然后查看生成的文件内容:
1 2 3 4 5 @Component ({ selector : 'hello-tony-best' , }) export class HelloTonyBestComponent {}
nice啊~
模板辅助函数 我们可以在这里使用模板辅助函数比如dasherize
和classify
是因为我们将strings
对象解构后传递给了模板。
这意味着我们可以传递任何方法给模板。比如,我们希望在name
的值后面加上感叹号,那么我们可以创建一个模板辅助函数:addExclamation
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function addExclamation (value: string ): string { return value + '!' ; } export function tcsSchema (_options: schemaOptions ): Rule { const sourceTemplates = url ('./files' ); const sourceParametrizedTemplates = apply (sourceTemplates, [ template ({ ..._options, ...strings, addExclamation, }) ]); return mergeWith (sourceParametrizedTemplates); }
然后在模板中使用:
1 2 3 4 5 6 @Component ({ selector : 'hello-<%= dasherize(name) %>' , }) export class Hello <%= classify (name) %>Component { <%= addExclamation (name) %> }
然后可以看到生成的文件中:
1 2 3 4 5 6 @Component ({ selector : 'hello-tony-best' , }) export class HelloTonyBest Component { tony best! }
angular-devkit/core
包的strings
里面包含有丰富的模板辅助函数,后续可以探索一下。
Angular Schematics 模板语法 我们目前使用了<%=
和%>
标签语法可以用来输出变量的值。我们可以使用模板语法包括条件判断(if / else
)甚至于循环(for...of
)
需要注意的是,控制语句的标签是<%
和%>
,输出值的标签是<%=
和%>
,他们是有区别的。
Angular Schematics
模板似乎支持所有的JavaScript语言功能,所以我们甚至可以传递数组?
那假设我们需要接受一个书籍变量为books
,这个books
是一个字符串的数组,我们需要在schema.json
中定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 { "$schema" : "http://json-schema.org/schema" , "id" : "tcsSchema" , "title" : "" , "type" : "object" , "description" : "custom schema" , "properties" : { "name" : { "type" : "string" , "description" : "name" , "$default" : { "$source" : "argv" , "index" : 0 } , "x-prompt" : "你想生成个啥?" } , "books" : { "type" : "array" , "items" : { "type" : "string" } , "x-prompt" : { "message" : "需要哪种类型的书?" , "type" : "list" , "items" : [ { "value" : [ "CSS初级" , "CSS高级" ] , "label" : "CSS" } , { "value" : [ "HTML初级" , "HTML高级" ] , "label" : "HTML" } ] } } } , "required" : [ "name" , "books" ] }
然后在schema.ts
中添加books
的类型:
1 2 3 4 export interface schemaOptions{ name : string ; book : string []; }
然后在模板语法中进行处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Component ({ selector : 'hello-<%= dasherize(name) %>' , template : ` <h1>book list</h1> <% if (books && books.length) { %> <ul> <% for (let b of books) { %> <li><%= b %> <% } %> </ul> <% } %> ` }) export class Hello <%= classify (name) %> Component { <%= addExclamation (name) %> }
最后运行:
1 2 3 4 $ schematics .:tcs-schema --debug=false ? 你想生成个啥? tony ? 需要哪种类型的书? CSS CREATE /hello-tony.ts (291 bytes)
查看生成的文件hello-tony.ts
:
1 2 3 4 5 6 7 8 9 10 11 12 13 @Component ({ selector : 'hello-tony' , template : ` <h1>book list</h1> <ul> <li>CSS初级 <li>CSS高级 </ul> ` }) export class HelloTony Component { tony! }
Good!
与Angular Cli的集成 Angular Cli有一些特定的环境因素,需要对其处理,才能使我们的自定义原理图与Angular Cli可以无缝集成。
每当我们运行ng generate component
的时候,我们是在某个项目中运行并生成对应组件。angular.json
文件包含所有工作去项目的定义以及defaultProject
属性,它将确定我们创建的组件的位置。我们可以通过在ng generate
命令中显式指定--project some-app
来覆盖它。
我们需要修改schema.json
以接受project
参数:
1 2 3 4 5 6 7 8 9 10 11 { "$schema" : "http://json-schema.org/schema" , "..." : "..." , "properties" : { "..." : "..." , "project" : { "type" : "string" , "description" : "Generation in spacific Angular Cli workspace project" } } }
同时修改schema.ts
:
1 2 3 4 export interface schemaOptions{ name : string ; project?: string ; }
现在我们可以对原理图的规则进行调整了。我们需要做的有:
需要从angular.json
文件中获取到工作区的配置。这个配置可以让我们访问到defaultProject
和projects
对象。
用他们来检索特定的项目配置。
需要从项目配置中找到项目的路径(比如标准Angular Cli工作区间的src/app
或者projects/some-app/src/app
)
解析名称和默认项目路径,以获取最终路径和所创建的文件的名称。
将最终名称传递到模板中,并使用路径将创建的文件移动到虚拟树种的适当位置。
修改我们的./src/tcs-schema/index.ts
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import { Rule , Tree , SchematicContext , SchematicsException , url, apply, template, move, mergeWith} from '@angular-devkit/schematics' ;import { schemaOptions } from './schema' ;import {buildDefaultPath} from '@schematics/angular/utility/project' ;import {parseName} from '@schematics/angular/utility/parse-name' ;import { strings } from '@angular-devkit/core' ;export function tcsSchema (_options: schemaOptions ): Rule { return (tree: Tree, _context: SchematicContext ) => { const workspaceConfigBuffer = tree.read ('angular.json' ); if (!workspaceConfigBuffer) { throw new SchematicsException ("Not an Angular Cli workspace" ); } const workspaceConfig = JSON .parse (workspaceConfigBuffer.toString ()); const projectName = _options.project || workspaceConfig.defaultProject ; const project = workspaceConfig.projects [projectName]; const defaultProjectPath = buildDefaultPath (project); const parsePath = parseName (defaultProjectPath, _options.name ); const {name, path} = parsePath; const sourceTemplate = url ('./files' ); const sourceParametrizedTemplate = apply (sourceTemplate, [ template ({ ..._options, ...strings, name, }), move (path) ]); return mergeWith (sourceParametrizedTemplate)(tree, _context); }; }
然后我们尝试运行。在非angular项目中运行:schematics .:tcs-schema tony
,会报错:
1 2 An error occured: Error: Not an Angular Cli workspace
很好,那我们继续在angular项目中运行看看:
1 ng g ../tcs-schema/src/collection.json:tcs-schema tony
可以看到确实生产了文件:CREATE src/app/hello-tony.ts (129 bytes)
。
上面是默认创建在src/app
路径下面,那么我们需要像ng的脚手架一样,我们希望带着路径,如何?来尝试下:
1 2 ng g ../tcs-schema/src/collection.json:tcs-schema pages/tony CREATE src/app/pages/hello-tony.ts (129 bytes)
可以看到,很神奇的将路径拼接在了一起,我们正确的在src/app/pages
路径下面创建了文件。
ng
方式的生成还带有对--help
标志的支持,该标志可以正确的打印我们在shema.json
文件中提供的信息,比如:
1 ng g ../tcs-schema/src/collection.json:tcs-schema --help
将会输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 Generates and/or modifies files based on a schematic. usage: ng generate ../tcs-schema/src/collection.json:tcs-schema <name> [options] arguments: schematic The schematic or collection:schematic to generate. name name options: --defaults When true, disables interactive input prompts for options with a default. --dry-run (-d) When true, runs through and reports activity without writing out results. --force (-f) When true, forces overwriting of existing files. --help Shows a help message for this command in the console. --interactive When false, disables interactive input prompts. --project Generation in spacific Angular Cli workspace project Help for schematic ../tcs-schema/src/collection.json:tcs-schema custom schema arguments: name name options: --project Generation in spacific Angular Cli workspace project To see help for a schematic run: ng generate <schematic> --help
到这里第一阶段就可以了,我们了解了基本的创建、使用、调试,以及和Angular Cli的结合。下一阶段来分析@schematics/angular
里面的component
原理图的源码。
参考链接:Total Guide To Custom Angular Schematics