将 TypeScript 包发布到 npm 并提供接受来自一个对等依赖项或另一个对等依赖项的输入的函数时,如何定义可选对等依赖项?
import { ExternalFoo } from 'foo';
import { ExternalBar } from 'bar';
export const customPackage = (source: ExternalFoo | ExternalBar) => {
/* ... */
}
当两个选项之一缺失时,如何防止使用我的包的人出现错误?
从 Typescript 3.8 开始,您可以使用以下语法:
import type { ExternalFoo } from "foo";
因此,如果您只是使用该库来获取类型信息,则可能不必再将其列为
dependency
或 optionalDependency
。您可能更愿意将其保留为 peerDependency
,这样如果您的用户有这些依赖项,他们将使用与您的库兼容的版本。当然,添加为 devDependency
也很有用。
导入将仅存在于生成的
d.ts
文件中,而不存在于 .js
转译代码中。但有一个问题是,如果用户没有安装该库,该类型就会变成 any
,这可能会稍微扰乱您自己的输入。例如,如果未安装 foo
,您的功能将变为
customPackage = (source: any | ExternalBar) =>
// equivalent to customPackage = (source: any) =>
对于这个特定的类型注释,它很糟糕,因为即使我安装了
bar
,它也不会用于该类型检查。因此,有有一种方法可以不再依赖于该库,但它并不能解决编写类型注释的困难,如果该类型不存在,这些注释也不会崩溃。
编辑:请查看这个答案,了解如何处理缺失的类型。
结合当前所有答案,这是我为当前版本的 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
,则导出的方法将采用 Foo
或 Bar
参数,如果未安装,则仅接受 Bar
(而不是 any
)。
你的情况是目前TypeScript支持不好的情况。
先总结一下你的情况:
foo
和 bar
是您的可选依赖项,这意味着您希望您的消费者将其中之一与您的库一起使用。dependencies
中的 package.json
customPackage
功能是公开的。由于第 3 点,您需要在库类型中包含该类型,这意味着您需要添加
foo
和 bar
作为依赖项。这与第 1 点和第 2 点相矛盾。
如果
foo
和bar
的类型来自DefinitelyTyped(即来自包@types/foo
和@types/bar
),那么您可以将它们添加为dependencies
中的package.json
。这样问题就解决了。
如果
foo
和 bar
的类型与库本身一起分发,则必须将库包含为 dependencies
(这是你不想要的),
或者自己创建 ExternalFoo
和 ExternalBar
类型的复制品。
这意味着您将不再依赖
foo
和 bar
。
另一种方法是仔细查看您的库,看看将
foo
和 bar
作为依赖项包含在内是否有任何危害。根据您的图书馆的性质,它可能没有您想象的那么糟糕。
就我个人而言,我通常会自己声明类型。 JavaScript 首先是一种动态语言。
这是一个复杂的情况,但我发现有效的是在导入用户环境中可能不存在的类型之前添加 ts-ignore:
// @ts-ignore
import type { Something } from "optional"
import type { SomethingElse } from "required"
然后您仍然可以将包添加到
peerDependencies
和 peerDependenciesMeta
作为可选。
当您在通过包的主入口点(即
./index.ts
)公开的文件中使用可选依赖项时,您总是会收到错误。
将代码拆分为多个入口点。在您的示例中,您将有一个主入口点,然后是用户必须显式导入的两个可选模块
FooModule
和 BarModule
。
这显示了您的包定义和库的使用(导入)。
您的 package.json 定义了多个入口点 (
exports
) 以及各自的类型定义 (typesVersions
)。
{
"name": "my-package"
"version": "1.0.0",
"main": "./dist/index.js",
"exports": {
".": "./dist/index.js",
"./foo": "./dist/modules/foo/index.js",
"./bar": "./dist/modules/bar/index.js"
},
"typesVersions": {
"*": {
"*": [
"dist/index.d.ts"
],
"foo": [
"dist/modules/foo/index.d.ts"
],
"bar": [
"dist/modules/bar/index.d.ts"
]
}
},
}
由于客户端仅导入
FooModule
,因此不需要安装 BarModule
的可选依赖项。
import { MyPackage } from 'my-package';
import { FooModule } from 'my-package/foo';
const myPackage = new MyPackage({ module: new FooModule() })