上一篇简单的对Vite插件进行了实现,这篇文章一起去处理对md文件的处理
上一篇:从Naive UI项目上学习Vite Plugin的使用
项目准备
可以直接拉取这里的代码 - 代码仓库
- tiny-dust/useless at doc-1.0-init是上一篇的代码,可以跟着文章自己去实现。
- tiny-dust/useless at doc-2.0-mdToVue这个分支是本文完成的代码,可以参照着进行校对。
好了,如果你是选择了doc-1.0-init分支
我们需要对项目的路由、路由文件进行处理,并且安装好对应的依赖文件。
pages
src下新建一个pages页面,里面存放一个md文件,供vue-router加载,内容我们摘抄Naive UI随便一个组件库的文档说明,比如他的Scrollbar,可以直接从这里进去复制。
router
// routes.js 存放路由数据
const commonRoutes = [
{
path: "/md",
name: "md",
component: () => import("../pages/index.md"),
},
];
const routes = [...commonRoutes];
export default routes;
// index.js 存放路由实例,已经路由相关的处理,暴露一个setupRouter
// 完整代码
import { createRouter, createWebHistory } from "vue-router";
import routes from "./routes";
const router = createRouter({
history: createWebHistory(),
routes,
});
const setupRouter = (app) => {
app.use(router);
};
export default setupRouter;
main.js和app.vue
<!-- app.vue直接引入router-view标签即可,删除其他 -->
<template>
<router-view />
</template>
// main 文件引入路由的setupRouter函数,然后将app实例传入
import { createApp } from "vue";
import App from "./App.vue";
import setupRouter from "./router";
const app = createApp(App);
setupRouter(app);
app.mount("#app");
解析md文件
安装插件
highlight.js实现相关的代码高亮,本文可能用处不大,但是顺便安装了markedmd文件解析工具naive-ui因为是参照naive,所以直接用它的组件进行自定义unplugin-vue-components自动按需加载naive组件
pnpm i highlight.js -S
pnpm i marked naive-ui unplugin-vue-components -D
按照naive官网的说明,将naive进行按需配置,修改我们的./plugins/index.js,在creatMyPlugin函数最终返回的数组中,加入
import Components from 'unplugin-vue-components/vite'
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'
return [
myPlugin,
vue({
include: [/\.vue$/, /\.md$/]
}),
Components({
// 默认是没有对md文件进行处理,所以需要加上,上面的同理
include: [/\.vue$/, /\.md$/],
resolvers: [NaiveUiResolver()],
}),
Inspect(),
];
自定义marked的render
我们需要重新定义渲染标签,来实现样式上的统一,并且考虑到项目文档中会有代码展示,所以要配合highlight进行处理。
首先考虑的是,我们需要分析自定义哪些标签,对于一个组件库的文档网站,table是必须的,然后就是各种标题heading,对于组件文档中,还会有代码文本,所以我们这里定义为codespan和blockquote,然后就是其他常规的list、hr、paragraph、link、list等等。我随便找了个Naive UI的组件文档,解析后如下:
其中有个比较特殊的一个点在于第五条数据,
type: 'code' , lang: 'demo',这是我们自定义的语言块,需要单独对其中的内容去处理,并解析同目录下的文件,收集成组件的方式进行加载。为了避免报错,无法进行下一步,我们可以删除```demo ```这一部分的内容,或者随便添加一段代码,测试代码高亮,比如添加这一段
``` js
const a = 1
```。
然后开始我们的自定义改造
新建文件./plugins/utils/md-renderer.js,详细代码如下(这里直接取Naive UI的自定义render):
const hljs = require('highlight.js')
const { marked } = require('marked')
function createRenderer (wrapCodeWithCard = true) {
const renderer = new marked.Renderer()
const overrides = {
table (header, body) {
if (body) body = '<tbody>' + body + '</tbody>'
return (
'<div class="md-table-wrapper"><n-table single-column class="md-table">\n' +
'<thead>\n' +
header +
'</thead>\n' +
body +
'</n-table>\n' +
'</div>'
)
},
tablerow (content) {
return '<tr>\n' + content + '</tr>\n'
},
tablecell (content, flags) {
const type = flags.header ? 'th' : 'td'
const tag = flags.align
? '<' + type + ' align="' + flags.align + '">'
: '<' + type + '>'
return tag + content + '</' + type + '>\n'
},
code: (code, language) => {
//这里需要解释一下,code是会识别md文件中的 ```language ```
if (language.startsWith('__')) {
language = language.replace('__', '')
}
const isLanguageValid = !!(language && hljs.getLanguage(language))
if (!isLanguageValid) {
throw new Error(
`MdRendererError: ${language} is not valid for code - ${code}`
)
}
const highlighted = hljs.highlight(code, { language }).value
const content = `<n-code><pre v-pre>${highlighted}</pre></n-code>`
return wrapCodeWithCard
? `<n-card embedded :bordered="false" class="md-card" content-style="padding: 0;">
<n-scrollbar x-scrollable content-style="padding: 16px;">
${content}
</n-scrollbar>
</n-card>`
: content
},
heading: (text, level) => {
const id = text.replace(/ /g, '-')
return `<n-h${level} id="${id}">${text}</n-h${level}>`
},
blockquote: (quote) => {
return `<n-blockquote>${quote}</n-blockquote>`
},
hr: () => '<n-hr />',
paragraph: (text) => {
return `<n-p>${text}</n-p>`
},
link (href, title, text) {
if (/^(http:|https:)/.test(href)) {
return `<n-a href="${href}" target="_blank">${text}</n-a>`
}
return `<router-link to="${href}" #="{ navigate, href }" custom><n-a :href="href" @click="navigate">${text}</n-a></router-link>`
},
list (body, ordered, start) {
const type = ordered ? 'n-ol' : 'n-ul'
const startatt = ordered && start !== 1 ? ' start="' + start + '"' : ''
return `<${type}${startatt}>\n` + body + `</${type}>\n`
},
listitem (text) {
return `<n-li>${text}</n-li>`
},
codespan (code) {
return `<n-text code>${code}</n-text>`
},
strong (text) {
return `<n-text strong>${text}</n-text>`
},
checkbox (checked) {
return `<n-checkbox :checked="${checked}" style="vertical-align: -2px; margin-right: 8px;" />`
}
}
Object.keys(overrides).forEach((key) => {
renderer[key] = overrides[key]
})
return renderer
}
module.exports = createRenderer
marked工具解析文本
在./plugins目录下新建文件mdToVue.js。这个文件主要就是用来解析md的格式,并输出合规的vue格式的代码,生成对应的template、script、style。
首先在这里我们先要理解一个很重要的点。我们现在的工作,是一种约定性的解析。
比如```demo ```和 ## API。
这就是一种约定性的代码块,后续的工作里,我们就去寻找解析后
type='code'并且 lang='code'的代码,获取里面的内容,并根据换行符去将所有xxx.vue处理成数组,然后处理成xxx.demo.vue格式,并匹配同路径下的同名文件,然后进一步解析。如果我们将其中任何一步更改,就会导致文档解析的失败。好了,废话不多说,开干~
首先将请求文件的文本进行词法分析(marked.lexer),它会将文本内容解析成一个对象数组,对象包含type、raw、depth、text,我们需要借助这种处理好的数组进行下一步处理。比如下面的代码:
# heading
就会被解析成这样:
[{
type: "heading",
raw: " # heading\n\n",
depth: 1,
text: "heading",
tokens: [
{
type: "text",
raw: "heading",
text: "heading"
}
]
}]
获取了分析后的数据,我们就大有可为了。比如获取所有的三级标题,获取demo代码块的内容,并处理成组件等等。
将md的内容分析后,紧接着进行数据解析(marked.parser),将数据处理成html元素,代码如下:
const docMainTemplate = marked.parser(tokens, {
gfm: true, // 启用github风格的markdown
renderer: mdRenderer,
});
放在<template></template>标签中,然后返回出去,就大功告成,我们就可以在我们的页面里,看到和Naive UI文档很像的页面了
你也可以通过
Inspect来查看解析后的代码:
最后贴上mdToVue的完整代码:
import { marked } from "marked";
import createRenderer from "./utils/md-renderer";
const mdRenderer = createRenderer();
const mdToVue = (code) => {
const tokens = marked.lexer(code);
const docMainTemplate = marked.parser(tokens, {
gfm: true,
renderer: mdRenderer,
});
const docTemplate = `<template>
${docMainTemplate}
</template>
`;
return docTemplate;
};
export default mdToVue;
结语
这篇简单的md解析就结束了,下一篇,就开始完整的实现Naive UI组件库文档的搭建和简单的组件编写。先写这两篇文章的目的,主要是为了大家能先有个实现的概念,避免直接一波输出,晕头转向的看不下去。好了,不废话了,好好学习,天天向上,每天进步一点点,一年后我也是大佬~