深圳幻海软件技术有限公司 欢迎您!

Umd 的包如何导出 TS 类型

2023-02-28

在TypeScript里声明模块,最早是用namespace和module的语法,后来支持了esmodule,类型和变量会用import来导入、用export导出。比如你写了一个库,导出的变量叫Guang,它下面有name和age两个属性,所以你是这样声明类型的:复制exportdefaultGua

在 TypeScript 里声明模块,最早是用 namespace 和 module 的语法,后来支持了 es module,类型和变量会用 import 来导入、用 export 导出。

比如你写了一个库,导出的变量叫 Guang,它下面有 name 和 age 两个属性,所以你是这样声明类型的:

export default Guang;

declare namespace Guang {
    export const name = 'guang';
    export const age = '20';
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

使用的时候用 import 来导入:

import Guang from 'xxx';

console.log(Guang.name, Guang.age);
  • 1.
  • 2.
  • 3.

这样是没啥问题。

但如果这个库除了支持 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';
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

这样是能解决问题,但这样在 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';
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

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 吧。