手搓Vite插件实现Markdown文档的解析与引入

1,578 阅读1分钟

起因

最近在折腾个人博客,其中花了挺多时间折腾的点就是如何解析与引入 Markdown 文档。
采用的技术栈是 Vite+Vue,所用的解析器是比较经典的 marked ,不过换成诸如 markdown-it 之类的其他解析器也无妨,其背后的原理都是一样的。

前期的探索

这里都是一些无聊至极的碎碎念,记录了一些探索过程。

最先想到的办法是,每写完一篇文档,就新建一个组件,然后把文档的全部内容手动复制进去解析。或许将文档内容全部复制到一个 json 格式的文件中然后导入这种做法会稍微聪明一些,不用面对恐怖的组件地狱,不过本质上没有区别,仍然会为人带来巨量的心智负担。显而易见这种方式也太蠢了。

人性化一些的方案是,通过网络请求的方式读取文件并解析,将需要渲染的文档放到public目录下,在构建时直接加载到网页的源文件中,然后发起请求,拿到文档的字符串用于解析。

const article = ref("");
onMounted(() => {
  fetch(`${mdPath}`)
    .then((res) => res.text())
    .then((data) => {
      article.value = data;
    });
});

这种方法本质上就是模拟了一个后端用于存储文件,不过这个后端位于前端而已。但这种方法仍然透露着些许幼稚与愚蠢,尽管这种方式解放了双手,不用再人工复制粘贴,但会给生成的前端项目会包含大量冗余的文件。

利用Vite插件解析与引入

利用 Vite 插件能够实现将 Markdown 文件直接导入为 Vue 组件。
尽管现成的插件很多,而且功能也很强大,但偶尔总会想自己造一造轮子,而且也方便后续的自定义与美化,因此决定自己搓一个插件出来。
不过需要注意的是, Vite 的版本更迭以及 API 更换实在太频繁了,很多博客站点上给出的方案已经过时,秉持着能抄绝不自己写的态度被误导了好些时。
建议是先快速阅览一遍 Vite 官方文档

新建插件

在项目的根目录下新建markdownToHtml.js,然后在该文件中默认导出一个函数作为插件。

export default function (options) {}

在 Vite 的配置文件vite.config.js中进行引入并配置。

import markdownToHtml from "./utils/markdownToHtml";

export default defineConfig({
  plugins: [markdownToHtml()],
});

接着便可以着手编写插件了,Vite 的插件以对象的方式存在,因此直接返回一个对象即可,如果想添加一些可配置项,可以引用函数的参数。

插件的开发规范

Vite 插件一般需要指明一个格式为vite-plugin-something的名字,不过自己用不用管这么多。
enforce字段用于指明插件的执行顺序,指明pre则插件将会拥有较高的执行优先级,不过插件的默认执行顺序也会在开始构建前。

export default function (options) {
  return {
    name: "vite-plugin-markdown",
    enforce: "pre",
  };
}

核心部分

转换模块需要用到transform, 这个钩子的参数是codeid, 根据模块id来选择对应模块的code进行转换,由于 Markdown 的文本结构比较简单,因此我们可以直接操作code的字符串。
我的目标是将 Markdown 文档转换为 Vue 组件,因此我还需要用到hdefineComponent这两个函数。
可以参考一下官方文档中的这一节 转换自定义文件类型

export default function (options) {
  return {
    name: "vite-plugin-markdown",
    enforce: "pre",
    async transform(code, id) {
      if (id.endsWith(".md")) {
        return {
          code: `import {h, defineComponent} from "vue";
                const article = defineComponent({
                    name: "Markdown",
                }).render =() => {
                    return h("div", {
                      id: "write",
                      innerHTML: ${marked(code)}
                    })
                };
                export default article`,
          map: null,
        };
      }
    },
  };
}

这样在项目中就可以直接引入 Markdown 文本并将其作为组件啦!