打造自己的Vue组件文档生成工具
程序员最讨厌的两件事情,第一种是写文档,另一种是别人没有写文档。有没有直接根据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组件文档
一.现状 Vue框架在前端开发中应用广泛,当一个多人开发的Vue项目经过长期维护之后往往会沉淀出很多的公共组件,这个时候经常会出现一个人 开发了一个组件而其他维护者或新接手的人却不知道这个组件是做什么 ...
- 【C#附源码】数据库文档生成工具支持(Excel+Html)
[2015] 很多时候,我们在生成数据库文档时,使用某些工具,可效果总不理想,不是内容不详细,就是表现效果一般般.很多还是word.html的.看着真是别扭.本人习惯用Excel,所以闲暇时,就简单的 ...
- 微软开源全新的文档生成工具DocFX
微软放弃Sandcastle有些年头了,微软最近开源了全新的文档生成工具DocFX,目前支持C#和VB,类似JSDoc或Sphinx,可以从源代码中提取注释生成文档之外,而且还有语法支持你加入其他的文 ...
- .NET平台开源项目速览(4).NET文档生成工具ADB及使用
很久以前就使用ADB这个工具来生成项目的帮助文档.功能强大,在学习一些开源项目的过程中,官方没有提供CHM帮助文档,所以为了快速的了解项目结构和注释.就生成文档来自己看,非常好用.这也是一个学习方法吧 ...
- 【C#附源码】数据库文档生成工具支持(Excel+Htm)
数据库文档生成工具是用C#开发的基于NPOI组件的小工具.软件源码大小不到10MB.支持生成Excel 和Html 两种文档形式.了解更多,请访问:http://www.oschina.net/cod ...
- Markdown 文档生成工具
之前用了很多Markdown 文档生成工具,发现有几个挺好用的,现在整理出来,方便大家快速学习. loppo: 非常简单的静态站点生成器 idoc:简单的文档生成工具 gitbook:大名鼎鼎的文档协 ...
- DBImport v3.44 中文版发布:数据库数据互导及文档生成工具(IT人员必备)
前言: 距离上一个版本V3.3版本的文章发布,已经是1年10个月前的事了. 其实版本一直在更新,但也没什么大的功能更新,总体比较稳定,所以也不怎么写文介绍了. 至于工作上的事,之前有半年时间跑去学英语 ...
- (转)Doxygen文档生成工具
http://blog.csdn.net/lostaway/article/details/6446786 Doxygen 是一个支持 C/C++,以及其它多种语言的跨平台文档生成工具.如同 Java ...
- Sandcastle----强大的C#文档生成工具
最近客户索要产品的二次开发类库文档,由于开发过程中并没有考虑过此类文档,而且项目规范比较,持续时间比较长,经手人比较多,还真是麻烦,如果人工制作文档需要是一个比较大的工程.还好有这个文档生成工具,能够 ...
随机推荐
- 快速简单的了解VLAN(VXLAN)和端口链路类型
目录 前言 一.VLAN是什么? 1.优点 2.为什么推出VXLAN 二.VXLAN又是什么? 1.优点 三.创建VLAN 四.介绍端口链路类型 五.Access 1.特性 六.Trunk 1.特性 ...
- 温故知新,.Net Core利用UserAgent+rDNS双解析方案,正确识别并反爬虫/反垃圾邮件
背景 一般有价值的并保有数据的网站或接口很容易被爬虫,爬虫会占用大量的流量资源,接下来我们参考历史经验,探索如何在.Net Core中利用UserAgent+rDNS双解析方案来正确识别并且反爬虫. ...
- css 设置body背景图片铺满
background-image: url(../../../assets/images/workflow/work.png); background-repeat: no-repeat; backg ...
- XCTF EasyRE
一.查壳 无壳,并且发现是vc++编译的 二.拖入ida,来静态分析,这题让我深刻感觉到汇编的nb. 这段算是灵性的一段了,单从静态语句来看,发现分析不出啥,只能靠猜一下,我当时猜的是将输入的字符串又 ...
- 使用Hugo框架搭建博客的过程 - 部署
前言 完成前期的准备工作后,在部署阶段需要配置服务器或对象存储服务. 对象存储和服务器对比 对象存储平台 国内有阿里云OSS.腾讯COS.又拍云.七牛云等.国外有Github Pages.Netlif ...
- 使用Hugo框架搭建博客的过程 - 前期准备
前言 这篇教程介绍了如何搭建这样效果的博客. 所需步骤 可以从这样的角度出发: 注册域名. 使用CDN加快网站访问速度. 网站内容需要部署在服务器或对象存储平台上. 重要的是放什么内容.博客需要选择框 ...
- django 使用jpype 报错:raise+OSError('JVM+cannot+be+restarted')
#调用jar包 def getJar(arg1,arg2): jarpath = os.path.join(os.path.abspath('.'), 'tools/GetTest-1.0-SNAPS ...
- 「BZOJ2839」集合计数
「BZOJ2839」集合计数 题目大意: 一个包含 \(n\) 个数的集合有 \(2^n\) 个子集,从这些子集中取出若干个集合(至少一个),使他们的交集的元素个数恰好为 \(k\),求方案数,答案对 ...
- AspNetCore&MassTransit Courier实现分布式事务
在之前的一篇博文中,CAP框架可以方便我们实现非实时.异步场景下的最终一致性,而有些用例总是无法避免的需要在实时.同步场景下进行,可以借助Saga事务来解决这一困扰.在一些博文和仓库中也搜寻到了.Ne ...
- 【spring源码系列】之【Bean的初始化】
只要不放弃,希望迟早都会到来! 1. Bean的初始化 如果把bean的生命周期看作一个婴儿诞生过程的,那么创建实例相当于婴儿从母体出来,一丝不挂光秃秃:属性赋值相当于给宝宝的头带帽子,上身穿衣服.下 ...