typescript 给 javascript 扩展了类型的语法和语义,让 js 代码达到了静态类型语言级别的类型安全,之前只能在运行时发现的类型不安全的问题,现在能在编译期间发现了,所以大项目越来越多的选择用 typescript 来写。除此之外,typescript 还能够配合 ide 做更好的智能提示,这也是用 typescript 的一个理由。
类型安全:如果一个类型的变量赋值给它不兼容类型的值,这就是类型不安全,如果一个类型的对象,调用了它没有的方法,这也是类型不安全。反之,就是类型安全。类型安全就是变量的赋值、对象的函数调用都是在类型支持的范围内。
最开始 typescript 代码只有自带的 tyepscript compiler(tsc)能编译,编译不同版本的 typescript 代码需要用不同版本的 tsc,通过配置 tsconfig.json 来指定如何编译。
但是 tsc 编译 ts 代码为 js 是有问题的:
tsc 不支持很多还在草案阶段的语法,这些语法都是通过 babel 插件来支持的,所以很多项目的工具链是用 tsc 编译一遍 ts 代码,之后再由 babel 编译一遍。这样编译链路长,而且生成的代码也不够精简。
所以,typescript 找 babel 团队合作,在 babel7 中支持了 typescript 的编译,可以通过插件来指定 ts 语法的编译。比如 api 中是这样用:
const parser = require('@babel/parser');
parser.parse(sourceCode, {
plugins: ['typescript']
});
这个插件是 typescript 团队与 babel 团队合作了一年的成果。
但是,这个插件真的能支持所有 typescript 代码么?答案是否定的。
我们来看一下 babel 不支持哪些 ts 语法,为什么不支持。
babel 的编译流程是这样的:
而 typescript compiler 的编译流程是这样的:
其实 babel 的编译阶段和 tsc 的编译阶段是类似的,只是 tsc 多了一个 checker,其余的部分没什么区别。
那么能不能基于 babel 的插件在 traverse 的时候实现 checker 呢?
答案是不可以。
因为 tsc 的类型检查是需要拿到整个工程的类型信息,需要做类型的引入、多个文件的 namespace、enum、interface 等的合并,而 babel 是单个文件编译的,不会解析其他文件的信息。所以做不到和 tsc 一样的类型检查。
一个是在编译过程中解析多个文件,一个是编译过程只针对单个文件,流程上的不同,导致 babel 无法做 tsc 的类型检查。
那么 babel 是怎么编译 typescript 的呢?
其实 babel 只是能够 parse ts 代码成 ast,不会做类型检查,会直接把类型信息去掉,然后打印成目标代码。
这导致了有一些 ts 语法是 babel 所不支持的:
上面两种两个是因为编译方式的不同导致的不支持。
这四种就是 babel 不支持的 ts 语法,其实影响并不大,这几个特性不用就好了。
结论:babel 不能编译所有 typescript 代码,但是除了 namespace 的两个特性外,其余的都可以做编译。
babel 是可以编译 typescript 代码,那么为什么要用 babel 编译呢?
babel 编译 typescript 代码有 3 个主要的优点:
tsc 如何配置编译目标呢?
在 compilerOptions 里面配置 target,target 设置目标语言版本
{
compilerOptions: {
target: "es5" // es3、es2015
}
}
typescript 如何引入 polyfill 呢?
在入口文件里面引入 core-js.
import 'core-js';
babel7 是如何配置编译目标呢?
在 preset-env 里面指定 targets,直接指定目标运行环境(浏览器、node)版本,或者指定 query 字符串,由 browserslist 查出具体的版本。
{
presets: [
[
"@babel/preset-env",
{
targets: {
chrome: 45
}
}
]
]
}
{
presets: [
[
"@babel/preset-env",
{
targets: "last 1 version,> 1%,not dead"
}
]
]
}
babel7 如何引入 polyfill 呢?
也是在 @babel/preset-env 里面配置,除了指定 targets 之外,还要指定 polyfill 用哪个(corejs2 还是 corejs3),如何引入(entry 在入口引入 ,usage 每个模块单独引入用到的)。
{
presets: [
[
"@babel/preset-env",
{
targets: "last 1 version,> 1%,not dead",
corejs: 3,
useBuiltIns: 'usage'
}
]
]
}
这样可以根据 @babel/compat-data 的数据来针对的做语法转换和 api 的 polyfill:
先根据 targets 查出支持的目标环境的版本,再根据目标环境的版本来从所有特性中过滤支持的,剩下的就是不支持的特性。只对这些特性做转换和 polyfill 即可。
而且 babel 还可以通过 @babel/plugin-transform-runtime 来把全局的 corejs 的 import 转成模块化引入的方式。
显然,用 babel 编译 typescript 从产物上看有两个优点:
从产物来看,babel 胜。
typescript 默认支持很多 es 的特性,但是不支持还在草案阶段的特性,babel 的 preset-env 支持所有标准特性,还可以通过 proposal 来支持更多还未进入标准的特性。
{
plugins: ['@babel/proposal-xxx'],
presets: ['@babel/presets-env', {...}]
}
从支持的语言特性来看,babel 胜。
tsc 会在编译过程中进行类型检查,类型检查需要综合多个文件的类型信息,要对 AST 做类型推导,比较耗时,而 babel 不做类型检查,所以编译速度会快很多。
从编译速度来看, babel 胜。
总之,从编译产物大小(主要)、支持的语言特性、编译速度来看,babel 完胜。
但是,babel 不做类型检查,那怎么类型检查呢?
babel 可以编译生成更小的产物,有更快的编译速度和更多的特性支持,所以我们选择用 babel 编译 typescript 代码。但是类型检查也是需要的,可以在 npm scripts 中配一个命令:
{
"scripts": {
"typeCheck": "tsc --noEmit"
}
}
这样在需要进行类型检查的时候单独执行一下 npm run typeCheck 就行了,但最好在 git commit 的 hook 里(通过 husky 配置)再执行一次强制的类型检查。
typescript 给 js 扩展了静态类型的支持,使得代码能够在编译期间检查出赋值类型不匹配、调用了没有的方法等错误,保证类型安全。
除了 tsc 之外,babel7 也能编译 typescript 代码了,这是两个团队合作一年的结果。
但是 babel 因为单文件编译的特点,做不了和 tsc 的多文件类型编译一样的效果,有几个特性不支持(主要是 namespace 的跨文件合并、导出非 const 的值),不过影响不大,整体是可用的。
babel 做代码编译,还是需要用 tsc 来进行类型检查,单独执行 tsc --noEmit 即可。
babel 编译 ts 代码,相比 tsc 有很多优点:产物体积更小,支持更多特性,编译速度更快。所以用 babel 编译 typescript 是一个更好的选择。
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8