【问题标题】:Wrap a problematic npm package, while maintaining type information包装有问题的 npm 包,同时维护类型信息
【发布时间】:2022-01-17 15:22:30
【问题描述】:

问题

我们正在编写一个 npm 包,其中包含将在各种(内部)网站中使用的 React 组件。我们不得不在我们的 react .tsx 文件中使用一个有问题的 npm 包依赖项,它有以下问题:

  1. 尽管有 .d.ts 文件,但它没有公开任何有用的类型...它们是空的。
  2. 它会在需要或导入服务器端时尝试运行,而不是等待直到被调用,因此我们必须避免顶级导入,而是执行if (window) { const module = require('package-name') },然后仅在该块内使用它。
  3. 这是一个常见的错误来源,因此该库中的所有内容都需要在 try ... catch 块内运行。

好吧,至少我们有类型

我们已经创建了自己的类型文件来解决问题 #1:

// problematic-package-types.d.ts

declare module 'problematic-package' {
  function doErrorProneButNecessaryThing(
    foo: Record<string, unknown>,
    bar: string
  ): void
}

所需的解决方案

长期解决方案是修复这个有问题的库,我们正在研究如何完成(但它不在我们的直接控制范围内)。

不过,在短期内,我们现在需要一个解决方案。

请注意,我们在我们的 npm 包捆绑器中配置动态需求以仅在使用时导入它们,而不是像其他导入/需求一样对待它们。由于我们的包在其他应用程序中使用,我们无法完全控制该应用程序捆绑的工作方式或何时需要组件,因此我们的组件可能最终在不应该被需要时被服务器端需要,我们必须容忍它。我们仍在了解这方面的某些方面。

我的狂野(但失败)刺

我的目标是做一些像这样更 DRY 的事情,我们解决强类型、检测服务器端执行和什么都不做以及添加错误处理这三个问题:

// hoping to leverage our module declaration above without importing anything
import type * as ProblematicPackage from 'problematic-package'
import wrapProblematicRequire from '../utils/my-sanity-preserving-module'
const wrappedProblematicPackage = wrapProblematicRequire<ProblematicPackage>()

// then later...

const foo: Record<string, unknown> = { property1: 'yes', property2: false }
const bar = 'yodeling'

wrappedProblematicPackage.invokeIfWindowReady(
  'doErrorProneButNecessaryThing',
   foo,
   bar
)

但是,TypeScript 不喜欢 import type,这很遗憾:

不能使用命名空间“ProblematicPackage”作为类型。

恳求

如何获取我们放入 problematic-package-types.d.ts 的类型信息以根据需要使用?

或其他任何东西。老实说,我对任何事情都持开放态度,无论多么粗俗或骇人听闻,只要我们在呼叫站点获得一些清晰和可靠的信息,并提供所描述的完整类型信息。建议/建议?

详细信息

这是wrapProblematicRequire 函数的完整实现。我没有测试过。这可能很糟糕。我敢肯定它可能会好得多,但我现在没有时间让这个帮助模块超级干净。 (我处理函数类型信息的尝试不太正确。)

type Func = (...args: any[]) => any
type FunctionNames<T, TName extends keyof T> = T[TName] extends Func ? TName : never
type FunctionNamesOf<T> = FunctionNames<T, keyof T>

const wrapProblematicRequire = <T>(packageName: string) => ({
  invokeIfWindowReady<TName extends FunctionNamesOf<T>>(
    name: T[TName] extends Func ? TName : never,
    ...args: T[TName] extends Func ? Parameters<T[TName]> : never
  ): T[TName] extends Func ? ReturnType<T[TName]> : never {
    if (!window) {
      // @ts-ignore
      return undefined
    }
    try {
      // @ts-ignore
      return require(packageName)[name] as T[TName](...args)
    } catch (error: unknown) {
      // ToDo: Log errors
      // @ts-ignore
      return undefined
    }
  }
})

export default wrapProblematicRequire

附: await import('problematic-package') 似乎没有用。是的,问题比比皆是。

【问题讨论】:

    标签: node.js reactjs typescript


    【解决方案1】:

    不能使用命名空间“ProblematicPackage”作为类型。

    嗯,你可以得到typeof那个命名空间,这似乎是你想要的。

    为了测试这一点,我设置了以下内容:

    // problem.js
    export function doErrorProneButNecessaryThing(n) {
      return n;
    }
    export function doErrorProneButNecessaryThing2(s) {
      return s;
    }
    
    console.log('did side effect');
    
    // problem.d.ts
    export function doErrorProneButNecessaryThing(n: number): number;
    export function doErrorProneButNecessaryThing2(s: string): string;
    

    现在你可以这样做了:

    import type * as ProblemNs from './problem';
    
    type Problem = typeof ProblemNs;
    
    // works
    type A = Problem['doErrorProneButNecessaryThing'] // type A = (n: number) => number
    

    然后wrapProblematicRequire 函数只是将函数的名称作为泛型,为其拉取参数,并拉取返回类型。

    const wrapProblematicRequire = <TName extends FunctionNamesOf<Problem>>(
      name: TName,
      ...args: Parameters<Problem[TName]>
    ): ReturnType<Problem[TName]> | undefined => {
      if (!window) return;
    
      const problem = require('./problem'); // type is any, but types are enforced above
    
      try {
        return problem[name](...args);
      } catch (err) {
        console.log('error!');
      }
    };
    

    这里require('./problem') 返回any 类型,但只要typeof ProblemNs 可以信任,泛型就会保证所有密钥的安全。

    现在来测试一下:

    console.log('start');
    const result: number = wrapProblematicRequire(
      'doErrorProneButNecessaryThing',
      123
    );
    console.log('end');
    

    哪些日志:

    start
    did side effect
    end
    

    这似乎有效!

    Codesandbox

    【讨论】:

    • 我永远不会想到typeof (NameSpace) 会起作用。我会用这个做实验。谢谢!
    猜你喜欢
    • 1970-01-01
    • 2023-03-27
    • 2016-03-13
    • 1970-01-01
    • 1970-01-01
    • 2021-12-23
    • 2011-04-16
    • 1970-01-01
    • 2017-11-05
    相关资源
    最近更新 更多