程序員最討厭的兩件事情,第一種是寫文檔,另一種是別人沒有寫文檔。有沒有直接根據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;
}
});
}
})
