解析如何⾃动化⽣成vue组件⽂档
⽬录
⼀、现状
⼆、社区解决⽅案
2.1、业务梳理
三、技术⽅案
3.1、Vue⽂件解析
3.2、信息提取
3.2.1、可直接获取的信息
3.2.2、需要约定的信息
四、总结
五、展望
⼀、现状
Vue框架在前端开发中应⽤⼴泛,当⼀个多⼈开发的Vue项⽬经过长期维护之后往往会沉淀出很多的公共组件,这个时候经常会出现⼀个⼈开发了⼀个组件⽽其他维护者或新接⼿的⼈却不知道这个组件是做什么的、该怎么⽤,还必须得再去翻看源码,或者压根就没注意到这个组件的存在导致重复开发。这个时候就⾮常需要维护对应的组件⽂档来保障不同开发者之间良好的协作关系了。
但是传统的⼿动维护⽂档⼜会带来新问题:
效率低,写⽂档是个费时费⼒的体⼒活,好不容易抽时间把组件开发完了回头还要写⽂档,想想都头⼤。
易出错,⽂档内容容易出现差错,可能与实际组件内容不⼀致。
巧妙评价领导的缺点不智能,组件更新迭代的同时,需要⼿动将变更同步到⽂档中,消耗时间还容易遗漏。
⽽理想中的⽂档维护⽅式则是:
⼯作量⼩,能够结合Vue组件⾃动获取相关信息,减少从头开始写⽂档的⼯作量。
信息准确,组件的关键信息与组件内容⼀致,不出错。
智能同步,Vue组件迭代升级时,⽂档内容可以⾃动的同步更新,⽆需⼈⼯校验信息是否⼀致。
镜神寂⼆、社区解决⽅案
2.1、业务梳理
为了能实现上述理想效果,我搜索并研究了⼀下社区中的解决⽅案,⽬前Vue官⽅提供了Vue-press可以⽤于快速搭建Vue项⽬⽂档,⽽且也已经有了可以⾃动从Vue组件中提取信息的库了。
但是已有的第三⽅库并不能完全满⾜需求,主要存在以下两个问题:
信息不全⾯,⼀些重要内容⽆法获取例如不能处理v-model,不能解析属性的修饰符sync,不能获取methods中函数⼊参的详细信息等。
⽐如下⾯的例⼦,value属性与input事件可以合起来构成⼀个v-model属性,但是这个信息在⽣成的⽂档中没有体现出来,要⽂档读者⾃⾏理解判断。⽽且⽣成的⽂档中没有展⽰是否⽀持sync。
有较多的⾃定义标识,⽽且标识的命名过于个性化,对原有的代码侵⼊还是⽐较⼤的。例如下图中的代码,为了标记注释,需要在原有的业务代码中额外添加"@vue" "@arg"等标识,使得业务代码多出了⼀些业务⽆关内容。
三、技术⽅案
针对以上⽂中提到的问题以及社区⽅案的不⾜,我们团队内沉淀出了⼀个⼩⼯具专门⽤于Vue组件信息获取并输出组件⽂档,⼤致效果如下:
上图中左边是⼀个常见的Vue单⽂件组件,右边是⽣成的⽂档。我们可以看到我们从组件中成功的提取到了以下⼀些信息:
组件的名称。
组件的说明。
props,slot,event,methods等。
组件的注释内容。
接下来我们将详细的讲解如何从组件中提取这些信息。
3.1、Vue⽂件解析
既然是要从Vue组件中提取信息,那么⾸先的问题就是如何解析Vue组件。Vue官⽅开发了Vue-template-compiler库专门⽤于Vue解析,这⾥我们也可以⽤同样的⽅式来处理。通过查阅⽂档可知Vue-template-compiler提供了⼀个parComponent⽅法可以对原始的Vue⽂件进⾏处理。
import { parComponent } from 'Vue-template-compiler'
const result = parComponent(VueFileContent, [options])
处理后的结果如下,其中template和script分别对应Vue⽂件中的template和script的⽂本内容。
export interface SFCDescriptor {
template: SFCBlock | undefined;
script: SFCBlock | undefined;
styles: SFCBlock[];
customBlocks: SFCBlock[];
}
当然仅仅是得到⽂本是不够的,还需要对⽂本进⾏更进⼀步的处理来获取更多的信息。得到script后,我们可以⽤babel把js编译成js的AST(抽象语法树),这个AST是⼀个普通的js对象,可以通过js进⾏遍历和读取有了Ast之后我们就可以从中获取到我们想到详细的组件信息了。
import { par } from '@babel/parr';
const jsAst = par(script, [options]);
接着我们来看template,继续查找Vue-template-compiler的⽂档我们找到compile⽅法,compile是专
门⽤于将template编译成AST的,正好可以满⾜需求。
import { compile } from 'Vue-template-compiler'
const templateAst = compile(template, [options]);
得到结果中的ast则为template的编译结果。
export interface CompiledResult {
ast: ASTElement,
render: string,
staticRenderFns: Array<string>,
errors: Array<string>
}
通过第⼀步的⽂件解析⼯作,我们成功获取到了Vue的模板ast和script中的js的AST,下⼀步我们就可以从中获取我们想要的信息了。
3.2、信息提取
根据是否需要约定,信息可以分为两种:
⼀种是可以直接从Vue组件中获取,例如props、events等。
另⼀种是需要额外约定格式的,例如:组件的说明注释,props的属性说明等,这部分可以放到注释⾥,通过对注释进⾏解析获取。
为了⽅便的从ast中读取信息,这⾥先简单介绍⼀个⼯具@babel/traver,这个库是babel官⽅提供的专门⽤于遍历js AST的。使⽤⽅式如下;
import traver from '@babel/traver'
traver(jsAst, options);
通过在options中配置对应内容的回调函数,可以获得想要的ast节点。具体的使⽤可以参考
3.2.1、可直接获取的信息
可以从代码中直接获取的信息可以有效的解决信息同步问题,⽆论代码怎么变动,⽂档的关键信息都
可以⾃动同步,省去了⼈⼯校对的⿇烦。
悬疑破案小说可以直接获取的信息有:
组件属性props
提供外部调⽤的⽅法methods
事件events
插槽slots
1、2都可以利⽤traver在js AST上直接遍历名称为props和methods的对象节点获取。
事件的获取稍微⿇烦⼀点,可以通过查找$emit函数来定位到事件的位置,⽽$emit函数可以在traver中监听MemberExpress(复杂类型节点),然后通过节点上的属性名是否是'$emit'判断是否是事件。如果是事件,那么在$emit⽗级中读取arguments字段, arguments的第⼀个元素就是事件名称,后⾯的元素为事件传参。
音乐大全
this.$emit('event', arg);
traver(jsAst, {
MemberExpression(Node) {
组合英文// 判断是不是event
if (de.property.name === '$emit') {
// 第⼀个元素是事件名称
const eventName = Node.parent.arguments[0];
}
}
});
在成功获取到Events后,那么结合Events和props,就可以进⼀步的判断出props中的两个特殊属性:
是否存在v-model:查找props中是否存在value属性并且Events中是否存在input事件来确定。
props的某个属性是否⽀持sync:判断Events的时间名中是否存在有update开头的事件,并且事件名称与属性名相同。
插槽slots的信息保存在上⽂的template的AST中,递归遍历template AST找到名为slots的节点,进⽽还可以在节点上查找到name。
3.2.2、需要约定的信息
为什么除了可直接获取的组件信息之外,还会需要额外的约定⼀部分内容呢?其⼀是因为可直接获取的信息内容⽐较单薄,还不⾜以⽀撑起⼀个相对完善的组件⽂档;其⼆是我们⽇常开发组件时本⾝就会写很多的注释,如果能直接将部分注释提取出来放到⽂档中,可以⼤⼤降低⽂档维护的⼯作量;
整理⼀下可以约定的内容有以下⼏条:
组件名称。
组件的整体介绍。
props、Events、methods、slots⽂字说明。
Methods标记和⼊参的详细说明。这些内容都可以放在注释中进⾏维护,之所以放在注释中进⾏维护是因为注释可以很容易从上⽂提到的js AST以及template AST中获取到,在我们解析Vue组件信息的同时就可以把这部分针对性的说明⼀起解析到。
接下来我们着重讲解如何将提取注释和注释与被注释的内容是如何对应起来的。
js中的注释根据位置不同可以分为头部注释(leadingComments)和尾部注释(trailingComments),不同位置的注释会存放在对应的字段中,代码展⽰如下:
// 头部注释export default {} // 尾部注释
解析结果
const exportNode = {
type: "ExportDefaultDeclaration",
leadingComments: [{
type: 'CommentLine',
value: '头部注释'
}],
trailingComments: [{
type: 'CommentLine',
value: '尾部注释'
}]
}
在同⼀个位置上,根据注释格式的不同⼜分为单⾏注释(CommentLine)和块级注释(CommentBlock),两种注释的区别会反应在注释节点的type字段中:
/** * 块级注释 */ // 单⾏注释 export default {}
解析结果
const exportNode = {
type: "ExportDefaultDeclaration",
leadingComments: [
{
type: 'CommentBlock',
value: '块级注释'
},
{
type: 'CommentLine',
value: '单⾏注释'
}
]
雇用}
圬工桥另外,从上⾯的解析结果我们也可以看到,注释节点是挂载在被注释的export节点⾥⾯的,这也解决我们上⾯提到的另⼀个问题:注释与被注释的关联关系怎么获取的--其实babel在编译代码的时候已经替我们做好了。
template查找注释与被注释内容的⽅法不同。template中注释节点与其他节点⼀样是作为dom节点存在的,在遍历节点的时候通过判断isComment字段的值是否为true来确定是否是注释节点。⽽被注释的内容的位置在兄弟节点的后⼀位:
<!--template的注释--> <slot>被注释的节点</slot>
解析结果
const templateAst = [
{
isComment: true,
text: "template的注释",
type: 3
},
{
tag: "slot",
type: 1
}
]
知道了如何处理注释内容,那么我们还可以利⽤注释做更多的事情。例如可以通过在methods的⽅法的注释中约定⼀个标记@public来区分是私有⽅法还是公共⽅法,如果更细节⼀点的话,还可以参考另⼀个专门⽤于解析js注释的库js-doc的格式,对⽅法的⼊参进⾏更进⼀步的说明,丰富⽂档的内容。
我们只需要在获取到注释内容之后对⽂本进⾏切割读取即可,例如:
export default {
methods: {
/**
* @public
* @param {boolean} value ⼊参说明
*/
文痞show(value) {}
}
}
当然了为了避免对代码侵⼊过多,我们还是需要尽量少的添加额外的标识。⽽⼊参说明采⽤了与js-doc相同的格式,主要还是因为这套⽅案使⽤⽐较普遍,⽽且代码编辑器都⾃动⽀持⽅便编辑。
四、总结
编写组件⽂档是⼀个可以很好的提升项⽬内各个前端开发成员之间协作的事情,⼀份维护良好的⽂档会极⼤的改善开发体验。⽽如果能进⼀步的使⽤⼯具把维护⽂档的过程⾃动化的话,那开发的幸福感还能得到再次提升。
经过⼀系列的摸索和尝试,我们成功的找到了⾃动化提取Vue组件信息的⽅案,⼤⼤减轻了维护Vue组件⽂档的⼯作量,提升了⽂档信息的准确度。具体实现上,先⽤vue-template-compiler对Vue⽂件进⾏处理,获得template的AST和js的AST,有了这两个AST后就可以去获取更加详细的信息了,梳理⼀下到⽬前为⽌我们⽣成的⽂档⾥可以获取到的内容及获取⽅式:
⾄于获取到内容之后是以Markdown的形式输出还是json⽂件的形式输出,就取决于实际的开发情况了。
五、展望
这⾥我们所讨论的是直接从单个Vue⽂件去获取信息并输出,但是像很多第三⽅组件库⾥例如elementUI的⽂档,不仅有组件信息还有展⽰实例。如果⼀个组件库维护的相对完善的话,⼀个组件应该会有对应的测试⽤例,那么是否可以将组件的测试⽤例也提取出来,实现组件⽂件中⽰例部分的⾃动提取呢?这也是值得研究的问题。
以上就是解析如何⾃动化⽣成vue组件⽂档的详细内容,更多关于⾃动化⽣成vue组件⽂档的资料请关注其它相关⽂章!