起因
最近在折腾个人博客,其中花了挺多时间折腾的点就是如何解析与引入 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, 这个钩子的参数是code和id, 根据模块id来选择对应模块的code进行转换,由于 Markdown 的文本结构比较简单,因此我们可以直接操作code的字符串。
我的目标是将 Markdown 文档转换为 Vue 组件,因此我还需要用到h和defineComponent这两个函数。
可以参考一下官方文档中的这一节 转换自定义文件类型
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 文本并将其作为组件啦!