本文转载自微信公众号《神说必有光zxg》,作者神光的编程秘籍。转载此文请联系大神说必有光zxg公众号。现在很多JS库都是用typescript写的,面试几乎都会问到typescript。可能你对ts的各种语法和内置的高级类型很熟悉,ts的配置和命令行的使用也没有问题,但我总觉得自己对ts的理解没有那么深,而且我苦于没有好的办法继续改进。这个时候推荐大家研究下typescriptcompilerapi。Typescript会将ts源码解析成AST,然后对AST进行各种变换,然后生成js代码。在此过程中,将对AST进行类型检查。Typescript将整个过程封装到tsc的命令行工具中。通常,我们通常使用tsc来编译ts代码并进行类型检查。但实际上,ts除了提供tsc命令行工具外,还对外暴露了很多API,还可以自定义transformer。就好比babel可以把esnext和ts语法编译成js,可以写babel插件转换代码,暴露各种api。只是typescripttransformer的生态远不如babel插件,知道的人少。其实typescripttransformer可以做一些babel插件做不到的事情:babel将ts、exnext等转换为js,而生成的js代码中会丢失类型信息,无法生成ts代码.Babel只是对ts代码进行转换,并不进行类型检查。这两个babel插件不能做的,可以用typescripttransformer来完成。而且,学习typescript编译器的api,可以帮助你深入typescript的编译过程,更好地掌握typescript。说了这么多,下面通过一个例子开始使用typescripttransformer。案例描述了这样一段ts代码:typeIsString=Textendsstring?'Yes':'No';typeres=IsString;typeres2=IsString<'aaa'>;我们希望将res和res2的类型值计算出来,稍后通过注释补充。像这样:typeIsString=Textendsstring?'是':'否';typeres=IsString//否;typeres2=IsString<'aaa'>//是;本例同时使用了transformerapi和To类型检查的API。下面分析一下思路:分析思路首先要将ts代码解析成AST,然后利用AST找到要转换的节点,这里就是TypeReference节点。可以用astexplorer.net看:IsString是一个TypeReference,也就是引用其他类型,然后还有typeName是IsString和类型参数typeArguments,这里的类型参数为true。它与函数调用非常相似吗?这就是进阶型的精髓。最终类型是通过将类型参数传递给引用的高级类型来获得的。然后我们找到TypeReference节点后,我们就可以使用类型检查器api找到type值,然后创建一个comment节点添加到后面。AST转换后,打印成ts代码字符串。这就是思维方式。接下来我们具体实现一下,熟悉一下ts的api。代码实现将代码解析成AST需要指定要编译的文件和编译参数(createProgramapi),然后就可以得到不同文件的AST(getSourceFileapi)。constts=require("typescript");constfilename="./input.ts";constprogram=ts.createProgram([filename],{});//第二个参数是compileroptions,即配置文件中的constsourceFile=program.getSourceFile(文件名);这里的sourceFile是AST的根节点。接下来我们需要使用转换api转换AST:const{transformed}=ts.transform(sourceFile,[function(context){returnfunction(node){returnts.visitNode(node,visit);functionvisit(node){(ts.isTypeReferenceNode(node)){//...}returnts.visitEachChild(node,visit,context)}};}]);transform遍历要传入的AST和transformerFactory。AST是上述解析的源文件。TransformerFactory可以被context中的很多API使用,它的返回值是转换函数transformer。transformer参数为node,返回值为修改后的node。修改节点需要遍历节点,使用visitapi和vistEachChildapi,过程中根据类型过滤掉TypeReference节点。然后转换TypeReference节点如下:;}}即使用typeChecker获取IsString类型的最终类型值,然后通过addSyntheticTrailingComment的api在最后添加注释。其中使用的typeChecker是通过getTypeChecker的api获取的:consttypeChecker=program.getTypeChecker();这样就完成了我们转换tsAST的目的。然后通过打印机将AST打印成ts代码。constprinter=ts.createPrinter();constcode=printer.printNode(false,transformed[0],transformed[0]);控制台日志(代码);就是这样,让我们??测试一下。测试之前所有的代码都放在这里:constts=require("typescript");constfilename="./input.ts";constprogram=ts.createProgram([filename],{});//第二个参数是编译器选项,那些constsourceFile=program.getSourceFile(filename);consttypeChecker=program.getTypeChecker();const{transformed}=ts.transform(sourceFile,[function(context){returnfunction(node){returnts.visitNode(node,visit);functionvisit(node){if(ts.isTypeReferenceNode(node)){consttype=typeChecker.getTypeFromTypeNode(node);if(type.value){ts.addSyntheticTrailingComment(node,ts.SyntaxKind.SingleLineCommentTrivia,type.value);}}returnts.visitEachChild(node,visit,context)}};}]);constprinter=ts.createPrinter();constcode=printer.printNode(false,transformed[0],transformed[0]);控制台.log(代码);测试效果经过测试,我们达到了找出类型并添加到后面注释中的目的。这是我们的第一个ts转换器示例。虽然功能比较简单,但是我们也学会了如何对ts代码做parse、transform、print、typecheck。其实babel也有parse、transform、generate三个步骤,只是没有类型检查的过程,无法打印成ts代码。在使用编译器api的过程中,你会发现原来高级类型是一个typeReference,需要传入typeArguments来求值,这样才能对高级类型有更深的理解。熟悉typescript语法和配置后,如果想更进一步,可以学习compilerapi深入ts编译过程。它包含了transformer、typechecker等API,可以像babel插件一样达到转换ts代码的目的,还可以做类型检查。我们通过一个例子来熟悉下typescript的编译过程和transformer的写法。当需要修改ts代码再生成ts代码时,babel做不到,只能生成js代码,这时可以考虑typescript的自定义transformer。并且使用typescriptcompilerapi可以加深你对ts编译过程和类型检查的理解。ts编译器api,尤其是其中的自定义转换器是让typescript更进一步的好方法。