You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.exportfunctiondemoSchema(_options: any): Rule{return(tree: Tree,_context: SchematicContext)=>{returntree;};}
// 这个是一个工具方法functionreadIntoSourceFile(host: Tree,modulePath: string): ts.SourceFile{// 先试着用Tree方法读文件consttext=host.read(modulePath);if(text===null){thrownewSchematicsException(`File ${modulePath} does not exist.`);}constsourceText=text.toString('utf-8');returnts.createSourceFile(modulePath,sourceText,ts.ScriptTarget.Latest,true);}
exportfunctionaddRoutesToModule(source: ts.SourceFile,fileToAdd: string,routeLiteral: string): Change{constrouterModuleExpr=getRouterModuleDeclaration(source);if(!routerModuleExpr){thrownewError(`Couldn't find a route declaration in ${fileToAdd}.`);}constscopeConfigMethodArgs=(routerModuleExprasts.CallExpression).arguments;if(!scopeConfigMethodArgs.length){const{ line }=source.getLineAndCharacterOfPosition(routerModuleExpr.getStart());thrownewError(`The router module method doesn't have arguments `+`at line ${line} in ${fileToAdd}`);}letroutesArr: ts.ArrayLiteralExpression|undefined;constroutesArg=scopeConfigMethodArgs[0];// 检查路由声明数组是RouterModule的内联参数还是独立变量if(ts.isArrayLiteralExpression(routesArg)){routesArr=routesArg;}else{constroutesVarName=routesArg.getText();letroutesVar;if(routesArg.kind===ts.SyntaxKind.Identifier){routesVar=source.statements.filter((s: ts.Statement)=>s.kind===ts.SyntaxKind.VariableStatement).find((v: ts.VariableStatement)=>{returnv.declarationList.declarations[0].name.getText()===routesVarName;})asts.VariableStatement|undefined;}if(!routesVar){const{ line }=source.getLineAndCharacterOfPosition(routesArg.getStart());thrownewError(`No route declaration array was found that corresponds `+`to router module at line ${line} in ${fileToAdd}`);}routesArr=findNodes(routesVar,ts.SyntaxKind.ArrayLiteralExpression,1)[0]asts.ArrayLiteralExpression;}constoccurrencesCount=routesArr.elements.length;consttext=routesArr.getFullText(source);letroute: string=routeLiteral;letinsertPos=routesArr.elements.pos;if(occurrencesCount>0){// 不一样的开始// 获取最后一个elementconstlastRouteLiteral=[...routesArr.elements].pop()asts.Expression;// 从当前元素的属性里面获取`children`属性token信息constchildren=(ts.isObjectLiteralExpression(lastRouteLiteral)&&lastRouteLiteral.properties.find(n=>{returnts.isPropertyAssignment(n)&&ts.isIdentifier(n.name)&&n.name.text==='children';}))asts.PropertyAssignment;if(!children){thrownewError('"children" does not exist.');}// 处理路由字符串constindentation=text.match(/\r?\n(\r?)\s*/)||[];constrouteText=`${indentation[0]||' '}${routeLiteral}`;// 获取当前`children`结束位置insertPos=(children.initializerasts.ArrayLiteralExpression).elements.end;// 拼接路由信息route=`${routeText},`;// 不一样的结束}returnnewInsertChange(fileToAdd,insertPos,route);}
使用angular schematics快速生成代码
什么是Schematics?
Schematics是改变现存文件系统的生成器。有了Schematics我们可以:
Schematics能做什么?
总体上,Schematics可以:
在你自己的工程或者在你所在的组织中使用Schematics是具有无限可能的。下面一些例子展现了你或者你的组织或如何从创建一个schematics collection中获益:
CLI集成?
是的,schematics与Angular CLI紧密集成。你可以在下列的CLI命令中使用schematics:
什么是Collection?
Collection是一系列的schematic。我们会在工程中collection.json中为每个schematic定义元数据。
安装
首先,使用npm或者yarn安装schematics的CLI:
快速开始
这会创建名为
demo-schema
的文件夹,在其中已经创建了多个文件,如下所示。我们使用 blank 为我们后继的工作打好基础。
collection.json
入口函数
打开
src/demo-schema/index.ts
文件,看看内容:demoSchema
函数_options
参数,它是命令行参数的键值对对象,和我们定义的schema.json
有关Tree
和SchmaticsContext
对象的函数什么是Tree?
我们能使用tree完成这些事情:
什么是Rule?
构建和执行
要运行我们的示例,首先需要构建它,然后使用schematics命令行工具,将schematic项目目录的路径作为集合。从我们项目的根:
如何使用在angular项目中
使用短连安装:
使用
ng generate
运行:package.json
的name
实战项目
最新在公司项目需要把之前的项目的通用组件提取出来,做成一个单独的组件库并且带上demo。创建了一个新的工程。组件库使用
ng-packagr
,如果直接使用它去打包,会全部打包到一起,这样就会有问题,加载的时候特别大。ng-packagr
提供的二次入口,可以解决这个问题,但是又会有新问题,必须要要和src
同级目录。如果使用angular-cli
默认去生成都会自动添加到src/lib
里面,虽然可以修改path
,但是问题是一个组件模块里面有一些特定的文件:大概就这些,如果这些用angular-cli,去生成,需要6次才能完成,也可以写一个
shell
,一次性完成。但是发现太麻烦,有些东西无法控制,如果需要定制,那就需要自己来写schematics
。这里有2部分不一样的实战内容,一种是根据固定内容直接生成模板,一种是生成模板以后修改已有关联的文件。
为什么会有这2个,第一个是为了生成组件模块,第二个是为了生成演示组件模块。
创建一个schematics
这时候也会创建
src/tools
,我们把它改成ui
,把collection.json
文件里面也修改了。实战1
创建
ui/schema.json
文件:这里可以设置你的
schematics
的命令选项,类似于在使用ng g c --name=user
的--name
命令。创建
ui/schema.ts
文件:修改
ui/index.ts
:我们模块在
files
文件下,applyTemplates
把我们的配置转换成模板可以使用的变量并生成文件,move
把生成好的文件移动到目标路径下。我们前面也也介绍了,入口函数总是要返回一个
rule
。chain
验证我们的配置规则。整体看起来比较简单,这样就已经完成的整个的生成命令,下面就是关键模块定义:
files
文件下.template
后缀结尾__变量__
方式__变量@方法名__
举例:
将name变量驼峰式写法转换为连字符的写法。
模板里面如何使用,语法和
EJS
一样标签含义:
<%
'脚本' 标签,用于流程控制,无输出。<%_
删除其前面的空格符<%=
输出数据到模板(输出是转义 HTML 标签)<%-
输出非转义的数据到模板<%#
注释标签,不执行、不输出内容<%%
输出字符串 '<%'%>
一般结束标签-%>
删除紧随其后的换行符_%>
将结束标签后面的空格符删除语法示例:
会大量使用变量和少量的流程判断,变量一般都会使用内置的模板方法来配合使用:
内置的模板变量方法:
内置的模板方法主要命名转换,如果不能满足你需求,可以自己定义:
举个几个例子:
name@dasherize.module.ts.template
name@dasherize.component.ts.template
index.ts.template
我们可以构建试一下:
基本已经完成我们想要的,还有几个文件生成是可选的,我们需要配置处理一下:
如果是true,就忽略,如果是false,就排除这个后缀结尾文件。
注意:不需要写
=true
。npm link tools ng g tools:ui --name="test" --dry-run
ng g tools:ui --name="test"
基本已经完成了,下面介绍一个进阶实战。
实战2
我们想要创建多个
schematics
,需要手动添加,我们需要文件:然后在
src/collection.json
里添加申明:配置
schema.json
:先保留这些
Schema
,后面来丰富。我们把前面介绍的入口函数拷贝到
src/demo/index.ts
里,构建编译:这个实战和前面实战有些不一样,前面的只是一个替换生成,相当于一个入门级的,很容易学会,现在介绍一个高级点的,不光要替换生成,还要去改变已有文件的依赖关系。
使用angular-cli的时候,创建组件以后,会自动去关联的模块里面去申明,这个是怎么做到的?
我们就需要实现一个类似的功能,有一个功能需要去展示UI组件的demo,每次创建都是相当于有一套对应的模板,但是每次创建以后都是一个新的的页面,也需要一个路由规则需要添加,如果我们单纯创建一套demo组件的文件,还需要去手动添加路由,这样就比较麻烦,现在就需要自动完成这个功能。我们一起来实现它吧。
这里我们就用上
beginUpdate
和commitUpdate
2个方法来实现先介绍一下需要实现的功能:
我有三个文件夹:
书写路由时候,每个ui组件,都是一个独立模块,使用懒加载模块方式,这样所有的懒加载路由都是平级的。
举个栗子:
其实angular也有自带添加路由依赖方法,但是只能添加一级路由,不能添加子路由,我们这个需求就是需要添加子路由。
模板这块就不在说明了,和实战1是一样处理的,去files文件夹里面创建对应的模板即可。
其他没有什么好说明的,
addDeclarationToNgModule
是我们需要重点说明的,也是这个实战的核心。这里有3个依赖方法:
ts.createSourceFile
为我们解析文件源 ASTaddRoutesToModule
内容太多,创建一个utils.ts
文件来处理它。大部分也是借鉴angular-cli的addRouteDeclarationToModule方法,改成我们想要。
注意:这里有些代码相当于写死了,因为我本身都是固定的。
那些一样都不过多的解释,你需要知道最终拿到的是:
const routes: Routes = []
即可。所以按angular自带的addRouteDeclarationToModule方法,操作总是
routes.push(newRoute)
这样的操作,而我们需要的操作是routes[0].children.push(newRoute)
,就需要自己弄了。我们拿到
lastRouteLiteral
,注意:其实这个有个bug,如果我们路由里面改了,这个就挂了。这里拿的对应信息就是:
lastRouteLiteral.properties
是一个数组,我们这个{}
里面有几项,就会有几个数组。我们只关心children
属性,就通过find查找目标,它有可能是undefined
,需要处理一下。我们来打印
children
:我大家演示2个不一样的:
路由配置里
children
空的一个路由配置里
children
有的这里给大家科普几个数据就好了:
主要看
elements
变化,空里面只有2个[pos, end],如果不是空里面就会有子节点,你现在是不是可以干点其他事情了。(ps:如果想批量更新之前内容是不是想想也容易了)注意:如果你要调试,一定要用
npm link
安装,根目录使用ng g tools:demo --name=test
。先试试默认的路由添加:
The text was updated successfully, but these errors were encountered: