node.js - 如何在我的环境声明文件中声明全局 NodeJS 变量?

标签 node.js typescript namespaces global declaration

这是对 this one 的后续问题.

鉴于该问题的答案,假设我有以下环境声明文件:

declare namespace Twilio {
    interface IDevice {
        ready(handler: Function): void;
    }

    let Device: IDevice;
}

这在我的应用程序上运行良好 .ts文件,Twilio.Device.ready被完全认可。但是我的单元测试使用 Jest 和 AFAIK 运行,它们在 NodeJS 环境中运行。

作为我测试中过于简化的模拟,我有这个(也是 .ts 文件):
global.Twilio = {
     Device: {
         ready: () => {},
         offline: () => {},
         error: () => {},
     }
};

但是这个Twilio实例不被识别。我可以通过向 .d.ts 添加类似下面的内容来解决这个问题文件:
declare global {
    namespace NodeJS {
        interface Global {
            Twilio: any;
        }
    }
}

export = {}; // This is needed otherwise `declare global` won't work

但是,我不能将这段代码与 declare namespace Twilio 放在同一个文件中。我第一次提到。它们需要在单独的文件中,否则 global.Twilio在我的测试中会被识别,但 Twilio.Device在我的代码上不会。

所以我的问题是,我怎样才能获得 Twilio 的两个实例?在应用程序代码和测试代码中被识别?

作为奖励问题,如果我可以使用 Twilio 就好了。命名空间声明,不知何故,作为 NodeJS 的类型 Twilio全局而不是 any .

编辑:

在与 Richard Seviora 进行了愉快的交谈,讨论了我所有的问题后,我最终得到了以下信息 twilio.d.ts我的项目的文件:
/**
 * Namespace declaration for the Twilio.js library global variable.
 */
declare namespace Twilio {

    type DeviceCallback = (device : IDevice) => void;
    type ConnectionCallback = (connection : IConnection) => void;
    type ErrorCallback = (error : IError) => void;

    interface IConnection {
        parameters: IConnectionParameters;
    }

    interface IConnectionParameters {
        From?: string;
        To?: string;
    }

    interface IDevice {
        cancel(handler: ConnectionCallback): void;
        connect(params: IConnectionParameters): IConnection;
        disconnect(handler: ConnectionCallback): void;
        error(handler: ErrorCallback): void;
        incoming(handler: ConnectionCallback): void;
        offline(handler: DeviceCallback): void;
        ready(handler: DeviceCallback): void;
        setup(token: string, params: ISetupParameters): void;
    }

    interface IError {
        message?: string;
        code?: number;
        connection?: IConnection;
    }

    interface ISetupParameters {
        closeProtection: boolean;
    }

    let Device: IDevice;

}

/**
 * Augment the Node.js namespace with a global declaration for the Twilio.js library.
 */
declare namespace NodeJS {

    interface Global {
        Twilio: {
            // Partial type intended for test execution only!
            Device: Partial<Twilio.IDevice>;
        };
    }

}

希望其他人发现这个问题和理查德在下面的回答很有见地(因为声明文档有点缺乏)。

再次感谢理查德的所有帮助。

最佳答案

这绝对是一个有趣的问题。问题是环境文件断言 Twilio.Device存在,因此像全局声明这样的任何东西都需要考虑到这一点。

结果证明解决起来相当简单。无需扩展声明文件或创建另一个声明文件。鉴于您提供的声明文件,您只需在测试设置中包含以下内容:

namespace Twilio {
    Device = {
        setup: function () { },
        ready: function () { },
        offline: function () { },
        incoming: function () { },
        connect: function (params): Twilio.Connection { return null },
        error: function () { }
    }
}

因为我们声明了let Twilio.Device : IDevice ,我们还允许消费者分配 Twilio.Device在以后的日子。如果我们声明 const Twilio.Device : IDevice,这是不可能的.

然而,我们不能只说:
Twilio.Device = { ... }

因为这样做需要Twilio命名空间存在,这是我们在说 declare namespace Twilio 时断言的情况。 .这个 Typescript 会编译成功,但是编译后的 JS 使用了环境 Twilio 的存在。变量被授予,因此失败。

TypeScript 也不允许你为现有的命名空间赋值,所以我们不能这样做:
const Twilio = {}; // Or let for that matter.
Twilio.Device = {...}

但是,由于 TypeScript 命名空间声明在编译后的 JS 中合并,因此将赋值包装在 Twilio 命名空间中会实例化 Twilio (如果它不存在)然后分配 Device ,同时都遵守环境文件中规定的类型规则。
namespace Twilio {
    Device = {
        // All properties need to be stubbed.
        setup: function () { },
        ready: function () { },
        offline: function () { },
        incoming: function () { },
        connect: function (params): Twilio.Connection { return null },
        error: function () { }
    }
}

beforeEach(() => {
    // Properties/mocks will need to be reset as the namespace declaration only runs once.
    Twilio.Device.setup = () => {return null;};
})

test('adds stuff', () => {
    expect(Twilio.Device.setup(null, null)).toBeNull()
})

这一切都假设您的测试文件可以访问声明文件。如果没有,您需要通过 tsconfig.json 包含它,或通过 /// <references path="wherever"/> 引用它指示。

编辑 1

更正 beforeEach在上面的例子中返回空值。否则测试将失败。

编辑 2 - 扩展 NodeJS 全局声明

我想我应该添加为什么要扩展 NodeJS.Global接口(interface)不是必需的。

当我们进行环境声明时,假定它作为全局上下文(以及任何子闭包)的一部分存在。因此,我们不需要扩展 GlobalTwilio因为 Twilio假定也存在于直接上下文中。

然而,这个特殊的声明方法意味着 global.Twilio不存在。我可以声明:
namespace NodeJS {
    interface Global {
        Twilio : {
            Device : Twilio.IDevice;
        };
    }
}

这将为 NodeJS.Global.Twilio 的 Device 属性提供类型推断。对象,但没有一种方法可以像我们键入的方式一样“共享”命名空间。

编辑 3 - 毕竟全局扩展是必要的

经过交谈,我们得出的结论是扩展NodeJS.Global是必要的,因为 Jest 不会在测试和它们正在执行的类之间共享上下文。

允许我们修改NodeJS.Global接口(interface),我们将以下内容附加到定义文件中:
declare namespace NodeJS {
    interface Global {
        Twilio: { Device: Partial<Twilio.IDevice> }
    }
}

这将启用:
beforeEach(() => {
    global.Twilio =
        {
            Device: {
                setup: function () { return null },
                ready: function () { },
                connect: function (params): Twilio.Connection { return null },
                error: function () { }
            }
        };
})

虽然这意味着 NodeJS.Global.Twilio不会与 Twilio 相同命名空间,它确实允许出于构建测试的目的进行相同的操作。

关于node.js - 如何在我的环境声明文件中声明全局 NodeJS 变量?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42398170/

相关文章:

javascript - 通过 webpack 为 Javascript 导入 QRCode 构造函数

javascript - 创建一个生成器来迭代对象属性排列

typescript - 避免对联合类型进行愚蠢的推断(元素隐式具有 'any' 类型,因为类型的表达式...)

node.js - Sequelize-typescript 'HasManyCreateAssociationMixin' 不是函数

PHP 命名空间未在同一目录中的同一命名空间中找到类 - 在开发中工作,而不是在生产中

ruby-on-rails - Rails 命名空间和反向路由

javascript - Web Audio API Inconsistent Unable to decode audio data DOMException

node.js - Elastic Beanstalk : How to write files to a folder in my node. js项目文件夹

javascript - 在 JavaScript 上将属性从字符串更新为对象而不发生突变

javascript - 命名空间,OOP JS,我这样做对吗?