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

问题描述 投票:0回答:5

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

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

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

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

typescript npm publish
5个回答
15
投票

从 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
,它也不会用于该类型检查。因此,有一种方法可以不再依赖于该库,但它并不能解决编写类型注释的困难,如果该类型不存在,这些注释也不会崩溃

编辑:请查看这个答案,了解如何处理缺失的类型。

参考


7
投票

结合当前所有答案,这是我为当前版本的 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
)。


6
投票

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

先总结一下你的情况:

  1. foo
    bar
    是您的可选依赖项,这意味着您希望您的消费者将其中之一与您的库一起使用。
  2. 您仅使用这些库中的类型信息,这意味着您没有任何代码依赖性,并且不想将它们添加为
    dependencies
    中的
    package.json
  3. 您的
    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 首先是一种动态语言。


2
投票

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

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

然后您仍然可以将包添加到

peerDependencies
peerDependenciesMeta
作为可选。


1
投票

背景

当您在通过包的主入口点(即

./index.ts
)公开的文件中使用可选依赖项时,您总是会收到错误。

解决方案

将代码拆分为多个入口点。在您的示例中,您将有一个主入口点,然后是用户必须显式导入的两个可选模块

FooModule
BarModule

代码

这显示了您的包定义和库的使用(导入)。

库的package.json

您的 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"
      ]
    }
  },
}
在客户端代码中使用 my-package

由于客户端仅导入

FooModule
,因此不需要安装
BarModule
的可选依赖项。

import { MyPackage } from 'my-package';
import { FooModule } from 'my-package/foo';

const myPackage = new MyPackage({ module: new FooModule() })
© www.soinside.com 2019 - 2024. All rights reserved.