如何在环境声明文件中声明全局NodeJS变量? [英] How to declare global NodeJS variables within my ambient declaration file?

查看:140
本文介绍了如何在环境声明文件中声明全局NodeJS变量?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是对这一个的后续问题.

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

Given the answer on that question, let's say I have the following ambient declaration file:

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

    let Device: IDevice;
}

这在我的应用程序.ts文件上很好地工作了,Twilio.Device.ready被完全识别.但是我的单元测试与Jest和AFAIK一起运行,它们在NodeJS环境中运行.

This is working nicely on my application .ts files, Twilio.Device.ready is fully recognized. But I have my unit tests running with Jest and AFAIK, they run on a NodeJS environment.

作为我测试中的过度简化模拟,我有这个(也是.ts文件):

As an over simplified mock on my tests I have this (which are .ts files too):

global.Twilio = {
     Device: {
         ready: () => {},
         offline: () => {},
         error: () => {},
     }
};

但是无法识别此Twilio实例.我可以通过在.d.ts文件中添加以下内容来解决此问题:

But this Twilio instance is not recognized. I can fix that by adding something like below to a .d.ts file:

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将不被识别.

However, I can't have this piece of code on the same file as the declare namespace Twilio I first mentioned. They need to be on separate files, otherwise global.Twilio on my tests will be recognized but Twilio.Device on my code won't.

所以我的问题是,如何在应用程序代码和测试代码中同时识别两个Twilio实例?

So my question is, how can I get both instances of Twilio to be recognized in the app code and in the tests code?

还有一个额外的问题,如果我能以某种方式使用Twilio命名空间声明作为NodeJS Twilio全局类型而不是any的类型,那就太好了.

And as a bonus question, it would be nice if I could use the Twilio namespace declaration, somehow, as the type of the NodeJS Twilio global instead of any.

与Richard Seviora进行了愉快的交谈,讨论了我所有的问题后,我得到了以下项目的twilio.d.ts文件:

After a nice chat with Richard Seviora, discussing all my issues, I ended up with the following twilio.d.ts file for my project:

/**
 * 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>;
        };
    }

}

希望其他人对此问题和理查德(Richard)的答案很有见地(由于缺少声明文档).

Hopefully others find this question and Richard's answer below insightful (since the declarations documentation is kinda lacking).

再次感谢Richard的所有帮助.

Thanks again Richard for all your assistance.

推荐答案

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

This is definitely an interesting question. The problem is that the ambient file asserts that Twilio.Device exists, and so anything like a global declaration needs to take that into account.

事实证明,解决起来非常简单.无需扩展声明文件或创建另一个文件.给定您提供的声明文件,您需要做的所有事情都包括以下内容:

It turned out being rather simple to solve. There was no need to extend the declaration file or create another one. Given the declaration file you provided, all you need to do include the following in your test setup:

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,这将是不可能的.

Because we declared let Twilio.Device : IDevice, we also permitted consumers to assign Twilio.Device at later date. This would not be possible if we declared const Twilio.Device : IDevice.

但是,我们不能只说:

Twilio.Device = { ... }

因为这样做需要存在Twilio名称空间,这就是我们说declare namespace Twilio时所断言的情况.该Typescript可以成功编译,但是已编译的JS将环境Twilio变量的存在视为已授予,因此失败了.

Because doing so required the Twilio namespace to exist, which is something that we had asserted was the case when we said declare namespace Twilio. This Typescript would compile successfully, but the compiled JS took the existence of the ambient Twilio variable as granted and therefore failed.

TypeScript也不允许您将值分配给现有的名称空间,所以我们不能这样做:

TypeScript also doesn't allow you to assign values to an existing namespace so we couldn't do:

const Twilio = {}; // Or let for that matter.
Twilio.Device = {...}

但是,由于TypeScript名称空间声明合并在已编译的JS中,因此将分配包装在Twilio名称空间中将实例化Twilio(如果尚不存在),然后分配Device,同时遵守所有规定的类型规则在环境文件中.

However, since TypeScript namespace declarations merge in the compiled JS, wrapping the assignment in the Twilio namespace would instantiate Twilio (if it doesn't already exist) and then assign Device, while all respecting the type rules stated in the ambient file.

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"/>指令引用它.

This all assumes that your test files have access to the declaration file. If not you'll need to include it through tsconfig.json, or reference it via /// <references path="wherever"/> directive.

在上面的示例中更正了beforeEach以返回null.否则测试将失败.

Corrected beforeEach in the example above to return null. Otherwise test would fail.

我想我应该补充一下为什么不需要扩展NodeJS.Global接口的原因.

I thought I should add why extending the NodeJS.Global interface isn't necessary.

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

When we make an ambient declaration, it is assumed to exist as part of the global context (and any child closures). Therefore, we don't need to extend Global with Twilio because Twilio is assumed to exist in the immediate context as well.

但是,这种特殊的声明方法意味着global.Twilio不存在.我可以声明:

HOWEVER, this particular declaration method means that global.Twilio does not exist. I can declare:

namespace NodeJS {
    interface Global {
        Twilio : {
            Device : Twilio.IDevice;
        };
    }
}

这将为NodeJS.Global.Twilio对象的Device属性提供类型推断,但是没有一种可以与我们键入相同的方式来共享"命名空间的方法.

And this will provide type inference for the Device property of the NodeJS.Global.Twilio object, but there isn't a way to "share" namespaces in the same way we can types.

交谈后,我们得出结论,扩展NodeJS.Global是必要的,因为Jest不会在测试与它们将在其中执行的类之间共享上下文.

After conversation, we came to the conclusion that extending NodeJS.Global is necessary because Jest does not share context between tests and the classes they're being executed in.

要允许我们修改NodeJS.Global接口,请将以下内容附加到定义文件中:

To allow us to modify the NodeJS.Global interface, we append the following to the definition file:

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命名空间不同,但它确实允许以相同的方式进行操作以构建测试.

While this means that the NodeJS.Global.Twilio will not be identical to the Twilio namespace, it does permit to operate identically for the purpose of constructing tests.

这篇关于如何在环境声明文件中声明全局NodeJS变量?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆