背景
作为技术出生,日常写文档基本是采用Markdown来编写的,对技术人员来说会简单很多,并且灵活性也非常强,只要你懂前端HTML就可以搞出非常丰富的内容。
日常编写的文档类型会很多,技术研究,问题整理,会议纪要,日常管理等等都会用markdown记录。前期还好,后面用着用着各类问题就来了:
- 文档的可阅读性,markdown一般需要解析过以后,才能是普通人能看的懂的内容,所以如果直接markdown文件发给别人,肯定是有问题的,所以需要一个方便能预览(不依赖任何工具),以及方便导出PDF(转换)等方式;
- 文档内容检索(磁盘管理这种就无法实现);
- 展现效果,人的习惯就是这样,喜欢漂亮的东西;
- 日常编写文档可能会画思维导图、流程图、时序图等各类图形,可能会牵涉Visio,Xminder等工具,这些内容只能以图片的形式嵌入到文档里,其他人无法编辑。所以是不是有一种方式,可以直接在markdown中生成各类需要的图形。并且后续能正常预览、转换。
例如:visio就是windows专有的,mac电脑上还无法直接使用。xminder如果不同电脑版本有差异,还无法解析。
选型
前期也想过选择typora、印象笔记、有道云笔记等各类支持markdown编辑的产品,但是基本上达不到我的要求,而且格式都是他们产品特定的,导入、导出、各种语法兼容还是比较糟糕的。
所以后面琢磨了一下,包括自己各种试验,终于搭建了一套全面符合我要求的markdown发布系统
- VSCode 核心编辑工具,依托于丰富的插件仓库,可以很方便的编辑和预览Markdown,包括导出PDF,HTML等。
- PlantUML + Graphviz,编写各类思维导图、类图、时序图、流程图等。
- DrawIO, 类Visio的画图工具,基本上满足所有功能,格式支持通用的SVG。
- VuePress, HTML发布框架,结合VuePress-theme-reco主题提供好看的样式。通过各种自定义插件实现特殊格式的渲染和转换
编辑效果:
预览效果
介绍
VsCode
这个产品就不介绍了,微软开源的轻量编辑器。通过丰富的插件市场,可以实现代码的开发,前端、java等都是可以用的。作为markdown编辑器更加没问题,支持各种语法,支持预览等等。所以当时就选择了VsCode作为核心编辑器。
vscode的资料很多,就不展开详细介绍了
插件介绍
介绍几个我自己用的比较好的markdown插件
Markdown All in One
提供了常用操作便利的快捷键,支持一边书写一边预览,可轻松转换为HTML文件和PDF文件,优化了List editing的编辑,可格式化table以及Task list,支持特殊数学符号渲染等等,效果还是比较强的。
直观的例子就是,我们如果在markdown中要用粗体标识,会在文字前后加 **
, 比如 **加粗**
这样,这个插件就支持,选中文字以后,直接按Ctrl+B,就自动给你补齐加粗需要的格式,编辑会简单很多。
MarkdownPDF
Markdown导出PDF的工具(还支持其他html等格式),通过设置可以实现PlantUML等插件的完美兼容。后面讲PlantUML的时候会详细介绍下。
Markdown Preview Enhanced
Markdown预览神器,通过设置以后可以完美的支持预览PlantUML。并且支持多种预览样式。
Markdown TOC
markdown目录、标题序号生成器,可以一键生成总目录,以及标注列表序号。
自动生成的目录
markdownlint
这个前端开发人员比较熟,eslint,tslint,markdownlint等,都是用来检查代码格式的。markdownlint也是来检查markdown代码格式的,怎么写出优雅的markdown代码。
他这个校验有点严格,很多时候处理起来比较麻烦,所以可以设置下,部分是在无法遵守的规则就关闭掉。
在工程根目录下创建 .markdownlint.json,然后根据提示的规则编号,设置成false,就可以不提醒了。
.markdownlint.json
一般如果不符合规则的格式,你鼠标放上去,就会有规则编号提示的
代码片段
工欲善其事必先利其器,有充足的准备以后,后面编写起来才会很顺畅。
智能提示开启
可以直接选择编辑settings.json
"[markdown]": {
"editor.wordWrap": "on",
//这里就是开启智能提示的地方
"editor.quickSuggestions": {
"comments": "on",
"strings": "on",
"other": "on"
},
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
代码片段
代码片段是个很有用的东西,日常你会用的比较多的复杂语法,你可以做成代码片段,后面敲一下命令+自动提示,就可以直接写入复杂语法,能省很多时间。
创建表格的代码片段
{
"mtable": {
"prefix": "mtable",
"body": [
"标题 | 标题 | 标题",
":---: | :---: | :---:",
" | | "
],
"description": "列表"
}
}
VuePress
安装也比较简单,打开一个文件
yarn init #初始化
yarn add -D vuepress #安装vuepress
这样一个vuepress的初始化工程就安装好了。
工程介绍
整体效果
enhanceApp.js
这个文件其实就是入口文件,其他没什么,就是如果引用第三方组件的时候要注意,ElementUI和Antd可以直接引用进来,但是vue-grid-layout就比较麻烦点。
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import Antd from 'ant-design-vue';
import 'ant-design-vue/dist/antd.css';
import VueClipboard from 'vue-clipboard2'
import Login from './components/login/login'
export default ({
Vue,
options,
router,
siteData
}) => {
// 使用element-ui
Vue.use(ElementUI)
// 使用ant design vue
Vue.use(Antd)
Vue.use(VueClipboard)
//部分第三方组件比较特殊,需要在入口注入下,内部页面才能访问
//后面介绍到VuePress支持markdown文档嵌入Vue页面
import ('vue-grid-layout').then(function(m) {
Vue.component('GridLayout', m.GridLayout)
Vue.component('GridItem', m.GridItem)
})
}
config.js
这个是核心配置文件,相关的主题,自定义插件,目录等都是在这里定义的。
目录篇
只需要在config.js的nav,sidebar节点下面配置好层级目录,前端显示就没有问题了
一级目录顶上的
二三级目录侧边的
这里其实就存在一个问题,我们的目录结构可能随时再变的,比如今天新增了几个文档,目录就要增加几个了,如果每次新增文档的时候都去修改一下目录的结构,太繁琐了。所以后面改造了他的部分代码,使的二三级目录自动获取,也就是你在Vscode新增一个文件,目录也会自动更新。
//config.js
const filehelper = require('./utils/initPage.js');
sidebar: {
'/business/xxxxxx/': utils.genSidebar('xxxx',
//这个方法就是动态获取目录的
filehelper.getFileName(rootpath, "/business/realestate/", []),
false) // 侧边栏配置
}
const fs = require('fs');
var path = require('path');
// 排除检查的文件
var excludes = ['.DS_Store']
var filehelper = {
getFileName: function(rpath, queryPath, tempValue, type) {
rpath = path.join(rpath, queryPath)
// let fileImg = /\.(png|jpe?g|gif|webp)(\?.*)?$/;
var filenames = [];
if (type === 'json') {
filenames = tempValue.children
} else {
filenames = tempValue
}
let fileTypes = /\.md$/; //只匹配以md结尾的文件
var fileList = fs.readdirSync(rpath)
//重新排序
fileList.sort(function(a, b) {
var fileinfoA = fs.statSync(path.join(rpath, a))
var fileinfoB = fs.statSync(path.join(rpath, b))
if (!fileinfoA.isFile() && !fileinfoB.isFile()) {
return fileinfoA.mtime.getTime() - fileinfoB.mtime.getTime()
} else {
if (!fileinfoA.isFile()) {
return -1
} else if (!fileinfoB.isFile()) {
return 1
} else {
return fileinfoB.mtime.getTime() - fileinfoA.mtime.getTime()
}
}
})
fileList.forEach(file => {
if (excludes.indexOf(file) < 0) {
fullpath = path.join(rpath, file)
var fileinfo = fs.statSync(fullpath)
if (fileinfo.isFile()) {
// if(file.indexOf('.md') > 0) {
if (fileTypes.test(file) > 0) {
if (file === 'readme.md') {
file = '';
} else {
file = file.replace('.md', '');
var tempPath = path.join(queryPath, file)
tempPath = tempPath.replace(/\\/g, "/")
filenames.push({
title: file,
mtime: fileinfo.mtime,
collapsable: true,
path: tempPath,
sidebarDepth: 1
});
}
}
} else {
var json = {
title: file,
collapsable: true,
children: []
}
filenames.push(json)
return this.getFileName(rpath, file, json, 'json')
}
}
})
return filenames;
},
sort: function(a, b) {
if (a.children && b.children) {
if (a.mtime && b.mtime) {
return a.mtime - b.mtime
} else {
return a.title - b.title
}
} else {
if (a.children) {
return -1
} else if (b.children) {
return 1
} else {
return a.mtime - b.mtime
}
}
}
}
module.exports = filehelper;
主题
网上也有很多开源的VuePress主题,我挑选了一款我认为比较好的,然后是直接源代码应用进来的,因为主题还是有些不符合我要求的地方。主题配置是在config.js下。
这个看个人喜好,不想改的就直接用,想改的就自己改一下。比如我就是对这块内容魔改了下。
自定义插件
VuePress在把markdown转换成html的过程中,很多你个性化的内容它是无法转换的,所以就需要通过自动以插件的形式,来实现完美的转换。插件配置也是在config.js下
插件开发过程就是你按照标准开发,基于markdown-it扩展就行,修改完成以后发布到npm仓库,然后你install到工程里就行。
module.exports = function(md, config) {
md.renderer.rules.image = function(tokens, idx) {
// xxxxxx
return '<img src="' + url + '" alt="' + caption + '" test="123" />';
};
};
例如: xxxxx-custom-plantuml
plantuml数据解析插件
function generateSourceDefault(umlCode, pluginOptions) {
var imageFormat = pluginOptions.imageFormat || 'svg';
var diagramName = pluginOptions.diagramName || 'uml';
//plantuml解析服务器,我是自己搭建了一个,所以外部传入就行,没有就用互联网的地址
var server = pluginOptions.server || 'https://www.plantuml.com/plantuml';
var deflate = require('./lib/deflate.js');
//针对plantuml的特殊标签进行处理,vscode预览需要,不是最原始的plantuml的格式。
umlCode = umlCode.replace("```puml", "") // 替换最前面的字符串
umlCode = umlCode.replace("```", "") //删除最后面的字符串
var zippedCode = deflate.encode64(
deflate.zip_deflate(
unescape(encodeURIComponent(
'@start' + diagramName + '\n' + umlCode + '\n@end' + diagramName)),
9
)
);
return server + '/' + imageFormat + '/' + zippedCode;
}
例如: xxxxx-it-image
image图片地址解析插件。
module.exports = function(md, config) {
md.renderer.rules.image = function(tokens, idx) {
var token = tokens[idx];
var srcIndex = token.attrIndex("src");
var url = token.attrs[srcIndex][1];
var caption = md.utils.escapeHtml(token.content);
if(url.indexOf("../../../.vuepress/public")>-1){
url = url.replace("../../../.vuepress/public","")
}
else if(url.indexOf("../../.vuepress/public")>-1){
url = url.replace("../../.vuepress/public","")
}
else if(url.indexOf("../.vuepress/public")>-1){
url = url.replace("../.vuepress/public","")
}
else if(url.indexOf(".vuepress/public")>-1){
url = url.replace(".vuepress/public","")
}
url = decodeURL(url, config);
return '<img src="' + url + '" alt="' + caption + '" test="123" />';
};
};
发布
工程就是类似一个Vue工程,直接yarn run build发布就行,生成的成果,就是一个前端静态页面,直接用nginx发布就行了。
Drawio
流程图这块是比较重要的场景,很多时候一个图带来的价值是非常大的。日常windows上可能会用Visio等工具,或者采用在线的ProcessON等平台。都会存在很多问题,Visio不是跨平台的,ProcessOn线的,也很多限制。所以想着怎么能有一个能跨平台使用,有方便没有限制的。
后来是看到一个在线的drawio平台,感觉这个还是很好用的,刚好这个有大神在Vscode做了一个drawio的插件,用了以后,完美。
Draw.io Integration
然后你可以创建一个后缀为 "xxxx.drawio.svg"的文件,这时候在Vscode打开就会直接转到drawio的编辑界面,可以可视化画各种图了。
它本质上就是一个svg矢量图,所以放到哪里都可以使用
markdown里面直接引入相对地址就行
这个功能对我来说就是神器,我后面基本上所有的图都是用他画的,保存方便,使用方便。
PlantUML
最开始是选用PlantUML来画流程图的,它也能画,就是一些复杂流程图展示效果不是太理想,所以后面转用drawio画流程图。
平常会写很多技术文档,我需要画类图、时序图、思维导图,那plantuml就是一个很好的选择了。
第一步提到的插件Markdown Preview Enhanced默认是支持预览plantuml的,不过实际渲染还是需要 graphviz 支持的
graphviz安装部署
下载地址: graphviz.org/download/
现在以后直接安装就行,注意记住安装位置,后面还要配置环境变量
变量名: GRAPHVIZ_DOT
变量值: C:\Program Files\Graphviz\bin\dot.exe #按照实际路径来
配置完成以后基本上可以正常预览PlantUML了
PlantUML渲染兼容
Markdown Preview Enhanced预览支持的PlantUML格式是这样的
而正常PlantUMLServer能解析的是这样的
差异其实就是起始标识符和结束标识符不一样(开头多了plumtuml的标识),中间内容都是一样的。这样就比较尴尬了,好在都可以通过其他方式解决这个问题。
PDF转换
直接设置下首尾标签就行
VuePress渲染
就是通过前面介绍的 xxxxx-custom-plantuml插件做了兼容
通用样式模板
plantuml支持配置通用样式模板,可以自定义个puml文件,然后画图的地方就可以引入这个最定义样式,这样你所有画出来的图样式效果都是一样的。如果有个性化的,可以扩展定义
样式文件内容
效果
类图
流程图
思维导图
时序图
Markdown和Vue界面结合
这块功能也是我最喜欢的功能,文档编辑出来都是静态的,但是结合Vue页面发布以后,就是动态的页面,有些动态如果无法用言语描述,就直接开发一个页面出来,有交互的,体现效果更直接。
检索
VuePress自带的目录检索功能基本能满足我的要求了,所以目前一直没有上全文检索的功能,思路山没有采用VuePress提供的全文检索功能,而是通过Elasticsearch来实现全文检索。但是有点大材小用,所以一直没有部署上去(云服务资源也有限)。
总结
整个打造这一套完整的体系,可能花了有两个月的时间,包括各种源代码改造,插件编写,工具调试等,但是花的时间还是非常值的。我写文档、管理文档效率非常高,很多设计文档就直接在这里编码实现效果。通过导出PDF等功能也不影响其他人查看,里面各种drawio图,plantuml发给其他人都可以直接编辑。