JS文档生成工具-Jsdoc

Jsdoc是一款JS文档的生成工具,提供了丰富的标签,写起文档来十分方便,具体标签用法参考 Jsdoc,这里就不多赘述。但它的用处,远远不止于生成文档,最近通过研究,发现利用Jsdoc工具,可以干更多的事情,待下面剖析。

生成原理

首先来谈谈Jsdoc的生成原理,即如何从注释代码生成最后的网页。这个过程其实很简单,通过文档解析器parser对每个js文件进行分析,提取出文档信息,之后的一步很关键,Jsdoc将提取出来的信息存放到了taffydb中,进而根据这些信息来生成网页。

为了更好的解释这个过程,来看Jsdoc的源码

1
2
3
4
/* templates/default/publish.js */
exports.publish = function(taffyData, opts, tutorials) {
//generate Jsdoc html
}

生成html的代码逻辑,就在publish这关键函数中,从接受的参数来看,taffyData中已经存放好解析之后的文档信息,opts配置文件的解析,tutorials是教程的信息,未做过多研究。那么taffyData中存放的信息试什么样子的呢?来看如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
***** Debug ******
[ { comment: '/**\n* <p>Start ...... @public\n* @since 1\n */',
meta:
{ range: [Object],
filename: 'LocationManager.js',
lineno: 69,
code: [Object],
vars: [Object] },
description: '<p>Start request location.</p> ......more.</p>\n',
permission: [ [Object] ],
see: [ '[removeLocationU...r#removeLocationUpdates}' ],
params: [ [Object] ],
returns: [ [Object] ],
access: 'public',
since: '1',
name: 'requestLocationUpdates',
longname: 'xxx.location.LocationManager#requestLocationUpdates',
kind: 'function',
memberof: 'xxx.location.LocationManager',
scope: 'instance',
___id: 'T000002R003347',
___s: true } ]

以上信息就是taffyData中存放的文档信息,可以看到,关键的Jsdoc信息,比如@since 1解析成了since:'1'@public解析成了access:publickind:'function',经过这般解析,taffyData中的数据已经足够可以用来生成网页,从而可以看出,在publish函数中,围绕着taffydb的数据,通过各种数据处理,可以做一些更多的事情,这里只是抛砖引玉,比如统计代码的各种信息、检查Jsdoc是否符合规范等等

以一张图简要描述下Jsdoc的原理

至此,我们分析出了Jsdoc的关键信息:Jsdoc解析之后的信息全部存放到taffyData中,而这些信息作为接口publish的参数,提供了良好的扩展性

如果进一步了解信息转储,请查阅:app.js # app.jsdoc.parser.parse -> parser.js # parser.createParser 这个方法

自定义扩展

接下来来看看Jsdoc提供的三种自定义扩展方式,分别是:自定义EventHandler级别的插件,自定义标签,自定义抽象语法树访问器,下面来分别解释下这三种扩展方式的区别

  • 自定义EventHandler级别的插件

    这种扩展方式是最高级别的扩展,如果学过监听器设计模式,看如下代码就一目了然了,一个EventHandler插件包含了以下几个阶段的监听

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    exports.handlers = {
    parseBegin: function(sourcefiles){},
    fileBegin: function(filename){},
    beforeParse: function(filename,content){},
    JsdocCommentFound: function(filename, comment, lineno){},
    symbolFound: function(..params){ },
    newDoclet: function(doclet) {},
    fileComplete: function(filename,content){},
    parseComplete: function(sourcefiles, doclets){},
    processingComplete: function(doclets){}
    };

    不难理解,Jsdoc在生成过程中,抛出了以上这些阶段事件供扩展,每个事件的不同参数,代表的当前阶段产生的中间结果。比如在解析之前(parseBegin),传入的参数代表源文件文件名列表,可以做一些正比表达式的匹配,指定生成某些文件的文档。在解析完一个文件的Jsdoc信息时(newDoclet),doclet中就包括了上面taffydb中的信息。有了这几个阶段,相信很轻松的就可以扩展出任何想要的插件,关键在于插件的行为是由哪些阶段完成的。

  • 自定义标签

    这种扩展方式是中间级别的扩展,主要用于当Jsdoc官方提供的标签不能满足文档注释需要的时候,选择自定义标签来补充完善Jsdoc的能力,比如,我需要在文档中注释一个权限声明的标签,@permission,那么可以这样做

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    exports.defineTags = function(dictionary) {
    var opts = {
    canHaveName: true,
    mustHaveValue: true,
    onTagged: function(doclet, tag) {
    doclet.permission = doclet.permission || [];
    doclet.permission.push(tag);
    }
    }
    dictionary.defineTag("permission", opts);
    }

    Jsdoc提供了defineTags接口来自定义标签,其参数dictionary可以理解为标签字典,如上述代码描述,将自定义标签和属性,添加到dictionary中,这样就能被Jsdoc在解析中识别了,onTagged方法定义了当解析到该标签时,Jsdoc应该做出的操作,这里还是doclet,还记得吗?就是那个taffydb的数据,翻到上面那个taffydb数据信息看看,permission就在其中。自定义标签相比上面的EventHandler级别的插件,实现起来简单、方便。

  • 自定义抽象语法树访问器

    这种扩展方式时最低级别的扩展,官方文档中用了(lowest)一词。同样,来看代码

    1
    2
    3
    4
    5
    exports.astNodeVisitor = {
    visitNode: function(node, e, parser, currentSourceName) {
    // do all sorts of crazy things here
    }
    };

    由于直接接触的对象是抽象语法数,那么可做的事情就更多了,就好比越是接近系统底层,可扩展的能力越高,越是上层的东西,可扩展能力就越小,Jsdoc提供了astNodeVisitor接口来扩展访问到AST每个节点的行为,感兴趣的同学可以好好实战一番,这里就不过多赘述了

更多

Jsdoc的主要原理和扩展方式在上面分析完了,更多完整功能用法,请参考Jsdoc

如需转载,请注明出处