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

时间:2017-02-22 17:18:23

标签: 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>;
        };
    }

}

希望其他人能够找到这个问题,理查德的回答低于洞察力(因为声明文件有点缺乏)。

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

1 个答案:

答案 0 :(得分: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添加,或通过/// <references path="wherever"/>指令引用它。

编辑1

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

编辑2 - 扩展NodeJS全球声明

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

当我们进行环境声明时,假定它作为全局上下文(以及任何子闭包)的一部分存在。因此,我们无需使用Global扩展Twilio,因为假定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命名空间不同,但它允许以相同的方式操作以构建测试。