【发布时间】:2021-04-07 07:26:06
【问题描述】:
我正在为我不拥有的名为 fessonia 的库编写类型定义。我有一些这样做的经验,但是这个库的组织方式与我合作过的其他库不同,我不知道如何处理它。
这个库的index.js 很小:
const getFessonia = (opts = {}) => {
require('./lib/util/config')(opts);
const Fessonia = {
FFmpegCommand: require('./lib/ffmpeg_command'),
FFmpegInput: require('./lib/ffmpeg_input'),
FFmpegOutput: require('./lib/ffmpeg_output'),
FilterNode: require('./lib/filter_node'),
FilterChain: require('./lib/filter_chain')
};
return Fessonia;
}
module.exports = getFessonia;
它导出一个返回对象的函数,该对象的每个成员都是一个类。 (到目前为止,我在 lib 中遇到的每个文件都使用默认导出。)我从 module function template 开始,但我很难在我的一些指导原则之间找到和谐:
-
类型定义应该促进使用这个库的最佳实践。我的意思是我不会费心为标记为
@private或不打算/不推荐的方法创建定义供外用。根据库文档,getFessonia是该库的唯一公共接口。虽然没有什么可以阻止开发人员直接导入FFmpegCommand,但不应该这样做(因为,例如,在getFessonia中设置的配置不会被设置,并且可能会导致错误)。李> - 类型定义应该很有用。下游开发人员应该能够为他们的变量分配类型:
import getFessonia from '@tedconf/fessonia';
// note the type assignment
const config: getFessonia.ConfigOpts = {
debug: true,
};
const { FFmpegCommand, FFmpegInput, FFmpegOutput } = getFessonia(config);
- 声明文件的布局应反映库的布局。根据official recommendation。
到目前为止,我采用的方法是为每个 .js 文件创建有用的类型定义所需的 .d.ts 文件,然后将它们导入 index.d.ts 并根据需要在 getFessonia 命名空间中重新导出.例如,为了为opts 参数提供类型定义,我需要阅读lib/util/config,它有一个默认导出getConfig。它的类型文件最终看起来像这样:
import getLogger from './logger';
export = getConfig;
/**
* Get the config object, optionally updated with new options
*/
declare function getConfig(options?: Partial<getConfig.Config>): getConfig.Config;
declare namespace getConfig {
export interface Config {
ffmpeg_bin: string;
ffprobe_bin: string;
debug: boolean;
log_warnings: boolean;
logger: getLogger.Logger;
}
}
...我在index.d.ts 中这样使用它:
import getConfig from './lib/util/config';
export = getFessonia;
/**
* Main function interface to the library. Returns object of classes when called.
*/
declare function getFessonia(opts?: Partial<getFessonia.ConfigOpts>): getFessonia.Fessonia;
declare namespace getFessonia {
export interface Fessonia {
// TODO
FFmpegCommand: any;
FFmpegInput: any;
FFmpegOutput: any;
FilterNode: any;
FilterChain: any;
}
// note I've just aliased and re-exported this
export type ConfigOpts = Partial<getConfig.Config>;
}
我认为我可能走错路的原因:
- 我认为我不需要定义函数
getConfig,尤其是因为我不想推广它的直接使用。lib/util/config有默认导出是否重要?我是否应该直接导出Config接口并从index.d.ts重新导出?或者我可能会删除函数定义并将Config接口保留在命名空间下;这样,如果getConfig将来成为公共函数,我可以添加函数的定义。 - 在
getFessonia命名空间下重新导出很乏味,而且不是特别优雅。 - 我可能会在
getFessonia下得到很多嵌套(和别名)。例如,FFmpegOutput的构造函数接受一个参数,该参数实际上只是内部类FFmpegOption的参数映射,因此下游代码可能最终看起来像这样:
import getFessonia from '@tedconf/fessonia';
const { FFmpegCommand, FFmpegInput, FFmpegOutput } = getFessonia();
// note the deep nesting
const outputOptions: getFessonia.FFmpeg.Output.Options = { /* some stuff */ };
const output = new FFmpegOutput('some/path', outputOptions);
- 将
getFessonia的参数定义和FFmpegOutput的形状定义为兄弟关系不是很直观。 - 出于组织/命名冲突避免原因,我正在编造
FFmpeg命名空间。
你做到了!感谢您阅读本文。虽然我怀疑没有一个“正确”的答案,但我期待阅读其他人采用的方法,我很高兴有人指出我可以通过示例学习的文章或相关代码存储库。谢谢!
【问题讨论】:
-
您确定要在此处使用命名空间吗?
import getFessonia, { FessoniaConfigOptions } from 'fessionia'会工作吗?更扁平的类型声明通常更容易管理。 -
不,我不确定!您描述的方法有效,尽管我可以发誓我之前尝试过并且得到了编译错误。对于交流子类的形状、它们的参数等,你有什么建议吗?我应该在单独的文件中定义它们,然后将它们导入 index.d.ts 并重新导出它们吗?
-
正如你所说,你可以通过很多不同的方式来解决这个问题。你在这里也有很多问题,所以你可能想把它们分开。您可以声明函数(例如
getConfig而不导出它们。您还可以出于实用目的导出类型,例如export type FFmpegOutputOptions = getFessonia.FFmpeg.Output.Options。
标签: typescript type-definition