Vue造輪子必備*.vue文件源碼讀取並高亮展示

2021-03-06 前端切圖工程師

相關依賴版本:

node v10.15.0

npm v6.4.1

yarn v1.22.10

vue-cli v4.5.9

@vue/compiler v3.0.4


GitHub:  vue-source-demo


1. 前言(需求)

就是想讀取 *.vue 文件的源碼並高亮展示到頁面上,又不想用第三方的依賴(其實是找不到)。


2. 實現思路

通過  vue-loader 自定義塊 功能,獲取目標文件的文件路徑,然後通過 fs 讀取源碼,再用 @vue/compiler-core 的 API baseParse將讀取到的內容轉換成 AST 語法抽象樹,然後將 fs 讀取的內容中 抽離出 自定義塊內容 和 需要的源碼,最後再將以上兩個內容重新掛到組件對象上,直接讀取組件相應的欄位就可以。

完美,關機,下班。


3. 實現

現在思路已經非常的清晰,時候實現它了。


3.1 項目初始化

vue-cli 創建快速模板搭建項目,這裡用的是 2版本的 vue,後面再用 vite + vue3 實現一個。

項目跑起來是下面這個樣子的,這裡大家應該都會的,就不多贅述了。

3.2 自定義塊

這裡參考  vue-loader 官網的例子,非常的簡單。不懂的同學,可以去官網查看。

創建loader文件 plugins/docs-loader.js

module.exports = function (source, map) {
   this.callback(
       null,
       `export default function (Component) {
           Component.options.__docs = ${
               JSON.stringify(source)
           }
       }`,
       map
  )
}


創建 vue.config.js 配置規則使用上面定義好的 loader

const docsLoader = require.resolve('./plugins/docs-loader.js')

module.exports = {
   configureWebpack: {
       module: {
           rules: [
              {
                   resourceQuery: /blockType=docs/,
                   loader: docsLoader
              }
          ]
      }
  }
}

註:修改了配置相關文件需要重跑一下項目


使用

src/components/demo.vue

<docs>
  我是ComponentB docs自定義快 內容
</docs>

<template>
   <div>
      ComponentB 組件
   </div>
</template>

<script>
   export default {
       name: "ComponentB"
  }
</script>

<style scoped>

</style>


src/App.vue

<template>
   <div id="app">
       <demo/>
       <p>{{demoDocs}}</p>
   </div>
</template>

<script>
   import Demo from './components/demo'

   export default {
       name: 'App',
       components: {
           Demo
      },
       data () {
           return {
               demoDocs: Demo.__docs
          }
      }
  }
</script>


效果:

將 Demo 組件在控制臺輸出效果會更明顯一點:


3.4 獲取文件路徑並顯示內容

在獲取文件的路徑的時候,瞎澤騰了好久(此處省略好多個字),結果 webpack 的英文官網是有提到。於是就去列印一下 loader 的 this ,真的什麼都有,早知道早點列印出來看了,害!!!留下了沒技術的眼淚。


現在已經拿到目標文件的完整路徑了,開始搞事情!給我們自定義的 loader 稍微加一點細節:

搞事前需要安裝一下相關依賴:

yarn add -D @vue/compiler-core


const fs = require('fs');
const {baseParse} = require('@vue/compiler-core');

module.exports = function (source, map) {
   // 1. 獲取帶有 <docs /> 標籤的文件完整路徑
   const {resourcePath} = this
   // 2. 讀取文件內容
   const file = fs.readFileSync(resourcePath).toString()
   // 3. 通過 baseParse 將字符串模板轉換成 AST 抽象語法樹
   const parsed = baseParse(file).children.find(n => n.tag === 'docs')
   // 4. 標題
   const title = parsed.children[0].content
   // 5. 將 <docs></docs> 標籤和內容抽離
   const main = file.split(parsed.loc.source).join('').trim()
   // 6. 回到並添加到 組件對象上面
   this.callback(
       null,
       `export default function (Component) {
         Component.options.__sourceCode = ${JSON.stringify(main)}
         Component.options.__sourceCodeTitle = ${JSON.stringify(title)}
       }`,
       map
  )
}

完成以上步驟,記得重跑項目。現在我們來看看效果如何:

em... 不錯,Demo 組件該有的都有了。再用 pre 標籤顯示出來看:

<template>
   <div id="app">
       <demo/>
       <p>{{sourceCodeTitle}}</p>
       <pre v-text="sourceCode"></pre>
   </div>
</template>

<script>
   import Demo from './components/demo'

   export default {
       name: 'App',
       components: {
           Demo
      },
       data () {
           return {
               sourceCodeTitle: Demo.__sourceCodeTitle,
               sourceCode: Demo.__sourceCode
          }
      },
       mounted() {
           console.log('Demo', Demo)
      }
  }
</script>


到這裡需求好像已經全部實現,很是輕鬆,作為一個剛畢業五個月的乾飯人怎麼能止步在這裡呢!我決定讓這平平無奇的代碼高亮起來,讓他變得漂漂亮亮的。


3.5 代碼高亮

代碼高亮用了一個 star 比較高的 highlightjs。

安裝:

yarn add highlight.js


使用:

src/App.vue

<template>
   <div id="app">
       <demo/>
       <p>{{sourceCodeTitle}}</p>
       <pre>
           <code class="language-html" ref="code" v-text="sourceCode" />
       </pre>
   </div>
</template>

<script>
   import Demo from './components/demo'
   import highlightjs from 'highlight.js'
   import 'highlight.js/styles/vs2015.css'

   export default {
       name: 'App',
       components: {
           Demo
      },
       data () {
           return {
               sourceCodeTitle: Demo.__sourceCodeTitle,
               sourceCode: Demo.__sourceCode
          }
      },
       async mounted() {
           await this.$nextTick()
           this.init()
      },
       methods: {
           init () {
               const codeEl = this.$refs.code
               highlightjs.highlightBlock(codeEl)
          }
      }
  }
</script>

效果:

代碼高亮了,是喜歡的顏色。亮是亮起來了,但是寫得是一次性代碼,不大符合乾飯人的要求,是不是可以封裝一個公共組件專門來看組件的效果和源碼的呢!


3.6 組件封裝

封裝組件之前需要構思一下這個組件應該長什麼樣呢?帶著樣的一個疑問,去瀏覽了各個優秀輪子的文檔頁面,畫出了下面的設計圖:

開始全局組件封裝:

src/components/component-source-demo/src/index.vue

<template>
   <div class="component-source-demo">
       <h2 class="component-source-demo__title">{{title || component.__sourceCodeTitle}}</h2>
       <div class="component-source-demo__description">{{description}}</div>
       <div class="component-source-demo__component">
           <component :is="component" :key="component.__sourceCodeTitle"/>
       </div>
       <div class="component-source-demo__action">
           <button type="button" @click="handleCodeVisible('hide')" v-if="codeVisible">隱藏代碼 ↑</button>
           <button type="button" @click="handleCodeVisible('show')" v-else>查看代碼 ↓</button>
       </div>
       <div class="component-source-demo__code" v-show="codeVisible">
     <pre>
       <code class="html" ref="code" v-text="component.__sourceCode"/>
     </pre>
       </div>
   </div>
</template>

<script>
   import {highlightBlock} from 'highlight.js';
   import 'highlight.js/styles/vs2015.css'

   export default {
       name: "component-source-demo",
       props: {
           title: String,
           description: String,
           component: {
               type: Object,
               required: true
          }
      },
       data() {
           return {
               codeVisible: true
          }
      },
       async mounted() {
           await this.$nextTick()
           this.init()
      },
       methods: {
           init () {
               const codeEl = this.$refs.code
               highlightBlock(codeEl)
          },
           handleCodeVisible(status) {
               this.codeVisible = status === 'show'
          }
      }
  }
</script>

<style scoped>

</style>


src/components/component-source-demo/index.js

import ComponentSourceDemo from './src/index'

ComponentSourceDemo.install = (Vue) => Vue.component(ComponentSourceDemo.name, ComponentSourceDemo)

export default ComponentSourceDemo


使用:

src/mian.js 全局註冊組件

src/App.vue

<template>
   <div id="app">
       <component-source-demo :component="Demo"/>
   </div>
</template>

<script>
   import Demo from './components/demo'

   export default {
       name: 'App',
       data () {
           return {
               Demo
          }
      }
  }
</script>

代碼非常的清爽,舒服!!!效果也非常的棒,甲方很滿意。

感覺還是有點美中不足,如果有很多個需要展示的組件呢。那豈不是要寫很多的重複代碼,作為優秀的乾飯人是不允許這種情況出現的,代碼還需再優化一下。


3.7 代碼優化


3.7.1 組件自動引入

src/App.vue

<template>
   <div id="app">
       <component-source-demo
               v-for="item in componentList"
               :key="item.name"
               :component="item"
       />
   </div>
</template>

<script>
   export default {
       name: 'App',
       data () {
           return {
               componentList: []
          }
      },
       mounted() {
           this.autoImportComponents()
      },
       methods: {
           autoImportComponents () {
               const moduleList = require.context('./components/demo', false, /\.vue$/)
               const requireAll = requireContext => requireContext.keys().map(requireContext)
               let targetModuleList = requireAll(moduleList)
               this.componentList = targetModuleList.map(module => {
                   return module.default
              })
          }
      }
  }
</script>


現在只需往 components/demo 添加的新的組件,我們只需刷新一下webpack 就會幫我們自動讀取組件了。


4. 總結

到這裡基本完工了,很多的知識點都是現學現賣的,如果哪裡講的不對希望大家指出,哪裡講得不好希望大家多多包涵。

在這裡需要感謝 方應杭 方方老師提供的思路。

相關焦點

  • Vue-Router源碼學習之index.js(vue-router類)
    前言上一篇我們聊了:Vue-Router源碼學習之install方法雖然最近需求著實不少,但是感覺自己學習勁頭還是蠻足的今天,帶來Vue-Router源碼解析系列的第二篇文章:index.js。正文vue-router類裡面都做了什麼?
  • Vue面試題(3)Vue-Router和Vuex
    _committing = committing}3、為啥要有vuex,使用localStorage本地存儲不行麼?Vuex 是一個專為 Vue.js 應用程式開發的狀態管理模式,Vuex 的狀態存儲是響應式的。當 Vue 組件從 store 中讀取狀態的時候,若 store 中的狀態發生變化,那麼相應的組件也會相應地得到高效更新。
  • vue-elemnt-admin學習
    vue-elemnt-admin學習vue-element-admin是一個基於vue,element-ui
  • Vue入門10 vue+elementUI
    npm i element-ui -SCDN目前可以通過unpkg.com/element-ui獲取到最新版本的資源,在頁面上會js和css文件即可開始使用。<!注意:命令行都要使用管理員模式運行1、創建一個名為hello-vue的工程vue init webpack hello-vue
  • Vue Element+Node.js開發企業通用管理後臺系統
    6-1 vuex實現原理講解6-2 vue-router實現原理講解6-3 vue-router路由守衛6-4 vue-router路由元信息6-5 vue-router API的使用第7章 前端開發框架搭建集成 Github 4.3w+ Star的明星項目 vue-element-admin,本節將基於該項目完成前端框架搭建。
  • Vue全家桶&vue-router原理
    原理 Vue全家桶:vue + vue-router(路由)+ vuex(狀態管理)+ axios(請求)本次主要分析並實現簡版vue-router,從源碼中學習借鑑,更好的理解源碼vue-routerVue Router 是 Vue.js 官⽅的路由管理器。
  • Vue的異步更新實現原理
    最近面試總是會被問到這麼一個問題:在使用vue的時候,將for循環中聲明的變量i從1增加到100,然後將i展示到頁面上,頁面上的i是從1跳到100,還是會怎樣?答案當然是只會顯示100,並不會有跳轉的過程。怎麼可以讓頁面上有從1到100顯示的過程呢,就是用setTimeout或者Promise.then等方法去模擬。
  • 【項目推薦】Vue.js
    在 package control中安裝Vuejs SnippetsVue Syntax Highlight推薦使用 npm 管理,新建兩個文件 app.html,app.js,為了美觀使用 bootstrap,我們的頁面模板看起來是這樣:<!
  • Springboot Vue Login(從零開始實現Springboot+Vue登錄)
    創建 Vue 項目 (進入自己想創建的文件夾位置,我放在 D:\VSCodeWorkSpace),創建語句 vue create vue-spring-login-summed,方向鍵選擇創建方式,我選擇的默認2.
  • Vue3.0新特性
    源碼就是ts寫的為什麼要有vue3.0?指向上下文,主要是沒考慮的ts的集成ts+ vue Cli 項目搭建環境2)將這個功能模塊化使用在src下新建hooks文件夾1.7hooks模塊化理解1)異步請求模塊在src/hooks中新建useUrlLoader.ts文件
  • vue基本知識點總結——面試必備
    ② 運行期間的生命周期函數:beforeUpdate():此時,data數據發生改變後,還沒有重新渲染DOM樹,data的數據是最新的,但是頁面上展示的還是舊數據。updated():此時,data中的數據和頁面中的渲染是一致的。③ 銷毀期間的生命周期函數:beforeDestroy():此時,實例的方法、指令都還可以使用,實例銷毀之前調用。
  • 深入認識 vue-cli:能做的不僅僅是初始化 vue 工程
    藉助 vue-cli,我們通過非常簡單的問答形式,方便地初始化一個vue工程,完全不需要擔心繁複的webpack、eslint配置等等。但是,仍然有許多同學沒有搞清楚 vue-cli和 vue工程之間的關係,導致沒有充分發揮 vue-cli的功能。在這篇文章中,我將從底層原理開始並結合幾個例子,告訴大家 vue-cli還能這樣用。
  • 實現最精簡的響應式系統來學習Vue的data、computed、watch源碼
    導讀記得初學Vue源碼的時候,在defineReactive、Observer、Dep、Watcher等等內部設計源碼之間跳來跳去,發現再也繞不出來了。Vue發展了很久,很多fix和feature的增加讓內部源碼越來越龐大,太多的邊界情況和優化設計掩蓋了原本精簡的代碼設計,讓新手閱讀源碼變得越來越困難,但是面試的時候,Vue的響應式原理幾乎成了Vue技術棧的公司面試中高級前端必問的點之一。
  • 解讀 vue-class-component 源碼實現原理
    vue-property-decorator 提供定義好的裝飾器,輔助完成所需功能,對這個過程好奇,就研究了源碼。內部主要依靠vue-class-component 實現,所以將重點放在對 vue-class-component 的解讀上。
  • vue-router 實現分析
    是 Vue.js 官方的路由庫,本著學習的目的,我對 vue-router 的源碼進行了閱讀和分析,分享出來給其他感興趣的同學做個參考吧。參考初步我們分別從不同的視角來看 vue-router。})源碼(https://github.com/vuejs/vue-router/blob/v2.2.1/src/history/hash.js#L21)這個動作是什麼時候執行的呢?是在 router.init()(源碼)中調用的,而 router.init() 則是在根組件創建時(源碼)調用的。
  • vue高級進階系列——用typescript玩轉vue和vuex
    本文轉載自【微信公眾號:趣談前端,ID:beautifulFront】經微信公眾號授權轉載,如需轉載與原文作者聯繫用過vue的朋友大概對vuex也不陌生,vuex的官方解釋是專為 Vue.js 應用程式開發的狀態管理模式。它採用集中式存儲管理應用的所有組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。
  • vue題
    當有一個通用的響應數據變化的時候,要執行一些業務邏輯或異步操作的時候建議使用watcher7、Vue的路由實現:hash模式 和 history模式hash模式:在瀏覽器中符號「#」,#以及#後面的字符稱之為hash,用window.location.hash讀取;特點:hash雖然在URL中,但不被包括在HTTP請求中;用來指導瀏覽器動作
  • Vue CLI 3.0 正式發布,Vue.js 開發標準化工具
    無需 Eject 即可配置上面列出的所有功能都可以零配置:當通過 Vue CLI 3 構建項目時,它會安裝 Vue CLI 運行時服務(@ vue / cli-service),選擇功能插件,並生成必要的配置文件。在多數情況下,你只需要專注於編寫代碼。
  • Vue知識點總結(24)——使用VueCli創建一個項目(超級詳細)
    首先我們打開終端,在合適的目錄下運行以下命令:(前提是你已經完整了上篇文章對於Vue-Cli3的基本配置)vue create hellovuecli3這個hellovuecli3是文件名,文件名大家可以自擬,但是注意,不要有大寫字母的出現,這裡是不支持大寫的。
  • 結合源碼理解vue列表渲染v-for中的key屬性
    剛開始使用vue框架的同學有的喜歡用v-for的index係數來作為key的值,這樣和用元素的唯一id作為key有什麼區別呢?這次來給大家講解。        上篇文章結合源碼給大家講解了vue2.0的生命周期和響應式原理,模板一開始要經過編譯成virtualDOM,之後vue在渲染VirtualDOM時通過get函數進行依賴收集和當數據改變時通過set函數進行派發更新。