Umd 的包如何匯出 TS 型別

語言: CN / TW / HK

在 TypeScript 裡宣告模組,最早是用 namespace 和 module 的語法,後來支援了 es module,型別和變數會用 import 來匯入、用 export 匯出。

比如你寫了一個庫,匯出的變數叫 Guang,它下面有 name 和 age 兩個屬性,所以你是這樣宣告型別的:

export default Guang;

declare namespace Guang {
    export const name = 'guang';
    export const age = '20';
}

使用的時候用 import 來匯入:

import Guang from 'xxx';

console.log(Guang.name, Guang.age);

這樣是沒啥問題。

但如果這個庫除了支援 es module 的方式使用,還支援 umd 呢?

UMD 規範想必大家很熟悉了,就是判斷是 CMD、AMD 還是全域性變數的方式,然後用合適的模組規範匯出模組的值:

但這裡面不包含 es module,因為它不是 api 而是語法。

那如果你構建出了 umd 規範的程式碼,使用者用 script 的方式給引入了:

這樣還能做型別提示和檢查麼?

不能了,因為你匯出是用的 esm 的 export,只有 import 引入才會有型別提示和對應的檢查。

那怎麼辦呢?

用 declare global 宣告為全域性型別?

declare global {
    namespace Guang {
        export const name = 'guang';
        export const age = '20';
    }
}

這樣是能解決問題,但這樣在 esm 模組裡也不用 import 就可以直接用了,而我們想在 esm 裡用 import,其他情況才用全域性型別。

有啥方式能約束在 esm 裡只能 import 用,但是其他地方可以做為全域性型別呢?

TypeScript 專門為這種情況設計個了語法,叫 export as namespace xxx;

比如上面的程式碼可以這樣寫:

export = Guang;
export as namespace Guang;

declare namespace Guang {
    export const name = 'guang';
    export const age = '20';
}

export = Guang 是相容老的 ts import 語法的,支援 umd 得加上這一行,然後加上 export as namespace Guang;

這樣你在非 esm 裡就可以通過全域性型別的方式使用它了:

而在 esm 裡,如果也是這樣用的,它會報錯:

說是你在 esm 模組裡用了一個 UMD 的 global 型別,建議用 import 的方式代替。

如果你用 import 的方式引入了這個型別,就不報錯了:

這就是它比 declare global 好的地方,可以約束在 esm 裡用 import 引入,非 es module 可以作為全域性型別。

這樣就完美相容了 esm 和 umd 兩種模組引入方式。

而且如果你不想要這種限制,也可以在 tsconfig.json 裡關掉。

有個 allowUmdGlobalAccess 的編譯選項就是控制是否支援在 es module 裡使用 UMD 全域性型別的:

預設是 false,開啟以後在 es module 裡使用 UMD 全域性型別就不報錯了:

很多庫都需要相容 esm 和 umd 的使用方式都會這樣用,比如 react:

所以,如果你開發的庫需要支援 esm 和 umd 的話,可以用 export namespace as xxx 來匯出,會比 declare global 更好。

總結

現在 TypeScript 的模組都是 es module 的方式引入的,但有一些包是支援 umd 的,它們可能用各種方式引入模組,為了實現 umd 模組的型別檢查,可以用 declare global 把匯出的變數變為全域性的。

但是在 es module 裡還是希望使用 import 引入,非 es module 才用全域性型別,所以更好的方式是使用 export as namespace xxx。

用這種方式宣告的型別,當在非 esm 中使用時,會作為全域性型別,而在 esm 中如果直接引用全域性型別會報錯,建議用 import 引入。這是它比 declare global 更好的地方。

當然,也可以把 allowUmdGlobalAccess 的編譯選項設定為 true 來放開這種約束。

像 react 這種支援 umd 的庫都是用這種方式匯出型別的,如果你也要開發一個支援 umd 的庫,不妨也試試 export as namespace 吧。