|文档:如何给你的组件库设计一个可交互式文档?

你好,我是大圣。

在我们实现了组件库核心的组件内容之后,我们就需要提供一个可交互的组件文档给用户使用和学习了。这个文档页面主要包含组件的描述,组件Demo示例的展示、描述和代码,并且每个组件都应该有详细的参数文档。

现在,我们从上述文档页包含的信息来梳理一下我们的需求。我们需要用最简洁的语法来写页面,还需要用最简洁的语法来展示 Demo + 源代码 + 示例描述。那么从语法上来说,首选就是 Markdown 无疑了,因为它既简洁又强大。

那在我们正式开始设计文档之前,我们还需要对齐一下。如果要展示 Demo 和源码的话,为了能更高效且低成本的维护,我们会把一个示例的 Demo + 源码 + 示例描述放到一个文件里,尽量多的去复用,这样可以减少需要维护的代码。而做示例展示的话,本质上可以说是跟 Markdown 的转译一致,都是 Markdown -> HTML,只是转译的规则我们需要拓展一下。接下来我们就正式开始了。

VuePress

首先我们需要一个能基于Markdown构建文档的工具,我推荐VuePress。它是Vue官网团队维护的在线技术文档工具,样式和Vue的官方文档保持一致。

VuePress内置了Markdown的扩展,写文档的时候就是用Markdown语法进行渲染的。最让人省心的是,它可以直接在Markdown里面使用Vue组件,这就意味着我们可以直接在Markdown中写上一个个的组件库的使用代码,就可以直接展示运行效果了。

我们可以在项目中执行下面的代码安装VuePress的最新版本:

yarn add -D vuepress@next

然后我们新建docs目录作为文档目录,新建docs/README.md文件作为文档的首页。除了Markdown之外,我们可以直接使用VuePress的语法扩展对组件进行渲染。

# 额外的信息

我们在README.md中输入上面的内容,通过title配置网站的标题、actions配置快捷链接、features配置详情介绍,这样我们就拥有了下面的首页样式:

图片

然后我们进入docs/.vuepress/目录下,新建文件config.js,这是这个网站的配置页面。下面的代码我们配置了logo和导航navbar,页面顶部右侧就会有首页和安装两个导航。

module.exports = {
  themeConfig: {
    title: "Element3",
    description: "vuepress搭建的Element3文档",
    logo: "/element3.svg",
    navbar: [{
      link: "/",
      text: "首页"
    }, {
      link: "/install",
      text: "安装"
    }, ]

  }
}

然后我们创建docs/install.md文件,点击顶部导航之后,就会显示install.md的信息。我们在文稿中就可以直接写上介绍Element3如何安装的文档了,下面的文稿就是Element3的安装使用说明。

## 安装
### npm 安装
推荐使用 npm 的方式安装,它能更好地和 [webpack](https://webpack.js.org/) 打包工具配合使用。

npm i element3 -S

  # # # CDN




  目前可以通过[unpkg.com / element3](https: //unpkg.com/element3) 获取到最新版本的资源,在页面上引入 js 和 css 文件即可开始使用。
  ::: tip

  我们建议使用 CDN 引入 Element3 的用户在链接地址上锁定版本, 以免将来 Element3 升级时受到非兼容性更新的影响。 锁定版本的方法请查看[unpkg.com](https: //unpkg.com)。  

      :::




      # # # Hello world




      通过 CDN 的方式我们可以很容易地使用 Element3 写出一个 Hello world 页面。[在线演示](https: //codepen.io/imjustaman/pen/abZajYg)

        [Element3 Demo](https: //codepen.io/imjustaman/pen/abZajYg)[@imjustaman](https://codepen.io/imjustaman)[CodePen](https://codepen.io)然后我们在浏览器里点击安装后,就会看到下图的页面显示。Markdown已经成功渲染为在线文档了,并且代码也自带了高亮显示。




          ![图片](https: //static001.geekbang.org/resource/image/09/c3/09d16ddc1080b4d8d4b04b934bdyy3c3.png?wh=1920x1655)




            然后我们需要在这个文档系统中支持Element3, 首先执行下面的代码安装Element3:

npm i element3 -D

  然后在项目根目录下的docs / .vuepress文件夹中新建文件clientAppEnhance.js, 这是VuerPress的客户端扩展文件。 我们导入了defineClientAppEnhance来返回客户端的扩展配置。 这个函数中会传递Vue的实例App以及路由配置Router, 我们使用app.use来全局注册Element3组件, 就可以直接在Markdown中使用Element3的组件了。

import { defineClientAppEnhance } from ‘@vuepress/client’

import element3 from ‘element3’

export default defineClientAppEnhance(({ app, router, siteData }) => { app.use(element3) })

  这样VuePress就内置了Element3。 我们在docs下面新建button.md文件, 可以直接在Markdown中使用Element3的组件进行演示。 下面的文稿中我们直接使用了el - button组件演示效果。

Button 按钮

常用的操作按钮。

基础用法

基础的按钮用法。

按钮
<el-button type="primary">
按钮
</el-button>
然后进入docs / .vuepress / config.js中, 新增侧边栏sidebar的配置之后, 就可以看到下图的效果了。

sidebar:[ { text:‘安装’, link:‘/install’ }, { text:‘按钮’, link:‘/button’ }, ]

  ![图片](https: //static001.geekbang.org/resource/image/d2/6d/d267f5d33cc9652c145d737f8f4dc96d.png?wh=1920x846)




    这样我们就基于VuePress支持了Element3组件库的文档功能, 剩下的就是给每个组件写好文档即可。




    但是这样的话, el - button的源码就写了两次, 如果我们想更好地定制组件库文档, 就需要自己解析Markdown文件, 在内部支持Vue组件的效果显示和源码展示, 也就相当于定制了一个自己的VuePress。

:::demo 使用typeplainroundcircle属性来定义 Button 的样式。

<template>
 <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>
</template>

:::

它能直接使用下面的::: demo语法, 在标记内部代码的同时, 显示渲染效果和源码, 也就是下图Element3官网的渲染效果。

  ![图片]( < https: //static001.geekbang.org/resource/image/85/c6/85c611ff85c7d69773e41c546f4eb3c6.png?wh=1920x1462>)

    那么接下来我们就看看如何定制, 具体操作一下。

    # # 解析Markdown

    我们需要自己实现一个Markdown - loader, 对Markdown语法进行扩展。

    Element3中使用Markdown - it进行Markdown语法的解析和扩展。 Markdown - it导出一个函数, 这个函数可以把Markdown语法解析为HTML标签。 这里我们需要做的就是解析出Markdown中的demo语法, 渲染其中的Vue组件, 并且同时能把源码也显示在组件下方, 这样就完成了扩展任务。

    Element3中对Markdown的扩展源码都可以在[GitHub]( < https: //github.com/hug-sun/element3/tree/master/packages/md-loader/src>)上看到。

      下面的代码就是全部解析的逻辑: 首先我们使用md.render把Markdown渲染成为HTML, 并且获取内部demo子组件; 在获取了demo组件内部的代码之后, 调用genInlineComponentText, 把组件通过Vue的compiler解析成待执行的代码, 这一步就是模拟了Vue组件解析的过程; 然后使用script标签包裹编译之后的Vue组件; 最后再把组件的源码放在后面, demo组件的解析就完成了。

const { stripScript, stripTemplate, genInlineComponentText } = require(’./util’) const md = require(’./config’)

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

const startTag = '' const endTagLen = endTag.length

let componenetsString = ” let id = 0 // demo 的 id const 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)

const demoComponentContent = genInlineComponentText(html, script)

const demoComponentName = element-demo${id} output.push(<template #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> import hljs from 'highlight.js' import \* as Vue from "vue" export default { name: 'component-doc', components: { ${componenetsString} } } </script> } else if (content.indexOf('') + ''.length pageScript = content.slice(0, start) }

output.push(content.slice(start)) return ` ${pageScript} ` }

  然后我们还要把渲染出来的Vue组件整体封装成为demo - block组件。 在下面的代码中, 我们使用扩展Markdown的render函数, 内部使用demo - block组件, 把Markdown渲染的结果渲染在浏览器上。

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 ?

${md.render(description)}
` : ”}

` } return '' } })

md.use(mdContainer, ‘tip’) md.use(mdContainer, ‘warning’) }

  然后我们就实现了demo - block组件。 接下来我们新建DemoBlock.vue, 在下面的代码中我们通过slot实现了组件的渲染结果和源码高亮的效果, 至此我们就成功了实现了Markdown中源码演示的效果。
  # # 总结




  我们来总结一下今天学到的内容。




  首先我们使用Vue官网文档的构建工具VuePress来搭建组件库文档, VuePress提供了很好的上手体验, Markdown中可以直接注册使用Vue组件, 我们在.vuepress中可以扩展对Element3的支持。




  如果我们定制需求更多一些, 就需要自己解析Markdown并且实现对Vue组件的支持了, 我们可以使用Markdown - it插件解析, 支持Vue组件和代码高亮, 这也是现在Element3文档的渲染方式。




  # # 思考题




  最后留给你一道思考题: 现在很多组件库开始尝试使用Storybook来搭建组件库的文档, 那么这个Storybook相比于我们实现的文档有什么特色呢?




  欢迎你在评论区分享你的看法, 也欢迎你把这节课的内容分享给你的同事和朋友们, 我们下一讲再见!