编译TypeScript代码用什么编译器?不用说了,肯定是ts自带的编译器。但其实babel也可以编译ts代码,那么用babel和tsc编译ts代码有什么区别呢?我们分别来看一下:tsc的编译过程和typescript编译器的编译过程是这样的:源码首先要用Scanner进行词法分析,拆分成一个个不能再细分的词,称为tokens。然后使用Parser进行语法分析,组装成抽象语法树(AbstractSyntaxTree)AST。然后做语义分析,包括用Binder做作用域分析,用Checker做类型检查。如果存在类型错误,则会在检查器阶段报告。如果有Transformer插件(tsc支持自定义transform),会在Checker之后调用,对AST进行各种增删改查。类型检查通过后,会使用Emmiter将AST打印到目标代码中,生成类型声明文件d.ts,以及sourcemap。sourcemap的作用是将源代码和目标代码的代码位置进行映射,这样在调试的时候,断点可以定位到对应的源代码,在线报错的时候,源代码的位置error也可以根据sourcemap定位。tsc生成的AST可以用astexplorer.net直观查看:生成的目标代码、d.ts和报错信息也可以直接用tsplayground查看:大致了解了tsc的编译过程后,再来看babel再次:babel编译过程babel的编译过程如下:源码通过Parser进行词法分析和解析,生成token和AST。AST会进行语义分析生成作用域信息,然后调用Transformer对AST进行转换。最后,Generator将用于将AST打印成目标代码并生成sourcemap。Babel的AST和token也可以通过astexplorer.net进行可视化查看:如果要查看tokens,需要点击设置启用tokens:并且babel还有一个playground(babel的叫repl)可以直接看到代码编译后生成:其实对比tsc的编译过程没有太大区别:Parser对应tsc的Scanner和Parser,都是做词法分析和语法分析的,只是babel没有细分。Transform阶段进行语义分析和代码转换,对应tsc的Binder和Transformer。只是babel没有做类型检查,没有Checker。Generator生成objectcode和sourcemap,对应tsc的Emitter。只是因为没有类型信息,所以不会生成d.ts。对比两者的编译过程,你会发现,除了babel不做类型检查和生成类型声明文件外,babel可以做tsc能做的一切。好像是这样,但是babel和tsc在实现这些功能上还是有区别的:babel和tsc的区别,先不管babel不支持的功能,比如类型检查,生成d.ts等,一起来看看在其他功能的比较处:分别比较语法支持和代码生成:语法支持tsc默认支持最新的es规范语法和一些还在草案阶段的语法(比如装饰器)。如果要支持新的语法,需要升级tsc的版本。Babel使用@babel/preset-env根据目标环境targets的配置自动引入所需的插件来支持标准语法。对于尚处于草案阶段的语法,需要单独引入@babel/proposal-xx插件来支持。所以如果你只使用标准语法,可以使用tsc或者babel,但是如果你想使用一些draft语法,tsc可能支持的不多,但是babel可以引入@babel/poposal-xx插件来支持。在支持的语法特性方面,babel有更多。代码生成tsc生成的代码没有经过polyfill处理。如果要做兼容处理,需要在入口处引入core-js(polyfill的实现)。import"core-js";Promise.resolve;Babel的@babel/preset-env可以根据targets的配置自动导入需要的插件,导入需要的core-js模块。导入方式可以通过useBuiltIns配置:entry是在入口处导入根据target过滤出来的所有需要??的core-js。usage是指根据使用到哪些模块,按需导入各个模块。module.exports={presets:[['@babel/preset-typescript','@babel/preset-env',{targets:'targetenvironment',useBuiltIns:'entry'//'usage'}]]}另外,babel会注入一些辅助代码,可以通过@babel/plugin-transform-runtime插件提取,并从@babel/runtime包中导入。使用transform-runtime之前:使用transform-runtime之后:(transformruntime,顾名思义就是transformtoruntime,转换成从runtime包导入helper代码的方式)所以一般babel会这样匹配:module.exports={presets:[['@babel/preset-typescript','@babel/preset-env',{targets:'目标环境',useBuiltIns:'usage'//'entry'}]],plugins:['@babel/plugin-transform-runtime']}当然,这不是关于如何配置babel。回到正题,babel和tsc生成代码的区别:tsc生成的代码不经过polyfill处理,需要全量导入core-js,而babel可以使用@babel/preset-envimportscore-js的一些模块根据targets的配置需要,所以生成的代码更小。好像用babel编译ts代码都是优点?不是全部,babel有一些不支持的ts语法:babeldoesnotsupporttssyntaxbabel是针对每个文件单独编译的,而tsc不是,tsc是针对整个项目一起编译的,它可以处理类型声明文件和合并类型跨文件声明。例如,命名空间和接口可以跨文件合并。所以babel编译ts代码时有一些不支持的特性:constenumdoesnotsupportenum编译是这样的:constenum编译后,直接把使用enum的地方替换成对应的值,就是这样:constenum是在编译时用特定值替换enum引用,需要解析类型信息,但是babel不会解析,所以会把constenum转成enum进行处理:namespacepartialsupport:不支持namespacemerging,不支持export的非常量值就是这样一段ts代码:namespaceGuang{exportconstname='guang';}namespaceGuang{exportconstname2=name;}console.log(Guang.name2);按理说guang.name2是'dong',因为ts会自动合并同名的命名空间。ts编译后的代码是这样的:它们都挂在了guang的对象上,所以name2可以拿到name的值。而babel对每一个命名空间都是单独处理的,所以是这样的:因为不会合并命名空间,所以name是undefined。此外,命名空间不支持导出非常量值。ts的命名空间可以导出非常量值,后面可以修改:但是babel不支持:原因也是因为不会解析命名空间,命名空间是全局的。如果命名空间导出的值在另一个文件中被更改,Babel不会处理。因此不支持修改命名空间导出的值。另外,还有一些语法不支持:一些语法不支持export=import=等过时的模块语法不支持:启用jsx编译后,不能使用尖括号进行类型断言:asweknow,ts可以做类型断言将某种类型修改为某种类型,使用asxx或尖括号。但是如果开启jsx编译,尖括号的形式会和jsx的语法冲突,所以不支持类型断言:tsc不支持,babel当然也一样:babel不支持ts的这些特性,那么能不能用babel编译ts呢?巴别塔还是tsc?Babel不支持constenum(会被当作enum),不支持命名空间的跨文件合并,导出非const值,不支持过时的export=import=module语法。这些其实影响不大。代码中只要不使用这些语法,就可以用babel编译ts。babel编译ts代码的好处是可以通过插件支持更多的语言特性,生成的代码根据targets的配置按需导入到core-js中,而tsc则不会这样做,只能导入到满的。而且tsc因为类型检查所以比较慢,而babel不做类型检查,所以编译会快很多。然后用babel编译,不做类型检查吗?您可以使用tsc--noEmit进行类型检查,并添加noEmit选项以不生成代码。如果要生成d.ts,还要单独运行tsc编译。综上所述,babel和tsc的编译过程是相似的。两者都有将源代码转换为AST的Parser,都进行语义分析(范围分析)和AST转换,最后使用Generator(或Emitter)将AST打印成目标代码并生成sourcemap。但是babel不做类型检查,也不生成d.ts文件。tsc支持最新的es标准特性和一些draft特性(比如decorator),而babel通过@babel/preset-env支持所有标准特性,也可以通过@babel/proposal-xx支持各种非标准特性,支持babel在语言功能方面更强。tsc不做polyfill处理,需要全量导入core-js,而babel的@babel/preset-env会根据targets的配置按需导入core-js,导入方式受useBuiltIns影响(入口为需要在入口处导入目标,每个模块导入使用usage)。但是因为babel是单独编译每个文件的(tsc是把整个工程一起编译的),不解析类型,所以不支持const枚举,命名空间合并,命名空间导出非const值。也不支持过时的export=module语法。但这些影响不大。可以使用babel编译ts代码,生成更小的代码,编译速度更快,无需类型检查。如果要进行类型检查,可以单独执行tsc--noEmit。当然,文章只讨论tsc和babel编译ts代码的区别,并没有说用哪个最好,用什么编译ts,大家可以根据场景选择。
