自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

編譯TS 代碼用TSC 還是Babel?

開發(fā) 前端
babel 和 tsc 的編譯流程大同小異,都有把源碼轉換成 AST 的 Parser,都會做語義分析(作用域分析)和 AST 的 transform,最后都會用 Generator(或者 Emitter)把 AST 打印成目標代碼并生成 sourcemap。

編譯 TypeScript 代碼用什么編譯器?

那還用說,肯定是 ts 自帶的 compiler 呀。

但其實 babel 也能編譯 ts 代碼,那用 babel 和 tsc 編譯 ts 代碼有什么區(qū)別呢?

我們分別來看一下:

tsc 的編譯流程

typescript compiler 的編譯流程是這樣的:

源碼要先用 Scanner 進行詞法分析,拆分成一個個不能細分的單詞,叫做 token。

然后用 Parser 進行語法分析,組裝成抽象語法樹(Abstract Syntax Tree)AST。

之后做語義分析,包括用 Binder 進行作用域分析,和有 Checker 做類型檢查。如果有類型的錯誤,就是在 Checker 這個階段報的。

如果有 Transformer 插件(tsc 支持 custom transform),會在 Checker 之后調用,可以對 AST 做各種增刪改。

類型檢查通過后就會用 Emmiter 把 AST 打印成目標代碼,生成類型聲明文件 d.ts,還有 sourcemap。

sourcemap 的作用是映射源碼和目標代碼的代碼位置,這樣調試的時候打斷點可以定位到相應的源碼,線上報錯的時候也能根據(jù) sourcemap 定位到源碼報錯的位置。

tsc 生成的 AST 可以用 astexplorer.net 可視化的查看:

生成的目標代碼和 d.ts 和報錯信息也可以用 ts playground 來直接查看:

大概了解了 tsc 的編譯流程,我們再來看下 babel 的:

babel 的編譯流程

babel 的編譯流程是這樣的:

源碼經(jīng)過 Parser 做詞法分析和語法分析,生成 token 和 AST。

AST 會做語義分析生成作用域信息,然后會調用 Transformer 進行 AST 的轉換。

最后會用 Generator 把 AST 打印成目標代碼并生成 sourcemap。

babel 的 AST 和 token 也可以用 astexplorer.net 可視化的查看:

如果想看到 tokens,需要點開設置,開啟 tokens:

而且 babel 也有 playground(babel 的叫 repl) 可以直接看編譯之后生成的代碼:

其實對比下 tsc 的編譯流程,區(qū)別并不大:

Parser 對應 tsc 的 Scanner 和 Parser,都是做詞法分析和語法分析,只不過 babel 沒有細分。

Transform 階段做語義分析和代碼轉換,對應 tsc 的 Binder 和 Transformer。只不過 babel 不會做類型檢查,沒有 Checker。

Generator 做目標代碼和 sourcemap 的生成,對應 tsc 的 Emitter。只不過因為沒有類型信息,不會生成 d.ts。

對比兩者的編譯流程,會發(fā)現(xiàn) babel 除了不會做類型檢查和生成類型聲明文件外,tsc 能做的事情,babel 都能做。

看起來好像是這樣的,但是 babel 和 tsc 實現(xiàn)這些功能是有區(qū)別的:

babel 和 tsc 的區(qū)別

拋開類型檢查和生成 d.ts 這倆 babel 不支持的功能不談,我們看下其他功能的對比:

分別對比下語法支持和代碼生成兩方面:

語法支持

tsc 默認支持最新的 es 規(guī)范的語法和一些還在草案階段的語法(比如 decorators),想支持新語法就要升級 tsc 的版本。

babel 是通過 @babel/preset-env 按照目標環(huán)境 targets 的配置自動引入需要用到的插件來支持標準語法,對于還在草案階段的語法需要單獨引入 @babel/proposal-xx 的插件來支持。

所以如果你只用標準語法,那用 tsc 或者 babel 都行,但是如果你想用一些草案階段的語法,tsc 可能很多都不支持,而 babel 卻可以引入 @babel/poposal-xx 的插件來支持。

從支持的語法特性上來說,babel 更多一些。

代碼生成

tsc 生成的代碼沒有做 polyfill 的處理,想做兼容處理就需要在入口引入下 core-js(polyfill 的實現(xiàn))。

import "core-js";
Promise.resolve;

babel 的 @babel/preset-env 可以根據(jù) targets 的配置來自動引入需要的插件,引入需要用到的 core-js 模塊,

引入方式可以通過 useBuiltIns 來配置:

entry 是在入口引入根據(jù) targets 過濾出的所有需要用的 core-js。

usage 則是每個模塊按照使用到了哪些來按需引入。

module.exports = {
presets: [
[
'@babel/preset-typescript',
'@babel/preset-env',
{
targets: '目標環(huán)境',
useBuiltIns: 'entry' // ‘usage
}
]
]
}

此外,babel 會注入一些 helper 代碼,可以通過 @babel/plugin-transform-runtime 插件抽離出來,從 @babel/runtime 包引入。

使用 transform-runtime 之前:

使用 transform-runtime 之后:

(transform runtime 顧名思義就是 transform to runtime,轉換成從 runtime 包引入 helper 代碼的方式)

所以一般babel 都會這么配:

module.exports = {
presets: [
[
'@babel/preset-typescript',
'@babel/preset-env',
{
targets: '目標環(huán)境',
useBuiltIns: 'usage' // ‘entry
}
]
],
plugins: [ '@babel/plugin-transform-runtime']
}

當然,這里不是講 babel 怎么配置,我們繞回主題,babel 和 tsc 生成代碼的區(qū)別:

tsc 生成的代碼沒有做 polyfill 的處理,需要全量引入 core-js,而 babel 則可以用 @babel/preset-env 根據(jù) targets 的配置來按需引入 core-js 的部分模塊,所以生成的代碼體積更小。

看起來用 babel 編譯 ts 代碼全是優(yōu)點?

也不全是,babel 有一些 ts 語法并不支持:

babel 不支持的 ts 語法

babel 是每個文件單獨編譯的,而 tsc 不是,tsc 是整個項目一起編譯,會處理類型聲明文件,會做跨文件的類型聲明合并,比如 namespace 和 interface 就可以跨文件合并。

所以 babel 編譯 ts 代碼有一些特性是沒法支持的:

const enum 不支持

enum 編譯之后是這樣的:

而 const enum 編譯之后是直接替換用到 enum 的地方為對應的值,是這樣的:

const enum 是在編譯期間把 enum 的引用替換成具體的值,需要解析類型信息,而 babel 并不會解析,所以它會把 const enum 轉成 enum 來處理:

namespace 部分支持:不支持 namespace 的合并,不支持導出非 const 的值

比如這樣一段 ts 代碼:

namespace Guang {
export const name = 'guang';
}
namespace Guang {
export const name2 = name;
}
console.log(Guang.name2);

按理說 Guang.name2 是 'dong',因為 ts 會自動合并同名 namespace。

ts 編譯之后的代碼是這樣的:

都掛到了 Guang 這個對象上,所以 name2 就能取到 name 的值。

而 babel 對每個 namespace 都是單獨處理,所以是這樣的:

因為不會做 namespace 的合并,所以 name 為 undefined。

還有 namespace 不支持導出非 const 的值。

ts 的 namespace 是可以導出非 const 的值的,后面可以修改:

但是 babel 并不支持:

原因也是因為不會做 namespace 的解析,而 namespace 是全局的,如果在另一個文件改了 namespace 導出的值,babel 并不能處理。所以不支持對 namespace 導出的值做修改。

除此以外,還有一些語法也不支持:

部分語法不支持

像 export = import = 這種過時的模塊語法并不支持:

開啟了 jsx 編譯之后,不能用尖括號的方式做類型斷言:

我們知道,ts 是可以做類型斷言來修改某個類型到某個類型的,用 as xx 或者尖括號的方式。

但是如果開啟了 jsx 編譯之后,尖括號的形式會和 jsx 的語法沖突,所以就不支持做類型斷言了:

tsc 都不支持,babel 當然也是一樣:

babel 不支持 ts 這些特性,那是否可以用 babel 編譯 ts 呢?

babel 還是 tsc?

babel 不支持 const enum(會作為 enum 處理),不支持 namespace 的跨文件合并,導出非 const 的值,不支持過時的 export = import = 的模塊語法。

這些其實影響并不大,只要代碼里沒用到這些語法,完全可以用 babel 來編譯 ts。

babel 編譯 ts 代碼的優(yōu)點是可以通過插件支持更多的語言特性,而且生成的代碼是按照 targets 的配置按需引入 core-js 的,而 tsc 沒做這方面的處理,只能全量引入。

而且 tsc 因為要做類型檢查所以是比較慢的,而 babel 不做類型檢查,編譯會快很多。

那用 babel 編譯,就不做類型檢查了么?

可以用 tsc --noEmit 來做類型檢查,加上 noEmit選項就不會生成代碼了。

如果你要生成 d.ts,也要單獨跑下 tsc 編譯。

總結

babel 和 tsc 的編譯流程大同小異,都有把源碼轉換成 AST 的 Parser,都會做語義分析(作用域分析)和 AST 的 transform,最后都會用 Generator(或者 Emitter)把 AST 打印成目標代碼并生成 sourcemap。

但是 babel 不做類型檢查,也不會生成 d.ts 文件。

tsc 支持最新的 es 標準特性和部分草案的特性(比如 decorator),而 babel 通過 @babel/preset-env 支持所有標準特性,也可以通過 @babel/proposal-xx 來支持各種非標準特性,支持的語言特性上 babel 更強一些。

tsc 沒有做 polyfill 的處理,需要全量引入 core-js,而 babel 的 @babel/preset-env 會根據(jù) targets 的配置按需引入 core-js,引入方式受 useBuiltIns 影響 (entry 是在入口引入 targets 需要的,usage 是每個模塊引入用到的)。

但是 babel 因為是每個文件單獨編譯的(tsc 是整個項目一起編譯),而且也不解析類型,所以 const enum,namespace 合并,namespace 導出非 const 值并不支持。而且過時的 export = 的模塊語法也不支持。

但這些影響不大,完全可以用 babel 編譯 ts 代碼來生成體積更小的代碼,不做類型檢查編譯速度也更快。

如果想做類型檢查可以單獨執(zhí)行 tsc --noEmit。

當然,文中只是討論了 tsc 和 babel 編譯 ts 代碼的區(qū)別,并沒有說最好用什么,具體用什么編譯 ts,大家可以根據(jù)場景自己選擇。

責任編輯:姜華 來源: 神光的編程秘籍
相關推薦

2021-12-01 19:32:14

原理Node代碼

2022-07-27 16:50:39

BabelTypeScript前端

2021-06-01 06:00:06

typescriptjavascript

2024-07-05 15:26:59

代碼Merge分支

2021-01-19 06:16:05

前端Babel 技術熱點

2022-05-06 08:26:21

babel編譯器

2022-02-25 14:04:56

TS前端代碼

2011-04-14 09:42:06

DataReaderDataSet

2021-12-09 17:21:48

TypeScript TS 前端

2013-10-15 10:24:23

hadoop大數(shù)據(jù)

2013-10-15 10:18:17

2022-05-22 21:16:46

TypeScriptOmit 工具

2017-02-20 13:54:14

Java代碼編譯

2022-12-27 09:22:06

Nest.js框架

2010-03-31 17:01:07

2011-09-05 10:30:51

重構代碼庫業(yè)務模型

2020-09-21 06:58:56

TS 代碼建議

2022-02-25 08:32:07

nodemon搭Node.jsJavascript

2018-10-31 14:00:05

LispJavaScript編程語言

2013-07-01 11:15:55

代碼產品
點贊
收藏

51CTO技術棧公眾號