【问题标题】:How do I write a TypeScript declaration file for a complex external commonjs module that has constructor, such as imap?如何为具有构造函数(例如 imap)的复杂外部 commonjs 模块编写 TypeScript 声明文件?
【发布时间】:2014-10-08 04:27:06
【问题描述】:

这个问题是对我之前的问题的改进:How do I write a TypeScript declaration file for an external commonjs module that has constructor?

我正在尝试为 imap 模块编写声明文件: - imap on npm - node-imap 这个文件应该是什么样的?

在没有找到对上述原始问题的满意答案后,我意识到该语言存在一些相互竞争的特性:

  • 任何导出或导入都会使模块成为外部模块
  • 似乎无法从具有(模块级)构造函数的外部模块导出多个实体,因此共享实体必须位于模块外部(环境)。
    请参阅我之前的问题(上)。
  • 无法将外部模块导入到声明模块之外的声明文件中
    不允许使用以下模式: import fs = require('fs'); declare module 'A' {}

这是我目前所拥有的声明文件:

interface IIMAPAccount {
    user:           string;
    password:       string;
    host:           string;
    port:           number;
    tls?:           boolean;
}

interface IEMail {
    mail:            any;
}

interface ICriteria {
    // The following message flags are valid types that do not have arguments:
    ALL:            void;    // All messages.
    ANSWERED:       void;    // Messages with the Answered flag set.
    DELETED:        void;    // Messages with the Deleted flag set.
    DRAFT:          void;    // Messages with the Draft flag set.
    FLAGGED:        void;    // Messages with the Flagged flag set.
    NEW:            void;    // Messages that have the Recent flag set but not the Seen flag.
    SEEN:           void;    // Messages that have the Seen flag set.
    RECENT:         void;    // Messages that have the Recent flag set.
    OLD:            void;    // Messages that do not have the Recent flag set. This is functionally equivalent to "!RECENT" (as opposed to "!NEW").
    UNANSWERED:     void;    // Messages that do not have the Answered flag set.
    UNDELETED:      void;    // Messages that do not have the Deleted flag set.
    UNDRAFT:        void;    // Messages that do not have the Draft flag set.
    UNFLAGGED:      void;    // Messages that do not have the Flagged flag set.
    UNSEEN:         void;    // Messages that do not have the Seen flag set.

    // The following are valid types that require string value(s):

    BCC:            any;    // Messages that contain the specified string in the BCC field.
    CC:             any;    // Messages that contain the specified string in the CC field.
    FROM:           any;    // Messages that contain the specified string in the FROM field.
    SUBJECT:        any;    // Messages that contain the specified string in the SUBJECT field.
    TO:             any;    // Messages that contain the specified string in the TO field.
    BODY:           any;    // Messages that contain the specified string in the message body.
    TEXT:           any;    // Messages that contain the specified string in the header OR the message body.
    KEYWORD:        any;    // Messages with the specified keyword set.
    HEADER:         any;    // Requires two string values, with the first being the header name and the second being the value to search for. If this second string is empty, all messages that contain the given header name will be returned.
    // The following are valid types that require a string parseable by JavaScripts Date object OR a Date instance:
    BEFORE:         any;    // Messages whose internal date (disregarding time and timezone) is earlier than the specified date.
    ON:             any;    // Messages whose internal date (disregarding time and timezone) is within the specified date.
    SINCE:          any;    // Messages whose internal date (disregarding time and timezone) is within or later than the specified date.
    SENTBEFORE:     any;    // Messages whose Date header (disregarding time and timezone) is earlier than the specified date.
    SENTON:         any;    // Messages whose Date header (disregarding time and timezone) is within the specified date.
    SENTSINCE:      any;    // Messages whose Date header (disregarding time and timezone) is within or later than the specified date.
    //The following are valid types that require one Integer value:
    LARGER:         number;    // Messages with a size larger than the specified number of bytes.
    SMALLER:        number;    // Messages with a size smaller than the specified number of bytes.
    // The following are valid criterion that require one or more Integer values:
    UID:            any;    // Messages with UIDs corresponding to the specified UID set. Ranges are permitted (e.g. '2504:2507' or '*' or '2504:*').
}

interface IFetchOptions {
    markSeen:       boolean;  // Mark message(s) as read when fetched. Default: false
    struct:         boolean;  // Fetch the message structure. Default: false
    envelope:       boolean;  // Fetch the message envelope. Default: false
    size:           boolean;  // Fetch the RFC822 size. Default: false
    modifiers:      any;      // Fetch modifiers defined by IMAP extensions. Default: (none)
    bodies:         any;      // A string or Array of strings containing the body part section to fetch. Default: (none) Example sections:
}

declare module "imap" {
    import events                           = require('events');
    import EventEmitter                     = events.EventEmitter;

    interface IMAPFetch extends EventEmitter {
    }

    class IMAP extends EventEmitter {
        constructor(account : IIMAPAccount);
        connect();
        openBox(name : string, flag : boolean, cb : (err : Error, box) => void);
        search(criteria : ICriteria, cb : (error : Error, uids : string[]) => void);
        fetch(source : any, object : IFetchOptions) : IMAPFetch;
    }

    var out: typeof IMAP;
    export = out;
}

理想情况下,所有接口都将在模块“imap”中定义,因此不会污染全局范围。

【问题讨论】:

    标签: node.js module typescript declaration


    【解决方案1】:

    理想情况下,所有接口都将在模块“imap”内定义,因此不会污染全局范围

    代替:

    interface IFetchOptions {
        markSeen:       boolean;
        // so on
    }
    interface ICriteria {        
        ALL:            void;
    }
    

    执行以下操作

    module imap {
        interface IFetchOptions {
            markSeen:       boolean;
            // so on
        }
        interface ICriteria {        
            ALL:            void;
        }
    }
    

    而且你只会用imap 污染全局范围。这是一个非初始化模块,只能用于类型信息。

    参考:查看 ghost 模块http://definitelytyped.org/guides/best-practices.html

    【讨论】:

    • 虽然我已经多次阅读“幽灵模块”部分,但我仍然觉得它令人困惑。但你的例子有帮助。但是,它仍然不能帮助我解决导出问题:由于模块级构造函数似乎需要 export =,接口不可见。是否有另一种方法来指定模块级构造函数?
    【解决方案2】:

    在继续研究这个问题后,我已经写了定义文件,但仍然没有找到将所有接口都放在模块内部的方法,以避免污染全局范围。

    我在一个 pull request 中将定义文件提交给了 distinctlyTyped,你可以在这里查看它: https://github.com/psnider/DefinitelyTyped/commit/460bb6d63e349918b20e49d7a62f3d2a7c2aea0a


    已编辑

    感谢 basarat 为我指明了正确的方向,尽管我无法将它缝合在一起,直到我找到了更多丢失的部分。

    我查看了其他答案和定义文件,发现了一种似乎可行的方法,并更新了我的拉取请求: https://github.com/borisyankov/DefinitelyTyped/pull/3324

    我在 imap.d.ts 中使用了这种模式:

    declare module IMAP {
        export interface Config {}
        export class Connection {
            constructor(config : Config);
        }
    }
    declare module "imap" {
        var out: IMAP.Connection;
        export = out;
    }
    

    正如 basarat 建议的那样,IMAP 是一个未初始化的模块,它使接口可以在 IMAP 命名空间内访问。

    然后在使用 imap 模块的代码中,example.ts

    /// <reference path='imap.d.ts' />   // brings module IMAP into scope
    import Imap = require('imap');       // gets the connection class
    var conn = new Imap({/* omitted for brevity*/});  // creates an imap connection
    

    【讨论】:

    • Where IMAP is a non-initializaed module, as basarat suggested 您的模块实际上 不是幽灵模块,因为它必须仅包含接口,并且您的示例包含class
    • 请注意,您可以将interfaces 用于classes :请参阅ObjectStatic typescript.codeplex.com/workitem/1085 但对于大多数目的,由于语言限制(非开放式课程),这是过度的人为努力。所以我会接受你在这里的一切,实际上我会自己写这个;)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-09-13
    • 2017-06-13
    • 2016-04-11
    • 1970-01-01
    • 2018-07-11
    • 1970-01-01
    • 2018-05-12
    相关资源
    最近更新 更多