vuepress-plugin-demo-container 之 containers.js
上一篇文章介绍了 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>\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源码解析系列
作者:Lyx
链接:https://juejin.cn/post/7171266648068849695
来源:https://www.77cxw.com/