一:elementui源码解析之markdown处理
一些参考网址:
markdown-it官网:markdown-it | markdown-it 中文文档 (docschina.org)markdown-it插件的分析和源码分析参考地址:https://zhuanlan.zhihu.com/p/64290806element-ui的源码地址:https://github.com/ElemeFE/element
一:先从package.json找到本地运行官网的指令是在scripts里的dev指令
"dev": "npm run build:file && cross-env NODE_ENV=development webpack-dev-server --config build/webpack.demo.js & node build/bin/template.js",
找到对应md解析的module的处理loader
处理markdown文件是先用build/md-loader/index.js处理了,然后再用vue-loader处理的;
md组件文件都在 examples/docs里,下面拿examples/docs/zh-cn/button.md组件的解析为例子
二: 进入build/md-loader/index.js里查看时如何解析的md文件的
// build/md-loader/index.js const { stripScript, stripTemplate, genInlineComponentText } = require('./util'); // 提取script内容和纯内容部分,以及拼接好模板的方法 const md = require('./config'); // 暴露出已经处理好markdownit插件的md module.exports = function(source) { const content = md.render(source); // config里对render的插件的处理 ... };
第一步是在 build/md-loader/config.js里处理的
// build/md-loader/config.js
const Config = require('markdown-it-chain'); // 支持链式调用 markdown-it const anchorPlugin = require('markdown-it-anchor'); // 配置标题目录跳转的插件 const slugify = require('transliteration').slugify; // 把中文翻译成拼音的插件 const containers = require('./containers'); // 内容块容器化处理插件 const overWriteFenceRule = require('./fence'); //修改默认的fence规则的渲染函数,加上展开的源码的部分 const config = new Config(); config .options.html(true).end() .plugin('anchor').use(anchorPlugin, [ { level: 2, slugify: slugify, permalink: true, permalinkBefore: true } ]).end() .plugin('containers').use(containers).end(); // 主要是这个插件里处理md里写的elementui组件的渲染的代码 const md = config.toMd(); overWriteFenceRule(md); module.exports = md;
主要的md里编写的elementui组件处理插件是 build/md-loader/containers.js里处理的,把 :::demo 和 ::: 之间的非描述部分,放到了 <!--element-demo: ${content}:element-demo-->,后面在 build/md-loader/index.js里处理;
// build/md-loader/containers.js
const mdContainer = require('markdown-it-container'); // 这个插件可以让你支持内容块,识别 markdown的::: module.exports = md => { // 用在markdown-it里,识别 ::: demo md.use(mdContainer, 'demo', { validate(params) { return params.trim().match(/^demo\s*(.*)$/); // *是匹配0次以上 }, /** * tokens 符合上面规则的所有内容 * idx 某一条的id */ 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] : ''; // 紧跟在demo后面的描述的内容 const content = tokens[idx + 1].type === 'fence' ? tokens[idx + 1].content : ''; // html的匹配内容 // 返回把内容放到已经写好的组件demo-block里;把html的内容放到!--element-demo里,便于后面处理抽取 // demo-block组件已经是在entry.js里作为全局组件注册过了可以直接使用 return `<demo-block> ${description ? `<div>${md.render(description)}</div>` : ''} <!--element-demo: ${content}:element-demo--> `; } return '</demo-block>'; } }); md.use(mdContainer, 'tip'); md.use(mdContainer, 'warning'); };
在 build/md-loader/index.js 里处理解析放入到 <!--element-demo: ${content}:element-demo--> 里的内容,提取里面的template部分和script部分,放到
<section class="content element-doc"> 里;
// build/md-loader/index.js const { stripScript, stripTemplate, genInlineComponentText } = require('./util'); const md = require('./config'); // 暴露出已经处理好markdownit插件的md module.exports = function(source) { const content = md.render(source); // md插件对里面的内容放到了<!--element-demo里,在这里做提取处理 const startTag = '<!--element-demo:'; const startTagLen = startTag.length; const endTag = ':element-demo-->'; const endTagLen = endTag.length; let componenetsString = ''; let id = 0; // demo 的 id let output = []; // 输出的内容 let start = 0; // 字符串开始位置 let commentStart = content.indexOf(startTag); // html开始的下标值 let commentEnd = content.indexOf(endTag, commentStart + startTagLen); // html结束的位置,从开始的标识结束的后面计算 // 循环处理html的内容,必须是有开始和结尾标识的 while (commentStart !== -1 && commentEnd !== -1) { output.push(content.slice(start, commentStart)); // 拿到前面非html内容的描述等文字部分 const commentContent = content.slice(commentStart + startTagLen, commentEnd); // 拿到除去前后标识的html内容部分 const html = stripTemplate(commentContent); // 拿到非script和style的内容部分 const script = stripScript(commentContent); // 拿到script部分 let demoComponentContent = genInlineComponentText(html, script); // 把md提取的内容拼接成vue的template编译的js const demoComponentName = `element-demo${id}`; output.push(`<template slot="source"><${demoComponentName} /></template>`); // 把内容放到demo-block的source的slot中 componenetsString += `${JSON.stringify(demoComponentName)}: ${demoComponentContent},`; // 把处理好的template都作为组件放到components里 // 重新计算下一次的位置 id++; start = commentEnd + endTagLen; commentStart = content.indexOf(startTag, start); commentEnd = content.indexOf(endTag, commentStart + startTagLen); } // 仅允许在 demo 不存在时,才可以在 Markdown 中写 script 标签
// componentsString是在上面提取的所有的 组件demo作为组件拼接的字符串
// todo: 优化这段逻辑 let pageScript = ''; if (componenetsString) { pageScript = `<script> export default { name: 'component-doc', components: { ${componenetsString} } } </script>`; } else if (content.indexOf('<script>') === 0) { // 硬编码,有待改善 start = content.indexOf('</script>') + '</script>'.length; pageScript = content.slice(0, start); } output.push(content.slice(start)); return ` <template> <section class="content element-doc"> ${output.join('')} </section> </template> ${pageScript} `; };
在 build/md-loader/fence.js里重写 md.renderer.rules.fence 规则,把md里的elementui组件放大demo-block的highlight插槽里,用于展示组件源码;
// 覆盖默认的 fence 渲染策略 module.exports = md => { const defaultRender = md.renderer.rules.fence; md.renderer.rules.fence = (tokens, idx, options, env, self) => { const token = tokens[idx]; // 判断该 fence 是否在 :::demo 内 const prevToken = tokens[idx - 1]; const isInDemoContainer = prevToken && prevToken.nesting === 1 && prevToken.info.trim().match(/^demo\s*(.*)$/); if (token.info === 'html' && isInDemoContainer) { // html的加上高亮标签 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); }; };
全局组件demo-block的组件展示和组件源码插槽代码如下:
// examples/components/demo-block.vue
<template> <div class="demo-block" :class="[blockClass, { 'hover': hovering }]" @mouseenter="hovering = true" @mouseleave="hovering = false"> <div class="source"> <!-- 组件渲染展示的位置 --> <slot name="source"></slot> </div> <div class="meta" ref="meta"> <div class="description" v-if="$slots.default"> <slot></slot> </div> <div class="highlight"> <!-- 组件源码展示的位置 --> <slot name="highlight"></slot> </div> </div> <div 。。。。