Why TypeScript?

豆皮範兒發表於2021-09-07

本文經作者授權,翻譯總結自 TypeScript Team 的成員 orta 的個人部落格 《Understanding TypeScript's Popularity》。

原作者: orta

原文連結:https://orta.io/notes/js/why-typescript

翻譯:ycaptain

TypeScript 是一種非常受歡迎的 JavaScript 語言擴充套件。它在現有的 JavaScript 語法之上加入了一層型別層,而這一層即使被刪除,也絲毫不會影響執行時的原有表現。許多人認為 TypeScript "只是一個編譯器",但更好的理解其實是把 TypeScript 看作兩個獨立的系統:編譯器(即處理語法的部分)和語言工具(即處理與編輯器整合的部分)。通過獨立看待這兩個系統,就可以得到能夠解釋我們之前所做決策的兩個重要視角。

npm 上,TypeScript 的下載量每年都在翻倍。如今(部落格釋出於 2021 年 4 月 14 日),它的每週下載量約為 2000 萬次。而在去年 4 月,這一數字約為 1000 萬次。它仍保持著高速增長的趨勢,沒有任何放緩的跡象。

1. 我們是怎麼令它變得如此受歡迎的?

從 2.0 版本開始,TypeScript 每兩月定期釋出一個 release。但是現在我們放緩了釋出的節奏,改為每三個月釋出一次。其中我們會花一個月編寫新 features 併發布 beta 版本,剩下兩個月對 beta 版進行測試和 bug 修復,這使得後續的釋出更加穩定。

1.1 大事件時間線

在我看來,下面這些都是讓 TypeScript 不斷突破流行極限的大事件:

  • 2014 - 重寫 TypeScript,TS v1.1 - TypeScript 上線後,我們重新 review 了 TypeScript 的程式碼庫,想明白了它到底是什麼,因此我們拋棄了原有的程式碼,並以函式式風格重新編寫(區別於原來具有可變特性的類) - 這套架構一直沿用至今,它能夠在極少發生變更的程式中穩定高效地長時間執行。有人曾提到 TypeScript 的前身是 C++ 編寫的,這點我不能確定其真實性。

  • 2015 - Angular 採用 TypeScript,TS v1.5 - 當時 Google 正在考慮為 Angular 構建他們自己的語言,而不是選擇使用 TypeScript。為了讓 TypeScript 能夠被納入考慮,TypeScript 打破了它的其中一條基本規則:不要過早實現 TC39。就這樣,TypeScript 支援了 experimentalDecorators。即使這一特性六年後還沒有被新增到 JavaScript 中,對於所有參與開發的人來說,這個結果也是完全值得的他們承受這項技術債務的。

  • 2015 - 在 TypeScript 中支援 JSX, TS v1.6 - 這時 React 也成長為了一個非常受歡迎的 UI 庫。而 React 使用 JSX: 一種可以在 JavaScript 中高效地編寫 HTML 的 JS 語言擴充套件。TypeScript 對 JSX 的支援使得其他人可以進行 TypeScript 對 React 的支援(由 @types/react 維護,而不是內建於 TypeScript 中)

  • 2016 - 未定義型別,基於控制流進行靜態型別分析 - TS v2.0 - 在 1.4 版本釋出的功能 union types 的基礎上,TypeScript 支援了 undefined 和 null 型別。這使得型別系統可以真正對大部分 JavaScript 程式碼建模。與此同時,程式碼控制流分析使得 if 語句及其它的使用者程式碼都能影響到變數在不同位置的型別分析結果。

  • 2016 - 擁抱 DefinitedTyped, TS v2.0 - DefinitelyTyped 是一個由志願者們編寫的業餘專案。當時有一些其它類似 DefinitelyTyped 的系統,TypeScript 團隊採用了 DefinitelyTyped 並將 @types/x 的概念融入他們的編譯器當中。在採用和維護 DefinitelyTyped 的過程中,TypeScript 團隊進行了認真的測試並改進了工作流,這幫助它成為了 GitHub 上最活躍的 Repo 之一。關於 DT 的故事值得一讀。

  • 2016 - 支援 JavaScript, TS v2.3 - 儘管當時已經有一些語言工具鏈支援 JavaScript 專案,對 JSDoc 的支援使得 JavaScript 專案即使不使用 TypeScript 也能獲得它的一些好處。這不僅開啟了 JavaScript 向 TypeScript 遷移之路,還為已存在的 JavaScript 專案提供了工具支援。

  • 2018 - 在 Babel 中增加 TypeScript 支援, Babel 7 - 這是 TypeScript 成為程式碼庫歸宿的起點。在 Babel 中增加 TypeScript 支援給 TypeScript 增加了一些約束(譯者注:isolated modules),但這是值得的。從此 TypeScript 不再需要從 eslint 遷移到 tslint 的複雜的遷移過程,而是像勾選一個核取方塊一樣輕鬆。

  • 2018 - Composite Projects, TS v3.0 - 有許多方式可以處理大規模原始碼庫,TypeScript 的處理方式就是 Composite Projects。通過使用 .d.ts 檔案作為專案的邊界,你可以在一個單體程式碼庫內維護很多 TypeScript 子專案。這節省了時間和記憶體,最重要的是這使得你可以將其擴充套件成非常大的程式碼庫。

  • 2019 - Optional Chaining, TS v3.7 - 雖然這個列表中還遺漏了一些更大的語言特性,但是與 TC39 結合的完美特性 Optional Chaining 使得人們對 TypeScript 支援非常興奮。作為 JS 生態系統和工具的優秀參與者,將可選鏈特性加入 JavaScript 的過程是一個 TypeScript 對於自身預期定位的完美例子。TypeScript 應該多做這類專案。

  • 2020 - esbuild / swc / sucrase - 這些新的編譯器和 JavaScript 執行時從第一個版本就支援 TypeScript 語法,任何基於這些工具的專案都可以直接使用 TypeScript。TypeScript 語法在 .ts 檔案中直接啟用進一步使得它作為 JavaScript 的內建擴充套件合法化。

  • 2020 - 重寫使用者文件 - 重寫文件是我的工作,所以不要完全相信這部分描述,但是這些年 TypeScript 的文件一直很薄弱。我與很多編譯器團隊裡的老人一起重新編寫面向使用者的文件,併為他們提供一個幫助理解 TypeScript 的線上編輯器。這是對於回答語言問題的第一步,優秀的文件可以讓編譯器團隊將注意力集中在編譯器上。

我的看法:TypeScript 之所以流行,是因為它不斷通過工具(DT / -- isolatedModules / JSDoc)降低入門門檻,與其它工具和組織合作(Babel / TC39),並通過漸進式的方式展示工具和型別安全在 JavaScript 中的價值。如今,那些寫 JSDoc 的人實際上就是 TypeScript 使用者,就像使用 --strict 的人一樣。

1.2 TypeScript 的競爭者有哪些?

TypeScript 的目標是為人們提供編寫大型 JavaScript 專案並對後期維護有信心的工具。JavaScript 本身沒有的語法支援表示每個識別符號的型別,除非執行 JavaScript 並在執行時進行檢測。為了解決這個問題,TypeScript 新增了額外的語法。

所以,如果說我們的目標是作為工具提供支援,那麼在這個領域有少數幾個競爭者是 TypeScript 無法與之競爭的:

  • ESLint 和 TSLint:與 TypeScript 的定位相同,它們都是用來突出程式碼中可能出現的錯誤,只是沒有為檢查過程新增新的語法。兩者都不打算作為 IDE 整合的工具執行,而且 TS 和 TS/ESLint 經常會說那些對專案沒有意義的特性“是對方的領域”。在現代程式碼中,TS/ESLint 的存在使得 TypeScript 可以做更少的檢查,這些檢查並不適用於所有程式碼庫。雖然有一些功能重疊了,但可以把它們作為很好的補充工具。

  • CoffeeScript:嘿,TypeScript 是 2012 年釋出的!CoffeeScript 和 TypeScript 的區別在於 CoffeeScript 想要改進 JavaScript 語言,比如給 JavaScript 新增一些特性。這意味著要了解 CoffeeScript 與其輸出的 JavaScript 的區別。隨著時間推移,CoffeeScript 的最佳理念反而將其變成了另一個 JavaScript,人們為幾乎成為了 JavaScript 的 CoffeeScript 感到困擾。

  • Flow:這是 Facebook 的 JavaScript 型別檢查工具和 IDE 工具語言。就像 TypeScript 一樣,Flow 為 JavaScript 新增了一些額外的語法支援,讓你擁有了一個更加豐富的型別系統,然後在編譯時再將其刪除。當我剛開始寫 JavaScript 時,Flow 是我最先使用的工具,因為它更接近標準的 JavaScript。Flow 是一個很棒的型別系統,它與 TypeScript 有著不同的目標。任何看不見的型別層系統都必須不斷做出“正確”或者“感覺足夠正確”的決定,Flow 的目標是“正確”(譯者注: Flow 偏向於 soundness,在型別判斷中更加悲觀),而 TypeScript 的目標是“感覺上大部分情況都是正確的”(譯者注:而 TS 官方聲稱 TS 不是型別完備的,允許 unsound 行為,偏向於 completeness,在型別判斷中更加樂觀)。魚和熊掌不可兼得,完備的型別推導、良好的開發體驗和完美的 JS 協同(Perfect JavaScript Interop)只能取其二。

那麼,為什麼大多數開源 Flow 程式碼庫最終都遷移到了 TypeScript 呢?在我看來,很大程度上是由兩個團隊不同的側重點決定。Flow 是為了維護 Facebook 的程式碼庫而建立的,而 TypeScript 是作為一種獨立的語言建立的。這裡有兩個證據可以證明:

  1. Facebook 的程式碼庫是一個不能被分割的巨大的 monorepo,而 Flow 團隊為了使型別執行在這樣的大程式碼庫下做了大量令人難以置信的工作。另一方面,TypeScript 可以說是“為構建小程式碼庫服務(use projects to make sets of smaller codebases)”,因為這符合人們在開源社群中編寫 JavaScript 模組的方式。我認為這麼說很合理,TypeScript 不能像 Flow 一樣執行在 Facebook 的程式碼庫上,它要麼需要大量重寫 Facebook 的程式碼來構建專案,要麼需要對 TypeScript 進行大量修改,這可能會影響到 TypeScript 整體開發者的體驗。

  2. 對比 DefinitelyTyped 和 Flow 對型別的做法,TypeScript 團隊會輪值一名編譯器工程師為 DefinitelyTyped 支援我們的構建工具,並幫助管理社群。而 Flow,它幾乎完全由社群維護。DT 現在規模更大了,因為它們一直致力於非 Facebook 程式碼的開發,這將很難獲得 Flow 團隊的資金支援。

微軟給 TypeScript 在內部創造的獨立環境讓它可以自由專注於工具開發和整個生態系統的維護,而不是隻專注於解決某個特別困難的問題。這讓 TypeScript 團隊能夠與許多人合作,不斷髮布社群想要的功能。隨著時間的推移,我猜想因為外部的需求增長放緩,Flow 團隊越來越難為社群工作分配時間。這就形成了一個惡性迴圈。這使得 Flow 今天不再是 TypeScript 的直接“競爭者”,而是一個關於如何從不同的角度,使用不同的約束去解決類似的問題的有趣視角。

2. 未來

2.1 對 TypeScript 的未來怎麼看?

目前阻礙人們使用 TypeScript 的最大障礙是它需要構建工具。我認為型別語法不太可能被加入 JavaScript 中,但是在 JavaScript 中“用註釋的方式定義型別”的可能性非常大。

這個想法是為 TypeScript 這樣的型別系統建立一套語法,但是不定義 JS 執行時會發生什麼。

const a: string = "1234"

// 將會變成這樣
const a/_: string _/ = "1234"

// 傳入 JS 引擎

在這個例子中,JS 引擎會知道 : string 是一個型別註釋,在 = 處結尾。這實際的工作方式是複雜的,需要時間來弄清楚。然而,讓 TypeScript 能在 JavaScript 中“原生地”執行將降低它被使用的障礙。它會像 Babel 新增 TypeScript 支援時一樣對 TypeScript 施加一些約束。但我覺得這是值得的。

Deno 是一個消除所有 TS 障礙的關鍵例子,它通過執行一個 Rust 編寫的工具,能夠非常快速地將 TS 編譯到 JS,模擬了當前 JavaScript 引擎對原生 TypeScript 的支援。

2.2 如今的競爭者

  • JetBrins WebStorm - 這是一個有高階 JavaScript 工具支援的 IDE。他們有自己的引擎用於重構、程式碼流分析並對 JavaScript 語法進行檢查。這很好,JetBrains 在他們所有的 IDE 上都做了紮實的工作。我過去經常使用 AppCode 處理 iOS 的工作。當你有一個 TypeScript 專案時,WebStorm 會將 TypeScript 的語言工具和自己的工具混合在一起,這對你來說是雙贏的。

  • 編譯到 JS 的語言 - 目前的例子有 Elm,ReScript,KotlinScript,這些語言的核心目標是與 JavaScript 互動。對於 TypeScript 來說,這些都是很有趣的語言,它們有一個乾淨的環境來實現型別系統 —— 也就是,沒有 JS 包袱。作為競爭對手,它們傾向於更細分的市場,因為它們的核心不是 JavaScript ,並且社群也被從 CoffeeScript 遷移所困擾過。

  • WASM - 我聽到 WASM 作為 TypeScript 競爭者的觀點是,WASM 可以作為語言取代 JS 控制瀏覽器 DOM。反對這一觀點的人普遍認為,WASM 沒有 DOM 繫結,而且可能永遠不會有。TypeScript 包含了 JavaScript 的缺點,如果你在 JavaScript 執行時中加入過 WASM 的話,你幾乎總是會更加喜歡它。也就是說,AssemblyScript 在這方面做了一些很好的工作。也許把 WASM 想成 JSON 會更好,它是另一個組成專案的工具,不太可能成為 JavaScript 的競爭者,除非 WASM 和 DOM 的互動方式有所改變。

  • 編譯到 WASM 的語言 - 比如 Rust,Go,Swift,等其它可以編譯到 WASM 的語言。這些語言都可能佔據 TypeScript 目前作為工具和 web 核心構建模組的位置,不過世事難料,誰知道會怎麼樣呢?這些語言能夠提供各種不同的基本型別,並且基於不同的目標從頭構建。如果 WASM 和 WASI 最終獲得成功,那麼我認為將會與平臺相關(想想 apps 等功能實現),看看它們的發展方向會很有趣。說心裡話,它們不會是 TypeScript 的競爭者,而是 JavaScript 的。

2.3 TypeScript 怎麼看它在生態中的位置?

TypeScript 希望在型別系統和編輯器工具領域進行創新。我們擁有在主流程式語言中表達能力最強的型別系統之一。

TypeScript 最初被建立時,對 JavaScript 進行修改的流程和現在非常不同,所以 TypeScript 中有一些特性實際上是 TC39 的領域,但仍然需要向後相容。這些特性可能在 JavaScript 中存在很多年,並且經過了多次迭代,這意味著 TypeScript 必須維護一個特定語言特性的兩種版本。

所以我們的目標是成為 TC39 JavaScript 語言委員會的優秀成員,就編輯器支援的語言特性進行反饋,支援 TypeScript 使用者想要看到的特性。通過這種協作方式,TC39 控制了 JavaScript,TypeScript 也支援他們。

2.4 TypeScript 怎麼看它的受眾?

TypeScript 的受眾主要有:

  • JavaScript 使用者(作為語言工具)
  • JS + JSDoc 使用者(作為語言工具)
  • TypeScript 使用者(作為編譯器,語言工具)
  • TypeScript 嚴格模式(作為編譯器,語言工具)

雖然專案使用 babel / swc / sucrase / esbuild 等工具構建時,tsc 是可選的,但是上面的幾種受眾仍然可以在每次或至少每兩次 TS 版本釋出中獲得新特性(譯者注:babel、esbuild 等會更新支援 TS 新特性。可能是 TS 團隊直接去這些專案裡做,也可能會在沒有 tsc 的情況下為這些專案提供特性,比如通過 vscode。在 TS roadmap 中可以瞭解更多釋出計劃)。

2.5 TypeScript 是如何跟蹤 JS 生態的?

團隊從以下幾個方式聽取反饋:

  • GitHub issues 有持續不斷的評論洪流
  • 微軟內部團隊要求提供特性,或者要求我們幫忙除錯他們緩慢的程式碼庫
  • 通過 Gitter 或者 TypeScript 社群的 Discord 與社群建立聯絡
  • 通過微團的內部工具對想法 / 設計進行使用者測試
  • 與 VS Code 有著非常緊密的聯絡,許多語言工具的反饋都來自於他們
  • 我們會閱讀每一條 @ TypeScript 團隊的推特
  • 我們會跟蹤遷移到 TypeScript 和從 TypeScript 遷走的部落格文章
  • 我們會跟蹤行業調查和程式語言概述

相關文章