【问题标题】:How to declare global NodeJS variables within my ambient declaration file?如何在我的环境声明文件中声明全局 NodeJS 变量?
【发布时间】:2017-07-12 21:31:15
【问题描述】:

这是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 global 而不是 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>;
        };
    }

}

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

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

【问题讨论】:

    标签: node.js typescript namespaces global declaration


    【解决方案1】:

    这绝对是一个有趣的问题。问题是环境文件断言 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 包含它,或者通过/// &lt;references path="wherever"/&gt; 指令引用它。

    编辑 1

    更正了上例中的 beforeEach 以返回 null。否则测试会失败。

    编辑 2 - 扩展 NodeJS 全局声明

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

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

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

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

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

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

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

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

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

    【讨论】:

    • 在我的测试文件中使用 namespace Twilio { Device= {} } 对我不起作用。我收到了Cannot find name 'Device'.
    • @RicardoAmaral 您可以尝试通过三斜杠指令引用声明文件吗?如果这不起作用,您能否包含您的 tsconfig.json 文件和项目结构?
    • 我相信你在这里遗漏了一些重要的东西......看看这个:stackoverflow.com/a/40460395/40480 在我的测试中,我目前有类似global.Twilio = { Device: { ready: () =&gt; {} } }; 的东西包含在beforeAll 调用中。这工作正常,我成功地模拟了Twilio.Device 并且我的测试通过了。如果我删除它,我的测试将失败。换句话说,我需要直接使用global.Twilio而不是Twilio
    • @RicardoAmaral 你能提供一个完整的代码示例吗?我有this gist,测试成功执行。
    • 老实说,我想避免使用三斜杠指令。感觉就像一个黑客。而且我相信 TypeScript 团队在最新的 TypeScript 版本上做了一些事情,这样我们就不需要这些烦人的三斜杠指令了。
    猜你喜欢
    • 2021-06-17
    • 2010-10-30
    • 2014-10-30
    • 2022-01-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多