转载本文请联系神光编程秘籍公众号。typescript将类型的语法和语义扩展到javascript,让js代码在静态类型语言层面实现类型安全。以前只能在运行时发现的类型不安全问题,在编译时也能发现,所以项目越大,用typescript写的选项也越来越多。另外typescript还可以配合ide做更好的智能提示,这也是使用typescript的一个原因。类型安全:如果一个类型的变量被赋予了一个不兼容类型的值,那么它就是类型不安全的。如果一个类型的对象调用了它没有的方法,它也是类型不安全的。反之,就是类型安全。类型安全是指变量的赋值和对象的函数调用都在类型支持的范围内。一开始,只有内置的typescript编译器(tsc)可以编译typescript代码。编译不同版本的typescript代码需要不同版本的tsc。你可以通过配置tsconfig.json来指定如何编译。但是tsc将ts代码编译成js有一个问题:tsc不支持很多还在草稿阶段的语法。这些语法都是babel插件支持的,所以很多项目的toolchain都是用tsc把ts代码编译一次,再用babel编译。这样编译环节长,生成的代码不够精简。所以typescript找babel团队合作,babel7支持typescript的编译,可以通过插件指定ts语法的编译。例如,这在API中使用:constparser=require('@babel/parser');parser.parse(sourceCode,{plugins:['typescript']});这个插件是typescript团队和babel团队合作一年的成果。但是,这个插件真的可以支持所有的typescript代码吗?答案是不。下面我们来看看Babel不支持哪些ts语法,以及为什么不支持。babel可以编译所有的typescript代码吗?babel的编译过程如下:parser:将源码解析成asttraverse:遍历ast,生成scope信息和path,调用各种插件转换astgenerator:将转换后的AST打印成目标码,并生成sourcemap。typescript编译器的编译过程如下:scanner+parser:ast的分词和组装,从sourcecode到ast的过程binder+checker:生成作用域信息,进行类型推导和checktransform:将经过类型检查的ast转换为emitter:printastintoobjectcode,generatesourcemapandtypedeclarationfile(根据配置)其实babel的编译阶段和tsc差不多,只是tsc多了一个checker,其余部分没有区别。babel的parser对应tsc的scanner+parserbabel的traversestage对应tsc的binder+transformbabel的generator对应tsc的emitter那么基于babel的插件是否可以实现遍历时的检查器呢?答案是不。因为tsc的类型检查需要获取整个项目的类型信息,所以需要引入类型,合并多个文件的namespace、enum、interface等,而babel编译单个文件不会解析信息其他文件。所以你不能做和tsc一样的类型检查。一种是在编译过程中解析多个文件,另一种是编译过程只针对单个文件。进程的不同导致babel无法进行tsc类型检查。那么babel是如何编译typescript的呢?其实babel只能将ts代码解析成ast,不会做类型检查,会直接去掉类型信息,然后打印到目标代码中。这就导致了一些babel不支持的ts语法:constenumdoesnotsupport。constenum就是在编译时将enum的引用替换成一个特定的值。需要解析类型信息,但是babel无法解析,所以不支持。可以使用相应的插件将constenum转换为enum。部分支持命名空间。不支持命名空间跨文件合并,不支持导出非常量值。这也是因为babel不解析类型信息,是单文件编译。上面两个因为编译方式不同,不支持。export=import=不支持这种ts特有的语法,可以通过插件转换成esm。如果开启了jsx编译,则不支持aa的类型断言,可以用aaasstring代替。这是因为两种语法冲突。在这两个语法插件(jsx、typescript)中,解决冲突的方法是改用as。这四种是babel不支持的ts语法。其实影响不大。不使用这些功能很好。结论:babel不能编译所有的typescript代码,但是除了namespace这两个特性,其余的都可以编译。babel可以编译typescript代码,那为什么要用babel编译呢?为什么要用babel来编译typescript代码?babel编译typescript代码主要有以下三个优势:产品体积更小。tsctsc如何配置编译目标?在compilerOptions中配置target,target设置目标语言版本{compilerOptions:{target:"es5"//es3,es2015}}typescript是如何引入polyfill的?在入口文件中引入core-js.import'core-js';babel7babel7如何配置编译目标呢?在preset-env中指定targets,直接指定目标运行环境(浏览器、节点)版本,或者指定一个查询字符串,从browserslist中查找具体版本。{预设:[["@babel/preset-env",{目标:{chrome:45}}]]}{预设:[["@babel/preset-env",{目标:"last1version,>1%,notdead"}]]}babel7是怎么引入polyfill的?它也在@babel/preset-env中配置。除了指定targets之外,还需要指定使用哪个polyfill(corejs2或corejs3),以及如何引入(entry在入口处引入,usage每个模块分别引入和使用)。{presets:[["@babel/preset-env",{targets:"last1version,>1%,notdead",corejs:3,useBuiltIns:'usage'}]]}这个可以基于@babel/compat-data数据用于语法转换和APIpolyfill:首先根据targets找出支持的目标环境的版本,然后根据目标环境的版本从所有的features中过滤支持的features,剩下的就是不支持的功能。只需对这些功能进行转换和polyfill。而且babel还可以使用@babel/plugin-transform-runtime将corejs的全局导入转换成模块化的导入方式。显然,用babel编译typescript从产品角度来说有两个好处:可以做更精准的按需编译和polyfill,产品体积更小。它可以使用插件将polyfill变成模块化的引入,不会污染全局环境。从产品上看,babel胜出。支持的语言特性Typescript默认支持很多es特性,但不支持尚处于草案阶段的特性。Babel的preset-env支持所有标准特性,也可以通过提案支持更多尚未进入标准的特性。{plugins:['@babel/proposal-xxx'],presets:['@babel/presets-env',{...}]}在支持的语言特性方面,babel胜出。编译速度tsc会在编译过程中进行类型检查。类型检查需要综合多个文件的类型信息。对AST做类型推导比较耗时,但是babel不做类型检查,所以编译速度会快很多。在编译速度上,babel胜出。总之,从编译产品大小(主要)、支持的语言特性、编译速度来看,babel胜出。但是babel不做类型检查,那么类型检查呢?babel和tsc的结合可以编译生成更小的产品,编译速度更快,特性支持更多,所以我们选择使用babel来编译typescript代码。但是类型检查也是需要的。可以在npmscripts中配置一个命令:{"scripts":{"typeCheck":"tsc--noEmit"}},这样在需要类型检查的时候可以单独执行npmruntypeCheck。,但最好在gitcommithook中执行强制类型检查(通过husky配置)。综上所述,typescript将静态类型支持扩展到js,使得代码在编译时可以检查出不匹配的赋值类型和调用不存在的方法等错误,确保类型安全。除了tsc,babel7还可以编译typescript代码,这是两个团队合作一年的成果。但是babel由于单文件编译的特点,无法达到tsc的多文件类型编译一样的效果。有几个特性不支持(主要是命名空间的跨文件合并和非常量值的导出),但影响不大。整体可用。使用babel进行代码编译,还是需要使用tsc进行类型检查,单独执行tsc--noEmit即可。用babel编译ts代码比tsc有很多优势:产品体积更小,支持更多特性,编译速度更快。所以用babel编译typescript是更好的选择。
