相關(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è)壳影。
項(xiàng)目跑起來是下面這個(gè)樣子的拱层,這里大家應(yīng)該都會(huì)的,就不多贅述了宴咧。
3.2 自定義塊
這里參考 vue-loader 官網(wǎng)的例子根灯,非常的簡單。不懂的同學(xué)掺栅,可以去官網(wǎng)查看岭接。
- 創(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
)
}
- 創(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)目
- 使用
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>
效果:
將 Demo
組件在控制臺(tái)輸出效果會(huì)更明顯一點(diǎn):
3.4 獲取文件路徑并顯示內(nèi)容
在獲取文件的路徑的時(shí)候畜隶,瞎?jié)沈v了好久(此處省略好多個(gè)字),結(jié)果 webpack 的英文官網(wǎng)是有提到。于是就去打印一下 loader
的 this
县踢,真的什么都有,早知道早點(diǎn)打印出來看了捌归,害Q窃佟!闪檬! 留下了沒技術(shù)的眼淚星著。
現(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)在我們來看看效果如何:
em... 不錯(cuò)样傍,Demo
組件該有的都有了横缔。再用 pre
標(biāo)簽顯示出來看:
<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>
效果:
代碼高亮了捌斧,是喜歡的顏色。亮是亮起來了泉沾,但是寫得是一次性代碼捞蚂,不大符合干飯人的要求,是不是可以封裝一個(gè)公共組件專門來看組件的效果和源碼的呢跷究!
3.6 組件封裝
封裝組件之前需要構(gòu)思一下這個(gè)組件應(yīng)該長什么樣呢姓迅?帶著樣的一個(gè)疑問,去瀏覽了各個(gè)優(yōu)秀輪子的文檔頁面俊马,畫出了下面的設(shè)計(jì)圖:
開始全局組件封裝:
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
全局注冊(cè)組件image-20201211004750178 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裎摇解寝! 效果也非常的棒,甲方很滿意艘儒。
感覺還是有點(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>
現(xiàn)在只需往 components/demo
添加的新的組件翻斟,我們只需刷新一下webpack
就會(huì)幫我們自動(dòng)讀取組件了逾礁。
4. 總結(jié)
到這里基本完工了,很多的知識(shí)點(diǎn)都是現(xiàn)學(xué)現(xiàn)賣的访惜,如果哪里講的不對(duì)希望大家指出嘹履,哪里講得不好希望大家多多包涵。
在這里需要感謝 方應(yīng)杭 方方老師提供的思路债热。