昨天釋出了vuese1.0,這是我的一個新的開源專案,用來解析Vue SFC並生成markdown文件,這裡: github.com/HcySunYang/… 。
這篇文章不會介紹如何使用,至於如何使用大家可以檢視 readme,這裡我們主要說一說實現的思路。
一、動機
你或者你的團隊也許會有一套自己的元件庫(或者是單純的一個元件),通常當你開發完一個元件之後,你需要手動的編寫markdown文件,從而讓其他人瞭解元件是如何使用的。這裡的問題在於:假若元件不停的在更新,你就必須要不停的手動維護對應的文件,使其與元件的功能保持一致。實際上這個過程是有些噁心?的,那麼有什麼更好的辦法嗎?基於此我才開發了 vuese 。
二、想法
我們知道一個 vue
元件所暴露的介面無非是 props
、events
、slots
(或scopedSlots
) 以及部分 methods
。那我們可不可以實現一個工具幫助我們分析一個vue元件並提取這些資訊呢?然後自動生成文件,這樣無論元件如何改動我們都不需要手動維護文件了,只需要使用該工具重新生成即可。
三、基本思路
對於一個 vue
元件,如果我們拋開 style
和自定義塊,那麼它由兩部分組成,即:模板 和 script
塊。甚至如果使用 render
函式代替模板的話,那麼就只剩一下一個 script
了。
對於 props
,methods
它們只能在 scirpt
塊內定義,如:
props: {
name: String
}
methods: {
clear () {/*...*/}
}
複製程式碼
而對於 slots 則既可以在 script 塊內定義,也可以在模板中定義,如:
<!-- 在模板中定義 -->
<div>
<slot name="header" />
</div>
複製程式碼
// 在 script 塊內定義了 slots
render (h) {
return h('div', this.$slots.header)
}
複製程式碼
以上兩種寫法是等價的,所以在提取 slots
資訊時即需要考慮模板中的slots
,也要相容 script
塊內的 slots
。
而且在函式式元件中,以下內容都應該作為 slots 處理:
// ctx.slots()
render (h, ctx) {
return h('div', ctx.slots().xxx)
}
// ctx.children
render (h, ctx) {
return h('div', ctx.children)
}
複製程式碼
這也是我們在提取slots
資訊時需要考慮在內的。
同樣的,對於 events
而言也是既可以出現在模板中,又可以出現在 script
塊中,如下:
<!-- 模板中的 events -->
<div @click="$emit('onclear')"></div>
複製程式碼
// 定義在 script 中的事件
methods: {
someMethod () {
this.$meit('onclear')
}
}
複製程式碼
所以在提取 events
資訊時也需要即考慮模板又考慮 script
塊。
對於模板我們預設是 html
語法,對於 script
塊我們預設為 js
。我們要做的第一件事兒就是將 html
和 js
單獨提取出來並單獨分析,好在巨人的肩膀厚實,已經有了 @vue/component-compiler-utils
模組和 vue-template-compiler
模組。其中我們使用 @vue/component-compiler-utils
模組解析 vue SFC 並分別得到 html(模板) 和 JavaScript(script塊) 的原始碼。對於 html 原始碼我們可以再次使用 vue-template-compiler
模組將其解析為模板對應的 AST
,然後通過編寫一個 traverse
函式對其進行分析,讀取slots
相關的內容。
而對於 JavaScript
原始碼的處理,我選擇了使用 babel7
,寫過 babel
外掛的同學或許已經猜到了實現的思路。我們將原始碼交由 @babel/travers
模組處理,然後通過編寫一些 helper
函式來輔助我們判斷出哪些是要真正處理的內容即可,如下原始碼段所示:
const mainTraveres = {
ObjectProperty(path: any) {
// Processing name
if (isVueOption(path, 'name')) {
if (onName) onName(path.node.value.value)
}
// Processing props
if (isVueOption(path, 'props')) {
// 一些邏輯
}
// more...
}
}
複製程式碼
其中 isVueOption
函式是我們自己編寫的 helper
函式,來輔助我們判斷一個物件的屬性是否是 Vue
選項物件中的特定屬性,如果是我們就進一步處理就可以了,對於 js 的處理基本都是這個思路,更多內容大家可以檢視原始碼:github.com/HcySunYang/…
四、生成目標
經過上一步的處理,我們可以編寫出一個 parser
模組,解析並組裝出我們需要的內容,有了需要的內容之後,我們就可以根據這些資訊編寫一個 Render
模組,本質就是一個程式碼生成的過程,至於生成的內容是什麼這取決於你想要的目標,vuese 內建的 Render
會根據這些資訊為你生成 markdown
檔案,或者生成一個整合 docute
的文件。但實際上只要你腦洞夠大你可以生成任何東西,試想一下,如果我們的 parser
模組編寫的更加完善,對一個 vue
元件的分析足夠細緻,這樣我們就能拿到一個 vue
元件全部的資訊,然後在程式碼生成階段將其生成一個 ts compatible 的元件,這不就實現了一個將非js編寫的vue元件轉換為ts相容的vue元件的外掛了嗎?(備註:後來一哥們兒提供了一個更好的辦法來實現這件事兒,為了避免尷尬,我只能說這也是一個思路嘛......)
回到 vuese 內建的 Render 模組,Render 的結果就是markdown資源,本質就是一個字串拼接的過程,具體可以檢視原始碼 github.com/HcySunYang/… ,並不複雜。
實際上 vuese 提供了很多有用的資訊和文件中沒有體現出來的功能,這多會在後續逐漸補充。舉個例子,使用 vuese 生成的markdown檔案大致如下:
你可能已經注意到了,在上面的 markdown 檔案中包含了很多諸如:
<!-- @vuese:CompName:props:start -->
<!-- @vuese:CompName:props:end -->
複製程式碼
之類的註釋,它的作用是告訴 vuese 在生成文件時要將生成的 markdown 程式碼放置在什麼位置,如果一個元件還沒有對應的markdown文件,則新生成之,否則會在已有的文件基礎上更新。這麼做的目的是出於真正使用場景的考慮,因為一個元件的文件不可能僅僅包含上圖中展示的內容,它還可能包含開發者自己編寫的demo,和其他描述內容。這樣我們生成文件的時候就不會覆蓋掉開發者自己編寫的內容,而是將生成的內容插入到指定的位置。
五、規劃
目前 vuese 已經實現的特性如下:
規劃中要實現的特性如下:另外目前還不支援外掛系統,這也在未來的規劃當初。並且在我們團隊內部已經將其應用在內部的元件庫的文件維護,也計劃關注 vue3.0 併相容之。最後 vuese 剛剛釋出有諸多不足之處,但是隨著後續的更新迭代,它會變得越來越好,也歡迎感興趣的同學共建。
其他規劃:模板支援 pug、外掛系統、還有啥??????