发布 TypeScript 包时如何处理可选的对等依赖项?

How do I handle optional peer dependencies when publishing a TypeScript package?

当将 TypeScript 包发布到 npm 时,它提供了接受来自一个对等依赖项或另一个对等依赖项的输入的函数,我该如何定义可选的对等依赖项?

import { ExternalFoo } from 'foo';
import { ExternalBar } from 'bar';

export const customPackage = (source: ExternalFoo | ExternalBar) => {
    /* ... */
}

当缺少两个选项之一时,如何防止使用我的包的人出错?

你的情况是目前TypeScript支持不好的情况

先总结一下你的情况:

  1. foobar 是您的可选依赖项,这意味着您希望您的消费者将其中之一与您的库一起使用。
  2. 您仅使用这些库中的类型信息,这意味着您没有任何代码依赖性并且不想将它们作为 dependencies 添加到您的 package.json
  3. 你的 customPackage 函数是 public.

由于第 3 点,您需要在库类型中包含该类型,这意味着您需要添加 foobar 作为依赖项。这与第 1 点和第 2 点相矛盾。

如果 foobar 的类型来自 DefinitelyTyped(即来自包 @types/foo@types/bar),那么您可以将它们添加为您的 dependenciespackage.json 中。这将解决问题。

如果 foobar 的类型与库本身一起分发,您必须将库作为 dependencies(您不需要)包含在内, 或自己创建 ExternalFooExternalBar 类型的副本。

这意味着您将摆脱对 foobar 的依赖。

另一种方法是仔细查看您的库,看看将 foobar 作为依赖项包含在内是否有任何危害。根据您图书馆的性质,它可能没有您想象的那么糟糕。

就个人而言,我通常会自己声明类型。 JavaScript 是一种动态语言。

从 Typescript 3.8 开始,您可以使用以下语法:

import type { ExternalFoo } from "foo";

因此,如果您只是将该库用于类型信息,您可能不必再将其列为 dependencyoptionalDependency。您可能更愿意将其保留为 peerDependency,这样如果您的用户具有这些依赖项,他们将使用与您的库兼容的版本。当然,添加 devDependency 也很有用。

导入将仅存在于生成的 d.ts 文件中,而不存在于 .js 转译代码中。但是,一个问题是,如果用户没有安装该库,该类型将变为 any,这可能会使您自己的输入有点混乱。例如,如果未安装 foo,您的函数将变为

customPackage = (source: any | ExternalBar) =>
// equivalent to customPackage = (source: any) =>

对于这个特定的类型注释,它很糟糕,因为即使我安装了 bar,它也不会用于该类型检查。所以一种不再依赖该库的方法,但它并没有解决编写类型注释的困难如果没有那种类型,它就不会崩溃

编辑:请查看 如何处理缺失类型。

Reference

这是一个复杂的情况,但我发现在导入用户环境中可能不存在的类型之前添加一个 ts-ignore 是可行的:

// @ts-ignore
import type { Something } from "optional"
import type { SomethingElse } from "required"

那么您仍然可以将软件包添加到 peerDependenciespeerDependenciesMeta 作为可选。

结合所有当前答案,这是我为当前版本的 TypeScript(截至 2021 年底)找到的最佳解决方案:

// @ts-ignore -- optional interface, will gracefully degrade to `any` if `foo` isn't installed
import type { Foo } from "foo";
import type { Bar } from "bar";

// Equates to `Bar` when `foo` isn't installed, `Foo | Bar` when it is
type Argument = any extends Foo ? Bar : (Foo | Bar);

export function customPackage(source: Argument): void {
  ...
}

你可以自己试试看。如果安装了 foo,导出的方法将采用 FooBar 参数,如果没有安装,它将只接受 Bar(而不是 any ).