你的.vue檔案就已經是你的文件了

hcysunyang發表於2018-10-24

昨天釋出了vuese1.0,這是我的一個新的開源專案,用來解析Vue SFC並生成markdown文件,這裡: github.com/HcySunYang/…

這篇文章不會介紹如何使用,至於如何使用大家可以檢視 readme,這裡我們主要說一說實現的思路。

一、動機

你或者你的團隊也許會有一套自己的元件庫(或者是單純的一個元件),通常當你開發完一個元件之後,你需要手動的編寫markdown文件,從而讓其他人瞭解元件是如何使用的。這裡的問題在於:假若元件不停的在更新,你就必須要不停的手動維護對應的文件,使其與元件的功能保持一致。實際上這個過程是有些噁心?的,那麼有什麼更好的辦法嗎?基於此我才開發了 vuese

二、想法

我們知道一個 vue 元件所暴露的介面無非是 propseventsslots(或scopedSlots) 以及部分 methods。那我們可不可以實現一個工具幫助我們分析一個vue元件並提取這些資訊呢?然後自動生成文件,這樣無論元件如何改動我們都不需要手動維護文件了,只需要使用該工具重新生成即可。

三、基本思路

對於一個 vue 元件,如果我們拋開 style 和自定義塊,那麼它由兩部分組成,即:模板 和 script 塊。甚至如果使用 render 函式代替模板的話,那麼就只剩一下一個 script 了。

對於 propsmethods 它們只能在 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。我們要做的第一件事兒就是將 htmljs 單獨提取出來並單獨分析,好在巨人的肩膀厚實,已經有了 @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 已經實現的特性如下:

你的.vue檔案就已經是你的文件了
規劃中要實現的特性如下:

你的.vue檔案就已經是你的文件了

另外目前還不支援外掛系統,這也在未來的規劃當初。並且在我們團隊內部已經將其應用在內部的元件庫的文件維護,也計劃關注 vue3.0 併相容之。最後 vuese 剛剛釋出有諸多不足之處,但是隨著後續的更新迭代,它會變得越來越好,也歡迎感興趣的同學共建。

其他規劃:模板支援 pug、外掛系統、還有啥??????

相關文章