markdown文件如何在前端框架中肆意运行(一)
我正在参加「掘金·启航计划」
第三方库通常有对应文档,尤其组件库文档会附带组件效果以及 demo 代码,对使用者十分友好;查看库源码发现使用 md 文件编写,今天通过解析 element-ui 组件库来看看 md 文件是如何一步步被处理的
处理流程
示例
md文件(效果预览)
分析
- 处理过程通过自定义 loader 实现,目录结构如下
md-loader
config.js
containers.js
fence.js
index.js
util.js
- 此篇先讲解 md 转 html 过程,依赖库如下
- markdown-it
- markdown-it-chain
- 依赖 markdown-it 后,通过链式 api 进行配置设置,获取 markdown-it 实例
- markdown-it-anchor
- 进行标题锚点生成,方便配合全局搜索快速定位标题锚点
- markdown-it-container
- 定义自定义容器格式
逐行解析
- index.js 中 md.render 会按照 config.js 中定义的部分规则、默认规则组成的规则链,逐步解析 md(省略部分为 html 转 vue 过程)
const md = require('./config');
module.exports = function(source) {
// md 转化 html
const content = md.render(source);
}
- conifg.js 对 markdown-it 进行自定义配置
/**
* markdown-it-chain 实例化配置后,通过链式 api 配置设置选项、插件
* 通过 toMd 使用上述配置创建一个 markdown-it 实例
* 注:直接使用 markdown-it 也能达到 markdown-it-chain 效果
*/
const Config = require('markdown-it-chain');
const anchorPlugin = require('markdown-it-anchor');
const slugify = require('transliteration').slugify;
const containers = require('./containers');
const overWriteFenceRule = require('./fence');
const config = new Config();
// 1.设置选项
config.options
.html(true)
.end()
.plugin('anchor')
// 2.标签锚点插件 --- 针对 md 中标题增加锚点
.use(anchorPlugin, [
{
level: 2,
slugify: slugify, // 中文转拼音
permalink: true,
permalinkBefore: true
}
])
.end()
// 3.自定义容器插件
.plugin('containers')
.use(containers)
.end();
const md = config.toMd();
// 4.复写代码块渲染规则 -- 针对 type 为 fence 进行处理
overWriteFenceRule(md);
module.exports = md;
- fence.js 覆盖渲染函数(type = fence)
// 覆盖默认的 fence 渲染策略
module.exports = md => {
const defaultRender = md.renderer.rules.fence;
md.renderer.rules.fence = (tokens, idx, options, env, self) => {
// type:fence 的渲染处理规则
const token = tokens[idx];
// 判断该 fence 是否在 :::demo 内
const prevToken = tokens[idx - 1];
const isInDemoContainer = prevToken && prevToken.nesting === 1 && prevToken.info.trim().match(/^demo\s*(.*)$/);
// 这里的 html 就是在 md 中 ```html ``` 解析出来的(可以任意定义)
if (token.info === 'html' && isInDemoContainer) {
// 使用 v-pre 会被保留当前以及子标签按原样渲染;路由切换时,会获取 code 对应的 dom,使用 highlight.js 进行代码高亮
return `<template slot="highlight"><pre v-pre><code class="html">${md.utils.escapeHtml(token.content)}</code></pre></template>`;
}
return defaultRender(tokens, idx, options, env, self);
};
};
- containers.js 使用 markdown-it-container 默认解析 ::: 标记,通过 validate 钩子来确保自定义标记;此外也定义对应的渲染函数
const mdContainer = require('markdown-it-container');
module.exports = md => {
// 自定义容器实现
md.use(mdContainer, 'demo', {
// 解析到 ::: ::: 就会触发 validate 函数
// params 就是第一个 ::: 与 ``` 间的内容,就是例子中的【 demo 使用`type`、`plain`、`round`和`circle`属性来定义 Button 的样式。】
validate(params) {
return params.trim().match(/^demo\s*(.*)$/);
},
/**
* validate 函数返回 true,会触发 render
* tokens 是通过 markdown-it 规则链解析 md 产生的
* idx 是对应容器 opening、closing tokens 的下标 --- 所以 render 会触发两次
*/
render(tokens, idx) {
const m = tokens[idx].info.trim().match(/^demo\s*(.*)$/);
// nesting `1` 容器打开标记、`-1` 容器关闭标记(标记就是 :::)
if (tokens[idx].nesting === 1) {
// 获取 demo-block 默认插槽内容
const description = m && m.length > 1 ? m[1] : '';
// type fence:代码块
const content = tokens[idx + 1].type === 'fence' ? tokens[idx + 1].content : '';
// 使用 demo-block 组件,<!--element-demo :element-demo--> 作为 html 转 vue 的标记
return `<demo-block>
${description ? `<div>${md.render(description)}</div>` : ''}
<!--element-demo: ${content}:element-demo-->
`;
}
return '</demo-block>';
}
});
};
md 转换 html 结果
在转换过程中将组件描述、组件运行占位注释、组件代码展示都放在 demo-block 全局组件内,方便后期将其转换为 vue 可执行代码
结尾
截止到此,md 转 html 过程已经结束;发现 markdown-it 很灵活,每个环节、标记都可以自定义;欢迎大家评论交流,后面会继续html 转换为具体前端框架可运行代码的文章
希望看完的朋友可以点个喜欢/关注,您的支持是对我最大的鼓励
转载自:https://juejin.cn/post/7245898637778845756