返回表示式中分支的細粒度檢查
考慮以下程式碼:
tsdeclare const untypedCache: Map<any, any>;function getUrlObject(urlString: string): URL {return untypedCache.has(urlString) ?untypedCache.get(urlString) :urlString;}
這段程式碼的意圖是從快取中檢索 URL 物件(如果存在),或者在不存在時建立一個新的 URL 物件。然而,這裡有一個錯誤:我們忘記了根據輸入真正構造一個新的 URL 物件。遺憾的是,TypeScript 通常不會捕獲這類錯誤。
當 TypeScript 檢查像 cond ? trueBranch : falseBranch 這樣的條件表示式時,其型別被視為兩個分支型別的聯合。換句話說,它獲取 trueBranch 和 falseBranch 的型別,並將它們合併為一個聯合型別。在本例中,untypedCache.get(urlString) 的型別為 any,而 urlString 的型別為 string。這就是問題所在,因為 any 在與其他型別互動時具有極強的感染力。聯合型別 any | string 會被簡化為 any,因此當 TypeScript 開始檢查 return 語句中的表示式是否與預期的 URL 返回型別相容時,型別系統已經丟失了本可以捕獲該錯誤的任何資訊。
在 TypeScript 5.8 中,型別系統對位於 return 語句內的條件表示式進行了特殊處理。條件表示式的每個分支都會根據包含函式的宣告返回型別(如果存在)進行檢查,因此型別系統可以捕獲上述示例中的錯誤。
tsdeclare const untypedCache: Map<any, any>;function getUrlObject(urlString: string): URL {return untypedCache.has(urlString) ?untypedCache.get(urlString) :urlString;// ~~~~~~~~~// error! Type 'string' is not assignable to type 'URL'.}
此項更改是在 此 Pull Request 中完成的,作為 TypeScript 未來更廣泛改進的一部分。
在 --module nodenext 中支援 require() ECMAScript 模組
多年來,Node.js 一直在 CommonJS 模組之外支援 ECMAScript 模組 (ESM)。遺憾的是,兩者之間的互操作性存在一些挑戰。
- ESM 檔案可以
importCommonJS 檔案 - CommonJS 檔案 不能
require()ESM 檔案
換句話說,從 ESM 檔案消費 CommonJS 檔案是可行的,反之則不然。這為想要提供 ESM 支援的庫作者帶來了許多挑戰。這些庫作者要麼必須破壞與 CommonJS 使用者的相容性,要麼“雙重發布”其庫(為 ESM 和 CommonJS 提供獨立的入口點),或者乾脆無限期地留在 CommonJS 上。雖然雙重發布聽起來像是一個很好的折中方案,但它是一個複雜且容易出錯的過程,還會使包中的程式碼量大約增加一倍。
Node.js 22 放寬了一些限制,允許從 CommonJS 模組 require("esm") 呼叫 ECMAScript 模組。Node.js 仍然不允許對包含頂層 await 的 ESM 檔案使用 require(),但現在大多數其他 ESM 檔案都可以從 CommonJS 檔案中進行消費。這為庫作者提供了一個無需雙重發布即可提供 ESM 支援的重要機會。
TypeScript 5.8 在 --module nodenext 標誌下支援此行為。啟用 --module nodenext 後,TypeScript 將不會再針對這些 require() ESM 檔案的呼叫發出錯誤。
由於此功能可能會向後移植到舊版本的 Node.js,目前還沒有穩定的 --module nodeXXXX 選項來啟用此行為;但是,我們預計未來的 TypeScript 版本或許能在 node20 下穩定此功能。與此同時,我們鼓勵 Node.js 22 及更高版本的使用者使用 --module nodenext,而庫作者和舊版本 Node.js 的使用者應繼續使用 --module node16(或進行微小的更新以使用 --module node18)。
更多資訊,請參閱我們關於 require(“esm”) 的支援說明。
--module node18
TypeScript 5.8 引入了穩定的 --module node18 標誌。對於固定使用 Node.js 18 的使用者,此標誌提供了一個穩定的參考點,且不會包含 --module nodenext 中的某些行為。具體來說:
- 在
node18下禁止require()ECMAScript 模組,但在nodenext下是允許的。 - 匯入斷言(import assertions,現已廢棄,改為支援匯入屬性 import attributes)在
node18下是允許的,但在nodenext下是不允許的。
詳情請參閱 --module node18 的 Pull Request 以及 對 --module nodenext 所做的更改。
--erasableSyntaxOnly 選項
最近,Node.js 23.6 取消了直接執行 TypeScript 檔案的實驗性支援的標誌;然而,此模式僅支援特定的語法結構。Node.js 取消了一個名為 --experimental-strip-types 的模式標誌,該模式要求任何 TypeScript 特有的語法都不能具有執行時語義。換句話說,必須能夠輕鬆地從檔案中“擦除”或“剝離”任何 TypeScript 特有的語法,並留下一個有效的 JavaScript 檔案。
這意味著以下結構是不支援的:
enum宣告- 包含執行時程式碼的
namespace和module - 類中的引數屬性 (parameter properties)
- 非 ECMAScript 的
import =和export =賦值
以下是一些無效示例:
ts// ❌ error: An `import ... = require(...)` aliasimport foo = require("foo");// ❌ error: A namespace with runtime code.namespace container {}// ❌ error: An `import =` aliasimport Bar = container.Bar;class Point {// ❌ error: Parameter propertiesconstructor(public x: number, public y: number) { }}// ❌ error: An `export =` assignment.export = Point;// ❌ error: An enum declaration.enum Direction {Up,Down,Left,Right,}
類似的工具,如 ts-blank-space 或 Amaro(Node.js 中型別剝離的底層庫)具有相同的侷限性。如果這些工具遇到不符合要求的程式碼,它們會提供有用的錯誤資訊,但你仍然只有在嘗試執行程式碼時才會發現它無法工作。
這就是為什麼 TypeScript 5.8 引入了 --erasableSyntaxOnly 標誌的原因。啟用此標誌後,TypeScript 將對大多數具有執行時行為的 TypeScript 特有結構報錯。
tsclass C {constructor(public x: number) { }// ~~~~~~~~~~~~~~~~// error! This syntax is not allowed when 'erasableSyntaxOnly' is enabled.}}
通常情況下,你會希望將此標誌與 --verbatimModuleSyntax 結合使用,後者可確保模組包含適當的匯入語法,並且不會發生匯入省略。
更多資訊,請參閱此處的實現。
--libReplacement 標誌
在 TypeScript 4.5 中,我們引入了用自定義檔案替換預設 lib 檔案的可能性。這是基於從名為 @typescript/lib-* 的包中解析庫檔案的能力。例如,你可以透過以下 package.json 將你的 dom 庫鎖定到特定版本的 @types/web 包。
json{"devDependencies": {"@typescript/lib-dom": "npm:@types/web@0.0.199"}}
安裝後,應該存在一個名為 @typescript/lib-dom 的包,目前 TypeScript 在設定中隱含 dom 時總是會查詢它。
這是一個強大的功能,但也會帶來一些額外的工作。即使你不使用此功能,TypeScript 也會始終執行此查詢,並且必須監視 node_modules 中的變化,以防 lib 替換包開始出現。
TypeScript 5.8 引入了 --libReplacement 標誌,允許你停用此行為。如果你不使用 --libReplacement,現在可以透過 --libReplacement false 停用它。將來 --libReplacement false 可能會成為預設設定,因此如果你目前依賴此行為,應考慮透過 --libReplacement true 顯式啟用它。
更多資訊,請參閱此處的更改。
宣告檔案中保留的計算屬性名
為了使計算屬性在宣告檔案中的輸出更具可預測性,TypeScript 5.8 將始終在類中的計算屬性名中保留實體名稱(bareVariables 和 dotted.names.that.look.like.this)。
例如,考慮以下程式碼:
tsexport let propName = "theAnswer";export class MyClass {[propName] = 42;// ~~~~~~~~~~// error!// A computed property name in a class property declaration must have a simple literal type or a 'unique symbol' type.}
以前版本的 TypeScript 在為此模組生成宣告檔案時會報錯,生成的最佳努力宣告檔案會產生一個索引簽名。
tsexport declare let propName: string;export declare class MyClass {[x: string]: number;}
在 TypeScript 5.8 中,上述示例程式碼現在被允許,且生成的宣告檔案將與你編寫的程式碼相匹配:
tsexport declare let propName: string;export declare class MyClass {[propName]: number;}
請注意,這不會在類上建立靜態命名的屬性。你最終得到的仍然是類似於 [x: string]: number 的索引簽名,因此對於該用例,你需要使用 unique symbol 或字面量型別。
請注意,編寫此類程式碼在 --isolatedDeclarations 標誌下過去是、現在仍然是錯誤的;但我們預計得益於此更改,計算屬性名通常可以在宣告輸出中被允許。
請注意,使用 TypeScript 5.8 編譯的檔案有可能會(儘管可能性極小)生成在 TypeScript 5.7 或更早版本中不向後相容的宣告檔案。
更多資訊,請參閱實現的 PR。
程式載入和更新的最佳化
TypeScript 5.8 引入了多項最佳化,既能縮短構建程式的時間,也能改進在 --watch 模式或編輯器場景下基於檔案更改更新程式的時間。
首先,TypeScript 現在 避免了在規範化路徑時涉及的陣列分配。通常,路徑規範化涉及將路徑的每一部分分割成字串陣列,根據相對段規範化結果路徑,然後使用規範的分隔符將它們重新連線在一起。對於檔案眾多的專案,這可能會產生大量重複的工作。TypeScript 現在避免了分配陣列,而是更直接地對原始路徑的索引進行操作。
此外,當進行的編輯不改變專案的基本結構時,TypeScript 現在避免重新驗證提供給它的選項(例如 tsconfig.json 的內容)。這意味著,例如,簡單的編輯可能不需要檢查專案的輸出路徑是否與輸入路徑衝突,而是可以使用上次檢查的結果。這應該能使大型專案中的編輯感覺更加靈敏。
顯著的行為更改
本節重點介紹了一組值得注意的更改,在進行任何升級時都應予以確認和理解。有時它會突出顯示棄用、移除和新的限制。它也可能包含功能上有所改進但透過引入新錯誤而可能影響現有構建的錯誤修復。
lib.d.ts
為 DOM 生成的型別可能會對程式碼庫的型別檢查產生影響。更多資訊,請檢視與此版本 TypeScript 的 DOM 和 lib.d.ts 更新相關的已關聯問題。
--module nodenext 下對匯入斷言的限制
匯入斷言(Import assertions)是 ECMAScript 的一項提案,旨在確保匯入的某些屬性(例如“此模組是 JSON,不打算作為可執行 JavaScript 程式碼”)。它們後來被重新設計為一個名為 匯入屬性 (import attributes) 的提案。作為轉型的一部分,它們將使用的 assert 關鍵字改為了 with 關鍵字。
ts// An import assertion ❌ - not future-compatible with most runtimes.import data from "./data.json" assert { type: "json" };// An import attribute ✅ - the preferred way to import a JSON file.import data from "./data.json" with { type: "json" };
Node.js 22 不再接受使用 assert 語法的匯入斷言。因此,當在 TypeScript 5.8 中啟用 --module nodenext 時,如果遇到匯入斷言,TypeScript 將發出錯誤。
tsimport data from "./data.json" assert { type: "json" };// ~~~~~~// error! Import assertions have been replaced by import attributes. Use 'with' instead of 'assert'
更多資訊,請參閱此處的更改。