百家饭团队开发的百家饭OpenAPI平台是用vuepress2.0搭建的,搭建的时候不知道2.0还处在beta状态,所以导致后来踩了一些坑,使用过程中vuepress2.0也从2.0.0-beta.18升到了2.0.0-beta.48,有很多的变化,所以想写一个教程介绍一下vuepress2.0的情况以及使用经验。
大致计划写这些内容吧:
- vuepress介绍、选型原因及使用感受(先介绍一下自己用的情况吧)
- vuepress2.0使用(搭建一个vuepress支撑的网站)-第一部分
- vuepress2.0使用(搭建一个vuepress支撑的网站)-修改默认模板配置
- vuepress2.0使用(搭建一个vuepress支撑的网站)-详细配置默认模板
- vuepress2.0使用(搭建一个vuepress支撑的网站)-md文件的玩法
- vuepress2.0使用(扩展默认主题)- 准备及扩展页面组件
- vuepress2.0使用(扩展默认主题)- 提供更多页面模板并引入ElementUI
- vuepress2.0使用(扩展默认主题)- 扩展MD页面功能(Section扩展及自定义语法)
- vuepress2.0使用(扩展默认主题)- 引入百度搜索支持及其他库的引用
- 编写vuepress主题(自定义自己的框架,开发复杂程序)
- vuepress2.0使用——编译及站点部署
- vuepress2.0当前的状态(版本变化,方便大家升级,占位中,未书写)
- 更多读者发起话题点击这里进专题查看,就不更新标题了
这里先给搜索进来的同学提个醒:凡是网上搜索到需要修改clientAppEnhance.js 文件的教程都已经过时,最新版本已不再使用。另外说个题外话,我查了下,我为数不多的搜索关键词里面,vuepress的占了大部分,这里感谢大家的关注,这个教程肯定有不合理的地方,如果有任何需要帮助的,大家都可以留言,我尽力解答。
本周,百家饭OpenAPI平台已经初步完成了以API工作台为核心功能的0.6.0版本的初步开发测试,下周基本可以发布了,希望到时候给大家能够提供更好的API开发服务。
本周继续来讲我们的Vuepress教程。
vuepress 2.0中的md扩展
md扩展对于希望文章能够更精彩的博主来讲,是非常重要的功能,比如百家饭就用了Element-Plus的时间线来显示编辑器的更新日志。
那如何将这样的功能放到MD文件中,使得编辑人员可以自由使用呢。通过学习,我们觉得大概有这么两种方案:
扩展MD中的容器
vuepress中使用到了容器(container)的概念,来在md文件中提供复杂功能。
什么是容器?
vuepress的容器是以:::括起来的内容,默认主题提供三种容器(tip/warning/danger),调用的方式就是在开头的:::后面跟名称
比如
::: tip
# 微服务版下载
隐私计算企业服务请拉取和使用微服务版。**微服务版本暂未提供给公共使用,请联系百家饭团队获得相关帮助**
> 百家饭OpenAPI微服务版为用户提供基于开发脚本的内部Web服务能力,将脚本的计算能力开放为Web Service接口,供其他业务系统调用。
微服务版采用Docker安装模式,请安装Docker服务之后,采用以下方式安装微服务版:
:::
显示为
因此,如果要扩展不同的容器,以我们用的timeline为例,那我们就可以增加一种名为timeline的容器
为主题增加自定义容器
自定义容器的增加,我们需要在主题的index.js里增加如下的内容:
const { containerPlugin } = require('@vuepress/plugin-container')
module.exports = ({ themePlugins = {}, ...localeOptions }) => {
// ctx.siteData.docs = option.sidebar
return {
……
plugins: [
……
containerPlugin({
type: 'timeline',
before: (info) =>
`<ClientOnly><el-timeline>\n`,
after: () => '</el-timeline></ClientOnly>\n'
}),
也就是在exports返回配置对象的plugins属性中增加一个containerPlugin,注意,该方法在旧版本中形式如下:
['@vuepress/container', {
type: 'timeline',
before: (info) =>
`<ClientOnly><el-timeline>\n`,
after: () => '</el-timeline></ClientOnly>\n'
}],
如果搜索到按上述方法进行容器扩展的,应该已经过期了。
我们可以看出,容器其实就是给md中的内容增加一个前(通过before)后(提供after)的内容,共同形成html的方式。
掌握了这个方式,我们就可以使用容器把我们想要的各种内容在md中组织起来。
添加自定义MD语法
上面的方法里,我们扩展了容器的用法,最后可以通过不同的容器实现各种自定义组件,类似:
:::: timeline
::: timeline-item 2022-06-13
### v0.2.0
* 更新了对于复杂属性的定义界面
* 提升了复杂接口的渲染速度
* 提升了Json编辑器的打开速度
* 支持array类型参数的详细字段定义
:::
::::
上面的代码里,我们定义了两级的容器(为了区分级别,md中的容器要求外层比里层至少多一个":"),但是这种结构用多了显得很麻烦,md里到处都是:。
那还有没有更高级的方式呢,有,就是自定义md语法。比如容器的语法就是通过三个以上的:来触发。那我们也可以自定义自己的语法。
比如我们定义了一个语法结构!{}}来表示中间的内容是纯html变量。
引用自定义组件
上述的方式是通过扩展md的方式完成的,同样,我们需要扩展index.js,引入以下内容
const plugin1 = require("./md_html_plugin")
module.exports = ({ themePlugins = {}, ...localeOptions }) => {
// ctx.siteData.docs = option.sidebar
return {
……
extendsMarkdown: (md) => {
md.use(plugin1)
},
在配置的extendsMarkdown属性中,告诉md组件去使用一个新的plugin,而这个plugin由我们自己进行定义即可。
定义组件
那如何定义这个组件呢,vuepress的md解析使用的是markdown-it,这里定义组件的方式就是定义markdown-it扩展的方式。
markdown-it扩展的定义有些复杂,这个部分可能不能覆盖所有的逻辑,有复杂需求的需要另行搜索。
定义组件的exports
组件定义的exports部分逻辑如下:
module.exports = function vue_html_plugin(md) {
md.block.ruler.before('heading', 'vue_html', container);
md.renderer.rules['vue_html_open'] = (tokens, index, opts, env) => {
var token = tokens[index];
return `<div v-html='${token.info}'>`;
};
md.renderer.rules['vue_html_close'] = () => "</div>";
}
这个部分的包含多个部分(我们的html渲染例子中为三个部分,复杂的插件可能更多),第一个部分,定义了我们这个插件所处的层级,这和md的解析有关系,我们定义的插件定义在解析heading之前,名称定为了vue_html,具体的解析函数是名为container的函数。
那第二个和第三个部分则对应处理我们插件增加的新的语法结构,在我们的例子中,我们定义的插件逻辑较为简单,把插件内部的内容作为v-html传给一个div即可,因此,层级简单,只需要把!{ content }}解析为<div v-html="content"></div>即可,因此我们只需要定义两个新的规则,一个是vue_html_open,一个是vue_html_close,分别用于渲染div定义的开始部分和结束部分即可。
定义组件的解析函数。
接下来就要具体写container函数了,具体如下:
var marker_str = '!{',
marker_char = marker_str.charCodeAt(0),
marker_len = 2,
marker_end_str = '}}',
marker_end_char = marker_end_str.charCodeAt(0)
function container(state, startLine, endLine, silent) {
var pos, nextLine, marker_count, markup, params, token,
old_parent, old_line_max,
auto_closed = false,
start = state.bMarks[startLine] + state.tShift[startLine],
max = state.eMarks[startLine];
// 如果传进来的内容的第一个字符不是我们需要的"!",直接返回
if (marker_char !== state.src.charCodeAt(start)) { return false; }
// 再继续匹配"!{"的其他部分,我们的例子里,开头是!{,可以不用循环,如果你定义的符号更为复杂,可以参考该循环的写法
for (pos = start + 1; pos <= max; pos++) {
if (marker_str[(pos - start) % marker_len] !== state.src[pos]) {
break;
}
}
marker_count = pos - start;
if (marker_count < marker_len) { return false; } //如果匹配长度不够,直接返回
markup = state.src.slice(start, pos - marker_len);
//内容初始位置指向开始匹配符后面
start = pos
//循环匹配内容,直到遇到结束符为止
for (; ;) {
pos++
if (marker_end_char !== state.src.charCodeAt(pos)) { continue; }
for (pos1 = pos + 1; pos1 <= max; pos1++) {
if (marker_end_str[pos1 - pos] !== state.src[pos1]) {
break;
}
}
break;
}
//将内容存储
params = state.src.slice(start, pos);
//缓存旧的状态情况
old_parent = state.parentType;
old_line_max = state.lineMax;
state.parentType = 'vue_html';
//将vue_html_open推送到解析栈中,传入内容作为info参数
token = state.push('vue_html_open', 'div', 1);
token.markup = markup;
token.block = true;
token.info = params;
token.map = [startLine, startLine];
//将vue_html_close推送到解析栈中
token = state.push('vue_html_close', 'div', -1);
token.markup = state.src.slice(start, pos);
token.block = true;
//将原有堆栈情况复原
state.parentType = old_parent;
state.lineMax = old_line_max;
state.line = startLine + (auto_closed ? 1 : 0);
return true;
}
注意,本例子只用于单行内容插件,多行内容插件可以参考vscode-markdown-extended/markdownItAdmonition.ts at master · qjebbs/vscode-markdown-extended · GitHub
插件的书写要求较高,需要有语法树解析的基础认知,要不理解比较困难。 总之,如果有复杂的需求,可以参考来写,这章写的也没啥底气,呵呵,有错误或者有问题的,欢迎提问,共同探讨。