Featured image of post 记一次 Vue 3 动态展示 markdown 文件

记一次 Vue 3 动态展示 markdown 文件

在 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 文件,下面是完整的实现。

  • router.ts
 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 },
      ]
    },
  ],
});
  • components/markdown.vue
 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);
    });
  }
};
  • main.ts
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 展示组件!

继续加油!

(完)