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組件文檔生成工具內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- 解析如何自動化生成vue組件文檔
- vue如何自定義事件傳參
- vue自定義組件如何通過v-model指令控制組件的隱藏、顯示
- vue在自定義組件上使用v-model和.sync的方法實例
- Vue組件通信方式(父傳子、子傳父、兄弟通信)