自定義 loader 讀取 *.vue 文件源碼

相關(guān)依賴版本:

  • 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 文件的源碼并高亮展示到頁面上渣触,又不想用第三方的依賴(其實(shí)是找不到)震捣。

2. 實(shí)現(xiàn)思路

通過 vue-loader 自定義塊 功能月幌,獲取目標(biāo)文件的文件路徑丽焊,然后通過 fs 讀取源碼诫咱,再用 @vue/compiler-core 的 API baseParse將讀取到的內(nèi)容轉(zhuǎn)換成 AST 語法抽象樹子寓,然后將 fs 讀取的內(nèi)容中 抽離出 自定義塊內(nèi)容 和 需要的源碼,最后再將以上兩個(gè)內(nèi)容重新掛到組件對(duì)象上,直接讀取組件相應(yīng)的字段就可以冠句。

完美,關(guān)機(jī)幸乒,下班懦底。

3. 實(shí)現(xiàn)

現(xiàn)在思路已經(jīng)非常的清晰,時(shí)候?qū)崿F(xiàn)它了罕扎。

3.1 項(xiàng)目初始化

vue-cli 創(chuàng)建快速模板搭建項(xiàng)目聚唐,這里用的是 2版本的 vue,后面再用 vite + vue3 實(shí)現(xiàn)一個(gè)壳影。

image-20201210225929248

項(xiàng)目跑起來是下面這個(gè)樣子的拱层,這里大家應(yīng)該都會(huì)的,就不多贅述了宴咧。

image-20201210231214294

3.2 自定義塊

這里參考 vue-loader 官網(wǎng)的例子根灯,非常的簡單。不懂的同學(xué)掺栅,可以去官網(wǎng)查看岭接。

  1. 創(chuàng)建loader文件 plugins/docs-loader.js
module.exports = function (source, map) {
    this.callback(
        null,
        `export default function (Component) {
            Component.options.__docs = ${
                JSON.stringify(source)
            }
        }`,
        map
    )
}
  1. 創(chuàng)建 vue.config.js 配置規(guī)則使用上面定義好的 loader
const docsLoader = require.resolve('./plugins/docs-loader.js')

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

注:修改了配置相關(guān)文件需要重跑一下項(xiàng)目

  1. 使用

src/components/demo.vue

<docs>
    我是ComponentB docs自定義快 內(nèi)容
</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>

效果:

image-20201210232732127

Demo 組件在控制臺(tái)輸出效果會(huì)更明顯一點(diǎn):

image-20201210232901114

3.4 獲取文件路徑并顯示內(nèi)容

在獲取文件的路徑的時(shí)候畜隶,瞎?jié)沈v了好久(此處省略好多個(gè)字),結(jié)果 webpack 的英文官網(wǎng)是有提到。于是就去打印一下 loaderthis 县踢,真的什么都有,早知道早點(diǎn)打印出來看了捌归,害Q窃佟!闪檬! 留下了沒技術(shù)的眼淚星著。

image-20201210234040621

現(xiàn)在已經(jīng)拿到目標(biāo)文件的完整路徑了,開始搞事情粗悯!給我們自定義的 loader 稍微加一點(diǎn)細(xì)節(jié):

搞事前需要安裝一下相關(guān)依賴:

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

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

完成以上步驟虚循,記得重跑項(xiàng)目。現(xiàn)在我們來看看效果如何:

image-20201210235104113

em... 不錯(cuò)样傍,Demo 組件該有的都有了横缔。再用 pre 標(biāo)簽顯示出來看:

image-20201210235401173
<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>

到這里需求好像已經(jīng)全部實(shí)現(xiàn),很是輕松衫哥,作為一個(gè)剛畢業(yè)五個(gè)月的干飯人怎么能止步在這里呢茎刚!我決定讓這平平無奇的代碼高亮起來,讓他變得漂漂亮亮的撤逢。

3.5 代碼高亮

代碼高亮用了一個(gè) 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>

效果:

image-20201211001635863

代碼高亮了捌斧,是喜歡的顏色。亮是亮起來了泉沾,但是寫得是一次性代碼捞蚂,不大符合干飯人的要求,是不是可以封裝一個(gè)公共組件專門來看組件的效果和源碼的呢跷究!

3.6 組件封裝

封裝組件之前需要構(gòu)思一下這個(gè)組件應(yīng)該長什么樣呢姓迅?帶著樣的一個(gè)疑問,去瀏覽了各個(gè)優(yōu)秀輪子的文檔頁面俊马,畫出了下面的設(shè)計(jì)圖:

image-20201211002904439

開始全局組件封裝:

  1. 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>

  1. src/components/component-source-demo/index.js
import ComponentSourceDemo from './src/index'

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

export default ComponentSourceDemo

使用:

  1. src/mian.js 全局注冊(cè)組件

    image-20201211004750178
  2. 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>

代碼非常的清爽丁存,舒服!2裎摇解寝! 效果也非常的棒,甲方很滿意艘儒。

03
感覺還是有點(diǎn)美中不足聋伦,如果有很多個(gè)需要展示的組件呢。那豈不是要寫很多的重復(fù)代碼界睁,作為優(yōu)秀的干飯人是不允許這種情況出現(xiàn)的觉增,代碼還需再優(yōu)化一下。

3.7 代碼優(yōu)化

3.7.1 組件自動(dòng)引入

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>
image-20201211012252290
image-20201211012523830

現(xiàn)在只需往 components/demo 添加的新的組件翻斟,我們只需刷新一下webpack 就會(huì)幫我們自動(dòng)讀取組件了逾礁。

4. 總結(jié)

到這里基本完工了,很多的知識(shí)點(diǎn)都是現(xiàn)學(xué)現(xiàn)賣的访惜,如果哪里講的不對(duì)希望大家指出嘹履,哪里講得不好希望大家多多包涵。

在這里需要感謝 方應(yīng)杭 方方老師提供的思路债热。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末砾嫉,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子阳柔,更是在濱河造成了極大的恐慌焰枢,老刑警劉巖蚓峦,帶你破解...
    沈念sama閱讀 221,548評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件舌剂,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡暑椰,警方通過查閱死者的電腦和手機(jī)霍转,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來一汽,“玉大人避消,你說我怎么就攤上這事低滩。” “怎么了岩喷?”我有些...
    開封第一講書人閱讀 167,990評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵恕沫,是天一觀的道長。 經(jīng)常有香客問我纱意,道長婶溯,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,618評(píng)論 1 296
  • 正文 為了忘掉前任偷霉,我火速辦了婚禮迄委,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘类少。我一直安慰自己叙身,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評(píng)論 6 397
  • 文/花漫 我一把揭開白布硫狞。 她就那樣靜靜地躺著信轿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪残吩。 梳的紋絲不亂的頭發(fā)上虏两,一...
    開封第一講書人閱讀 52,246評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音世剖,去河邊找鬼定罢。 笑死,一個(gè)胖子當(dāng)著我的面吹牛旁瘫,可吹牛的內(nèi)容都是我干的祖凫。 我是一名探鬼主播,決...
    沈念sama閱讀 40,819評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼酬凳,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼惠况!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起宁仔,我...
    開封第一講書人閱讀 39,725評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤稠屠,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后翎苫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體权埠,經(jīng)...
    沈念sama閱讀 46,268評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評(píng)論 3 340
  • 正文 我和宋清朗相戀三年煎谍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了攘蔽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,488評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡呐粘,死狀恐怖满俗,靈堂內(nèi)的尸體忽然破棺而出转捕,到底是詐尸還是另有隱情,我是刑警寧澤唆垃,帶...
    沈念sama閱讀 36,181評(píng)論 5 350
  • 正文 年R本政府宣布五芝,位于F島的核電站,受9級(jí)特大地震影響辕万,放射性物質(zhì)發(fā)生泄漏与柑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評(píng)論 3 333
  • 文/蒙蒙 一蓄坏、第九天 我趴在偏房一處隱蔽的房頂上張望价捧。 院中可真熱鬧,春花似錦涡戳、人聲如沸结蟋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嵌屎。三九已至,卻和暖如春恍涂,著一層夾襖步出監(jiān)牢的瞬間宝惰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評(píng)論 1 272
  • 我被黑心中介騙來泰國打工再沧, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留尼夺,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,897評(píng)論 3 376
  • 正文 我出身青樓炒瘸,卻偏偏與公主長得像淤堵,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子顷扩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評(píng)論 2 359

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