简体   繁体   English

如何在环境声明文件中声明全局NodeJS变量?

[英]How to declare global NodeJS variables within my ambient declaration file?

This is a follow-up question to this one . 这是一个后续问题这一个

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

This is working nicely on my application .ts files, Twilio.Device.ready is fully recognized. 这在我的应用程序.ts文件上运行良好,可以完全识别Twilio.Device.ready But I have my unit tests running with Jest and AFAIK, they run on a NodeJS environment. 但是我的单元测试与Jest和AFAIK一起运行,它们在NodeJS环境中运行。

As an over simplified mock on my tests I have this (which are .ts files too): 作为我测试中的过度简化模拟,我有这个(也是.ts文件):

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

But this Twilio instance is not recognized. 但是无法识别此Twilio实例。 I can fix that by adding something like below to a .d.ts file: 我可以通过在.d.ts文件中添加以下内容来解决此问题:

declare global {
    namespace NodeJS {
        interface Global {
            Twilio: any;
        }
    }
}

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

However, I can't have this piece of code on the same file as the declare namespace Twilio I first mentioned. 但是,我无法将这段代码与我第一次提到的declare namespace Twilio放在同一个文件中。 They need to be on separate files, otherwise global.Twilio on my tests will be recognized but Twilio.Device on my code won't. 它们必须位于单独的文件中,否则为global.Twilio 。我的测试中的global.Twilio将被识别,但我的代码中的Twilio.Device无法被识别。

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两个实例?

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 . 作为一个额外的问题,如果我能以某种方式使用Twilio名称空间声明作为NodeJS Twilio全局类型而不是any的类型,那就太好了。

EDIT: 编辑:

After a nice chat with Richard Seviora, discussing all my issues, I ended up with the following twilio.d.ts file for my project: 与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>;
        };
    }

}

Hopefully others find this question and Richard's answer below insightful (since the declarations documentation is kinda lacking). 希望其他人可以在下面有洞察力的地方找到这个问题和理查德的答案(因为缺少声明文档)。

Thanks again Richard for all your assistance. 再次感谢Richard的所有协助。

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. 问题在于环境文件断言Twilio.Device存在,因此像全局声明之类的任何事情都需要考虑到这一点。

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 () { }
    }
}

Because we declared let Twilio.Device : IDevice , we also permitted consumers to assign Twilio.Device at later date. 因为我们声明了let Twilio.Device : IDevice ,所以我们也允许消费者在以后分配Twilio.Device This would not be possible if we declared const Twilio.Device : IDevice . 如果我们声明const Twilio.Device : IDevice这将是不可能的。

However, we couldn't just say: 但是,我们不能只说:

Twilio.Device = { ... }

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 . 因为这样做需要Twilio名称空间存在,所以当我们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可以成功编译,但是已编译的JS将环境Twilio变量的存在视为已授予,因此失败了。

TypeScript also doesn't allow you to assign values to an existing namespace so we couldn't do: TypeScript也不允许您将值分配给现有的名称空间,因此我们不能这样做:

const Twilio = {}; // Or let for that matter.
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. 但是,由于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()
})

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. 如果不是,则需要通过tsconfig.json包含它,或者通过/// <references path="wherever"/>指令对其进行/// <references path="wherever"/>

Edit 1 编辑1

Corrected beforeEach in the example above to return null. 上面示例中的beforeEach更正为返回null。 Otherwise test would fail. 否则测试将失败。

Edit 2 - Extending NodeJS Global Declaration 编辑2-扩展NodeJS全局声明

I thought I should add why extending the NodeJS.Global interface isn't necessary. 我想我应该补充一下为什么不需要扩展NodeJS.Global接口的原因。

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. 因此,我们不需要使用Twilio扩展Global ,因为假定Twilio也存在于即时上下文中。

HOWEVER, this particular declaration method means that global.Twilio does not exist. 但是,此特定的声明方法意味着global.Twilio不存在。 I can declare: 我可以声明:

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

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.Twilio对象的Device属性提供类型推断,但是没有一种可以像键入一样“共享”名称空间的方法。

EDIT 3 - Global Extension IS Necessary After All 编辑3-毕竟需要全球扩展

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是必要的,因为Jest不会在测试和执行它们的类之间共享上下文。

To allow us to modify the NodeJS.Global interface, we append the following to the definition file: 为了允许我们修改NodeJS.Global接口,我们将以下内容附加到定义文件中:

declare namespace NodeJS {
    interface Global {
        Twilio: { Device: Partial<Twilio.IDevice> }
    }
}

This then enables: 然后启用:

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

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM