缘起

想必你阅读过诸如Element UiAnt Design Vue的文档,其中的示例示例的源码通常同时展示。这种形式是怎么实现的?当然最傻的做法是写一遍使用示例,然后通过将示例的源码复制粘贴到示例的下面。当然这种方式后期维护会很麻烦。更加合理和高效的做法是通过某种形式自动完成复制粘贴的过程。

基本原理

  1. 编写一个webpackloader将Markdown格式的文档渲染成html;
  2. 将其中的示例代码单独包装成一个单文件组件
  3. 将整个文档包装成一个单文件组件,并将步骤2的示例代码包装的单文件组件import 进来;
  4. 利用loader的链式调用特性将这些处理结果交给vue-loader

实现步骤

步骤1:将Markdown文件渲染成html

这个步骤相对简单,可以使用markdown-it直接将Markdown格式的文档渲染成html,然后用<template></template>标签包裹起来即可。

loader 部分代码:
const MarkdownIt = require('markdown-it')
module.exports = function (mdFileString) {
    const md = new MarkdownIt({
        html: true,
        breaks: true
    })
    const html = md.render(mdFileString)
}
步骤2:将文档的示例代码单独包装成一个[单文件组件]

一般文档使用Markdown格式书写,而Markdown格式中代码示例的格式如下:

``` html
代码示例
```

根据这种规则,我们可以将代码块通过正则方式提取出来:

module.exports = function (mdFileString) {
   const codeReg = new RegExp('``` html\n(.|\n)*?\n```', 'g')
   const demoCodes = mdFileString.match(codeReg)
}

接着将正则提取出来的代码块内容写入到本地文件目录中:

const fs = require('fs')
module.exports = function (mdFileString) {
   const codeReg = new RegExp('``` html\n(.|\n)*?\n```', 'g')
   const demoCodes = mdFileString.match(codeReg)
   for (let index = 0; index < demoCodes.length; index++) {
   const vueFileContent = demoCodes[index].replace(/``` html\n|\n```/g, '')
    const vueFilePathAndName = '文件路径和名称可以随意,但是但是后缀必须是vue,建议在文档所在目录建立一个隐藏文件夹存放'
      fs.writeFileSync(vueFilePathAndName, vueFileContent)
   }
}
步骤3:将文档及上面包装的示例代码整合

步骤1中我们将整个Markdown文档渲染成了html,步骤2中我们将示例代码分别包装成了单文件组件并写入本地目录系统中。
现在我们将整个Markdown文档渲染成的html包装成一个单文件组件

const MarkdownIt = require('markdown-it')
module.exports = function (mdFileString) {
    const md = new MarkdownIt({
        html: true,
        breaks: true
    })
    const html = md.render(mdFileString)
    return `<template>
              <div class="vue-markdown-loader_markdown">
                ${html}
              </div>
            </template>
            <script>
            export default {
            ${importVuesString}
            components: {
                ${components}
              }
            }
            </script>`;
}

非常简单,我们只是把html包裹在一个普通单文件组件<template></template>标签中。
细心的你应该发现上面有两个变量是我们没有的提到的,分别是:importVuesStringcomponents。这个两个也很好理解,步骤2中我们将每份示例代码都包装成了单独的单文件组件,这里就需要将他们import进来,然后在components中注册。当然这里面还缺少了在<template></template>中对应位置写入我们分离的每个示例代码组件,这个步骤就不详述,主要是一些简单的逻辑。

步骤4:将处理结果交给vue-loader

我们处理完的结果就是返回一个单文件组件,里面import了每个示例代码包装的单文件组件。我们再通过webpack的配置将这些内容传递给vue-loader,这样就相当于你写了Markdown格式的文档,但是自动装换成了一个vue组件,将其中的示例代码运行起来,并且示例的代码也展示出来了。

其它细节

1. 其它实现方式及优劣分析

Element Ui文档实现的方式也是通过一个loader来将Markdown格式的文档装换成一个vue单文件组件。不同的是Element Uicomponent-compiler-utils将示例代码直接编译成了JavaScript在将其内联在主文档装换的单文件组件中。这样做的好处是不会产生额外的文件,缺点是实现逻辑较为复杂,相比本文的方法来说不够简洁。

2. 更加合理放置示例代码生成的单文件组件和对应的源码

在步骤3中有个内容没有详述,是关于如何在主文档放置示例代码的单文件组件,很明显示例代码的单文件组件应该放置在对应的示例代码源码旁边,这样用户在阅读示例代码的时候可以参照示例运行的结果。更加合理的方式是:

<demo-cntainer>
  <template slot="component">
    <示例组件名 />
  </template>
  <template slot="code">
    <div v-pre>示例代码</div>
  </template>
</demo-cntainer>

上面的做法是将示例代码的单文件组件和示例代码放置在一个有两个插槽的组件中,组件名可以通过loaderoptions传递进来。这样就可以实现类似点击查看源码的交互体验。

3. 如何语法高亮

使用highlight.js在文档的外部组件对代码进行高亮处理。

4. 选择性装换处理示例代码

文档中不是所有示例代码都需要通过上面这一系列处理来运行,但是我们从上面的步骤中2中知道我们见所有一下格式的代码都进行了处理:

``` html
代码示例
```

这里一个小偷懒技巧,我们只装换处理 三个反引号+ 空格 + html 的示例代码,如果你不想装换处理某段代码只需要反引号html中间不加空格即可。当然肯定有更合理的做法,这里不展开讨论。

5.Markdown渲染成html后的样式

Markdown渲染后的html代码的样式表可以去github等网站拉取一份,然后在根据情况做修改。

6.给Markdown增加更多自定义容器

Markdown的原生格式有时候过于单调不符合需求,通过markdown-it-container配合相应的css,你可以增加更多自定义容器。

相关项目

基于上面的原理的开源项目vue-markdown-loader可以让你开箱即用。

03-06 00:07