创建一个TypeScript库,其中包含由应用程序解析的可选依赖项



我已经编写了一个库,发布到我的应用程序使用的私有npm repo。这个库包含实用程序,并且与其他库有依赖关系,例如,让我们选择@aws-sdk/client-lambda

我的一些应用程序只使用了一些实用程序,不需要对外部库的依赖关系,而有些应用程序使用了所有的实用程序。

为了避免所有应用程序都得到很多不需要的间接依赖项,我尝试将依赖项声明为peerDependencies,并让应用程序解析它们需要的依赖项。它可以很好地发布包,并从那些将peerDependencies所有声明为自己的本地dependencies的应用程序中使用它,但当库中包含的.d.ts文件导入应用程序代码时,未能声明其中一个依赖项的应用程序会出现构建错误

error TS2307: Cannot find module '@aws-sdk/client-kms' or its corresponding type declarations.

有没有可能解决这种情况,使我的库可以包含许多不同的utils;樱桃采摘";它们在运行时满足这些实用程序要求所需的依赖关系?我必须使用动态导入才能做到这一点吗?或者还有其他方法吗?

我尝试在库代码中使用@ts-ignore,它被传播到应用程序导入的d.ts文件中,但没有帮助。

设置:

我的库

package.json:

peerDependencies: {
"@aws-sdk/client-lambda": "^3.27.0"
}

foo.ts:

import {Lambda} from '@aws-sdk/client-lambda';
export function foo(lambda: Lambda): void {
...
}

bar.ts:

export function bar(): void {
...
}

index.ts:

export * from './foo';
export * from './bar';

my-application1-运行良好

package.json:

dependencies: {
"my-library": "1.0.0",
"@aws-sdk/client-lambda": "^3.27.0" 
}

测试.ts:

import {foo} from 'my-library';
foo();

my-application2-不编译

package.json:

dependencies: {
"my-library": ...
}

测试:ts:

import {bar} from 'my-library';
bar();

我找到了两种处理方法:

1.仅对可选依赖项使用动态导入

如果您确保包的根文件导出的类型只包括类型和接口,而不包括类等,则传输的JS将不包含任何指向可选库的require语句。然后使用动态导入从函数导入可选库,以便只有当客户端显式使用库的这些部分时才需要它们。在@aws-sdk/client-lambda的情况下,这是我的可选依赖项之一,我想公开一个函数,它可以采用Lambda对象的实例,也可以自己创建一个:

import {Lambda} from '@aws-sdk/client-lambda';
export function foo(options: {lambda?: Lambda}) { 
if (!lambda) {
lambda = new Lambda({ ... });  
}
...
}

由于Lambda是一个类,因此它将作为require语句作为transpiled JS的一部分,因此这不是一个可选的依赖项。因此,我必须1)使导入成为动态的,2)定义一个接口来代替函数参数中的Lambda,以消除包根路径上的require语句。不幸的是,在这种特殊的情况下,AWS SDK没有提供该类实现的任何类型或接口,所以我不得不想出一个最小的类型,比如

export interface AwsClient {
config: {
apiVersion: string;
}
}

当然,如果没有代表Lambda类的类型,您甚至可以使用any

然后是动态导入部分:

export async function foo(options: {lambda?: AwsClient}) { 
if (!lambda) {
const {Lambda} = await import('@aws-sdk/client-lambda');
lambda = new Lambda({ ... });  
}
...
}

使用此代码,包的根路径上不再有任何require('@aws-sdk/client-lambda'),只在foo函数内。只有调用foo函数的客户端的CCD_ 20。

正如您所看到的,这样做的一个副作用是,使用可选库的每个函数都必须是async,因为动态导入返回promise。就我而言,这是可行的,但可能会使事情复杂化。在一个案例中,我有一个非异步函数(比如类构造函数)需要一个可选库,所以我别无选择,只能缓存承诺的导入,然后在从异步成员函数使用时解决它,或者在需要时进行延迟导入。这有可能严重扰乱代码。。。

因此,总结一下:

  • 确保从可选库导入代码的任何代码都放在要使用该功能的客户端调用的函数中
  • 从包根目录中的可选库导入类型是可以的,因为它在传输时被剥离了
  • 如果需要,定义替代类型作为任何类参数的占位符(因为类既是类型又是代码!)
  • Transpile并研究生成的JS,看看根目录中是否有可选库的require语句,如果有,则说明您遗漏了一些内容

请注意,如果使用webpack等,使用动态导入也可能很棘手。如果导入路径是常量,它通常是有效的,但除非您给出webpack提示,否则动态构建路径(await import('@aws-sdk/' + clientName))通常是无效的。这让我困惑了一段时间,因为我在可选的AWS依赖项前面写了一个包装器,但由于这个原因,它根本不起作用。

2.将使用可选依赖项的文件放在包的根文件(即index.ts)未导出的.ts文件中

这意味着想要使用可选功能的客户端必须通过子路径导入这些文件,例如:

import {OptionalStuff} from 'my-library/dist/optional;

这显然不太理想。

在我的情况下,vscode中的typescript IDE无法导入可选类型,因此我使用相对导入路径

// fix: Cannot find module 'windows-process-tree' or its corresponding type declarations
//import type * as WindowsProcessTree from 'windows-process-tree';
import type * as WindowsProcessTree from '../../../../../node_modules/@types/windows-process-tree';
// global variable
let windowsProcessTree: typeof WindowsProcessTree;
if (true) { // some condition
windowsProcessTree = await import('windows-process-tree');
windowsProcessTree.getProcessTree(rootProcessId, tree => {
// ...
});
}

软件包.json

{
"devDependencies": {
"@types/windows-process-tree": "^0.2.0",
},
"optionalDependencies": {
"windows-process-tree": "^0.3.4"
}
}

基于vscode/src/vs/platform/terminal/node/windowsShellHelper.ts

最新更新