开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情
上一篇文章介绍了 vuepress-plugin-demo-container
的整体工作流程,本篇来详细介绍如何识别自定义代码块并构建HTML字符串
enhanceAppFiles
前文说到 enhanceAppFiles
是用来增加一些应用级别的配置,在本插件中,就是用来注册一个包裹示例代码的组件:
import DemoBlock from './DemoBlock.vue'
export default ({ Vue }) => {
Vue.component('DemoBlock', DemoBlock)
}
展开、关闭、复制源代码都是依托于该组件实现,该组件的源代码可点击 DemoBlock.vue 查看
containers.js 详解
该文件就是用来识别自定义代码块的文件,首先列出文件源码:
const mdContainer = require('markdown-it-container');
// options 就是在config.js中plugins参数的配置
module.exports = options => {
const {
component = 'demo-block'
} = options;
const componentName = component
.replace(/^\S/, s => s.toLowerCase())
.replace(/([A-Z])/g, "-$1").toLowerCase();
return md => {
md.use(mdContainer, 'demo',
{
// 验证:::之后是否为 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 : '';
const encodeOptionsStr = encodeURI(JSON.stringify(options));
return `<${componentName} :options="JSON.parse(decodeURI('${encodeOptionsStr}'))">
<template slot="demo"><!--pre-render-demo:${content}:pre-render-demo--></template>
${description ? `<div slot="description">${md.render(description).html}</div>` : ''}
<template slot="source">`
}
return `</template></${componentName}>`;
}
});
};
}
代码并不长,我们一点一点来看。
options
是 .vuepress/config.js
中 plugins
参数的配置,如下所示:
plugins: [
[
require('../../src'),
{
component: 'DemoBlock',
locales: [
...
]
}
]
],
component
和 locales
都会被传入到options
中。在对options
做解构的同时,给component
属性赋予默认值demo-block
。在 plugins
参数中没有传入component
属性的情况下,该属性就是 demo-block
const componentName = component
.replace(/^\S/, s => s.toLowerCase())
.replace(/([A-Z])/g, "-$1").toLowerCase();
该处的代码是为了实现 DemoBlock
到 demo-block
的转换
render函数解析
下面,就是最为重要的渲染流程。首先来了解一下 markdown 转换为 HTML 的流程
总体可以概括为两步:
markdown
经过词法解析得到token
- 将
token
渲染为HTML
几个相关的token属性为:
- nesting:嵌套层级,1 对应 HTML 中的开始标签,-1 对应 HTML 中的结束标签,0表示中间值
- info:
:::
后的信息 - type:token 类型,fence 表示代码块
以下面代码块为例,将其转为token
::: demo 此处放置代码示例的描述信息,支持 `Markdown` 语法,**描述信息只支持单行**
`` `html
<template>
<div class="red-center-text">
<p>{{ message }}</p>
<input v-model="message" placeholder="Input something..."/>
</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello Vue'
}
}
}
</script>
<style>
.red-center-text {
color: #ff7875;
text-align: center;
}
</style>
`` `
:::
转化后得到的token为:
[
Token {
type: 'container_demo_open',
tag: 'div',
attrs: null,
map: [ 2, 26 ],
nesting: 1,
level: 0,
children: null,
content: '',
markup: ':::',
info: ' demo 此处放置代码示例的描述信息,支持 `Markdown` 语法,**描述信息只支持单行**',
meta: null,
block: true,
hidden: false
},
Token {
type: 'fence',
tag: 'code',
attrs: null,
map: [ 3, 26 ],
nesting: 0,
level: 1,
children: null,
content: '<template>\n' +
' <div class="red-center-text">\n' +
' <p>{{ message }}</p>\n' +
' <input v-model="message" placeholder="Input something..."/>\n' +
' </div>\n' +
'</template>\n' +
'<script>\n' +
'export default {\n' +
' data() {\n' +
' return {\n' +
" message: 'Hello Vue'\n" +
' }\n' +
' }\n' +
'}\n' +
'</script>\n' +
'<style>\n' +
'.red-center-text { \n' +
' color: #ff7875;\n' +
' text-align: center;\n' +
'}\n' +
'</style>\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
}
]
render函数过程
- 遍历token数组。如果
nesting = 1
就构建HTML开标签。标签名就是上文解构出的componentName
。 - 找到fencetoken,取出content属性,该属性就是代码块中的所有代码。并将代码以注释的形式放在插槽中,将其传入到DemoBlock.vue 中。
- 如果
nesting = -1
,构建 HTML 闭合标签。 - 返回整个字符串
总结
这一步的主要作用就是识别 :::demo
代码块,构建HTML字符串。下一步就是处理HTML字符串,例如,将 vue template 转化为 render 函数、合并script、style等。
vuepress-plugin-demo-container源码解析系列
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END
暂无评论内容