扒一扒 Element UI
中对于 md
文件的编译,来实现在页面上同时展示代码和效果。
传送门
可能有人要问,什么是 Markdown
文件(传送门:Markdown 教程)?为什么这里要讲到如何解析 Markdown
文件?我们接着往下看:
Markdown
编写的文档可以导出 HTML 、Word、图像、PDF、Epub 等多种格式的文档。
注意,Markdown
可以导出 HTML
,我们在网页中需要展示代码,如果我们自己往文件中填入 pre、code
内容,需要消耗大量时间和代码,而 Markdown
文件已经提供了这种转换,我们只需要在其基础上做进一步的解析,将其与 Vue
内容融合在一起,就能完美展示。而且 Vue
也已经有了支持 Markdown
转换的 loader -> Vue-markdown-loader
。
npm install vue-markdown-loader -D
// rules
// 解析md文件我们需要在 Webpack 的配置汇总加上如下规则
{
test: /\.md$/,
loader: 'vue-markdown-loader'
}
此处对于 Vue-markdown-loader
只做简单介绍,详情请参考 npm/vue-markdown-loader,因为笔者最后使用的不是 Vue-markdown-loader
,而是参照 Element UI
自定义的loader实现的。
ELement UI 源码传送门
md-loader
在看代码的时候,我们先了解一下用到的插件及用途(个人理解,如有不准确的地方欢迎指正):
markdown-it // 将文本解析为数据
markdown-it-chain // 配置化 markdown-it
markdown-it-anchor // 为各级标题添加锚点
markdown-it-container // 用于创建自定义的块级容器
transliteration // 中文转拼音
cheerio // 服务器版jQuery
highlight.js // 代码块高亮实现
striptags // 利用cheerio实现两个方法,module.exports 去除标签以及内容,export.fetch 获取标签中的文本内容
md文件内的代码模板
我们观察 md
文件内容可以发现,里面展示代码的部分会使用 :::demo ... :::demo
将代码块包裹起来,这并不是 md
的标记格式,所以推断此处的渲染逻辑改变了。那么怎么将自定义的 demo
标记让 markdown-it
识别呢,使用 markdown-container
插件。
container.js
const mdContainer = require('markdown-it-container');
module.exports = md => {
md.use(mdContainer, 'demo', {
validate(params) {
return params.trim().match(/^demo\s*(.*)$/);
},
render(tokens, idx) {
const m = tokens[idx].info.trim().match(/^demo\s*(.*)$/);
if (tokens[idx].nesting === 1) {
// 判断是否是开始标签
const description = m && m.length > 1 ? m[1] : ''; // 获取描述内容
const content = tokens[idx + 1].type === 'fence' ? tokens[idx + 1].content : ''; // 判断下一个是否是 fence 类型并获取 fence 内容
// 内容和描述以 demo-block 组件作为模板
return `<demo-block>
<div>${
md.render(description)}</div>
<!--element-demo: ${
content}:element-demo-->
`; // 将内容放到 demo-block 组件中,后续交由 Vue 操作
}
return '</demo-block>';
}
});
md.use(mdContainer, 'tip');
md.use(mdContainer, 'warning');
};
这里使用的 demo
作为新的标记名称,这种标记的类型为 container_demo_open、container_demo_close
示例:
::: demo
// ```html 因为md文件会识别标记,所以使用注释
<m-row>
<m-button type="primary" icon="m-icon-search">搜索</m-button>
<m-button type="success" icon="m-icon-edit">编辑</m-button>
<m-button type="success" icon="m-icon-download" align="right">下载</m-button>
</m-row>
// ```
::: demo
经过 markdown-it
编译后会转换为 ->
[ Token {
type: 'container_demo_open',
tag: 'div',
attrs: null,
map: [ 41, 53 ],
nesting: 1,
level: 0,
children: null,
content: '',
markup: ':::',
info: 'demo',
meta: null,
block: true,
hidden: false },
Token {
type: 'fence',
tag: 'code',
attrs: null,
map: [ 43, 53 ],
nesting: 0,
level: 1,
children: null,
content:
'<m-row>\n <m-button disabled>默认按钮</m-button>\n <m-button type="primary" disabled>主要按钮</m-button>\n <m-button type="success" disabled>成功按钮</m-button>\n <m-button type="info" disabled>信息按钮</m-button>\n <m-button type="warning" disabled>警告按钮</m-button>\n <m-button type="danger" disabled>危险按钮</m-button>\n</m-row>\n',
markup: '```',
info: 'html',
meta: null,
block: true,
hidden: false },
Token {
type: 'container_demo_close',
tag: 'div',
attrs: null,
map: null,
nesting: -1,
level: 0,
children: null,
content: '',
markup: ':::',
info: '',
meta: null,
block: true,
hidden: false }
]
demo
标记内的内容都以 demo-block
组件作为模板:
<template>
<div class="demo-block"">
<div class="source">
<!-- 代码展示区域占位符 -->
<slot name