一:elementui源码解析之markdown处理

一些参考网址:

markdown-it插件的分析和源码分析参考地址:https://zhuanlan.zhihu.com/p/64290806
element-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 。。。。

 

 

posted @ 2021-11-30 10:40  missLiuliu  阅读(887)  评论(0编辑  收藏  举报