如何实现企业级组件库文档–浅谈ElementUI的md-loader(1)

预计阅读时间: 6分钟

前言

一个好的组件库,其文档必然是易读且易于维护的。 element-ui 作为一个优秀的组件库显然做到了这一点。 element-ui 的官网是使用 vue 开发的,将官方仓库的代码 clone 到本地运行,就可以在本地构建 element-ui 的官网。值得注意的是,根据源码中 vue-router 的路由信息,element-ui 的所有组件文档页引入的并不是一个.vue文件,而是一个 .md 文件。 打开其中的一个.md文件( examples/docs/zh-CN/backtop.md ),发现 element-uimarkdown 语法和 vue 代码结合到了一起,官网中的组件文档页也是由这些文件生成 ?

## Backtop 回到顶部

返回页面顶部的操作按钮

### 基础用法

滑动页面即可看到右下方的按钮。
:::demo

​```html
<template>
  Scroll down to see the bottom-right button.
  <el-backtop target=".page-component__scroll .el-scrollbar__wrap"></el-backtop>
</template>
​```
:::
 

这种做法节省了大量的维护成本。跟写页面相比,写markdown 显然省事多了。那么 element-ui 是如何做到将一个 .md 文件生成官网中独立的页面呢?

从webpack入手

我们可以在 build/webpack.demo.js 文件中看到 element-uiwebpack 配置,在其中我们可以找到 element-ui 对于 .md 文件的处理。


   {
        test: /\.md$/,
        use: [
          {
            loader: 'vue-loader',
            options: {
              compilerOptions: {
                preserveWhitespace: false
              }
            }
          },
          {
            loader: path.resolve(__dirname, './md-loader/index.js')
          }
        ]
      }

 

根据这段配置,webpack 一旦遇到以 .md 结尾的文件,就会读取文件中的内容,将这些内容传入相应的 loader 中进行处理。由于 loader 的调用顺序是从下往上的,所以会先调用 md-loader,然后将 md-loader 处理好的内容传入 vue-loader 中 。只要传入 vue-loader 中的内容符合 vue 的单文件组件的规范,就可以当做一个 vue 文件处理。这样一来,我们就将 .md 文件处理为 vue 文件了。扩展开来,只要我们配置好相应的 loader ,不只是 .md 文件,任意的文件都可以通过 vue-loader 进行处理。

经过以上的分析,我们可以得知,在整个过程中最关键的部分就是 md-loader 了。那么在 md-loader 中, element-ui 是如何做到将 .md 文件处理为 vue 格式的呢?接下来我们可以打开 md-loader 的入口文件一起了解( md-loader/index.js )。

md-loader地址

md-loader

首先我们需要了解 element-ui 对于 .md 文件的处理思路,主要分为两步:

  1. .md 文件转换为普通的 html

  2. 将转换后的 html 代码处理为 vue 格式。

关于第一步有现成的工具可以实现。element-ui 使用的是 markdown-it。幸运的是这个工具有在线demo,我们可以直接将 markdown 代码复制到其中查看解析结果。在这里我们使用 element-uibutton 组件文档作为示例( examples/docs/zh-CN/button.md )。将该文件复制到在线demo中,即可看到效果。注意右上角的三个标记,分别表示了查看的三种内容:

  1. html,可以查看 markdown 转换后的页面效果。

WX20210318-163248.png

  1. source,可以查看 markdown 转换后生成的 html 代码。

屏幕快照 2021-03-19 下午2.02.01.png

3.debug,可以查看 markdown 转换后生成的 tokens 数组,后面会用到。

屏幕快照 2021-03-19 下午2.02.10.png

有了 markdown-it 插件的帮助,现在我们已经可以将 .md 文件转换为普通的 html 了。以上的过程如果用代码描述的话就是:

npm i markdown-it 

var md = require('markdown-it')();
var result = md.render('button.md中的内容');
 

没错只有三行 ?

当然,转换后的代码不可能和我们的需求完全一致, element-ui 在转换的过程中也进行了其他的处理。这些配置都放在了 config.js 中。

const md = require('./config');

module.exports = function(source) {
  const content = md.render(source);
}
 

这些配置中主要包括标题锚点和自定义代码块。关于锚点,细心的朋友可以发现,当你将鼠标放在 element-ui 文档的标题上时,会出现一个符号标记,这就是锚点了。

1616479556702.jpg

锚点作用是点击之后可以跳转到页面的对应位置。虽然 element-ui 的文档中的标题设置了锚点,但是并没有对应的页面大纲。所以只能通过点击标题触发锚点 ? ( 在 Element Plus 中优化了这个问题 )。

另一个配置是自定义代码块,这是一个很重要的配置。我们可以观察到文档中的有一段内容被 :::demo 标记包裹了起来

::: demo
xxxx
xxxx
:::
 

很显然这个标记不属于正文里的内容,这里使用了 markdown-it 的一个插件,名叫 markdown-it-container。这个插件的作用是获取标记内的代码( :::demo )并返回自定义内容。这个过程就类似于 JavaScriptreplace 方法。

这一段的代码在 build/md-loader/container.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');
};

 

其中的render 函数返回的 <demo-block>element-ui 中的用于 demo 展示的组件,暂时不用关心。这段代码中最令人费解的部分就是这个 tokens 参数了,这个参数代表的是什么呢?这就要回到我们最开始的在线demo中的 debug 内容了。从中我们可以看到 tokens 是一个数组,里面有一系列的 token 对象,用于描述如何生成 html 标签。token 对象的每一个属性都有各自的意义,如上述代码中的 info( 代码块中的内容 ),nesting( 判断标签是否闭合,1 代表标签打开标志,-1 代表标签关闭标志 )。具体可以查看 markdown-it 中文文档,里面还有对 markdown-it 更详尽的介绍。

经过以上的步骤之后,我们终于拿到了一段经过我们转化后的代码了。

这就是 md-loader 的前三行的内容?

const md = require('./config');

module.exports = function(source) {
  const content = md.render(source);
}
 

没错,整篇文章到现在,我们终于讲完了md-loader入口文件的前三行,大致了解了 content 变量里面的内容了,yeah!

不过别着急,万事开头难。踏出第一步,才会有更多可能。

总结

我们现在已经可以将 .md 文件转换为 html 了。下一篇文章中,我们再一起聊聊如何将转换后的 html 代码处理为 vue 格式。

如果这篇文章对你有所帮助,或者有所启发的话,求关注!求点赞!各位的支持和认可,就是我写作的最大动力?

(2)
上一篇 2021年3月27日 上午9:07
下一篇 2021年3月27日 上午9:22

相关推荐

发表评论

登录后才能评论