在 Vue 3 将 .md
展示在页面中并且高亮
在搭建组件库官网的过程中,因为需要将 markdown 转换为 html,并且提供代码块高亮展示,这时候就遇到了一些问题。可以点击 Desn-UI 官网 进行预览。
- 因为不能把每个想用 markdown 写的文档页面单独写为组件,那样太不程序员了。
- 但如果只用一个模版展示,其他的用
slot
插槽呢?这又有一个问题,必须得用 html 来编写文档,这样也太不程序员了。
如果我可以用 markdown 来写文档,用单个 vue 组件来读取这些 .md
文件,那就太棒了!于是我就跳进了这个坑里。
期间尝试各种插件,各种高亮引入等等,失败了一天多,这个过程就不赘述了,大抵就是从早到晚搜搜搜,试试试,最后也没弄出来的难过心情。
然后在翻看 Vite 2 文档时,看到了这个:静态资源处理
这里 Vite 官方为静态资源提供了多种导入形式,其中有一项: 将资源引入为字符串,那这个就是我想要的了,于是就开始了尝试。
实现思路
在组件内,将 markdown.md 引入为字符串。打印出来会发现,它把 markdown.md 里面的 markdown 格式内容完全打印了出来。
1
2
|
import markdown from 'markdown.md?raw'
console.log(markdown)
|
拿到了 markdown 字符串之后就可以把它转换为 html 字符串进行渲染了,然后添加高亮等等都可以。
最终实现
尝试完了之后发现其实还是有点麻烦,最终我采用了动态组件的实现方式,用到了 vite-plugin-md
这个插件,并且通过 Vue Router
实现了单个展示组件展示任意 markdown 文档。
首先创建一个用来展示 markdown 的页面组件。这个组件里面有一个动态组件 <component :is="" />
。
is 什么呢?那就在 script 里面引入 .md 文件,下面是完整的实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
import { createRouter, createWebHistory } from 'vue-router';
import Markdown from './components/Markdown.vue';
const history = createWebHistory();
export default createRouter({
history,
routes: [
{
path: '/doc',
component: Doc,
children: [
{ path: '/doc/:name', component: Markdown },
]
},
],
});
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
<template>
<component :is = "content" :key="name" />
</template>
<script lang="ts" setup>
import { shallowRef, watchEffect, computed, ComputedRef } from 'vue';
import { useRoute } from 'vue-router';
import router from '../router';
const route = useRoute();
const content: any = shallowRef(null);
const currentName: ComputedRef = computed(() => {
return route.params.name;
});
watchEffect(() => {
if (route.path.startsWith('/doc/')) {
import(/* @vite-ignore */'../markdown/' + currentName.value + '.md')
.then(e => {
content.value = e.default;
}).catch(() => {
router.replace('/404');
});
}
});
</script>
|
这样就可以实现用户在访问 /doc/*
的路径时,自动展示 src/markdown
内名字为 *.md
的 markdown 文件内容。不需要一个文件一个组件,也不需要新建 markdown 就要去别的地方加一点东西了。
主要原理就是:
- 在 router.ts 注册路由为
/doc/*
的所有路由都展示 Markdown 组件,这个路由添加了一个动态参数,可以将 /doc/
后面的参数传给 Markdown 组件。
- 在 Markdown 组件使用
watchEffect
对全局路由变化进行监听,当匹配到路由前缀为 /doc/
的路由变化时,动态 import src/markdown
内的同名 .md
的内容。
- 由于我安装了
vite-plugin-md
插件,它会自动把 .md
引入为 vue 组件,这时就可以使用 vue 的动态组件来动态展示内容了。
这里有几个点需要注意:
- 组件内匹配到路由前缀后,如未找到对应文件,则需要跳转到 404 页面;
- 动态组件必须有一个不会重复的
:key
,否则视图可能不会正确地更新;
代码高亮
至于代码部分高亮,我使用了 highlight.js
, 在 main.ts
内注册了一个全局指令 highlight
,之后在我需要高亮的代码容器添加一个 v-highlight
即可实现代码高亮。
- highlight.js 这里我使用了单语言引入,你可以查看官方文档进行其他方式引入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
// 引入 highlight 核心和语言
import hljs from 'highlight.js/lib/core';
import javascript from 'highlight.js/lib/languages/javascript';
import xml from 'highlight.js/lib/languages/xml';
import shell from 'highlight.js/lib/languages/shell';
// 注册 highlight 语言
hljs.registerLanguage('javascript', javascript);
hljs.registerLanguage('xml', xml);
hljs.registerLanguage('shell', shell);
hljs.configure({
ignoreUnescapedHTML: true
});
export default {
mounted(el) {
const blocks = el.querySelectorAll('pre code');
blocks.forEach((block: HTMLDivElement) => {
hljs.highlightElement(block);
});
}
};
|
1
2
3
4
5
6
|
// 引入自定义指令
import highlight from './directives/highlight';
import './assets/style/highlight.scss';
// 添加自定义指令
app.directive('highlight', highlight);
|
- Markdown.vue 刚才的例子里,直接添加一个指令即可
1
2
3
|
<template>
<component :is = "content" :key="name" v-highlight/>
</template>
|
OK,我们完成了一个动态 markdown 展示组件!
继续加油!
(完)