技术原理揭秘:Element官网显示代码、隐藏代码块竟然是Markdown编译的

Element 官网显示代码、隐藏代码块

element ui 官网隐藏代码块、显示组件代码块,大家都使用过,但是你知道他是怎么实现的吗?背后涉及到了什么原理?

在没看过具体实现的话我也认为不就一个展开收起代码块得组件嘛?
简单简单分分钟实现一个

实际情况是没那么简单!!! 可以看下如下一段markdown语法:

:::demo 使用type、plain、round和circle属性来定义 Button 的样式。

<el-row>
  <el-button>默认按钮</el-button>
  <el-button type="primary">主要按钮</el-button>
  <el-button type="success">成功按钮</el-button>
  <el-button type="info">信息按钮</el-button>
  <el-button type="warning">警告按钮</el-button>
  <el-button type="danger">危险按钮</el-button>
</el-row>

<el-row>
  <el-button plain>朴素按钮</el-button>
  <el-button type="primary" plain>主要按钮</el-button>
  <el-button type="success" plain>成功按钮</el-button>
  <el-button type="info" plain>信息按钮</el-button>
  <el-button type="warning" plain>警告按钮</el-button>
  <el-button type="danger" plain>危险按钮</el-button>
</el-row>

<el-row>
  <el-button round>圆角按钮</el-button>
  <el-button type="primary" round>主要按钮</el-button>
  <el-button type="success" round>成功按钮</el-button>
  <el-button type="info" round>信息按钮</el-button>
  <el-button type="warning" round>警告按钮</el-button>
  <el-button type="danger" round>危险按钮</el-button>
</el-row>

<el-row>
  <el-button icon="el-icon-search" circle></el-button>
  <el-button type="primary" icon="el-icon-edit" circle></el-button>
  <el-button type="success" icon="el-icon-check" circle></el-button>
  <el-button type="info" icon="el-icon-message" circle></el-button>
  <el-button type="warning" icon="el-icon-star-off" circle></el-button>
  <el-button type="danger" icon="el-icon-delete" circle></el-button>
</el-row>

:::

展示效果如下(对,就这段语法就变成了一个可以展示、并且支持复制得代码段):

技术上就是使用markdown 定制:::demo ::::语法转成了对应得html代码,那么咱们接下来就一步一步分析具体怎么做到的!!!

手动实现组件隐藏、组件显示

咱们现手动拿到element uidemo-block组件拿到项目里面把相关markdown:::demo :::内容复制给demo-block组件,看用代码怎么实现一个组件的显示隐藏以及展现的,其实跟你分分钟实现的组件是一样的!!

demo-block组件原代码地址

下面咱们启动一个vue 项目然后把demo-block引入到项目里面,以及引入element ui组件,下载highlight.js包 要用这个展示代码块先看下在elemnetUI 中是怎么用的如下:

highlightjs使用链接

// entry.js 中
import hljs from 'highlight.js';

router.afterEach(route => { // router的导航守卫里面
  // https://github.com/highlightjs/highlight.js/issues/909#issuecomment-131686186
  Vue.nextTick(() => {
    const blocks = document.querySelectorAll('pre code:not(.hljs)');
    Array.prototype.forEach.call(blocks, hljs.highlightBlock);
  });
});

其实跟下面一样的(上面那个是在路由执行后就立刻获取dom 然后调用hljs.highlightBlock):

<template>
  <pre>
    <code class="language-js">
      {{ code }}
    </code>
  </pre>
</template>

<script>
import 'highlight.js/styles/monokai-sublime.css';
import hljs from 'highlight.js';

export default {
  props: {
    code: String,
  },
  mounted() {
    this.highlightCode();
  },
  methods: {
    highlightCode() {
      const codeBlocks = this.$el.querySelectorAll('pre code');
      codeBlocks.forEach((block) => {
        hljs.highlightBlock(block);
      });
    },
  },
};
</script>

是不是highlight.js怎么使用也学到了!!! 接着往下看

我把demo-block拿到了项目里面稍微改了下就是把语言啥的都去掉了,在我的项目里面能正常运行了!

还以上面el-button为例开始使用demo-block组件如下:

<template>
  <div class="app">
    <demo-block>
      <template #source>
        <el-row>
          <el-button>默认按钮</el-button>
          <el-button type="primary">
            主要按钮
          </el-button>
          <el-button type="success">
            成功按钮
          </el-button>
          <el-button type="info">
            信息按钮
          </el-button>
          <el-button type="warning">
            警告按钮
          </el-button>
          <el-button type="danger">
            危险按钮
          </el-button>
        </el-row>

        <el-row>
          <el-button plain>
            朴素按钮
          </el-button>
          <el-button type="primary" plain>
            主要按钮
          </el-button>
          <el-button type="success" plain>
            成功按钮
          </el-button>
          <el-button type="info" plain>
            信息按钮
          </el-button>
          <el-button type="warning" plain>
            警告按钮
          </el-button>
          <el-button type="danger" plain>
            危险按钮
          </el-button>
        </el-row>

        <el-row>
          <el-button round>
            圆角按钮
          </el-button>
          <el-button type="primary" round>
            主要按钮
          </el-button>
          <el-button type="success" round>
            成功按钮
          </el-button>
          <el-button type="info" round>
            信息按钮
          </el-button>
          <el-button type="warning" round>
            警告按钮
          </el-button>
          <el-button type="danger" round>
            危险按钮
          </el-button>
        </el-row>

        <el-row>
          <el-button icon="el-icon-search" circle />
          <el-button type="primary" icon="el-icon-edit" circle />
          <el-button type="success" icon="el-icon-check" circle />
          <el-button type="info" icon="el-icon-message" circle />
          <el-button type="warning" icon="el-icon-star-off" circle />
          <el-button type="danger" icon="el-icon-delete" circle />
        </el-row>
      </template>
      <template>
        使用
        <code>type</code>、
        <code>plain</code>、
        <code>round</code>和
        <code>circle</code>属性来定义 Button 的样式。
      </template>

      <template #highlight>
        <pre>
              <code class="html">
                {{ code }}
              </code>
            </pre>
      </template>
    </demo-block>
  </div>
</template>

<script>
import DemoBlock from './components/demo-block.vue';
// import 'highlight.js/styles/monokai-sublime.css';
import hljs from 'highlight.js';

export default {
  name: 'App',
  components: {
    DemoBlock,
  },
  data() {
    return {
      code: `<el-row>
                <el-button>默认按钮</el-button>
                <el-button type="primary">主要按钮</el-button>
                <el-button type="success">成功按钮</el-button>
                <el-button type="info">信息按钮</el-button>
                <el-button type="warning">警告按钮</el-button>
                <el-button type="danger">危险按钮</el-button>
              </el-row>

              <el-row>
                <el-button plain>朴素按钮</el-button>
                <el-button type="primary" plain>主要按钮</el-button>
                <el-button type="success" plain>成功按钮</el-button>
                <el-button type="info" plain>信息按钮</el-button>
                <el-button type="warning" plain>警告按钮</el-button>
                <el-button type="danger" plain>危险按钮</el-button>
              </el-row>

              <el-row>
                <el-button round>圆角按钮</el-button>
                <el-button type="primary" round>主要按钮</el-button>
                <el-button type="success" round>成功按钮</el-button>
                <el-button type="info" round>信息按钮</el-button>
                <el-button type="warning" round>警告按钮</el-button>
                <el-button type="danger" round>危险按钮</el-button>
              </el-row>

              <el-row>
                <el-button icon="el-icon-search" circle></el-button>
                <el-button type="primary" icon="el-icon-edit" circle></el-button>
                <el-button type="success" icon="el-icon-check" circle></el-button>
                <el-button type="info" icon="el-icon-message" circle></el-button>
                <el-button type="warning" icon="el-icon-star-off" circle></el-button>
                <el-button type="danger" icon="el-icon-delete" circle></el-button>
              </el-row>`
    };
  },
  mounted() {
    this.highlightCode();
  },
  methods: {
    highlightCode() {
      const codeBlocks = this.$el.querySelectorAll('pre code');
      codeBlocks.forEach((block) => {
        hljs.highlightBlock(block);
      });
    },
  },
};
</script>

效果如下图(样式就不调整了,基本都出来了):

技术原理揭秘:Element官网显示代码、隐藏代码块竟然是Markdown编译的

demo-block 分了3个插槽一个是默认插槽描述,第二个插槽是界面展示效果,第三个插槽也就是让用户复制的代码

<template>
  <demo-block>
    <template>描述信息</template>
    <template #source>源码展示效果</template>
    <template #highlight>源码高亮让支持复制</template>
  </dome-block>
</template>

markdown 转成对应的html

怎么才能像咱们手动复制样把对应的markdown转成对应的vue呢! 编写webpack插件loader实现转换,咱们先改下配置使用element ui内置编好的loader先用,先实现效果,后面再一步步分析怎么编写这个loader的。更改配置如下:

第一步:编写这个loader用了几个插件咱们先下载如下:

{
    "transliteration": "^1.1.11",
    "markdown-it": "^8.4.1",
    "markdown-it-anchor": "^5.0.2",
    "markdown-it-chain": "^1.3.0",
    "markdown-it-container": "^2.0.0"
}

第二步:拿了一下官网md-loader文件夹到我本地准备引入vue

第三步:vue.config.js配置如下:

const path = require('path');

module.exports = {
 configureWebpack: (config) => {
  config.module.rules.push(
    {
      test: /\.md$/,
      use: [
        {
          loader: 'vue-loader',
          options: {
            compilerOptions: {
              preserveWhitespace: false
            }
          }
        },
        {
          loader: path.resolve(__dirname, './md-loader/index.js')
        }
      ]
    },
  )
},
}

第四步:我拿了个官网的button.md放在了跟App.vue同级下,替换掉了原App.vue代码如下:

<template>
  <div>
    <button-docs />
  </div>
</template>

<script>
import ButtonDocs from './button.md';

export default {
  name: 'App',
  components: {
    ButtonDocs,
  }
};
</script>

<style lang="scss">
.demo-block.demo-button {
  .el-row {
    margin-bottom: 20px;

    &:last-child {
      margin-bottom: 0;
    }
  }
  .el-button + .el-button {
    margin-left: 10px;
  }
  .el-button-group {
    .el-button + .el-button {
      margin-left: 0;
    }

    & + .el-button-group {
      margin-left: 10px;
    }
  }
}
</style>

展示效果如下:

可以看的出来都展示出来了,这下知道element ui的官网是怎么做的了吧!!

解析深挖my-loader(一) 使用的插件解析

使用的插件解析:咱们进一步分析一下md-loader是如何实现具体markdown to vue的。 先了解下它用了的那些插件都是干啥的如下:

  1. markdown-it 是一个基于 JavaScriptMarkdown 解析器,可以将 Markdown 文本转换成 HTML 标签。
    与其他 Markdown 解析器相比,markdown-it 具有以下优点:
  • 快速高效:markdown-it 使用了高效的解析算法,相比其他解析器能够更快地处理大量的 Markdown 文本。
  • 灵活可扩展:markdown-it 的插件机制非常灵活,用户可以根据需要自定义和扩展不同的解析规则。
  • 易于使用:markdown-it 提供了简单易用的 API,让用户可以在几行代码内实现 MarkdownHTML 的转换。

简单使用示例:

const md = require('markdown-it')();
const result = md.render('# Hello, World!')
console.log(result) // 输出 '<h1>Hello, World!</h1>'

  1. transliteration 是作为中文转换的如下:
import { transliterate as tr, slugify } from 'transliteration';

tr('你好, world!');
// Ni Hao , world!
slugify('你好, world!');
// ni-hao-world

  1. markdown-it-anchormarkdown-it 设计的标题锚点的生成。这个插件处理锚点生成,不是解析markdown的主要流程,可以暂时不去关注,知道功能就可以了,后面有时间可以看看里面的源码。

  2. markdown-it-chain是干什么的呢? 解说如下:

markdown-it-chain 是一个用于构建 markdown-it 插件链的工具库,它通过一种链式调用的方式来简化插件的配置和使用过程,使得用户可以更加方便地定义自己的 Markdown 解析规则。

具体来说,markdown-it-chain 可以将多个 markdown-it 插件组合起来,形成一个完整的解析链,并提供了一些常用的 API 和钩子函数,帮助用户在解析过程中添加、修改或删除某些解析规则。同时,由于 markdown-it-chain 使用了链式调用的方式,因此用户可以通过连续调用多个方法来按顺序设置不同的插件参数,从而实现高度定制化的解析效果。

总之,markdown-it-chain 旨在让用户更加方便地配置和使用 markdown-it 插件,提高其 Markdown 解析效率和灵活性。

使用方式如下:

config
  .options.html(true).end()

  .plugin('anchor').use(anchorPlugin, [
    {
      level: 2,
      slugify: slugify,
      permalink: true,
      permalinkBefore: true
    }
  ]).end()

  .plugin('containers').use(containers).end();

const md = config.toMd();
  1. markdown-it-container 是什么呢?

markdown-it-container 是一个用于在 Markdown 解析器中创建自定义块级容器的插件。通过使用 markdown-it-container 插件,用户可以方便地定义自己的容器格式,并在解析过程中将其转换成相应的 HTML 标签。

就是解析::: demo ::::、::: AAA ::: …这个的!!!!用3个冒号包裹叫做自定义块级容器

具体来说markdown-it-container 可以让用户对指定的 Markdown 文本进行标记,将其包裹在自定义的块级容器中,并为该容器添加任意的 CSS 类名和样式。这样做既能提高文章的可读性,又能使其呈现出更美观的排版效果。

使用示例如下:

const md = require('markdown-it')();
const container = require('markdown-it-container');

// 定义自定义容器
md.use(container, 'AAA', {
  render: function (tokens, idx) {
    const token = tokens[idx]
    if (token.nesting === 1) {
      // 开始标记
      return `<div class="my-aaa-container">`;
    } else {
      // 结束标记
      return '</div>';
    }
  }
});

// 使用自定义容器
const result = md.render(`
::: AAA
 这是a AAA 容器块
:::
`);  // 输出 <div class="my-aaa-container"><p>这是a AAA 容器块</p> </div>

经过上面分析每个插件怎么使用的,咱们再去读代码就更会清晰了。

解析深挖my-loader(二) 源码解析

源码解析:咱们进一步分析一下md-loader是如何实现具体markdown to vue的。

md-loader总共分了4个文件如下:

|--
  |-- config.js
  |-- containers.js
  |-- fence.js
  |-- index.js
  |-- util.js

containers.js中下面这段代码很清晰了就是把:::demo 描述信息 内容部分 :::替换成了

<demo-block>
  <div>描述信息<div>
   <!--element-demo: 内容部分 :element-demo-->
</demo-block>

containers.js 代码如下:

const mdContainer = require('markdown-it-container');

module.exports = md => {
  md.use(mdContainer, '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 : '';
        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');
};

config.js是使用markdown-it-chain把插件markdown-it-anchor生成锚点和containers串起来了最后调用了下fence.js如下:

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();

config
  .options.html(true).end()

  .plugin('anchor').use(anchorPlugin, [
    {
      level: 2,
      slugify: slugify,
      permalink: true,
      permalinkBefore: true
    }
  ]).end()

  .plugin('containers').use(containers).end();

const md = config.toMd();
overWriteFenceRule(md);

module.exports = md;

fence.js是把:::demo 内容部分::: 内容部分生成了如下:

<template slot="highlight">
   <pre v-pre>
     <code class="html"> 
     {{ md.utils.escapeHtml(内容部分) }}
     </code>
   </pre>
<template slot="highlight">

md.utils.escapeHtml 类似咱们前面使用highlight.js把代码转成html的代码展示。

fence.js代码如下:

// 覆盖默认的 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) {
      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);
  };
};

经过咱们上面一部分render后咱们的button.md也就变成了如下:

<h2 id="button-an-niu"><a class="header-anchor" href="#button-an-niu"></a> Button 按钮</h2>
<p>常用的操作按钮。</p>
<h3 id="ji-chu-yong-fa"><a class="header-anchor" href="#ji-chu-yong-fa"></a> 基础用法</h3>
<p>基础的按钮用法。</p>
<demo-block>
<div><p>使用<code>type</code><code>plain</code><code>round</code><code>circle</code>属性来定义 Button 的样式。</p>
</div>
<!--element-demo: <el-row>
<el-button>默认按钮</el-button>
<el-button type="primary">主要按钮</el-button>
<el-button type="success">成功按钮</el-button>
<el-button type="info">信息按钮</el-button>
<el-button type="warning">警告按钮</el-button>
<el-button type="danger">危险按钮</el-button>
</el-row>
<el-row>
<el-button plain>朴素按钮</el-button>
<el-button type="primary" plain>主要按钮</el-button>
<el-button type="success" plain>成功按钮</el-button>
<el-button type="info" plain>信息按钮</el-button>
<el-button type="warning" plain>警告按钮</el-button>
<el-button type="danger" plain>危险按钮</el-button>
</el-row>
<el-row>
<el-button round>圆角按钮</el-button>
<el-button type="primary" round>主要按钮</el-button>
<el-button type="success" round>成功按钮</el-button>
<el-button type="info" round>信息按钮</el-button>
<el-button type="warning" round>警告按钮</el-button>
<el-button type="danger" round>危险按钮</el-button>
</el-row>
<el-row>
<el-button icon="el-icon-search" circle></el-button>
<el-button type="primary" icon="el-icon-edit" circle></el-button>
<el-button type="success" icon="el-icon-check" circle></el-button>
<el-button type="info" icon="el-icon-message" circle></el-button>
<el-button type="warning" icon="el-icon-star-off" circle></el-button>
<el-button type="danger" icon="el-icon-delete" circle></el-button>
</el-row>
:element-demo-->
<template slot="highlight"><pre v-pre><code class="html">&lt;el-row&gt;
&lt;el-button&gt;默认按钮&lt;/el-button&gt;
&lt;el-button type=&quot;primary&quot;&gt;主要按钮&lt;/el-button&gt;
&lt;el-button type=&quot;success&quot;&gt;成功按钮&lt;/el-button&gt;
&lt;el-button type=&quot;info&quot;&gt;信息按钮&lt;/el-button&gt;
&lt;el-button type=&quot;warning&quot;&gt;警告按钮&lt;/el-button&gt;
&lt;el-button type=&quot;danger&quot;&gt;危险按钮&lt;/el-button&gt;
&lt;/el-row&gt;
&lt;el-row&gt;
&lt;el-button plain&gt;朴素按钮&lt;/el-button&gt;
&lt;el-button type=&quot;primary&quot; plain&gt;主要按钮&lt;/el-button&gt;
&lt;el-button type=&quot;success&quot; plain&gt;成功按钮&lt;/el-button&gt;
&lt;el-button type=&quot;info&quot; plain&gt;信息按钮&lt;/el-button&gt;
&lt;el-button type=&quot;warning&quot; plain&gt;警告按钮&lt;/el-button&gt;
&lt;el-button type=&quot;danger&quot; plain&gt;危险按钮&lt;/el-button&gt;
&lt;/el-row&gt;
&lt;el-row&gt;
&lt;el-button round&gt;圆角按钮&lt;/el-button&gt;
&lt;el-button type=&quot;primary&quot; round&gt;主要按钮&lt;/el-button&gt;
&lt;el-button type=&quot;success&quot; round&gt;成功按钮&lt;/el-button&gt;
&lt;el-button type=&quot;info&quot; round&gt;信息按钮&lt;/el-button&gt;
&lt;el-button type=&quot;warning&quot; round&gt;警告按钮&lt;/el-button&gt;
&lt;el-button type=&quot;danger&quot; round&gt;危险按钮&lt;/el-button&gt;
&lt;/el-row&gt;
&lt;el-row&gt;
&lt;el-button icon=&quot;el-icon-search&quot; circle&gt;&lt;/el-button&gt;
&lt;el-button type=&quot;primary&quot; icon=&quot;el-icon-edit&quot; circle&gt;&lt;/el-button&gt;
&lt;el-button type=&quot;success&quot; icon=&quot;el-icon-check&quot; circle&gt;&lt;/el-button&gt;
&lt;el-button type=&quot;info&quot; icon=&quot;el-icon-message&quot; circle&gt;&lt;/el-button&gt;
&lt;el-button type=&quot;warning&quot; icon=&quot;el-icon-star-off&quot; circle&gt;&lt;/el-button&gt;
&lt;el-button type=&quot;danger&quot; icon=&quot;el-icon-delete&quot; circle&gt;&lt;/el-button&gt;
&lt;/el-row&gt;
</code></pre></template></demo-block><h3 id="jin-yong-zhuang-tai"><a class="header-anchor" href="#jin-yong-zhuang-tai"></a> 禁用状态</h3>
<p>按钮不可用状态。</p>
<demo-block>
<div><p>你可以使用<code>disabled</code>属性来定义按钮是否可用,它接受一个<code>Boolean</code>值。</p>
</div>
<!--element-demo: <el-row>
<el-button disabled>默认按钮</el-button>
<el-button type="primary" disabled>主要按钮</el-button>
<el-button type="success" disabled>成功按钮</el-button>
<el-button type="info" disabled>信息按钮</el-button>
<el-button type="warning" disabled>警告按钮</el-button>
<el-button type="danger" disabled>危险按钮</el-button>
</el-row>
<el-row>
<el-button plain disabled>朴素按钮</el-button>
<el-button type="primary" plain disabled>主要按钮</el-button>
<el-button type="success" plain disabled>成功按钮</el-button>
<el-button type="info" plain disabled>信息按钮</el-button>
<el-button type="warning" plain disabled>警告按钮</el-button>
<el-button type="danger" plain disabled>危险按钮</el-button>
</el-row>
:element-demo-->
<template slot="highlight"><pre v-pre><code class="html">&lt;el-row&gt;
&lt;el-button disabled&gt;默认按钮&lt;/el-button&gt;
&lt;el-button type=&quot;primary&quot; disabled&gt;主要按钮&lt;/el-button&gt;
&lt;el-button type=&quot;success&quot; disabled&gt;成功按钮&lt;/el-button&gt;
&lt;el-button type=&quot;info&quot; disabled&gt;信息按钮&lt;/el-button&gt;
&lt;el-button type=&quot;warning&quot; disabled&gt;警告按钮&lt;/el-button&gt;
&lt;el-button type=&quot;danger&quot; disabled&gt;危险按钮&lt;/el-button&gt;
&lt;/el-row&gt;
&lt;el-row&gt;
&lt;el-button plain disabled&gt;朴素按钮&lt;/el-button&gt;
&lt;el-button type=&quot;primary&quot; plain disabled&gt;主要按钮&lt;/el-button&gt;
&lt;el-button type=&quot;success&quot; plain disabled&gt;成功按钮&lt;/el-button&gt;
&lt;el-button type=&quot;info&quot; plain disabled&gt;信息按钮&lt;/el-button&gt;
&lt;el-button type=&quot;warning&quot; plain disabled&gt;警告按钮&lt;/el-button&gt;
&lt;el-button type=&quot;danger&quot; plain disabled&gt;危险按钮&lt;/el-button&gt;
&lt;/el-row&gt;
</code></pre></template></demo-block><h3 id="wen-zi-an-niu"><a class="header-anchor" href="#wen-zi-an-niu"></a> 文字按钮</h3>
<p>没有边框和背景色的按钮。</p>
<demo-block>
<!--element-demo: <el-button type="text">文字按钮</el-button>
<el-button type="text" disabled>文字按钮</el-button>
:element-demo-->
<template slot="highlight"><pre v-pre><code class="html">&lt;el-button type=&quot;text&quot;&gt;文字按钮&lt;/el-button&gt;
&lt;el-button type=&quot;text&quot; disabled&gt;文字按钮&lt;/el-button&gt;
</code></pre></template></demo-block><h3 id="tu-biao-an-niu"><a class="header-anchor" href="#tu-biao-an-niu"></a> 图标按钮</h3>
<p>带图标的按钮可增强辨识度(有文字)或节省空间(无文字)。</p>
<demo-block>
<div><p>设置<code>icon</code>属性即可,icon 的列表可以参考 Element 的 icon 组件,也可以设置在文字右边的 icon ,只要使用<code>i</code>标签即可,可以使用自定义图标。</p>
</div>
<!--element-demo: <el-button type="primary" icon="el-icon-edit"></el-button>
<el-button type="primary" icon="el-icon-share"></el-button>
<el-button type="primary" icon="el-icon-delete"></el-button>
<el-button type="primary" icon="el-icon-search">搜索</el-button>
<el-button type="primary">上传<i class="el-icon-upload el-icon--right"></i></el-button>
:element-demo-->
<template slot="highlight"><pre v-pre><code class="html">&lt;el-button type=&quot;primary&quot; icon=&quot;el-icon-edit&quot;&gt;&lt;/el-button&gt;
&lt;el-button type=&quot;primary&quot; icon=&quot;el-icon-share&quot;&gt;&lt;/el-button&gt;
&lt;el-button type=&quot;primary&quot; icon=&quot;el-icon-delete&quot;&gt;&lt;/el-button&gt;
&lt;el-button type=&quot;primary&quot; icon=&quot;el-icon-search&quot;&gt;搜索&lt;/el-button&gt;
&lt;el-button type=&quot;primary&quot;&gt;上传&lt;i class=&quot;el-icon-upload el-icon--right&quot;&gt;&lt;/i&gt;&lt;/el-button&gt;
</code></pre></template></demo-block><h3 id="an-niu-zu"><a class="header-anchor" href="#an-niu-zu"></a> 按钮组</h3>
<p>以按钮组的方式出现,常用于多项类似操作。</p>
<demo-block>
<div><p>使用<code>&lt;el-button-group&gt;</code>标签来嵌套你的按钮。</p>
</div>
<!--element-demo: <el-button-group>
<el-button type="primary" icon="el-icon-arrow-left">上一页</el-button>
<el-button type="primary">下一页<i class="el-icon-arrow-right el-icon--right"></i></el-button>
</el-button-group>
<el-button-group>
<el-button type="primary" icon="el-icon-edit"></el-button>
<el-button type="primary" icon="el-icon-share"></el-button>
<el-button type="primary" icon="el-icon-delete"></el-button>
</el-button-group>
:element-demo-->
<template slot="highlight"><pre v-pre><code class="html">&lt;el-button-group&gt;
&lt;el-button type=&quot;primary&quot; icon=&quot;el-icon-arrow-left&quot;&gt;上一页&lt;/el-button&gt;
&lt;el-button type=&quot;primary&quot;&gt;下一页&lt;i class=&quot;el-icon-arrow-right el-icon--right&quot;&gt;&lt;/i&gt;&lt;/el-button&gt;
&lt;/el-button-group&gt;
&lt;el-button-group&gt;
&lt;el-button type=&quot;primary&quot; icon=&quot;el-icon-edit&quot;&gt;&lt;/el-button&gt;
&lt;el-button type=&quot;primary&quot; icon=&quot;el-icon-share&quot;&gt;&lt;/el-button&gt;
&lt;el-button type=&quot;primary&quot; icon=&quot;el-icon-delete&quot;&gt;&lt;/el-button&gt;
&lt;/el-button-group&gt;
</code></pre></template></demo-block><h3 id="jia-zai-zhong"><a class="header-anchor" href="#jia-zai-zhong"></a> 加载中</h3>
<p>点击按钮后进行数据加载操作,在按钮上显示加载状态。</p>
<demo-block>
<div><p>要设置为 loading 状态,只要设置<code>loading</code>属性为<code>true</code>即可。</p>
</div>
<!--element-demo: <el-button type="primary" :loading="true">加载中</el-button>
:element-demo-->
<template slot="highlight"><pre v-pre><code class="html">&lt;el-button type=&quot;primary&quot; :loading=&quot;true&quot;&gt;加载中&lt;/el-button&gt;
</code></pre></template></demo-block><h3 id="bu-tong-chi-cun"><a class="header-anchor" href="#bu-tong-chi-cun"></a> 不同尺寸</h3>
<p>Button 组件提供除了默认值以外的三种尺寸,可以在不同场景下选择合适的按钮尺寸。</p>
<demo-block>
<div><p>额外的尺寸:<code>medium</code><code>small</code><code>mini</code>,通过设置<code>size</code>属性来配置它们。</p>
</div>
<!--element-demo: <el-row>
<el-button>默认按钮</el-button>
<el-button size="medium">中等按钮</el-button>
<el-button size="small">小型按钮</el-button>
<el-button size="mini">超小按钮</el-button>
</el-row>
<el-row>
<el-button round>默认按钮</el-button>
<el-button size="medium" round>中等按钮</el-button>
<el-button size="small" round>小型按钮</el-button>
<el-button size="mini" round>超小按钮</el-button>
</el-row>
:element-demo-->
<template slot="highlight"><pre v-pre><code class="html">&lt;el-row&gt;
&lt;el-button&gt;默认按钮&lt;/el-button&gt;
&lt;el-button size=&quot;medium&quot;&gt;中等按钮&lt;/el-button&gt;
&lt;el-button size=&quot;small&quot;&gt;小型按钮&lt;/el-button&gt;
&lt;el-button size=&quot;mini&quot;&gt;超小按钮&lt;/el-button&gt;
&lt;/el-row&gt;
&lt;el-row&gt;
&lt;el-button round&gt;默认按钮&lt;/el-button&gt;
&lt;el-button size=&quot;medium&quot; round&gt;中等按钮&lt;/el-button&gt;
&lt;el-button size=&quot;small&quot; round&gt;小型按钮&lt;/el-button&gt;
&lt;el-button size=&quot;mini&quot; round&gt;超小按钮&lt;/el-button&gt;
&lt;/el-row&gt;
</code></pre></template></demo-block><h3 id="attributes"><a class="header-anchor" href="#attributes"></a> Attributes</h3>
<table>
<thead>
<tr>
<th>参数</th>
<th>说明</th>
<th>类型</th>
<th>可选值</th>
<th>默认值</th>
</tr>
</thead>
<tbody>
<tr>
<td>size</td>
<td>尺寸</td>
<td>string</td>
<td>medium / small / mini</td>
<td></td>
</tr>
<tr>
<td>type</td>
<td>类型</td>
<td>string</td>
<td>primary / success / warning / danger / info / text</td>
<td></td>
</tr>
<tr>
<td>plain</td>
<td>是否朴素按钮</td>
<td>boolean</td>
<td></td>
<td>false</td>
</tr>
<tr>
<td>round</td>
<td>是否圆角按钮</td>
<td>boolean</td>
<td></td>
<td>false</td>
</tr>
<tr>
<td>circle</td>
<td>是否圆形按钮</td>
<td>boolean</td>
<td></td>
<td>false</td>
</tr>
<tr>
<td>loading</td>
<td>是否加载中状态</td>
<td>boolean</td>
<td></td>
<td>false</td>
</tr>
<tr>
<td>disabled</td>
<td>是否禁用状态</td>
<td>boolean</td>
<td></td>
<td>false</td>
</tr>
<tr>
<td>icon</td>
<td>图标类名</td>
<td>string</td>
<td></td>
<td></td>
</tr>
<tr>
<td>autofocus</td>
<td>是否默认聚焦</td>
<td>boolean</td>
<td></td>
<td>false</td>
</tr>
<tr>
<td>native-type</td>
<td>原生 type 属性</td>
<td>string</td>
<td>button / submit / reset</td>
<td>button</td>
</tr>
</tbody>
</table>

到这里是不是都能看得懂了! 别着急还有 这才是把界面都展示了但是少了个界面<template #source>组件展示样式</template>

解析深挖my-loader(三) 源码解析之vue编译

源码解析之vue编译:咱们进一步分析一下md-loader是如何实现具体markdown to vue的。

看咱们的index.js如下:

const {
stripScript,
stripTemplate,
genInlineComponentText
} = require('./util');
const md = require('./config');
module.exports = function(source) {
const content = md.render(source);
console.log(content,'content...'); // 这里就是咱们上面打印的
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);
let commentEnd = content.indexOf(endTag, commentStart + startTagLen);
while (commentStart !== -1 && commentEnd !== -1) {
output.push(content.slice(start, commentStart));
const commentContent = content.slice(commentStart + startTagLen, commentEnd);
const html = stripTemplate(commentContent);
const script = stripScript(commentContent);
let demoComponentContent = genInlineComponentText(html, script);
const demoComponentName = `element-demo${id}`;
output.push(`<template slot="source"><${demoComponentName} /></template>`);
componenetsString += `${JSON.stringify(demoComponentName)}: ${demoComponentContent},`;
// 重新计算下一次的位置
id++;
start = commentEnd + endTagLen;
commentStart = content.indexOf(startTag, start);
commentEnd = content.indexOf(endTag, commentStart + startTagLen);
}
// 仅允许在 demo 不存在时,才可以在 Markdown 中写 script 标签
// 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}
`;
};

具体怎么做的呢! 其实就是查找indexOf <!--element-demo: 内容 :element-demo-->'; 开始、闭合的索引然后呢使用stripTemplatestripScript方法去生成对应的template、script代码

let componenetsString = '';
const commentContent = `<el-button>内容</el-button>....<el-button>内容</el-button>`;
const html = stripTemplate(commentContent);
const script = stripScript(commentContent);
// 封成一个个组件
const demoComponentName = `element-demo${id}`;
// 供给去使用 如下:
output.push(`<template slot="source"><${demoComponentName} /></template>`);
// 这个是最后放在script的 components中去注册组件的。
componenetsString += `${JSON.stringify(demoComponentName)}: ${demoComponentContent},`;
// 这里是写了个基础模板
pageScript = `<script>
export default {
name: 'component-doc',
components: {
${componenetsString}
}
}
</script>`;
return `
<template>
<section class="content element-doc">
${output.join('')}
</section>
</template>
${pageScript}
`;

咱们再看下把button.md整个转成了个什么样的vue如下(删除了部分不主要的代码):


<template>
<section class="content element-doc">
<template slot="source"><element-demo0 /></template>
</section>
</template>
<script>
export default {
name: 'component-doc',
components: {
"element-demo0": (function() {
var render = function () {
var _vm = this
var _h = _vm.$createElement
var _c = _vm._self._c || _h
return _c(
"div",
[
_c(
"el-row",
[
_c("el-button", [_vm._v("默认按钮")]),
_vm._v(" "),
_c("el-button", { attrs: { type: "primary" } }, [_vm._v("主要按钮")]),
_vm._v(" "),
_c("el-button", { attrs: { type: "success" } }, [_vm._v("成功按钮")]),
_vm._v(" "),
_c("el-button", { attrs: { type: "info" } }, [_vm._v("信息按钮")]),
_vm._v(" "),
_c("el-button", { attrs: { type: "warning" } }, [_vm._v("警告按钮")]),
_vm._v(" "),
_c("el-button", { attrs: { type: "danger" } }, [_vm._v("危险按钮")]),
],
1
),
_vm._v(" "),
_c(
"el-row",
[
_c("el-button", { attrs: { plain: "" } }, [_vm._v("朴素按钮")]),
_vm._v(" "),
_c("el-button", { attrs: { type: "primary", plain: "" } }, [
_vm._v("主要按钮"),
]),
_vm._v(" "),
_c("el-button", { attrs: { type: "success", plain: "" } }, [
_vm._v("成功按钮"),
]),
_vm._v(" "),
_c("el-button", { attrs: { type: "info", plain: "" } }, [
_vm._v("信息按钮"),
]),
_vm._v(" "),
_c("el-button", { attrs: { type: "warning", plain: "" } }, [
_vm._v("警告按钮"),
]),
_vm._v(" "),
_c("el-button", { attrs: { type: "danger", plain: "" } }, [
_vm._v("危险按钮"),
]),
],
1
),
_vm._v(" "),
_c(
"el-row",
[
_c("el-button", { attrs: { round: "" } }, [_vm._v("圆角按钮")]),
_vm._v(" "),
_c("el-button", { attrs: { type: "primary", round: "" } }, [
_vm._v("主要按钮"),
]),
_vm._v(" "),
_c("el-button", { attrs: { type: "success", round: "" } }, [
_vm._v("成功按钮"),
]),
_vm._v(" "),
_c("el-button", { attrs: { type: "info", round: "" } }, [
_vm._v("信息按钮"),
]),
_vm._v(" "),
_c("el-button", { attrs: { type: "warning", round: "" } }, [
_vm._v("警告按钮"),
]),
_vm._v(" "),
_c("el-button", { attrs: { type: "danger", round: "" } }, [
_vm._v("危险按钮"),
]),
],
1
),
_vm._v(" "),
_c(
"el-row",
[
_c("el-button", { attrs: { icon: "el-icon-search", circle: "" } }),
_vm._v(" "),
_c("el-button", {
attrs: { type: "primary", icon: "el-icon-edit", circle: "" },
}),
_vm._v(" "),
_c("el-button", {
attrs: { type: "success", icon: "el-icon-check", circle: "" },
}),
_vm._v(" "),
_c("el-button", {
attrs: { type: "info", icon: "el-icon-message", circle: "" },
}),
_vm._v(" "),
_c("el-button", {
attrs: { type: "warning", icon: "el-icon-star-off", circle: "" },
}),
_vm._v(" "),
_c("el-button", {
attrs: { type: "danger", icon: "el-icon-delete", circle: "" },
}),
],
1
),
],
1
)
}
var staticRenderFns = []
render._withStripped = true
const democomponentExport = {}
return {
render,
staticRenderFns,
...democomponentExport
}
})(),
}
}
</script>

可以看到了吧!!! 知道这几行代码是干啥得了把:

const html = stripTemplate(commentContent);
const script = stripScript(commentContent);
let demoComponentContent = genInlineComponentText(html, script);

加油剩不多了!接着分析stripTemplatestripScriptgenInlineComponentText、就完事了,相关函数代码如下

  • stripTemplatestripScript 就是截取到template script的相关内容去除前后空格换行。

  • genInlineComponentText关键如下:

const { compileTemplate } = require('@vue/component-compiler-utils');
const compiler = require('vue-template-compiler');
function genInlineComponentText(template, script) {
// https://github.com/vuejs/vue-loader/blob/423b8341ab368c2117931e909e2da9af74503635/lib/loaders/templateLoader.js#L46
const finalOptions = {
source: `<div>${template}</div>`,
filename: 'inline-component', // TODO:这里有待调整
compiler
};
const compiled = compileTemplate(finalOptions);
let demoComponentContent = `
${compiled.code}
`;
// todo: 这里采用了硬编码有待改进
script = script.trim();
if (script) {
script = script.replace(/export\s+default/, 'const democomponentExport =');
} else {
script = 'const democomponentExport = {}';
}
demoComponentContent = `(function() {
${demoComponentContent}
${script}
return {
render,
staticRenderFns,
...democomponentExport
}
})()`;
return demoComponentContent;
}

总结

咱们先从自己拿demo-block组件自己手动生成HTML组件代码, 再到使用官网md-loader插件解析button.md,再到一步步解析md-loader。 总算是把Markdown -> vue完成了!!! 期间也学会了使用markdown-it等相关插件使用。 保持好奇心、保持好学心 冲冲!!!

原文链接:https://juejin.cn/post/7241567583505481783 作者:三原

(0)
上一篇 2023年6月7日 上午10:25
下一篇 2023年6月7日 上午10:36

相关推荐

发表回复

登录后才能评论