<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          大規(guī)模采用 TypeScript 之后的 10 個(gè)見(jiàn)解

          共 11910字,需瀏覽 24分鐘

           ·

          2020-12-27 03:08



          幾年前,彭博社工程部決定采用 TypeScript 作為首選開(kāi)發(fā)語(yǔ)言。在這篇文章中,將分享我們?cè)谶@次遷移過(guò)程中學(xué)到的經(jīng)驗(yàn)教訓(xùn)以及一些見(jiàn)解。

          總體而言,我們認(rèn)為 TypeScript 是個(gè)完全正向的升級(jí)。當(dāng)你讀到那些我們發(fā)現(xiàn)的困擾時(shí),請(qǐng)記住這一點(diǎn)。作為工程師,我們天然的會(huì)對(duì)發(fā)現(xiàn)、解決和分享問(wèn)題給吸引,即使在娛樂(lè)的時(shí)候。??

          背景

          在 TypeScript 問(wèn)世以前,彭博社就已經(jīng)對(duì) JavaScript 有著巨量的開(kāi)發(fā)投入—— 5000 萬(wàn)行以上的 JS 代碼。我們的主要產(chǎn)品是包含了一萬(wàn)多個(gè)應(yīng)用的彭博社終端。這些應(yīng)用的種類差異性巨大,從顯示密集的實(shí)時(shí)財(cái)務(wù)數(shù)據(jù)和新聞,到交互式交易解決方案以及多種格式的消息傳遞等。早在 2005 年,公司就開(kāi)始將這些應(yīng)用的實(shí)現(xiàn)方式從 Fortran 和 C/C++ 遷移至基于服務(wù)器端的 JavaScript,而在 2012 年左右,已經(jīng)遷移為基于客戶端 JavaScript 的版本。

          將這樣大規(guī)模的純 JavaScript 代碼轉(zhuǎn)換為 TypeScript 是一個(gè)很大的工程。我們下了很大的功夫,確保在遷移時(shí)有一個(gè)穩(wěn)妥的過(guò)程 —— 既遵循代碼標(biāo)準(zhǔn)又能保證我們既有的功能可以快速安全的轉(zhuǎn)化和部署。

          如果你在一個(gè)大公司經(jīng)歷過(guò)技術(shù)遷移,那你一定對(duì)那種用嚴(yán)格的項(xiàng)目管理來(lái)迫使技術(shù)團(tuán)隊(duì)向前推進(jìn)的做法不會(huì)感到陌生,因?yàn)橥@些團(tuán)隊(duì)寧可去開(kāi)發(fā)新的功能也不愿意來(lái)做這件事。但是我們發(fā)現(xiàn),TypeScript 的遷移過(guò)程卻完全不同。工程師們會(huì)自發(fā)的進(jìn)行代碼轉(zhuǎn)換,并且非常的支持這個(gè)過(guò)程!當(dāng)我們發(fā)布 beta 版的 TypeScript 平臺(tái)支持時(shí),僅第一年就有超過(guò) 200 個(gè)項(xiàng)目選擇切換到了 TypeScript,并且沒(méi)有一個(gè)回頭。

          是什么讓這次 TypeScript 實(shí)踐如此特別呢?

          除了規(guī)模之外,這次集成 TypeScript 的特別之處在于,我們有自己的 JavaScript 運(yùn)行時(shí)環(huán)境。也就是說(shuō),除了像瀏覽器和 Node 這類眾所周知的 JavaScript 運(yùn)行環(huán)境外,我們也直接嵌入了 V8 引擎以及 Chromium 內(nèi)核來(lái)建造自己的 JavaScript 平臺(tái)。這樣帶來(lái)的好處就是,我們的平臺(tái)和軟件包生態(tài)原生支持 TypeScript,這使得我們可以向開(kāi)發(fā)者提供一個(gè)簡(jiǎn)單的開(kāi)發(fā)體驗(yàn)。Ryan Dahl 的 Deno 通過(guò)將 TypeScript 編譯到運(yùn)行時(shí)中來(lái)實(shí)現(xiàn)相似的目標(biāo),而我們將其保留為獨(dú)立于運(yùn)行時(shí)外的可以進(jìn)行版本控制的工具。一個(gè)有趣的結(jié)論是,我們開(kāi)始探索在跨客戶端和服務(wù)端且不滿足 Node 使用規(guī)則(例如,沒(méi)有 node_modules 目錄)的獨(dú)立 JS 環(huán)境中使用 TypeScript 編譯器將會(huì)如何。

          我們的平臺(tái)支持使用通用的加工和發(fā)布系統(tǒng)的內(nèi)部的軟件包生態(tài)系統(tǒng)。這使我們可以促進(jìn)和實(shí)施最佳的開(kāi)發(fā)實(shí)踐,比如默認(rèn)使用 TypeScript 的“嚴(yán)格模式”,來(lái)保障全局的不變量。例如,我們保證了所有發(fā)布的類型是模塊化的,而不是全局性的。同時(shí)也代表著,工程師們可以專注于編寫代碼,而不是去花精力解決如何讓 TypeScript 去兼容某個(gè)打包器或者測(cè)試框架。開(kāi)發(fā)者工具和錯(cuò)誤堆棧可以正確的使用 sourcemaps。測(cè)試也可以用 TypeScript 編寫,并依據(jù)原始 TypeScript 代碼準(zhǔn)確的展示代碼覆蓋率。就是這么好用。

          我們的目標(biāo)是讓常規(guī)的 TypeScript 文件成為我們 API 實(shí)質(zhì)上的唯一來(lái)源,從而不需要手動(dòng)維護(hù)聲明文件。這意味著我們有大量的代碼非常依賴于 TypeScript 編譯器從源碼中自動(dòng)生成的 .d.ts 聲明文件。如你所見(jiàn),當(dāng)聲明文件沒(méi)有像預(yù)想的一樣產(chǎn)生時(shí),我們會(huì)立刻發(fā)現(xiàn)它。

          原則

          我來(lái)列出我們追求的三條關(guān)鍵原則:

          • 可擴(kuò)展性:隨著越多的包采用 TypeScript,項(xiàng)目的開(kāi)發(fā)速度應(yīng)該越來(lái)越快。花費(fèi)在安裝、編譯和檢查代碼上的時(shí)間應(yīng)該最小化。

          • 系統(tǒng)一致性:軟件包工作時(shí)需要互相兼容;升級(jí)依賴需要可以無(wú)痛進(jìn)行。

          • 遵循標(biāo)準(zhǔn):我們堅(jiān)持和標(biāo)準(zhǔn)保持一致,例如 ECMAScript,隨時(shí)準(zhǔn)備好接受他們的下一個(gè)版本。

          一些讓我們感到驚訝的發(fā)現(xiàn),通常都是來(lái)自于一些我們不確定是否能夠維持這些原則的案例。

          10 個(gè)重點(diǎn)

          1. TypeScript 可以是 JavaScript + Types

          多年以來(lái),TypeScript 團(tuán)隊(duì)一直積極的追求采用和兼容標(biāo)準(zhǔn)的 ECMAScript 語(yǔ)法和運(yùn)行時(shí)語(yǔ)義。這使得 TypeScript 專注于在 JavaScript 之上提供一層定義類型的語(yǔ)法和檢查類型的語(yǔ)義。代碼的職能被清晰的劃分開(kāi)來(lái):TypeScript = JavaScript + Types!

          這是一個(gè)極好的模型。這意味著編譯出來(lái)的代碼是可讀的 JavaScript,就像編程人員自己寫的一樣。這也使得即使在沒(méi)有原始代碼的情況下,調(diào)試生產(chǎn)環(huán)境下的代碼也會(huì)變得容易。你不需要擔(dān)心選擇了 TypeScript 之后會(huì)斬?cái)嗄阍趯?lái)使用 ECMAScript 新功能的可能性。TypeScript 為當(dāng)前的運(yùn)行時(shí)敞開(kāi)了大門,甚至于將來(lái)的 JavaScript 引擎也許可以忽略類型定義語(yǔ)法,原生“運(yùn)行” TypeScript 代碼。一種更簡(jiǎn)單的開(kāi)發(fā)體驗(yàn)指日可待!

          在發(fā)展過(guò)程中,TypeScript 擴(kuò)展了一小部分不太適合這個(gè)模型的功能。enum, namespace, parameter properties 以及 experimental decorators 都需要有將他們擴(kuò)展為運(yùn)行時(shí)代碼的語(yǔ)義,而 JavaScript 引擎很可能永遠(yuǎn)都不會(huì)為這些功能提供支持。

          遵循標(biāo)準(zhǔn)?

          這不是大問(wèn)題。TypeScript Design Goals 明確表示了避免在未來(lái)引入更多的運(yùn)行時(shí)特征。TypeScript 團(tuán)隊(duì)的一名成員 Orta 制作了一個(gè) MEME 幻燈片來(lái)強(qiáng)調(diào)了對(duì)這一說(shuō)法的認(rèn)可。

          我們的工具鏈通過(guò)阻止使用這些功能來(lái)避免這些不良的設(shè)計(jì),以確保我們不斷增長(zhǎng)的 TypeScript 代碼庫(kù)是真正的 JS + Types。

          遵循標(biāo)準(zhǔn) ??


          2. 持續(xù)更新編譯器的版本是值得的

          TypeScript 發(fā)展的很快。新版本一般會(huì)引入新的類型層面的功能、對(duì) JavaScript 功能的支持、提升性能和穩(wěn)定性,同時(shí)也會(huì)增強(qiáng)類型檢測(cè)器,用以發(fā)現(xiàn)更多的類型錯(cuò)誤。所以,使用新版本是一件非常吸引人的事情!

          當(dāng) TypeScript 努力保持兼容性時(shí),改進(jìn)的類型檢查會(huì)對(duì)構(gòu)建過(guò)程表現(xiàn)出一些破壞性改變,因?yàn)樾碌腻e(cuò)誤會(huì)在過(guò)去沒(méi)有錯(cuò)誤的代碼中被識(shí)別出來(lái)。因此,需要一些干預(yù)才能完成對(duì) TypeScript 版本的升級(jí),從而獲得新版帶來(lái)的優(yōu)勢(shì)。

          還有另一種兼容性問(wèn)題需要考慮,也就是跨項(xiàng)目的兼容性。隨著 JavaScript 和 TypeScript 語(yǔ)法的發(fā)展,聲明文件也需要容納新的語(yǔ)法。

          假設(shè)某一個(gè)庫(kù)升級(jí)了 TypeScript 版本并且使用新的語(yǔ)法輸出了聲明文件。而引用了這個(gè)庫(kù)的項(xiàng)目,如果它們的 TypeScript 版本無(wú)法理解這些語(yǔ)法,那么這些項(xiàng)目將會(huì)編譯失敗。例如 TypeScript 3.5 或更早的版本就無(wú)法理解 TypeScript 3.7 新增的 getter/setter 存取器方法。這也就意味著,在同一個(gè)生態(tài)系統(tǒng)中如果各個(gè)項(xiàng)目使用不同版本的編譯器,情況就會(huì)很不理想。

          系統(tǒng)一致性?

          在彭博社,代碼分布在各種使用通用工具的 Git 倉(cāng)庫(kù)中。盡管沒(méi)有使用 Monorepo 來(lái)統(tǒng)一管理,但我們使用一個(gè)注冊(cè)表集中式管理 TypeScript 項(xiàng)目。這樣就使我們可以創(chuàng)建一個(gè)持續(xù)集成的任務(wù)來(lái) “構(gòu)建一切”,并且檢驗(yàn)升級(jí)后的編譯器對(duì)每個(gè) TypeScript 項(xiàng)目構(gòu)建和運(yùn)行時(shí)的影響。

          這個(gè)全局檢查很強(qiáng)大,我們用它評(píng)估 TypeScript 的 Beta 版和 RC 版,以便在常規(guī)版本發(fā)布前發(fā)現(xiàn)問(wèn)題。擁有多樣的真實(shí)代碼作為資料集意味著我們可以找到邊際情況。我們用這個(gè)系統(tǒng)來(lái)引導(dǎo)項(xiàng)目為編譯器升級(jí)做好準(zhǔn)備,使得它們完美的完成升級(jí)。到目前為止,這個(gè)策略運(yùn)行的很好,因此我們可以使整個(gè)代碼庫(kù)保持在最新的 TypeScript 之上。這樣就意味著我們不需要采取諸如對(duì) DTS 文件降級(jí)之類的緩解措施來(lái)應(yīng)對(duì)版本升級(jí)。

          系統(tǒng)一致性 ??


          3. 保持一致的 tsconfig 設(shè)置是非常重要的

          tsconfig配置文件提供了很大的靈活性,使得你可以根據(jù)運(yùn)行時(shí)平臺(tái)來(lái)調(diào)整 TypeScript。但是在一個(gè)追求多項(xiàng)目共存且長(zhǎng)時(shí)間持續(xù)運(yùn)行的環(huán)境中,對(duì)每個(gè)項(xiàng)目單獨(dú)配置卻是極具風(fēng)險(xiǎn)的事情。

          系統(tǒng)一致性?

          因此,我們讓工具鏈來(lái)負(fù)責(zé)在編譯時(shí)基于 “最優(yōu)” 設(shè)置生成 tsconfig 。例如,默認(rèn)啟用 "Strict" 模式來(lái)增強(qiáng)類型的安全性;強(qiáng)制使用 "isolatedModules" 則可以確保我們的代碼可以使用簡(jiǎn)單轉(zhuǎn)義器每次對(duì)單個(gè)文件進(jìn)行快速編譯。

          將 tsconfig 視作被生成的文件而不是源文件的另一個(gè)好處就是,它允許高級(jí)工具通過(guò)不同選項(xiàng)(如 “references”,“paths” 等)靈活地將多個(gè)項(xiàng)目的 “工作區(qū)” 鏈接在一起。

          也有一些例外情況,少數(shù)項(xiàng)目需要自定義的配置,比如使用寬松模式來(lái)減少遷移負(fù)擔(dān)。

          舉個(gè)例子。最初我們?cè)噲D使用更少的選項(xiàng)來(lái)滿足一致性的要求。但后來(lái)我們發(fā)現(xiàn)了軟件包間的沖突,當(dāng)使用某組選項(xiàng)構(gòu)建的軟件包被使用其他選項(xiàng)構(gòu)建的軟件包引用時(shí),這些沖突就發(fā)生了。

          合理的做法是創(chuàng)建一個(gè)帶條件的類型,用于指向被 "strictNullChecks" 檢測(cè)到的類型值。

          type?A?=?unknown?extends?{}???string?:?number;

          如果啟用了 “strictNullChecks”,那么 A 的類型就是 number;如果沒(méi)啟用,則是 string。如果軟件包導(dǎo)出的這個(gè)類型和它導(dǎo)入的軟件包沒(méi)有使用相同的嚴(yán)格模式設(shè)置,那么程序?qū)?huì)出錯(cuò)。

          這是在現(xiàn)實(shí)中我們碰到問(wèn)題的一個(gè)簡(jiǎn)單事例。最終我們放棄了嚴(yán)格模式,選擇犧牲靈活性來(lái)保持所有項(xiàng)目配置的一致性。

          系統(tǒng)一致性 ??


          4. 如何指定依賴關(guān)系的位置很重要

          我們需要顯式地向 TypeScript 代碼聲明依賴的位置。這是因?yàn)槲覀兊?ES 模塊系統(tǒng)不會(huì)像通常的 Node 程序那樣,向上遞歸查找 node_modules 文件夾。

          我們需要對(duì)修飾符(例如 “l(fā)odash”)和其在磁盤上的目錄位置(“c:\dependencies\lodash”)的映射進(jìn)行聲明。這類似于在 Web 中引入 maps 的方式來(lái)解決映射問(wèn)題。最初,我們嘗試在 tsconfig 中使用 "paths" 這一選項(xiàng):

          //?tsconfig.json
          ??"paths":?{
          ????"lodash":?[?"../../dependencies/lodash"?]
          ??}

          這種方式在幾乎所有的用例中都運(yùn)行的很好。盡管如此,我們還是發(fā)現(xiàn)這種方式降低了自動(dòng)生成的聲明文件的質(zhì)量。TypeScript 編譯器必須在聲明文件中注入復(fù)合的導(dǎo)入語(yǔ)句,以實(shí)現(xiàn)復(fù)合類型的聲明 —— 某些類型的定義依賴于其他模塊下的類型。當(dāng)復(fù)合引用依賴中的類型時(shí),我們發(fā)現(xiàn) "paths" 并未使用已經(jīng)定義的修飾符(import "lodash"),而是引入了相對(duì)路徑(import("../../dependencies/lodash"))。對(duì)于我們的系統(tǒng)來(lái)說(shuō),一些類型定義引入自外部軟件包,而他們的相對(duì)位置是可能會(huì)發(fā)生改變,這種情況是不可接受的。

          系統(tǒng)一致性?

          我們最終的解決方案是使用 Ambient Modules

          //?ambient-modules.d.ts
          declare?module?"lodash"?{
          ??export?*?from?"../../dependencies/lodash";
          ??export?default?from?"../../dependencies/lodash";
          }

          Ambient Modules 特別之處在于,TypeScript 在發(fā)表聲明時(shí)保持對(duì)修飾符的引用,從而避免將它們轉(zhuǎn)化為相對(duì)路徑。

          系統(tǒng)一致性 ??


          5. 類型去重很重要

          程序的性能很關(guān)鍵,所以我們要盡量使在運(yùn)行時(shí)中的 JS 保持最小的體積。我們的平臺(tái)會(huì)確保在運(yùn)行時(shí)中每個(gè)包只有一個(gè)版本的存在。通過(guò)這種方式,確保了給定的包不會(huì)因?yàn)榘姹静煌i定和引入不同的依賴。因此,這也使得軟件包必須隨時(shí)保持對(duì)系統(tǒng)的兼容性。

          我們希望對(duì)類型提供一種 “精確且唯一” 的定義,以確保對(duì)于給定的編譯項(xiàng)目,類型檢查只需要對(duì)依賴進(jìn)行單一版本的檢查。除了增加編譯時(shí)的效率以外,這么做的另一個(gè)動(dòng)機(jī)就是確保類型檢查能夠更好的反應(yīng)運(yùn)行時(shí)環(huán)境。我們尤其希望避免落入失效定義問(wèn)題和 “聲明地獄”,即通過(guò) “菱形模式” 導(dǎo)入同一類型聲明的多個(gè)版本。隨著生態(tài)采用的聲明增加,這個(gè)問(wèn)題的危害會(huì)被放大。

          可擴(kuò)展性 ?

          系統(tǒng)一致性?


          我們編寫了一個(gè)決策式解析器用來(lái)根據(jù)正在構(gòu)建的包約束中正確的選擇出一個(gè)依賴的版本。

          可擴(kuò)展性 ??

          系統(tǒng)一致性 ??


          這就意味著依賴的類型關(guān)系圖是動(dòng)態(tài)生成的——而不是靜態(tài)的鎖定某個(gè)版本。雖然這種不鎖依賴版本的方法帶來(lái)了很多優(yōu)點(diǎn)并且回避了很多危險(xiǎn),但我們后來(lái)發(fā)現(xiàn),這個(gè)方式會(huì)因?yàn)?TypeScript 編譯器的一些古怪行為引入一些不一樣的危險(xiǎn)。在 9. 聲明文件中生成的類型會(huì)內(nèi)聯(lián)傳遞自依賴中的類型 中會(huì)詳細(xì)說(shuō)明。

          這些權(quán)衡和選擇并不是特定于我們的平臺(tái)。它們同樣適用于任何基于類型定義的 npm 項(xiàng)目,并且應(yīng)當(dāng)根據(jù) package.json 文件 "dependencies" 中每個(gè)包版本約束的綜合影響進(jìn)行判斷。

          6. 應(yīng)該避免隱式類型依賴關(guān)系

          在 TypeScript 中引入全局類型很容易,依賴全局類型更容易。如果不加檢查,就很有可能在不相關(guān)的包之間發(fā)生隱式耦合。TypeScript 手冊(cè)將這種行為稱為 “有些危險(xiǎn)”。

          可擴(kuò)展性 ?

          系統(tǒng)一致性 ?
          //?A?declaration?that?injects?global?types
          declare?global?{
          ??interface?String?{
          ????fancyFormat(opts?:?StringFormatOptions):?string;
          ??}
          }

          //?Somewhere?in?a?file?far,?far?away...
          String.fancyFormat();??//?no?error!

          解決這個(gè)問(wèn)題的方法顯而易見(jiàn):使用顯示依賴而不是全局狀態(tài)棧。TypeScript 很早以前就為 ECMAScript 導(dǎo)入和導(dǎo)出語(yǔ)句提供了支持,從而實(shí)現(xiàn)了這一目標(biāo)。

          剩下我們唯一要防止就是意外創(chuàng)建的全局類型。幸運(yùn)的是,我們?cè)?TypeScript 中可以靜態(tài)地檢測(cè)到每一個(gè)局類型的引入用例。因此我們可以通過(guò)升級(jí)工具鏈來(lái)發(fā)現(xiàn)這些全局類型的每個(gè)用例并拋出錯(cuò)誤,因此我們可以安全地依賴于無(wú)副作用的包類型引入。

          可擴(kuò)展性 ??

          系統(tǒng)一致性 ??


          7. 聲明文件有三種輸出模式

          不同的聲明文件并不完全等價(jià)。內(nèi)容的不同決定了一個(gè)聲明文件屬于以下三種形式中的哪一種。特別是 importexport 關(guān)鍵字的使用:

          1. 全局 —— 不使用importexport 關(guān)鍵字的聲明文件就被認(rèn)為是全局聲明。頂級(jí)聲明都是輸出在全局作用域。

          2. 模塊 —— 至少包含一個(gè) export 關(guān)鍵字的聲明文件即為模塊聲明。只有 export 關(guān)鍵字引導(dǎo)的聲明會(huì)被輸出,而且其作用域不會(huì)是全局。

          3. 隱式輸出 —— 不使用關(guān)鍵字 export 引導(dǎo)聲明,但使用 import 關(guān)鍵字導(dǎo)入時(shí)會(huì)觸發(fā)未文檔化的已定義行為。也就是不再將頂級(jí)聲明視作全局作用域,而是作為命名空間的聲明導(dǎo)出。

          我們不使用模式一。我們使用工具鏈防止全局作用域的聲明文件(詳見(jiàn) 6. 隱式類型依賴關(guān)系應(yīng)當(dāng)避免)。所有的聲明文件均遵循 ES Module 語(yǔ)法。

          可擴(kuò)展性 ??

          系統(tǒng)一致性 ??

          兼容標(biāo)準(zhǔn) ??

          有些令人驚訝的是,我們發(fā)現(xiàn)看上去有些令人不安的第三種模式卻非常有用。通過(guò)在聲明文件的頂部添加一行 “自引導(dǎo)” 的方式,就可以防止它們污染全局命名空間:import {} from "./"。這個(gè)單行使得將第三方聲明(如 lib.dom.d.ts)模塊化變得非常容易,而且可以避免去維護(hù)一個(gè)更復(fù)雜的代碼克隆。

          然而 TypeScript 團(tuán)隊(duì)看上去并不喜歡第三種模式,所以盡可能的回避這種方式。


          8. 包的封裝可能會(huì)被破壞

          如在前文中表述的(5. 類型去重很重要),我們不鎖定依賴版本,這意味著我們的包不僅需要保持對(duì)運(yùn)行時(shí)的兼容性,同時(shí)也要保證在版本更迭時(shí)保持類型的兼容性。這是一個(gè)挑戰(zhàn),為了實(shí)現(xiàn)對(duì)兼容性的保護(hù),我們必須真正的了解哪些類型是公開(kāi)的,且需要對(duì)版本加以限制。第一件要干的事情,就是明確區(qū)分公共模塊和私有模塊。

          Node 通過(guò)在 package.json 中的 exports 字段來(lái)實(shí)現(xiàn)這個(gè)功能。通過(guò)顯示列出可從包外訪問(wèn)文件的方式,定義封裝邊界。

          目前,TypeScript 并不在意包的導(dǎo)出,因此也不知道依賴中哪些文件是公開(kāi)的,哪些是不公開(kāi)的。在聲明生成過(guò)程中,TypeScript 將導(dǎo)入的語(yǔ)句合成并傳遞為類型定義再封裝成 .d.ts 文件,這時(shí)就會(huì)產(chǎn)生問(wèn)題 —— 我們的 .d.ts 文件中可能引用了其他包里的私有文件,這是不可接受的。下面是一個(gè)錯(cuò)誤的示例:

          //?index.ts
          import?boxMaker?from?"another-package"
          export?const?box?=?boxMaker();

          上面引入的源文件可能導(dǎo)致 tsc 發(fā)出以下不正確的聲明。

          //?index.d.ts
          export?const?box?:?import("another-package/private").Box

          這是很糟糕的。“來(lái)自另一個(gè)私有包” 不能保證兼容性,因?yàn)檫@些聲明很可能在某個(gè)微小的改動(dòng)時(shí)就被移除或者重命名了。到目前為止,TypeScript 仍無(wú)法知曉它生成的文件中是否存在不安全的導(dǎo)入。

          系統(tǒng)一致性 ?

          我們通過(guò)兩個(gè)步驟來(lái)緩解這個(gè)問(wèn)題:

          1. 我們的工具鏈會(huì)將試圖公開(kāi)的修飾符指向的路徑(例如:"lodash/public1", "lodash/public2")告知 TypeScript 解析器。在 TypeScript 文件進(jìn)行編譯之前在它的尾部添加允許導(dǎo)入的類型聲明,通過(guò)這種方式確保 TypeScript 知曉所有合法的依賴入口。

          //?user's?source?code

          //?injected?by?toolchain?to?assist?declaration?emit
          import?type?*?as?__fake_name_1?from?"lodash/public1";
          import?type?*?as?__fake_name_2?from?"lodash/public2";

          當(dāng)生成引用文件的推斷類型時(shí),TypeScript 的聲明執(zhí)行器將使用這些已知的命名空間修飾符來(lái)代替路徑方式實(shí)現(xiàn)對(duì)私有文件的導(dǎo)入。

          1. 如果 TypeScript 生成了一個(gè)路徑,而我們已知其為某個(gè)依賴的私有文件,這時(shí)我們的工具鏈就會(huì)拋出一個(gè)錯(cuò)誤。這就像是 TypeScript 自己意識(shí)到它正在將一個(gè)有潛在風(fēng)險(xiǎn)的路徑指向到某個(gè)依賴一樣,拋出一個(gè) TypeScript 的錯(cuò)誤。

          error?TS2742:?The?inferred?type?of?'...'?cannot?be?named?without?a?reference?to?'...'.
          This?is?likely?not?portable.?A?type?annotation?is?necessary.

          這樣就會(huì)通知到用戶需要注釋掉這個(gè)輸出才能解決這個(gè)錯(cuò)誤。或者,在某些情況下,它們可以更新依賴,直接從公共包入口輸出內(nèi)部類型。

          系統(tǒng)一致性 ??

          我們期待 TypeScript 能夠?qū)θ肟邳c(diǎn)問(wèn)題提供更好的支持,這也就不需要使用這種替代方案了。

          9. 聲明文件中生成的類型會(huì)內(nèi)聯(lián)傳遞自依賴中的類型

          軟件包需要輸出 .d.ts 聲明文件給用戶使用。我們選擇用 TypeScript 的 declaration 選項(xiàng)依照原始 .ts 文件生成 .d.ts 文件。盡管也可以在編寫代碼的同時(shí)手寫和維護(hù) .d.ts 文件,但這種做法并不科學(xué),因?yàn)闀r(shí)刻維護(hù)它們的一致性是件非常難做的事情。

          TypeScript 在大多數(shù)情況下自動(dòng)生成聲明文件都沒(méi)問(wèn)題。我們發(fā)現(xiàn)的其中一個(gè)問(wèn)題是有時(shí) TypeScript 會(huì)將依賴中的類型內(nèi)聯(lián)傳遞給當(dāng)前的類型。這就意味著相對(duì)于用 import 語(yǔ)句標(biāo)識(shí)為引用,這種方式的類型定義被重定向了,并且存在潛在的重復(fù)定義。對(duì)于結(jié)構(gòu)化的類型定義,編譯器不會(huì)強(qiáng)制性驗(yàn)證被引用的類型是否和源定義一致 —— 因此重復(fù)的類型定義是不會(huì)報(bào)錯(cuò)的。

          我們見(jiàn)過(guò)一些更極端的例子,由于這些重復(fù)的類型定義,聲明文件的大小從 7KB 膨脹到了 700KB。這使得程序運(yùn)行時(shí)需要下載和解析大量的冗余代碼。

          可擴(kuò)展性 ?

          包內(nèi)的內(nèi)聯(lián)類型定義不會(huì)造成系統(tǒng)性問(wèn)題,因?yàn)樗鼈儗?duì)外是不可見(jiàn)的。但是當(dāng)類型定義在不同包里使用了不同的版本時(shí),問(wèn)題就出現(xiàn)了。在我們這樣不鎖定包版本的系統(tǒng)中,各個(gè)包可以獨(dú)立演化,這帶來(lái)了類型兼容性的風(fēng)險(xiǎn),特別是類型失效的風(fēng)險(xiǎn)。

          系統(tǒng)一致性 ?

          通過(guò)實(shí)驗(yàn),我們找到了一個(gè)能防止內(nèi)聯(lián)類型聲明的潛在技術(shù):

          • 使用 interface 代替 type (interface 接口是不存在內(nèi)聯(lián)問(wèn)題的)

          • 如果一個(gè) interface 沒(méi)有在聲明中輸出,tsc 不會(huì)去內(nèi)聯(lián)查詢這個(gè)類型,而是拋出一個(gè)異常(例如:TS4023: Exported variable has or is using name from external module but cannot be named.)。

          • 如果一個(gè) type 沒(méi)有在聲明中輸出,tsc 會(huì)在依賴中內(nèi)聯(lián)尋找這個(gè)類型的定義。

          • Nicholas Jamieson 寫了一篇文章來(lái)推薦使用接口來(lái)替代類型,以及相應(yīng)的 Eslint 規(guī)則。

          • 使用標(biāo)明類型定義(像 enum, class 這些有私有成員的標(biāo)明類型是不會(huì)內(nèi)聯(lián)定義的)

          • 對(duì)輸出添加類型注釋

          • 沒(méi)有類型注釋時(shí),發(fā)生了內(nèi)聯(lián)引用

          • 使用顯示類型注釋后,我們強(qiáng)制指定了引用的行為

          可擴(kuò)展性 ?

          系統(tǒng)一致性 ?

          這種內(nèi)聯(lián)行為似乎沒(méi)有被嚴(yán)格的指出。這只是構(gòu)造聲明文件方式的副作用,因此上述方式有可能會(huì)在將來(lái)失效。希望這能在 TypeScript 中被正式化。在此之前,我們將依靠用戶教育來(lái)降低這種風(fēng)險(xiǎn)。

          10. 生成的聲明文件有可能會(huì)包含不必要的依賴

          TypeScript 聲明文件的使用者通常只關(guān)心包的公有類型的API。TypeScript 聲明生成器會(huì)對(duì)項(xiàng)目中的每個(gè) TypeScript 文件只產(chǎn)生一個(gè)聲明文件。其中一些內(nèi)容可能與用戶無(wú)關(guān),并可能會(huì)暴露私有部分的實(shí)現(xiàn)細(xì)節(jié)。這種行為可能會(huì)讓 TypeScript 新手感到驚訝,他們往往會(huì)期待類型定義應(yīng)該像 Definitely Typed 那樣,僅僅展示公有API。

          這種情況的一個(gè)例子是:生成的聲明文件中包含了僅僅用于內(nèi)部測(cè)試方法的類型。

          可擴(kuò)展性 ?

          由于我們的包管理系統(tǒng)知曉所有的公共包入口,我們的工具可以在可訪問(wèn)類型圖中爬取所有不需要公開(kāi)的類型。這是 Dead Type Elimination(DTE),或者更準(zhǔn)確的說(shuō),Tree-Shaking。我們編寫了一個(gè)工具來(lái)做這件事 —— 它僅通過(guò)消除聲明文件中的冗余來(lái)完成最小化代碼的工作。它不會(huì)去重寫或者重定向代碼 —— 他不是一個(gè)打包器。也就是說(shuō),最終發(fā)布的聲明文件是 TypeScript 自動(dòng)生成的聲明文件的子集。

          軟件包減少發(fā)布類型的體積有以下好處:

          • 減少了與其他包的耦合性(一些包不會(huì)對(duì)依賴中的類型重新輸出)

          • 它通過(guò)防止完全私有類型的泄漏來(lái)幫助封裝

          • 在發(fā)布中減少了用戶需要下載并解壓的聲明文件的數(shù)量和體積

          • 減少了 TypeScript 編譯器在類型檢查時(shí)需要解析的代碼量

          "Shaking" 有時(shí)會(huì)效果極為顯著。我們?cè)?jīng)遇到過(guò)一些包中超過(guò) 90% 文件中有超過(guò) 90% 的類型定義行是可以去掉的。

          可擴(kuò)展性 ??

          一些選項(xiàng)有嚴(yán)格的使用場(chǎng)景

          我們發(fā)現(xiàn)一些 tsconfig 選項(xiàng)中的語(yǔ)義是令人驚訝的。

          tsconfig?中強(qiáng)行使用 baseUrl

          在 TypeScript 4.0 中。如果你希望使用項(xiàng)目引用或者某個(gè)“路徑”,你就同時(shí)需要指定一個(gè) baseUrl。這樣做的副作用就是會(huì)導(dǎo)致所有的修飾符導(dǎo)入相對(duì)路徑時(shí)都會(huì)被補(bǔ)全為相對(duì)于根目錄的路徑形式。

          //?package-a/main.ts
          import?"sibling"???//?Will?auto-complete?and?type-check?if?`package-a/sibling.js`?exists

          這樣做的危險(xiǎn)在于,如果你想引入任何形式的“路徑”,它會(huì)帶來(lái)一個(gè)額外的結(jié)果, import "sibling" 會(huì)被TypeScript 自動(dòng)補(bǔ)全為 /sibling.js

          遵循標(biāo)準(zhǔn) ?


          為了解決這個(gè)問(wèn)題,我們使用了一個(gè)糟糕的 baseUrl。使用 null 來(lái)防止不必要的自動(dòng)補(bǔ)全。我們不建議你在家里嘗試這樣做。

          我們?cè)?TypeScript issue 上報(bào)告了這個(gè)問(wèn)題,很高興地看到 Andrew 已經(jīng)在 TypeScript 4.1 解決了這個(gè)問(wèn)題,這將使我們告別 null 字符!

          遵循標(biāo)準(zhǔn) ??


          JSON 模塊導(dǎo)入沒(méi)有默認(rèn)開(kāi)啟

          如果你希望使用 resloveJsonModules,你就同時(shí)需要開(kāi)啟 useSyntheticDefaultImports 選項(xiàng),從而使 TypeScript 識(shí)別導(dǎo)入 JSON 模塊。在將來(lái),使用導(dǎo)入的方式處理 JSON 模塊,很可能成為 Node 和 Web 的標(biāo)準(zhǔn)方式。

          啟用 useSyntheticDefaultImports 會(huì)有一個(gè)不幸的結(jié)果,即允許導(dǎo)入沒(méi)有默認(rèn)輸出的常規(guī) ES 模塊!這是一種風(fēng)險(xiǎn),你只有在運(yùn)行代碼時(shí)才會(huì)發(fā)現(xiàn),并且它一閃而過(guò)。

          遵循標(biāo)準(zhǔn) ?

          理想情況下,應(yīng)該有一種不需要啟用 useSyntheticDefaultImports 而能夠?qū)隞SON模塊的方法。

          非常好的部分

          從工具化的角度來(lái)看,TypeScript 中展現(xiàn)出的一些特別好的東西是值得一提的。

          增量構(gòu)建成為基本功能。TypeScript 3.6 的 API 對(duì)增量構(gòu)建提供支持對(duì)我們來(lái)說(shuō)是一件有巨大推動(dòng)作用的事,這使得自定義工具鏈可以進(jìn)行快速重建。在我們報(bào)告了將 incrementalnoEmitOnError 結(jié)合使用而產(chǎn)生的性能問(wèn)題時(shí),Sheetal 使它們?cè)?TypeScript 4.0 中運(yùn)行速度更快了。

          可擴(kuò)展性 ??

          "isolatedModules" 在確保我們可以執(zhí)行快速的獨(dú)立(一個(gè)進(jìn),一個(gè)出)置換時(shí)至關(guān)重要。TypeScript團(tuán)隊(duì)修復(fù)了一系列問(wèn)題來(lái)改進(jìn)這個(gè)選項(xiàng),包括:

          • 同時(shí)允許 emitDeclaration?和 isolatedModules

          • 同時(shí)允許 noEmitOnErrorisolatedModules

          • 當(dāng)啟用 isolatedModules?時(shí),類型必須顯式輸出

          可擴(kuò)展性 ??

          系統(tǒng)一致性 ??

          項(xiàng)目引用是提供無(wú)縫 IDE 體驗(yàn)的關(guān)鍵。我們利用它們極大地提升了基于多個(gè)包工作區(qū)的項(xiàng)目開(kāi)發(fā)體驗(yàn),使它變得像單個(gè)項(xiàng)目開(kāi)發(fā)一樣靈活。多虧了 Sheetal,它們現(xiàn)在更好了,并且支持不需要文件的 "Solution Style"的tsconfigs。

          可擴(kuò)展性 ??

          僅類型導(dǎo)入是非常有用的。我們?cè)趯?dǎo)出都會(huì)用到它們來(lái)安全地區(qū)分運(yùn)行時(shí)導(dǎo)入還是編譯時(shí)導(dǎo)入。它們?cè)趩⒂?"isolatedModules" 模式時(shí)必不可少,并且允許我們使用 "importsNotUsedAsValues":"error" 來(lái)獲得最佳的安全性。感謝 Andrew 提交了這個(gè)功能!

          系統(tǒng)一致性 ??

          遵循標(biāo)準(zhǔn) ??

          "useDefineForClassFields" 對(duì)于確保我們發(fā)布的 ESNext 代碼不會(huì)被重寫,保持語(yǔ)言的JS + 類型特性非常重要。這使得我們可以原生的使用 Class 字段。感謝 Nathan 提供了這個(gè)功能,并盡可能順利地進(jìn)行了遷移。

          遵循標(biāo)準(zhǔn) ??

          TypeScript 中的新增特性有時(shí)會(huì)有驚喜。每當(dāng)我們意識(shí)到我們需要一個(gè)特性時(shí),我們經(jīng)常發(fā)現(xiàn)它已經(jīng)在下一個(gè)版本中交付了。

          總結(jié)

          最終,TypeScript 現(xiàn)在是我們應(yīng)用平臺(tái)的首選語(yǔ)言了。在需要將 TypeScript 與另一種運(yùn)行時(shí)集成在一起時(shí),語(yǔ)言和編譯器表現(xiàn)的似乎和 JavaScript 一樣靈活 —— 它們都可以在任何地方使用。

          雖然一路上我們遇到了很多問(wèn)題,但沒(méi)有什么是不可逾越的。當(dāng)我們需要支持時(shí),我們?yōu)閬?lái)自社區(qū)和 TypeScript 團(tuán)隊(duì)本身的響應(yīng)感覺(jué)驚喜。使用共享開(kāi)源技術(shù)的一個(gè)明顯好處是,當(dāng)您遇到問(wèn)題時(shí),您通常會(huì)發(fā)現(xiàn)您并不孤單。當(dāng)你找到了答案,你就能從分享中得到樂(lè)趣。


          瀏覽 58
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  北条麻妃在线直播 | 俺去俺也去88 | 成人91无码在线18 | 中文字幕免费在线视频 | 黄色的艹逼视频网站 |