將超過5000萬行JS代碼遷移到TypeScript,我們得到的10大見解

{"type":"doc","content":[{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"幾年前,彭博工程公司決定採用TypeScript作爲一等語言。本文分享了我們在這一旅程中學到的一些見解和教訓。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"本文的重點是我們在採用TypeScript後獲得的巨大收益,當然,作爲工程師,在醉心於TS的同時我們也會發現、解決和分享其中存在的問題。"},{"type":"text","text":" "}]},{"type":"image","attrs":{"src":"https:\/\/static001.infoq.cn\/resource\/image\/b3\/06\/b36c83bbd4d55c243e20a37e9fyyf506.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic"}],"text":"本文最初發佈於彭博技術博客,原標題"},{"type":"link","attrs":{"href":"https:\/\/www.techatbloomberg.com\/blog\/10-insights-adopting-typescript-at-scale\/","title":"xxx","type":null},"content":[{"type":"text","marks":[{"type":"italic"}],"text":"《10 Insights from Adopting TypeScript at Scale》"}],"marks":[{"type":"italic"}]},{"type":"text","marks":[{"type":"italic"}],"text":",經InfoQ中文站翻譯並分享。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"背景"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"在TypeScript出現之前,彭博社已經在JavaScript上投入了大量資源——超過5,000萬行JS代碼。我們的主要產品是彭博終端,其中包含10,000多個應用。這些應用種類繁多,包括顯示大量實時財務數據和新聞的應用、提供交互式交易解決方案的應用,還有多種消息應用,等等。早在2005年,公司就開始將這些應用從Fortran和C\/C++遷移到服務端JavaScript,而客戶端JavaScript於2012年左右推出。今天,我們公司有2,000多名軟件工程師在編寫JavaScript。"}]},{"type":"image","attrs":{"src":"https:\/\/static001.infoq.cn\/resource\/image\/6f\/5e\/6fbbce7822853665145f1ecb0375ae5e.jpg","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"將這麼大規模的代碼庫從標準JavaScript轉換爲TypeScript是一件大事。因此,我們努力制定了完善的遷移流程,使我們得以遵循標準,並保留現有的特性,進而快速安全地改進和部署代碼。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"如果你曾在一家大公司中參與過技術遷移,那麼你肯定見識過繁重的項目管理工作——這種工作的目的是爲了強迫團隊繼續遷移,雖然他們寧願去開發新特性。但我們發現TypeScript的採用過程完全不是這回事。工程師們是在自發遷移並推動這個過程!當我們啓用TypeScript平臺支持的beta版後,僅第一年就有200多個項目選擇了TypeScript。沒有一個項目選擇回退。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"是什麼讓如此大規模的TypeScript採用與衆不同?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"除了規模之外,這次TypeScript遷移活動很特殊的一點在於,我們擁有自己的JavaScript運行時環境。這意味着除了著名的JavaScript主機環境(例如瀏覽器和Node)之外,我們還直接嵌入了V8引擎和Chromium,以創建我們自己的JavaScript平臺。這裏的好處在於,我們可以提供簡單便捷的開發體驗,讓自己的平臺和軟件包生態系統直接支持TypeScript。Ryan Dahl的Deno的理念是類似的,他們的辦法是將TypeScript編譯放入了運行時,而我們將其保留在獨立於運行時進行版本控制的工具中。一個有趣的結果是,我們得以探索在跨客戶端和服務器、且不使用Node專屬約定的獨立JS環境中使用TypeScript編譯器的體驗(例如,這裏沒有node_modules目錄)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我們的平臺支持一個使用一套通用工具鏈和發佈系統的內部軟件包生態系統。這樣我們就能鼓勵和推行最佳實踐,例如默認使用TypeScript的“嚴格模式”以及確保全局不變量。例如,我們可以保證所有發佈的類型都是模塊化的,而非全局的。這樣一來,工程師可以專注於代碼編寫,而無需操心如何讓TypeScript與打包程序或測試框架完美搭配。我們的DevTools和錯誤棧正確使用了源映射。我們可以使用TypeScript編寫測試,並且可以根據原始TypeScript代碼準確地表示代碼覆蓋率。一切都很好用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我們的目標是讓常規TypeScript文件成爲我們API的唯一事實來源,而不用維護手寫聲明文件。也就是說我們有很多代碼都非常依賴TypeScript編譯器從TypeScript源代碼自動生成的.d.ts聲明文件。因此如你所見,當聲明發射出問題時我們會察覺的。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"關鍵原則"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"下面概括一下我們正在努力遵循的三大關鍵原則。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"Scalability(可擴展性):隨着越來越多的軟件包採用TypeScript,開發速度應維持在較高水平。應該儘量減少花在安裝、編譯和檢查代碼上的時間。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"Ecosystem Coherence(生態系統一致性):程序包應該協同工作。升級依賴項應該很容易。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"Standards Alignement(標準一致性):我們希望堅持使用ECMAScript等標準,併爲將來的標準做好準備。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"令我們意外的發現通常來自於我們不知道是否能夠遵循這三大原則的場景。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"10大學習要點"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"1. TypeScript可以是JavaScript+Types"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"多年來,TypeScript團隊一直積極推行和遵循標準ECMAScript語法和運行時語義。這樣TypeScript可以集中精力在JavaScript之上提供一層類型語法和類型檢查語義。職責很明確:"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"TypeScript=JavaScript+Types!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"這個模型很厲害,意味着編譯器輸出是人類可讀的JavaScript,就像是程序員寫的一樣。即使你沒有原始源代碼,生產代碼也很容易調試。你不必擔心選擇TypeScript可能會讓你錯過將來的ECMAScript特性。它爲運行時,甚至未來的JavaScript引擎打開了大門;未來的引擎可以忽略類型語法,從而原生地“運行”TypeScript。開發體驗會越來越簡單輕鬆的!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"在此過程中,TypeScript擴展了一些不太適合該模型的特性。enum、namespace、參數屬性和experimental修飾符都具有需要它們擴展爲運行時代碼的語義,很可能永遠不會被JavaScript引擎直接支持。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"標準對齊?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"這不是什麼問題。TypeScript設計目標明確了避免將來引入更多運行時特性的需求。TypeScript團隊的一名成員Orta做了一張meme幻燈片來強調這一原則。"}]},{"type":"image","attrs":{"src":"https:\/\/static001.infoq.cn\/resource\/image\/48\/7d\/48a92735aed28d519351fdb53b8eaf7d.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我們的工具鏈會避免使用這些前景不明的特性,確保我們不斷增長的TypeScript代碼庫是真正的JS+Types。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"標準對齊,OK!"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"2. 跟上編譯器是值得的"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"TypeScript發展迅速。新版語言引入了新的類型級別特性、增加了對JavaScript特性的支持、提高了性能和穩定性、並改進了類型檢查器以查找更多類型錯誤。因此新版本很誘人!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"雖然TypeScript在努力保持兼容性,但是這些類型檢查改進會對構建流程引入重大更改,因爲以前看起來沒有錯誤的代碼庫中會因此出現新的錯誤。因此,升級TypeScript時需要一些干預才能獲得這些收益。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"還可以考慮另一種形式的兼容性,即項目間兼容性。隨着JavaScript和TypeScript語法的發展,聲明文件需要包含新的語法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"如果一個庫升級到TypeScript,並開始使用新語法生成新的聲明文件,那麼如果使用該庫的應用項目的TypeScript版本不理解新語法,就會無法編譯。新聲明語法的一個示例是TypeScript 3.7中的getter\/setter訪問器的發射。TypeScript 3.5或更早版本無法理解這些內容。這意味着使用不同編譯器版本的項目生態系統並不好用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"生態系統一致性?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"在彭博社,我們的代碼庫分佈各個Git存儲庫中,它們使用的是通用的工具鏈。儘管沒有單體代碼庫,但我們確實有一個TypeScript項目的中心化存儲庫。這樣我們就能創建一個持續集成(CI)作業來“構建世界”,並驗證每個TypeScript項目上編譯器升級的構建時間和運行時效果。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"這種全局檢查非常強大。我們用它來評估TypeScript的Beta和RC版本,以便在升級標準版本之前發現問題。擁有各種各樣的實踐代碼還意味着我們可以找到很多邊緣情況。我們使用這套系統在編譯器升級之前爲項目提供修復指導,以便確保升級完美實現。到目前爲止,這一策略的效果很不錯,我們已經能將整個代碼庫保持在最新版本的TypeScript上。這意味着我們不需要採取緩解措施,例如降低DTS文件的等級之類。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"生態系統一致性,OK!"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"3. 一致的tsconfig設置是值得的"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"tsconfig提供的靈活性主要在於,它使你可以讓TypeScript適應你的運行時平臺。在所有項目都以同一個常綠運行時爲目標的環境中,事實證明對每個項目進行單獨配置是風險很大的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"生態系統一致性?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"因此,我們讓工具鏈負責在構建時使用“理想”設置生成tsconfig。例如,默認情況下啓用“strict”模式以增加類型安全性。強制執行“isolatedModules”,以通過每次操作一個文件的簡單編譯器快速編譯我們的代碼。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"將tsconfig視爲生成的文件(而非源文件)的另一個好處是,它允許高層工具鏈負責定義“references”和“paths”之類的選項,從而將多項目“工作區”靈活地鏈接在一起。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"這裏出了些問題,因爲少數項目希望能夠自定義,例如切換到較寬鬆的模式以減輕遷移負擔。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"一開始我們試圖滿足這些要求,並提供了一些選項。後來我們發現,當使用一組選項構建的聲明文件被使用不同選項的程序包占用時,就會導致程序包間衝突。下面是一個例子。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"可以創建一個由“strictNullChecks”值定向的條件類型。"}]},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"type A = unknown extends {} ? string : number;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"如果啓用了“strictNullChecks”,則A爲一個number。如果禁用了“strictNullChecks”,則A爲一個string。如果導出此類型的包未使用與導入它的包相同的嚴格性設置,這段代碼就會中斷。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"以上是我們面臨的現實問題的簡化示例。結果,我們選擇棄用嚴格性模式的靈活性,換取對所有項目都有一致的配置。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"生態系統一致性,OK!"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"4. 如何指定依賴項的位置很重要"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我們需要明確聲明TypeScript依賴項的位置。這是因爲我們的ES模塊系統不依賴“通過遍歷一系列名爲node_modules的目錄來查找依賴項”的Node文件系統約定。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我們需要能夠聲明bare-specifier(裸指定符,例如“lodash”)到磁盤上目錄位置(“c:\\dependencies\\lodash”)的映射。這很像是試圖解決Web類似問題的import maps。首先,我們嘗試在tsconfig中使用“paths”選項。"}]},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"\/\/ tsconfig.json\n \"paths\": {\n \"lodash\": [ \"..\/..\/dependencies\/lodash\" ]\n }"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"這幾乎適用於所有用例。但我們發現它降低了生成的聲明文件的質量。TypeScript編譯器必須將合成(synthetic)的import語句注入聲明文件中,以允許使用複合類型——其中的類型可以取決於其他模塊的類型。當合成的import引用依賴項中的類型時,我們發現“paths”方法注入了相對路徑(import(\"..\/..\/dependencies\/lodash\")),而不是保留裸指定符(import \"lodash\")。對於我們的系統來說,外部包類型的相對位置是可能會更改的實現細節,因此這是不可接受的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"生態系統一致性?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我們找到的解決方案是使用Ambient模塊:"}]},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"\/\/ ambient-modules.d.ts\ndeclare module \"lodash\" {\n export * from \"..\/..\/dependencies\/lodash\";\n export default from \"..\/..\/dependencies\/lodash\";\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"Ambient模塊是特殊的。TypeScript的聲明發射保留對它們的引用,而不是將其轉換爲相對路徑。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"生態系統一致性,OK!"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"5. 避免重複類型很重要"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"應用的性能是關鍵指標,因此我們試着儘量減少應用在運行時加載的JS數量。我們的平臺確保在運行時僅使用一個版本的軟件包。移除版本的重複數據意味着給定的包不能“凍結”或“固定”其依賴項。因此,這意味着軟件包必須時刻保持兼容性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我們希望爲類型提供相同的“完全唯一(exactly-one)”保證,以確保對於給定的項目編譯,類型檢查僅考慮軟件包依賴項的一個版本。除了提高編譯時效率外,這裏的動機還在於確保類型檢查的世界更好地反映運行時世界。我們特別想避免陳舊(staleness)問題和“nominal地獄”,在這些情況下可能會通過“鑽石模式”導入兩個不兼容的nominal類型版本。隨着生態系統採用的nominal類型日益增多,這種危害也可能隨之加劇。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"可擴展性?生態系統一致性?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我們編寫了一個確定性解析器,其根據所構建軟件包的聲明版本,確保爲每個依賴項只選擇一個版本。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"可擴展性,OK!生態系統一致性,OK"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"這意味着類型依賴圖是動態組合的——它不會凍結。儘管這種非固定的依賴方法可以帶來很多好處並避免了某些危害,但我們後來瞭解到,由於TypeScript編譯器中的一些行爲細節,它可能會帶來新的危害。請參閱第9部分以瞭解更多信息。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"這些折衷和選擇不是隻適用於我們自己的平臺。它們同樣適用於發佈到DefinitelyTyped\/npm的任何人,並取決於package.json \"dependencies\"中表示的所有包版本約束的累加效果。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"6. 應避免隱式類型依賴"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"在TypeScript中引入全局類型很容易。依賴全局類型甚至更容易。如果不加以檢查,那麼在距離遙遠的包之間可能出現隱藏的耦合。TypeScript手冊稱其爲“有點危險”。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"可擴展性?生態系統一致性?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"\/\/ A declaration that injects global types\ndeclare global {\n interface String {\n fancyFormat(opts?: StringFormatOptions): string;\n }\n}\n\n\n\/\/ Somewhere in a file far, far away...\nString.fancyFormat(); \/\/ no error!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"這裏的解決方案大家都熟悉:相對於全局狀態,優先使用顯式依賴。TypeScript長期以來一直爲ECMAScript的import和export語句提供支持,從而實現了這一目標。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"因此,剩下的唯一需求是防止意外創建全局類型。所幸我們可以靜態檢測TypeScript允許引入全局類型的所有情況。於是我們更新了工具鏈,以檢測並報錯這些情況。也就是說我們可以放心地確認一個事實,即導入一個包的類型是無副作用的操作。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"可擴展性,OK!生態系統一致性,OK!"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"7. 聲明文件具有三種導出模式"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"並非所有的聲明文件都相等。聲明文件根據其內容,會以三種模式之一運行;特別是import和export關鍵字的用法會有不同。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"global——不使用import或export的聲明文件將被視爲global。頂級聲明是全局導出的。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"module——具有至少一個export聲明的聲明文件將被視爲模塊。只有export聲明會被導出,不會定義任何global。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"隱式export——沒有export聲明,但使用import的聲明文件將觸發已定義但尚未說明的行爲。也就是將頂級聲明視爲命名的export聲明,並且不會定義global。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我們不使用第一種模式。我們的工具鏈會避免使用全局聲明文件(請參見上一節)。這意味着所有聲明文件都使用ES模塊語法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"可擴展性,OK!生態系統一致性,OK!標準對齊,OK!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"也許會令人驚訝的是,我們發現看起來有點詭異的第三種模式很有用。通過在ambient聲明文件的頂部只添加單行self-import,可以防止它們污染全局名稱空間:import {} from \".\/\";。這種單行代碼簡化了將第三方聲明(例如lib.dom.d.ts)轉換爲模塊化的操作,並且避免了維護更復雜的fork的麻煩。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"TypeScript團隊似乎並不喜歡第三種模式,因此請儘可能避免使用第三種模式。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"8. 包的封裝可能出問題"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"如前所述(第5節),我們使用未固定的依賴項意味着:對於我們的包來說,不僅要保留運行時兼容性,還要時刻保持類型兼容性,這一點很重要。這是一個挑戰,因此要確保兼容性能保持下去,我們必須深度瞭解哪些類型被公開,並且必須以這種方式加以約束。第一步是明確區分公共模塊與私有模塊。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"Node最近以package.json “exports” 字段的形式獲得了這種能力。它通過顯式列出可從包外部訪問的文件來定義封裝邊界。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"如今,TypeScript尚不瞭解package exports,因此不理解依賴項中的哪些文件被視爲公共或私有的概念。在聲明生成期間,當TypeScript在發射的.d.ts文件中合成import語句以傳遞類型時,這就成爲了一個問題。我們的.d.ts文件引用其他包中的私有文件是不可接受的。下面是一個出錯的例子。"}]},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"\/\/ index.ts\nimport boxMaker from \"another-package\"\nexport const box = boxMaker();"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"以上源可能導致tsc發出以下不良聲明。"}]},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"\/\/ index.d.ts\nexport const box : import(\"another-package\/private\").Box"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"這就不對了,因爲“another-package\/private”不屬於這個包的兼容性保證,因此可以在沒有SemVer重大bump的情況下進行移動或重命名。如今,TypeScript無法知道它生成的是一個脆弱的導入。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"生態系統一致性?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我們使用兩個步驟來緩解這一問題:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我們的工具鏈會向TypeScript解析器通知指向依賴項的,有意公開的裸指示符路徑(例如“lodash\/public1”“lodash\/public2”)。我們在TypeScript文件流入編譯器之前,靜默地將type-only的導入語句添加到TypeScript文件的底部,從而確保TypeScript瞭解全部合法依賴項的入口點。"}]}]}]},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"\/\/ user's source code\n\n\n\/\/ injected by toolchain to assist declaration emit\nimport type * as __fake_name_1 from \"lodash\/public1\";\nimport type * as __fake_name_2 from \"lodash\/public2\";"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"在生成對推斷的傳遞類型的引用時,TypeScript的聲明發射會優先使用這些現有的名稱空間標識符,而不是合成對私有文件的導入。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"numberedlist","attrs":{"start":"2","normalizeStart":"2"},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"如果TypeScript對我們知道是私有的依賴項中的文件生成路徑,則工具鏈會報錯。當TypeScript意識到它正在生成一個依賴項的潛在危險路徑時,也會報錯,這兩種錯誤很像。"}]}]}]},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"error TS2742: The inferred type of '...' cannot be named without a reference to '...'.\nThis is likely not portable. A type annotation is necessary."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"這會通過顯式註解導出來通知用戶解決問題。或者在某些情況下,他們需要直接從公共包入口點導出內部類型來更新依賴項,以公開內部類型。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"生態系統一致性,OK!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我們期待TypeScript獲得對入口點的一等支持,這樣就用不着這種解決方法了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"9. 生成的聲明可以內聯依賴項中的類型"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"程序包需要導出.d.ts聲明,以便用戶可以消費它們。我們選擇使用TypeScript的declaration選項從原始.ts文件生成.d.ts文件。儘管我們可以與常規代碼一起手寫和維護.d.ts兄弟文件,但這種方法不太可取,因爲保持它們同步意味着一種危險。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"在大多數情況下,TypeScript的聲明發射很好用。我們發現的一個問題是,有時TypeScript會將類型從依賴項內聯到生成的類型中(#37151)。這意味着類型定義將被重定位,並可能被複制,而不是通過導入語句進行引用。使用結構化類型時,編譯器不必強制類型是從一個定義站點引用的——這些類型可以複製。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我們還發現了一些極端情況,其中這種複製讓聲明文件從7KB膨脹到了700KB,冗餘代碼實在太多了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"可擴展性?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"包內類型的內聯不是生態系統問題,因爲它在外部不可見。當跨包邊界內聯類型時就出問題了,因爲它將這兩個特定版本耦合在一起。在我們的非固定包系統中,每個包都可以獨立進化。這意味着存在類型不兼容的風險,尤其是類型陳舊的風險。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"生態系統一致性?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"通過實驗,我們發現了防止內聯類型聲明的一些可選方法,例如:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"首選interface而不是type(接口不內聯)"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"如果未導出聲明所需的interface,則tsc將拒絕內聯該類型並生成明顯錯誤(例如,TS4023: Exported variable has or is using name from external module but cannot be named.)"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"如果未導出生成的聲明所需的type,則tsc將靜默內聯該類型"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"Nicholas Jamieson寫了一篇關於優先使用interface而非type的文章,包括了一條ESlint規則"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"使類型nominal(帶有私有成員的enum和class之類的nominal類型不被內聯)"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"將類型註解添加到導出"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"沒有註解,就會內聯"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"用顯式類型註解,我們可以強制引用行爲"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"可擴展性,OK;生態系統一致性,OK"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"這種內聯行爲似乎沒有被嚴格指定。這是聲明文件構造方式的副作用。因此,上述方法將來可能無法使用。我們希望這是可以在TypeScript中形式化的內容。在此之前,我們將依靠用戶培訓來緩解這種風險。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"10. 生成的聲明可以包含非必要依賴項"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"TypeScript聲明文件的消費者通常只關心包的公共類型API。TypeScript聲明發射會爲項目中的每個TypeScript文件恰好生成一個聲明文件。這些內容中某些可能與用戶無關,並且可能會暴露私有的實現細節。這種行爲對於TypeScript的新手來說可能很難想象,他們希望類型是公共API的表示,就像在“Definitely Typed”上找到的手寫類型一樣。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"其中一個示例是:生成的聲明包括僅用於內部測試的函數類型。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"可擴展性?"}]},{"type":"image","attrs":{"src":"https:\/\/static001.infoq.cn\/resource\/image\/95\/50\/95253f4643400bed3813cb87787e2250.jpg","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"由於我們的包系統知道所有公共包的入口點,因此我們的工具鏈可以爬取可達類型的圖,以識別出不需要公開的所有類型。這就是死類型消除(DTE),或更確切地說是搖樹。我們編寫了一個工具來執行這一操作——它"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"只"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"從聲明文件中消除代碼,這樣任務最輕鬆。它不會重寫或重定位代碼——畢竟它不是打包器。這意味着發佈的聲明是TypeScript生成聲明的一個不變子集。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"減少發佈類型的數量有幾個優點:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"它減少了與其他軟件包的耦合(某些軟件包不會從其依賴項中重新導出類型)"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"它防止了完全私有的類型泄漏,從而改善了封裝"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"它減少了需要用戶下載和解壓縮的已發佈聲明文件的數量和大小"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"它減少了TypeScript編譯器在類型檢查時必須解析的代碼量"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"這種“搖樹”會帶來顯著的效果。我們發現,有些包可以刪除90%以上的文件和90%以上的類型代碼行。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"可擴展性,OK!"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"有些選項效果很不錯"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我們在某些tsconfig選項的語義中發現了一些驚喜。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"tsconfig中的baseUrl"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"在TypeScript 4.0中,如果要使用項目引用或“paths”,則還需要指定一個baseUrl。這樣做的副作用是導致所有裸指定符的導入都相對於項目的根目錄進行解析。"}]},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"\/\/ package-a\/main.ts\nimport \"sibling\" \/\/ Will auto-complete and type-check if `package-a\/sibling.js` exists"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"這裏的危險在於,如果要引入任何形式的“paths”,則會帶來額外的含義,使import \"sibling\"被TypeScript意外地解析爲從源目錄內部導入的\/sibling.js。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"標準對齊?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"爲解決問題,我們使用了一個baseUrl。使用null字符可以防止意外的自動完成。我們不建議你在家嘗試。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我們在TypeScript問題跟蹤器上報告了這個issue,很高興看到Andrew在TypeScript 4.1中解決了它,我們可以告別null字符了!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"標準對齊,OK!"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"JSON模塊暗示合成默認導入"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"如果你要使用“resolveJsonModules”,則還必須啓用“useSyntheticDefaultImports”,以便TypeScript將JSON模塊視爲默認導入。將來,使用默認導入可能會成爲Node和Web處理JSON模塊的方式。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"不幸的是,啓用“useSyntheticDefaultImports”會人爲地允許從不具有默認導出的常規ES模塊中默認導入!這是一種危險,你只有在開始運行代碼時纔會發現它,而且它很快就會崩潰。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"標準對齊?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"理想情況下,應該有一種方法可以導入不涉及全局啓用合成默認值的JSON模塊。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"值得稱讚的內容"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"從工具鏈的角度來看,我們在TypeScript中看到的一些出色內容也是值得一提的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"增量構建"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"很有用。TypeScript 3.6對增量構建的API支持給我們帶來了巨大的收益,讓我們可以自定義工具鏈進行快速重建。我們報告了一個incremental與noEmitOnError結合使用時的性能問題後,Sheetal在TypeScript 4.0中解決了它。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"可擴展性,OK!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"“"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"isolatedModules"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"”可以確保我們進行快速的獨立(一進一出)轉譯。TypeScript團隊修復了許多問題來改進這一選項,包括:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"允許isolatedModules的emitDeclaration"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"允許isolatedModules的noEmitOnError"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"聲明必須使用isolatedModules顯式導出類型"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"可擴展性,OK!生態系統一致性,OK!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"項目引用"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"是提供無縫IDE體驗的關鍵所在。我們充分利用它們來進行基於多包工作區的開發工作,開發起來就像單項目一樣流暢。感謝Sheetal爲其帶來的改進,還支持了無文件的“解決方案樣式”tsconfigs。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"可擴展性,OK!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"僅類型導入"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"非常有用。我們在各處都在使用它們,以安全地區分運行時導入和編譯時導入。對於使用“isolatedModules”的某些模式而言它們是必不可少的,還允許我們使用\"importsNotUsedAsValues\": \"error\"來獲得最大安全性。感謝Andrew!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"生態系統一致性,OK!標準對齊,OK!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"“"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"useDefineForClassFields"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"”可以確保我們發射的ESNext代碼不會被重寫,從而保持語言的JS+Types性質。這意味着我們可以原生地使用類字段。感謝Nathan提供了這一特性,使我們的遷移過程更加順利。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"標準對齊,OK!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"TypeScript中的特性交付經常給人驚喜。每次我們意識到自己需要一個特性時,經常發現它已經在下一版本中提供了。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"結論"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"如今,TypeScript是我們應用平臺的一等語言。將TypeScript與另一個運行時集成在一起的過程,證明這種語言和編譯器似乎和JavaScript一樣靈活——它們幾乎都可以在任何地方使用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.infoq.cn\/resource\/image\/d6\/0f\/d615ba1882d5332f0bf06bc28897ba0f.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"雖然我們需要一路學習很多東西,但過程中沒有什麼不可逾越的障礙。當我們需要支持時,社區和TypeScript團隊的反饋讓我們如沐春風。使用共享開源技術的一個明顯好處是,當你遇到問題時,常常會發現自己並不孤單。當你找到答案時,也會分享它們。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"致謝"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"非常感謝Thomas Chetwin、Robin Ricard、Kubilay Kahveci、ScottWhittaker、Daniel Rosenwasser、Nathan Shively-Sanders、Titian Dragomir和Maxwell Heiber的審閱。感謝Orta提供的Twoslash代碼格式。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"原文鏈接:"},{"type":"link","attrs":{"href":"https:\/\/www.techatbloomberg.com\/blog\/10-insights-adopting-typescript-at-scale\/","title":null,"type":null},"content":[{"type":"text","text":"https:\/\/www.techatbloomberg.com\/blog\/10-insights-adopting-typescript-at-scale\/"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章