讀?VuePress(四)插件系統(tǒng)的設(shè)計

前言

從 9 月份開始,vuepress 源碼進行了重新設(shè)計和拆分。先是開了個 next 分支氏淑,后來又合并到 master 分支兵琳,為即將發(fā)布的 1.x 版本做準(zhǔn)備狂秘。

最主要的變化是:大部分的全局功能都被拆分成了插件的形式,以可插拔的方式來支撐 vuepress 的運作躯肌,這一點很像 webpack者春。

具體架構(gòu)如下:


架構(gòu)

從圖中我們可以看出,vuepress 被劃分成了兩個部分:前端部分和服務(wù)端(Node.js)部分清女。

  1. 前端部分
  1. 服務(wù)端部分
  • 2.1 構(gòu)建流程盼砍,這部分暴露出了 webpackwebpack-dev-server逝她、markdown-it浇坐、動態(tài)模塊的配置。
  • 2.2 用戶文件黔宛,包括配置文件和 markdown 文件(文檔)近刘,這些文件相當(dāng)于站點的元數(shù)據(jù)。
  • 2.3 主題臀晃,這部分被劃分為配置文件和布局組件觉渴。vuepress 提供了一份默認(rèn)的主題。

在這個架構(gòu)中徽惋,主題即插件案淋。也就是說使用(開發(fā))一個主題和使用(開發(fā))一個插件的方式幾乎一致。

  • 2.4 插件 API险绘,這是今天我們重點介紹的部分踢京,特別是插件機制的核心實現(xiàn)。

根據(jù)這個架構(gòu)宦棺,vuepress 的插件便可以做很多事情了瓣距。具體用法可以參考文檔

內(nèi)部插件和官方插件

讓我們先來了解一下 vuepress 的內(nèi)部插件和官方插件都有些什么代咸,借助插件機制做了哪些事情蹈丸。

內(nèi)部插件

  1. 全局增強:默認(rèn)用來實現(xiàn)全局應(yīng)用增強的邏輯。
    它使用 enhanceAppFiles 指定增強全局應(yīng)用和主題的文件路徑呐芥。憑著這個逻杖,vuepress 就能準(zhǔn)確地找到你全局增強或是主題的文件所在地。

  2. 布局組件:默認(rèn)提供的布局組件思瘟。
    它使用 clientDynamicModules 來實現(xiàn)動態(tài)引入布局相關(guān)的組件荸百。

  3. 頁面組件:默認(rèn)提供的頁面組件(布局組件的子組件)。
    它使用 clientDynamicModules 來實現(xiàn)動態(tài)引入頁面相關(guān)的組件潮太。

  4. 根組件混入:默認(rèn)往根組件混入的邏輯管搪。
    它使用 clientDynamicModules 來實現(xiàn)動態(tài)混入元信息。包括根組件的標(biāo)題铡买、語言等更鲁。

  5. 路由:默認(rèn)的生成路由邏輯。
    它使用 clientDynamicModules 來實現(xiàn)動態(tài)注冊路由奇钞。我們的 markdown 文件在轉(zhuǎn)換成 vue 組件后就是通過它自動注冊到 vue-router 的澡为。

  6. 站點數(shù)據(jù):默認(rèn)的生成站點數(shù)據(jù)邏輯。
    它使用 clientDynamicModules 來實現(xiàn)生成全局站點數(shù)據(jù)景埃。我們在頁面里拿到的全局計算屬性 $site 就是這樣來的媒至。

  7. 模塊化轉(zhuǎn)化:將 cmd 代碼轉(zhuǎn)成 esm 代碼的邏輯。
    還是用 clientDynamicModules 來實現(xiàn)將 cmd 代碼轉(zhuǎn)成 esm 代碼谷徙。主要是因為 ClientComputedMixin 這個類前后端代碼都要使用拒啰。

  8. 樣式增強
    全局樣式增強。使用 enhanceAppFiles 和 ready 鉤子來實現(xiàn)(主題樣式+用戶樣式+父主題樣式)完慧。

  9. 樣式覆蓋
    全局樣式覆蓋谋旦,使用 ready 鉤子來實現(xiàn),覆蓋 config.styl 和父主題的 palette屈尼。

  10. dataBlock數(shù)據(jù)注入
    解析 blockType=data 的數(shù)據(jù)册着,使用 chainWebpack 和 enhanceAppFiles 來實現(xiàn),對 blockType=data 類型的數(shù)據(jù)注入到 markdown 生成的 vue 組件里去脾歧,每個組件可以訪問自己的 $dataBlock 屬性拿到甲捏。

官方插件

  1. 活動的標(biāo)題鏈接
    它會在用戶滾動頁面時自動轉(zhuǎn)變側(cè)邊欄的高亮標(biāo)題。
    它使用了 clientRootMixin 和 define 往根組件混入了滾動邏輯:監(jiān)聽 onScroll 事件鞭执,獲取所有錨點元素并根據(jù)滾動距離計算出高亮的錨點司顿。

  2. 回到頂部
    使用了 enhanceAppFiles 和 globalUIComponents 注冊了一個全局組件:點擊后可以滾動到頁面頂部。

  3. 博客

    • 3.1 使用 extendPageData 創(chuàng)建標(biāo)簽頁和目錄頁
    • 3.2 使用 ready兄纺、clientDynamicModules免猾、enhanceAppFile 創(chuàng)建頁面元數(shù)據(jù)。
  4. ga
    谷歌分析站點的庫囤热。使用了 define 和 enhanceAppFiles 初始化了 ga猎提。

  5. 國際化(廢棄)
    可以讓你的站點擁有切換語言的能力。使用了 enhanceAppFiles 和 additionalPages 注冊了個 I18n 布局組件旁蔼。

  6. 文檔的最近更新時間
    可以讓每個文檔頁下面顯示最近的 git 提交時間锨苏。使用 extendPageData 拓展了 $page 的 lastUpdated 屬性。

  7. 圖片預(yù)覽
    集成了 medium-zoom棺聊。使用了 define伞租、clientRootMixin 往根組件里混入了 zoom 的初始化和更新邏輯。

  8. 分頁
    讓共享側(cè)邊菜單欄的文檔擁有分頁切換的能力限佩。使用了 enhanceAppFiles 定義了所有頁面的索引和順序葵诈。ready 定義了分頁的規(guī)則如排序規(guī)則等裸弦、clientDynamicModules 生成動態(tài)模塊給前端代碼使用。

  9. pwa
    集成 service-worker 功能
    - 9.1. 使用 ready 開啟 serviceWorker 選項
    - 9.2. 使用 alias 實現(xiàn)用 vue 當(dāng)事件通道
    - 9.3. 使用 define作喘、globalUIComponents 注冊更新 PWA 應(yīng)用按鈕組件
    - 9.4. 使用 enhanceAppFiles 注入 register-service-worker 的初始化和更新邏輯
    - 9.5. 使用 generated 通過 workbox-build 完成 sw 功能

  10. 注冊全局 Vue 組件
    使用 enhanceAppFiles 把一個文件夾中的 vue 組件文件都注冊好理疙。

  11. 搜索框
    使用 alias 和 define 讓搜索框可以動態(tài)引入。

  12. 進度條
    使用 clientRootMixin 和 enhanceAppFiles 集成 nprogress泞坦。

lerna

項目管理上窖贤,插件機制也使得原來的一個大項目拆成了 1 + N 的形式,package.json 也變得多了起來贰锁,為了管理這種項目赃梧,vuepress 引入了 lerna。

關(guān)于 lerna 的知識豌熄,有興趣的讀者可以參考:lerna管理前端packages的最佳實踐授嘀。

核心實現(xiàn)


當(dāng)一系列插件要使用時,需要通過 PluginAPI 和組成它的各種 Option 來實現(xiàn)锣险。

整體流程大致如下:


這里我劃分成了兩個階段粤攒,用虛線分隔,一個是調(diào)用前階段囱持,一個是調(diào)用后階段夯接。插件們被調(diào)用前,是會被載入以及注冊的纷妆,之后化整為零盔几,映射成若干個 Option 實例。

源碼

  1. PluginAPI 類掩幢,這部分代碼包含了插件機制中的注冊調(diào)用實現(xiàn)逊拍。
    • 構(gòu)造(constructor):初始化選項、插件上下文际邻、插件隊列(可注冊插件列表)芯丧、日志插件、初始化標(biāo)志位世曾、插件解析器屬性缨恒,然后把選項們都裝載進來(initializeOptions)。這里會把一個插件映射成若干個 Option 實例轮听。
      例如骗露,一個插件只有 ready、chainWebpack血巍、additionalPages 三個選項萧锉,則會得到三個 Option 實例。


    • 使用(use)述寡,需要 _initialized 標(biāo)志為 false 才能調(diào)用柿隙,用于確認(rèn)哪些插件是可以被注冊的:
      • 對于非對象類型的插件叶洞,會調(diào)用 normalizePlugin 方法將之轉(zhuǎn)成對象
        • 期間會調(diào)用 _pluginResolver(ModuleResolver 實例) 來解析模塊
          • 用于解析模塊的 ModuleResolver 類,工作原理類似 webpack 的模塊解析禀崖。源碼
          • 這里值得一提的是 resolve 方法衩辟,它支持從非字符串包、npm 包帆焕、絕對路徑、相對路徑中解析模塊不恭。


          • 相對路徑的模塊先使用 node 的原生 path.resolve 方法解析得到絕對路徑叶雹,然后交給解析絕對路徑模塊的方法處理。
          • 絕對路徑换吧、非字符串包和 npm 包會用通用模塊 CommonModule 表示折晦。




          • 通用模塊有四個屬性:entry、shortcut沾瓦、name满着、fromDep。
        • 還會調(diào)用 flattenPlugin 拍平插件贯莺,主要是獲取配置风喇。
          • 如果傳入配置是函數(shù),則返回調(diào)用后的結(jié)果缕探,入?yún)椴寮x項魂莫、插件上下文、PluginAPI 實例爹耗。


          • 傳入的配置是對象耙考,則返回一個拷貝后的對象。


      • 非 multiple 的插件潭兽,會根據(jù)插件名字去重倦始。


      • 標(biāo)準(zhǔn)化后的插件,會加入到插件隊列中去山卦。
      • 最后鞋邑,存在插件中使用插件的情況時,會調(diào)用 useByPluginsConfig 來實現(xiàn)账蓉。


        • 這里面的 normalizePluginsConfig 會將配置格式化成[[p1]炫狱、[p2]的形式]。
    • 初始化(initialize):先將 _initialized 標(biāo)志位置為 true剔猿,然后注冊所有可用的插件视译。
      • 在初始化之前,內(nèi)部插件的使用归敬,會先于用戶的插件酷含。


      • 注冊(applyPlugin):到這里鄙早,插件已經(jīng)被拆分成細(xì)化的選項,按照信息類(pluginName椅亚、shortcut)限番、鉤子類(ready、compiled 等)呀舔、其他類(chainWebpack弥虐、chainMarkdown、enhanceAppFiles 等)按順序鏈?zhǔn)阶裕╮egisterOption)媚赖。




        此時霜瘪,一個 Option 實例中已經(jīng)承載了若干個插件的邏輯了。

    • enabledPlugins 和 disabledPlugins 兩個只讀屬性可以取啟用(可注冊)或禁用(不可注冊)的插件列表惧磺。
    • getOption 可以取具體的一個選項實例颖对,applyAsyncOption 和 applySyncOption 分別應(yīng)用異步選項和同步選項中的邏輯(回調(diào)函數(shù))。

選項和異步選項磨隘,插件的本體

  1. Option 類
    - 每個實例初始化 key(選項標(biāo)識) 和 items(這個選項所對應(yīng)的函數(shù)們) 屬性缤底。

    • 重要方法:syncApply(也叫 apply),對之前保存在實例中的 items 遍歷調(diào)用 add 方法番捂,如果 item 中的值是函數(shù)个唧,則執(zhí)行之取其返回值。
    • 在插件應(yīng)用選項時如果匹配成功设预,會調(diào)用 add 方法將選項映射成 1-n 個對象推入 items 屬性里坑鱼。


    • 除了 add 還有 delete 和 clear 方法,不做贅述絮缅。(增刪清)
    • 另外有 values鲁沥、entries 和 appliedValues 三個只讀屬性,用于獲取值耕魄、實體画恰、已應(yīng)用的值。
    • 管道方法(pipeline)吸奴,它將實例的 values 屬性柯里化成一個組合函數(shù)允扇,依次執(zhí)行。


  2. AsyncOption 類

    • asyncApply 異步版syncApply则奥,調(diào)用函數(shù)的時候使用了 await考润。
    • parallelApply 如果說 pipeline 是串行,它就是并行:使用了 Promise.all


    • pipeline 同理读处,調(diào)用函數(shù)的時候使用了 await糊治。

特殊選項

  1. EnhanceAppFilesOption、ClientDynamicModulesOption罚舱、GlobalUIComponentsOption井辜、DefineOption绎谦、AliasOption 類
    • AliasOption
      • 在創(chuàng)建 webpack 配置的時候調(diào)用
      • 重寫 apply 方法:先調(diào)用 syncApply,然后將 appliedValues 取出粥脚,設(shè)置為 webpack 的 alias
    • ClientDynamicModulesOption
      • 在 prepare 階段調(diào)用
      • 重寫 apply 方法:從 appliedItems 取出應(yīng)用的插件信息窃肠,遍歷寫入文件以待使用
    • DefineOption
      • 類似 AliasOption,只不過是設(shè)置 webpack 的全局變量
      • 最后在 injections 插件(DefinePlugin)觸發(fā)時收集選項將 define 注入進去
    • EnhanceAppFilesOption
      • 在 prepare 階段調(diào)用
      • 重寫 apply 方法:從 appliedItems 取出插件信息刷允,生成引入模塊或者注冊組件的代碼文件
    • GlobalUIComponentsOption
      • 類似 ClientDynamicModulesOption冤留,寫全局 ui 組件文件

調(diào)用函數(shù)型 Option 時機

  1. extendCli
    創(chuàng)建 cli 命令時
  2. chainMarkdown 和 extendMarkdown
    創(chuàng)建 MarkdownIt 實例時
  3. additionalPages
    解析完所有頁面后
    3、extendPageData
    additionalPages 執(zhí)行完之后树灶,依賴 additionalPages 執(zhí)行完的結(jié)果
  4. ready
    緊跟 additionalPages 之后
  5. clientDynamicModules纤怒、enhanceAppFiles、globalUIComponents
    緊跟 ready 之后
  6. define破托、alias
    創(chuàng)建公共 webpack 配置后
  7. chainWebpack
    創(chuàng)建 dev webpack 配置后肪跋、創(chuàng)建 build webpack 配置后
  8. beforeDevServer
    webpack-dev-server 的 before 選項執(zhí)行后
  9. afterDevServer
    webpack-dev-server 的 after 選項執(zhí)行后
  10. generated
    build 完成后
  11. updated
    文件更新后
  12. clientRootMixin
    clientDynamicModules 選項執(zhí)行時

編寫一個 vuepress 插件

我也寫了一個小插件歧蒋,它可以將你的 vuepress 站點下載成一個 pdf 文件:vuepress-plugin-export-site

源碼

  1. 使用 ready 選項
  2. 借助 puppeteer 和 easy-pdf-merge 實現(xiàn):從上下文中拿到路由信息土砂,然后使用 puppeteer 遍歷訪問并下載,最后合并成一個大 PDF谜洽。
    • 因為需要下載 chromium萝映,所以國內(nèi)網(wǎng)絡(luò)受限。我們換成了 puppeteer-cn阐虚。
    • easy-pdf-merge 如果在 windows 下運行需要指定 jar 環(huán)境變量序臂。

后記

我們熟悉的 webpack、vue 也有插件系統(tǒng)实束,它們都有兩個共同的特點:

  1. 提供一個功能擴展點奥秆,讓插件能夠去擴展它。
  2. 提供一個功能注冊功能咸灿,讓插件注冊進來构订。

其實插件機制也可以看做設(shè)計模式的一種體現(xiàn):抽離出變化的部分,保留不變的部分避矢。這些變化的部分悼瘾,便可以稱之為插件。

在我們造輪子的時候审胸,如果輪子的功能越來越多亥宿,代碼越來越臃腫的話,引入插件機制會讓后續(xù)的開發(fā)更加靈活砂沛。

最后烫扼,幫插件機制的開發(fā)者真山同學(xué)宣傳一下,屆時會有更加精彩的 vuepress 分享:


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末碍庵,一起剝皮案震驚了整個濱河市材蛛,隨后出現(xiàn)的幾起案子圆到,更是在濱河造成了極大的恐慌,老刑警劉巖卑吭,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件芽淡,死亡現(xiàn)場離奇詭異,居然都是意外死亡豆赏,警方通過查閱死者的電腦和手機挣菲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來掷邦,“玉大人白胀,你說我怎么就攤上這事「Ц冢” “怎么了或杠?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長宣蔚。 經(jīng)常有香客問我向抢,道長,這世上最難降的妖魔是什么胚委? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任挟鸠,我火速辦了婚禮,結(jié)果婚禮上亩冬,老公的妹妹穿的比我還像新娘艘希。我一直安慰自己凛虽,他們只是感情好锌杀,可當(dāng)我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著寥袭,像睡著了一般营袜。 火紅的嫁衣襯著肌膚如雪撒顿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天连茧,我揣著相機與錄音核蘸,去河邊找鬼。 笑死啸驯,一個胖子當(dāng)著我的面吹牛客扎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播罚斗,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼徙鱼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起袱吆,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤厌衙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后绞绒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體婶希,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年蓬衡,在試婚紗的時候發(fā)現(xiàn)自己被綠了喻杈。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡狰晚,死狀恐怖筒饰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情壁晒,我是刑警寧澤瓷们,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站秒咐,受9級特大地震影響谬晕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜反镇,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一固蚤、第九天 我趴在偏房一處隱蔽的房頂上張望娘汞。 院中可真熱鬧歹茶,春花似錦、人聲如沸你弦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽禽作。三九已至尸昧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間旷偿,已是汗流浹背烹俗。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留萍程,地道東北人幢妄。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像茫负,于是被迫代替她去往敵國和親蕉鸳。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Vue 實例 屬性和方法 每個 Vue 實例都會代理其 data 對象里所有的屬性:var data = { a:...
    云之外閱讀 2,214評論 0 6
  • 第一部分 HTML&CSS整理答案 1. 什么是HTML5? 答:HTML5是最新的HTML標(biāo)準(zhǔn)潮尝。 注意:講述HT...
    kismetajun閱讀 27,489評論 1 45
  • GitChat技術(shù)雜談 前言 本文較長榕吼,為了節(jié)省你的閱讀時間,在文前列寫作思路如下: 什么是 webpack勉失,它要...
    蕭玄辭閱讀 12,697評論 7 110
  • 因新工作主要負(fù)責(zé)微信小程序這一塊羹蚣,最近的重心就移到這一塊,該博客是對微信小程序整體的整理歸納以及標(biāo)明一些細(xì)節(jié)點乱凿,初...
    majun00閱讀 7,341評論 0 9
  • 今日任務(wù)1500度宦,未完成。 比學(xué)習(xí):學(xué)習(xí)別人的長處告匠,彌補自己的短處戈抄。 比付出:撥打回訪。 比改變:不要糾結(jié)一些小事...
    姜博士眼鏡任亞茹1885331閱讀 167評論 0 0