目录
  • 提取组件events
  • 提取组件slots

程序员最讨厌的两件事情,第一种是写文档,另一种是别人没有写文档。有没有直接根据vue组件生成文档的呢?当然是有的的。但第三方使用起来不一定能和现有项目结合使用,往往需要额外的注释用来标记提取信息。使用第三方的一些比较常见问题

  • 文档提取信息不全面,可能有些信息你需要提取但是它又不支持。这种情况下就只能修改三方的插件源码了。
  • 需要额为的注释信息来标记,例如 vuese 需要给方法 打 @vuese、@arg 等标记来提供方法信息。

俗话说自己动手丰衣足食,打造自己的vue文档生成工具与自己项目结合使用。一个组件文档大致需要提供 组件名称和描述(name)、组件属性(props)、组件方法(methods)、组件事件(event)、插槽(slot) 这几个部分,以及还需要这个几个部分的注释组成生成描述信息。接下来一步步实现对着几个部分的提取实现。

解析.vue 文件

一般一个.vue文件分三个部分 template、script、style、style部分的内容我们不需要,我们需要分别提取出 template 和 script 内容。vue官方开发了 vue-template-compiler 库专门用于vue解析,我们可以直接使用它来解析提取.vue文件, vue-template-compiler 提供了一个 parsecomponent 方法可以对原始的vue文件进行处理。

const compiler = require('vue-template-compiler')
const result = compiler.parsecomponent(vuestr, [options])

// parsecomponent 返回  template、script、style内容,
export interface sfcdescriptor {
  template: sfcblock | undefined;
  script: sfcblock | undefined;
  styles: sfcblock[];
  customblocks: sfcblock[];
}

拿到各个部分文本后,还需要将它转成ast(抽象语法树),template 部分内容可以直接使用 vue-template-compiler 提供的 compile 方法直接生成ast, script部分需要借助其他的生成ast了,这里使用 babel 的模块来处理 js 文本。

const compiler = require('vue-template-compiler')
//vuestr .vue 文件内容
const vue = compiler.parsecomponent(vuestr)

//生成html部分的 ast 
let template = compiler.compile(vue.template.content, {
    preservewhitespace: false,
    comments: true // 生成注释信息
})

使用 @babel/parser(babel解析器,是babel中使用的javascript解析器)来处理js 文本内容。

const parse = require('@babel/parser');

//生成js部分的 ast
let jsast = parse.parse(vue.script.content, {
    allowimportexporteverywhere: true 
})

提取文档信息

通过上一步的文件解析工作,我们成功获取到了vue的模板ast和script中的js的ast,下一步我们就可以从中获取我们想要的信息了。这里需要使用到 @babel/traverse 这个工具,用来遍历 js ast 的节点工具。可以在这里查看 ast 的生成内容,方便查看各种节点信息。

const traverse = require('@babel/traverse');
traverse.default(jsast, {
  enter(path){ // 开始

  },
  // 支持自定义节点 比如当节点类型 为 exportdefaultdeclaration 时掉这个方法
  exportdefaultdeclaration(){

  }
})

提取组件名称、描述、props、methods、model

export default 生成的对应节点类型是 exportdefaultdeclaration,declaration 属性就是对应的组件的 options 了,遍历 declaration 的属性可以获取到 name、props、methods、model 等节点信息。

示例

let componentinfo = {}
traverse.default(jsast, {
  exportdefaultdeclaration(path){
    path.node.declaration.properties.foreach(item => {
        switch (item.key.name) {
            case 'props':
                componentinfo.props = extractprops(item) // 提取 props
                break;
            case 'methods':
                componentinfo.methods = extractmethods(item)  // 提取 methods
                break
            case 'name':
                componentinfo.name = item.value.value // 获取组件名称
                break
            case 'model':
                componentinfo.model = extractmodel(item)  // 提取 model
                break
            default:
                break;
        }
    });
  }
})

提取描述

js中注释分为单行和多行两种,生成ast也会生成不同类型的,可以看下面例子。

/** 
 * 多行备注
 * 用来上传文档信息
 */
// 单行备注
export default {
}
// 结尾注释

可以看到会 commentblock、 commentline 两种类型的节点,还有头部的会放在 leadingcomments 里,底部的注释在 trailingcomments 里。

一般会把组件描述注释放在 export default 上面,简单提取注释信息

// exportdefaultdeclaration 插入如下代码 
if (path.node.leadingcomments) {
    componentinfo.desc = path.node.leadingcomments.map(item => {
        if (item.type === 'commentline') {
            return item.value.trim()
        } else {
            return item.value.split('\n').map(item => item.replace(/[\s\*]/g, '')).filter(boolean)
        }
    }).tostring()
}

提取 methods

因为 methods 中的注释需要额外描述 出参、入参等信息需要额外处理,jsdoc注释规范使用还是比较大众的,这里根据需要自己定义提取规则,还需要提取 async 用来标识是否是异步函数。

/**
 * 方法描述
 * @param {bool} type 参数描述
 * @returns 返回值描述
 */

提取 props

props 的提取需要区分下面几种情况,default 和 validator 还是提取还是有点麻烦的,validator 校验还可以通过注释简单描述来提取,但是 default 就不好处理了。

{
    propa: number, // 只有类型
    propb: [string, number], // 只有类型但是支持多种
    propc: { 
      type: string,
      required: true
    },
    propd: {
      type: number,
      default: 100 // 带有默认值 
    },
    prope: {
      type: object,
      default () { // 默认值 需要函数返回
        return { message: 'hello' }
      }
    },
    propf: {
      default: function () { // 默认值 需要函数返回 和上面的 default 的 ast 节点类型是不同的
        return { message: 'hello' }
      }
      validator: function (value) { // 校验
        return ['success', 'warning', 'danger'].indexof(value) !== -1
      }
    }
}

我这里对 default 处理是借助 @babel/generator 将 default 转换代码, 通过eval转成函数调用返回会默认值。types 是 @babel/types 模块,用来判断节点类型的。

// 获取props默认值
function getdefaultval (node) {
    if (types.isregexpliteral(node) || types.isbooleanliteral(node) || types.isnumericliteral(node) || types.isstringliteral(node)) {
        return node.value
    } else if (types.isfunctionexpression(node) || types.isarrowfunctionexpression(node) || types.isobjectmethod(node)) {
        try {
            let code = generate.default(types.isobjectmethod(node) ? node.body : node).code
            let fun = eval(**0,${types.isobjectmethod(node) ? 'function ()' : ''} $[code]**)
            return json.stringify(fun())
        } catch (error) {
        }
    }
}

提取 model

这个比较简单,直接获取就可以了。

提取组件events

组件的事件没法直接获取到对应节点,只能通过 $emit 方法来定位事件位置,在 traverse 中可以使用 memberexpress(复杂类型节点),然后通过节点上的属性名是否是 $emit 判断是否是事件。

可以看到事件名称在 memberexpress 父级上的 arguments 里,而备注则在更上一层的里。

const extractevents = (path) => {
    // 第一个元素是事件名称
    const eventname = path.parent.arguments[0]; 
    let comments = path.parentpath.parent.leadingcomments
    return {
        name: eventname.value,
        desc: comments ? comments.map(item => item.value.trim()).tostring() : '——'
    }
}

memberexpression (path) {
    // 判断是不是event
    if (path.node.property.name === '$emit') {
        let event = extractevents(path)
        !componentinfo.events && (componentinfo.events = {});
        if (componentinfo.events[event.name]) {
            componentinfo.events[event.name].desc = event.desc ? event.desc : componentinfo.events[event.name].desc
        } else {
            componentinfo.events[event.name] = event
        }
    }
}

在成功获取到events后,那么结合events、props、model,就可以进一步的判断属性是否支持 .sync 和 v-model。

提取组件slots

首先需要写一个对vue模板的ast遍历的函数,vue-template-compiler 没有提供类似于 @babel/traverse 用来 遍历 ast 的。

简单实现个遍历模板抽象树函数

const traversertemplateast = (ast, visitor = {}) => {
    function traversearray (array, parent) {
        array.foreach(child => {
            traversenode(child, parent);
        });
    }

    function traversenode (node, parent) {
        visitor.enter && visitor.enter(node, parent);
        visitor[node.tag] && visitor[node.tag](node, parent);
        node.children && traversearray(node.children, node);
        visitor.exit && visitor.exit(node, parent);
    }

    traversenode(ast, null);
}

vue模板的ast的结构还是比较清晰的,没有js ast 那么多的类型,只需要区分不同tag就可以了。注释会单独一个节点,所以在查找 slot 节点时候,还需要去找它上一个相邻节点,判断是否是注释。

traversertemplateast(template.ast, {
    slot (node, parent) {
        !componentinfo.slots && (componentinfo.slots = {})
        // 获取节点位置
        let index = parent.children.findindex(item => item === node)
        let desc = '无描述', name = '-';
        if (index > 0) {
            let tag = parent.children[index - 1]
            // iscomment 判断是否是 注释
            if (tag.iscomment) {
                desc = tag.text.trim()
            }
        }
        if (node.slotname) name = node.attrsmap.name
        componentinfo.slots[name] = {
            name,
            desc
        }
    }
})

结语

到这里简单的实现了自动化生成vue组件信息了,当然还有几种情况还没有考虑进去,例如事件$emit 在 template 中,slot 在 render 函数中时候的情,不过提取这部分实现也是大同小异的了。可以在这里查看 本文源码。

到此这篇关于vue组件文档生成工具库的方法的文章就介绍到这了,更多相关vue组件文档生成工具内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!