感謝 Babel 的 TypeScript 外掛(原文地址:TypeScript With Babel: A Beautiful Marriage 作者: Matt Turnbull, Feb 12,2019
@babel/preset-typescript
),TypeScript 從未變得如此簡單,這是 TypeScript 和 Babel 團隊長達一年的官方合作成果。本文列舉出了4條理由來證明 TypeScript 和 Babel 是完美的一對,以及10分鐘內升級到 TypeScript 的步驟指南。
哈?什麼?為什麼?
我一開始並不理解這個新 preset 要解決的需求。
Babel 和 TypeScript 難道不是2種完全不同的東西嗎?Babel 是如何處理 TypeScript 的型別檢查的?TypeScript 已經可以像 Babel 那樣輸出 ES5 了,所以目的是啥?合併 Babe 和 TypeScript 會不會讓事情變得更復雜?
經過 1 個小時的研究,我的結論是: TypeScript 和 Babel 的結合是一場美麗的婚姻。
Let me show you.
1)已經使用了 Babel(或者應該使用)
您一定屬於以下三類情況之一:
- 已經在使用 Babel,如果不是直接地使用,Webpack 也會將
*.js
檔案提供給 Babel (很多框架模板就屬於這類情況,包括 creat-react-app)。 - 使用 TypeScript 但不使用 Babel。請考慮向專案的工具庫中新增 Babel,它會帶來很多獨一無二的功能,請繼續讀下去。
- 壓根不使用 Babel? 那麼現在是時候跳上 Babel 這條船了。
在不會跳出任何錯誤的情況下編寫現代 JavaScript
需要在老舊瀏覽器中執行 JavaScript 程式碼?沒問題,Babel 會將程式碼轉換,並且搞定所有問題。直接使用最新和最棒的特性,而且不用擔心任何事情。
TypeScript 的編譯器也有相似的功能,是通過設定 target
的值為 ES5
或 ES6
來達到。但是 Babel 利用 babel-preset-env 來改善這個功能,而不是鎖定一組特定的 JavaScript 功能(ES5,ES6 等等),只需要列出所需要支援的環境即可:
"targets": {
"browsers": ["last 2 versions", "safari >= 7"],
"node": "6.10"
}
複製程式碼
Babel 使用 compat-table 來檢查需要轉換的 JavaScript 特性,以及指定的目標環境所需要的 polyfill。
creat-react-app
使用過的一種有意思的技術:在開發過程中,按照最新的瀏覽器進行編譯(為了加快編譯速度),而在產品釋出階段按照多種瀏覽器進行編譯(為了相容性),Nice~
Babel 是超級可配置的
想要 JSX?Flow?TypeScript?只要安裝外掛,Babel 就能處理它們了。這裡有相當多的官方外掛可供選擇,涵蓋了大多數即將推出的 JavaScript 語法,也有非常多的第三方外掛:改進 lodash import,加強版 console.log,或者 清理 console.log。可以在 awesome-babel 的列表中找到更多外掛。
但是請小心,若外掛明顯地改變了語法,TypeScript 可能會不能解析它。例如,有一個 Babel 外掛:@babel/plugin-proposal-optional-chaining,能夠實現備受期待的可選鏈提案 。
const obj = {
foo: {
bar: {
baz: 42
}
}
};
const baz = obj?.foo?.bar?.baz; // 42
const safe = obj?.qux?.baz; // undefined
複製程式碼
遺憾的是,TypeScript 無法理解這個即將推出的語法。
但不要有壓力,還有替代方案...
Babel Macros
不知道您是否聽說過 Kent C Dodds,他創造了一個改變 Babel 遊戲規則的外掛:babel-plugin-macros。
它由直接向 Babel config 檔案中新增外掛,改為把巨集指令(macro)作為依賴安裝並且 import 到程式碼中。當 Babel 正在編譯的時候,巨集指令開始起作用,並且修改程式碼。
舉個例子,在可選鏈特性正式加入到標準之前,我們可以先使用 idx.macro 來達到相同的效果。
import idx from 'idx.macro';
const friends = idx(
props,
_ => _.user.friends[0].friends
);
複製程式碼
編譯後:
const friends =
props.user == null ? props.user :
props.user.friends == null ? props.user.friends :
props.user.friends[0] == null ? props.user.friends[0] :
props.user.friends[0].friends
複製程式碼
Babel Macro 是相當的新的概念,但是很快受到了熱捧。尤其是登陸到 create-react-app v2.0,JS 中的 CSS 包括 styled-jsx,styled-components,和 emotion。正在移植 Webpack 外掛:raw-loader,url-loader和 filesize-loader。 還有更多列在 awesome-babel-macros 上。
最棒的是,不同於 Babel 外掛,所有的 Babel Macro 都和 TypeScirpt 相容。它們仍可以幫助減少執行時的依賴,避免客戶端計算,並且在構建時提前捕獲異常。檢視這篇帖子獲取瞭解詳情。
上圖演示了一個更好的 console.log: scope.macro2)只管理一個編譯器更輕鬆
TypeScirpt 需要它自己的編譯器——因為它提供了超棒的型別檢查功能。
在灰暗的日子裡(Babel 7之前)
將兩個獨立的編譯器(TypeScript 和 Babel)串聯在一起是可不是一件容易的工作。編譯流程變成:TS > TS 編譯器 > JS > Babel > JS (再次)
。
Webpack 經常用於解決這個問題,調整 Webpack 的配置。將 *.ts
提供給 TypeScript,然後將執行的結果提供給 Babel。但是用哪個 TypeScript loader 呢?2個非常流行的選擇是:ts-loader 和 awesome-typescript-loader。awesome-typescript-loader的README.md
中提到,在某些工作量中它可能會比較慢,並且建議使用 ts-loader 配合 HappyPack 或者 thread-loader。ts-loader 的README.md
檔案推薦結合使用 fork-ts-checker-webpack-plugin、HappyPack、thread-loader 以及(或者)cache-loader。
夠了。。這就是吞沒大多數同學的地方,也是大家還給 TypeScript 貼上“太難”標籤的原因之一。這不怪大家。
光明降臨的日子(Babel 7)
只有一個 JavaScript 編譯器難道不好嗎?無論程式碼是否具有 ES2015 特性,JSX,TypeScript,還是其他瘋狂的自定義————編譯器都知道要做什麼。
我剛剛只是在描述Babel。
通過允許 Babel 作為唯一的編譯器來工作,就再也沒必要利用一些複雜的 Webpack 魔法來管理、配置或者合併兩個編譯器。
它還精簡了整個 JavaScript 生態系統。取代了 ESLint、測試 runner、build 系統,以及開發模板提供的不同的編譯器,它們只需要支援 Babel 即可。然後配置 Babel 來處理具體的需求。向 ts-loader、ts-jest、ts-karma、create-react-app-typescript 等等說再見就好啦,使用 Babel 代替它們。Babel 的支援無處不在,檢視 Babel setup 頁面:
3)更快地編譯
Babel 是如何處理 TypeScript 的?它移除了 TypeScript。
是的,它刪除了所有 TypeScript ,將其轉換為“常規” JavaScript,並繼續使用它高興的方式。
聽起來挺荒唐的,但是這種實現具有兩個非常強大的優勢。
第一個優勢:⚡️快如閃電⚡️
大多數的 TypeScript 開發者在 devlement/watch 模式中,會遇到非常緩慢的編譯速度。你現在正在敲程式碼,儲存檔案,接下來是很漫長的等待。。。終於,你看到了你更改的內容。啊哦,不小心寫錯字了,修改,儲存,然後。。。夠了!它不僅慢得令人煩躁,還打擊了你程式設計的動力。
我們也不能去責怪 TypeScript 編譯器,它在做的工作實在太多了。它在掃描型別定義檔案(*.d.ts
),包括node_modules
裡的,以確保你的程式碼里正確地使用。這就是為什麼很多人將 TypeScript 型別檢查分為一個獨立的程式。然而,Babel + TypeScript 的組合套餐依舊會提供更快的編譯,這要歸功於 Babel 的優秀的快取和單檔案散發架構。
因此,如果 Babel 剝離了 TypeScript 的程式碼,那麼編寫 TypeScript 的意義何在呢?這帶來了第二個優勢。。。
4)只在當你準備好的時候檢查型別錯誤
現在你正在開心地程式設計,不假思索地提出解決方案來驗證你的想法是否奏效。你儲存檔案,TypeScript 卻對你大喊:
“不!我不會編譯這玩意兒的!你的程式碼在42個不同的檔案中出現異常!”
對,你知道它有異常。可能在幾個單元測試中已經出現異常了。但是你可能只是想在這裡做一個實驗。總是持續不斷地進行型別安全性檢查有時候是挺煩人的。
這就是 Babel 在編譯過程中剝離 TypeScript 的第二個優勢。編寫,儲存,然後它會快速編譯(速度很快)而不需要進行型別安全性檢查。這樣就可以在你準備好檢查程式碼錯誤之前,盡情地去對解決方案做實驗。
那如何去檢查型別錯誤呢?新增一段 npm run check-types
指令碼來喚起 TypeScript 編譯器。我將我的 npm test
命令調整為先檢查型別,然後再繼續執行單元測試。
這不是一段完美的婚姻
根據專案公告可以瞭解到,由於 Babel 的單檔案發射架構,有四種TypeScript 特性無法在 Babel 中編譯。
不過不用擔心,還不算太糟糕。而且當啟用 isolatedModules
的配置選項時,TypeScript 將對這些問題做出警告。
1)Namespace
解決方案:不要用!它們已經是過時的了。改用標準的 ES6 module(import
/export
),在推薦的 tslint 規則中也建議不要使用 namesapce。
2)使用 <newtype>x
語法轉換型別
解決方案:改用 x as newtype
。
3)const 列舉
這個鍋沒得甩,目前只能用常規的列舉,期待未來能夠支援。
4)歷史遺留風格的 import/export 語法
比如:import foo = require(...)
和 export = foo
。
我寫 TypeScript 這麼多年,就從來沒這麼寫過。誰是這麼寫的?可別再這麼幹了!
OK, 準備好嘗試一下使用 Babel 來寫 TypeScript 了
開始搞起!僅僅需要10分鐘。
假定你已經安裝了 Babel 7,如果沒有,請檢視 Babel 遷移指南。
1)將 .js 重新命名為 .ts
假設檔案儲存在 /src
目錄下:
find src -name "*.js" -exec sh -c 'mv "$0" "${0%.js}.ts"' {} ;
複製程式碼
2)向 Babel 新增 TypeScript
安裝幾個依賴:
npm install --save-dev @babel/preset-typescript @babel/plugin-proposal-class-properties @babel/plugin-proposal-object-rest-spread
複製程式碼
Babel 配置檔案(.babelrc
或 babel.config.js
):
{
"presets": [
"@babel/typescript"
],
"plugins": [
"@babel/proposal-class-properties",
"@babel/proposal-object-rest-spread"
]
}
複製程式碼
TypeScript 有幾個 Babel 需要了解的額外特性(通過上面列出的2個外掛)。
Babel 預設查詢 .js 檔案,遺憾的是,你還沒辦法在 Babel 的 config 檔案中進行配置。
如果使用 Babel CLI,新增 --extensions '.ts'
。
如果使用 Webpack,向 resolve.extensions
陣列中新增 'ts'
。
3)新增 'check-type' 命令
在 package.json
中新增:
"scripts": {
"check-types": "tsc"
}
複製程式碼
這條命令只是簡單地喚起 TypeScript 編譯器(tsc
)。
通過安裝 TypeScript 來獲取 tsc
:
npm install --save-dev typescript
複製程式碼
在根目錄裡新增 tsconfig.json
檔案來配置 TypeScript 和 tsc
:
{
"compilerOptions": {
// Target latest version of ECMAScript.
"target": "esnext",
// Search under node_modules for non-relative imports.
"moduleResolution": "node",
// Process & infer types from .js files.
"allowJs": true,
// Don't emit; allow Babel to transform files.
"noEmit": true,
// Enable strictest settings like strictNullChecks & noImplicitAny.
"strict": true,
// Disallow features that require cross-file information for emit.
"isolatedModules": true,
// Import non-ES modules as default imports.
"esModuleInterop": true
},
"include": [
"src"
]
}
複製程式碼
完成
OK,配置完成。現在執行 npm run check-types
(監聽模式:npm run check-types -- --watch
),確保 TypeScript 程式碼正常執行。你可能會發現一些未知但確實存在的錯誤,這未必是件壞事,這篇Javascript 遷移指南可以提供一些幫助。
微軟的 TypeScript-Babel-Starter 包含其他的設定說明,包括從零安裝 Babel,生成型別定義(d.ts)檔案,以及將其與 React 一起使用。
程式碼檢查工具咋弄?
使用 tslint
於2019年2月更新:使用 ESLint !TypeScript 團隊自從1月份開始就在專注於 ESLint 整合。歸功於 @typescript-eslint 專案,配置 ESLint 變的非常簡單。如需靈感,請檢視我的 終極ESLint配置,其中包括 TypeScript,Airbnb,Prettier和 React。
Babel + TypeScript = 美麗的婚姻
Babel是唯一需要的 JavaScript 編譯器,它可以通過配置來處理任何事情。
沒有必要讓兩個 JavaScript 編譯器競爭,簡化專案配置,並充分利用 Babel 與 ESLint,單元測試,構建系統和專案模板整合在一起的優勢。
Babel 和 TypeScript 的組合可以快速編譯,並允許在編碼時留在免打擾的空間裡,並且只有在準備好的時候才檢查型別。
預言: TypeScript 將會崛起
根據最新的 Stack Overflow 開發者調查,JavaScript 是最流行的語言,TypeScript 落後於第12名。對於TypeScript來說,這仍然是一項偉大的成就,因為它擊敗了 Ruby,Swift 和 Go。
我預測 TypeScript 將在明年進入前10名。
TypeScript 團隊正在努力和其他團隊合作。這個 Babel preset 是為期一年的合作成果,他們的新焦點是改進ESLint整合。這是一個聰明的舉措——利用現有工具的功能,社群和外掛。開發編譯器和程式碼檢查器去和別人競爭簡直是浪費精力。
只需調整我們喜愛的工具的配置即可鋪設進入 TypeScript 的路徑。TypeScript 入口的障礙已被掃清。
隨著 VS Code 的普及,開發人員已經設定了一個非常棒的 TypeScript 環境。
它現在也整合到 create-react-app v2.0 中,將 TypeScript 以每月20萬次下載量的規模提供給使用者。
如果你遲遲不願接觸 TypeScript,因為它很難設定,它不再是一個藉口,現在是時候去試一試了。