# 26|文档:如何给你的组件库设计一个可交互式文档? 你好,我是大圣。 在我们实现了组件库核心的组件内容之后,我们就需要提供一个可交互的组件文档给用户使用和学习了。这个文档页面主要包含组件的描述,组件Demo示例的展示、描述和代码,并且每个组件都应该有详细的参数文档。 现在,我们从上述文档页包含的信息来梳理一下我们的需求。我们需要用最简洁的语法来写页面,还需要用最简洁的语法来展示 Demo + 源代码 + 示例描述。那么从语法上来说,首选就是 Markdown 无疑了,因为它既简洁又强大。 那在我们正式开始设计文档之前,我们还需要对齐一下。如果要展示 Demo 和源码的话,为了能更高效且低成本的维护,我们会把一个示例的 Demo + 源码 + 示例描述放到一个文件里,尽量多的去复用,这样可以减少需要维护的代码。而做示例展示的话,本质上可以说是跟 Markdown 的转译一致,都是 Markdown -> HTML,只是转译的规则我们需要拓展一下。接下来我们就正式开始了。 ## VuePress 首先我们需要一个能基于Markdown构建文档的工具,我推荐VuePress。它是Vue官网团队维护的在线技术文档工具,样式和Vue的官方文档保持一致。 VuePress内置了Markdown的扩展,写文档的时候就是用Markdown语法进行渲染的。最让人省心的是,它可以直接在Markdown里面使用Vue组件,这就意味着我们可以直接在Markdown中写上一个个的组件库的使用代码,就可以直接展示运行效果了。 我们可以在项目中执行下面的代码安装VuePress的最新版本: ```bash yarn add -D vuepress@next ``` 然后我们新建docs目录作为文档目录,新建docs/README.md文件作为文档的首页。除了Markdown之外,我们可以直接使用VuePress的语法扩展对组件进行渲染。 ```yaml --- home: true heroImage: /theme.png title: 网站快速成型工具 tagline: 一套为开发者、设计师和产品经理准备的基于 Vue 3 的桌面端组件库 heroText: 网站快速成型工具 actions: - text: 快速上手 link: /install type: primary - text: 项目简介 link: /button type: secondary features: - title: 简洁至上 details: 以 Markdown 为中心的项目结构,以最少的配置帮助你专注于写作。 - title: Vue 驱动 details: 享受 Vue 的开发体验,可以在 Markdown 中使用 Vue 组件,又可以使用 Vue 来开发自定义主题。 - title: 高性能 details: VuePress 会为每个页面预渲染生成静态的 HTML,同时,每个页面被加载的时候,将作为 SPA 运行。 footer: powdered by vuepress and me --- # 额外的信息 ``` 我们在README.md中输入上面的内容,通过title配置网站的标题、actions配置快捷链接、features配置详情介绍,这样我们就拥有了下面的首页样式: ![图片](https://static001.geekbang.org/resource/image/d8/ba/d8cd69f08b0932a670c74c80ef6699ba.png?wh=1920x1785) 然后我们进入docs/.vuepress/目录下,新建文件config.js,这是这个网站的配置页面。下面的代码我们配置了logo和导航navbar,页面顶部右侧就会有首页和安装两个导航。 ```javascript module.exports = { themeConfig:{ title:"Element3", description:"vuepress搭建的Element3文档", logo:"/element3.svg", navbar:[ { link:"/", text:"首页" },{ link:"/install", text:"安装" }, ] } } ``` 然后我们创建docs/install.md文件,点击顶部导航之后,就会显示install.md的信息。我们在文稿中就可以直接写上介绍Element3如何安装的文档了,下面的文稿就是Element3的安装使用说明。 ```markdown ## 安装 ### npm 安装 推荐使用 npm 的方式安装,它能更好地和 [webpack](https://webpack.js.org/) 打包工具配合使用。 ```shell npm i element3 -S ``` ### CDN 目前可以通过 [unpkg.com/element3](https://unpkg.com/element3) 获取到最新版本的资源,在页面上引入 js 和 css 文件即可开始使用。 ```html ``` :::tip 我们建议使用 CDN 引入 Element3 的用户在链接地址上锁定版本,以免将来 Element3 升级时受到非兼容性更新的影响。锁定版本的方法请查看 [unpkg.com](https://unpkg.com)。 ::: ### Hello world 通过 CDN 的方式我们可以很容易地使用 Element3 写出一个 Hello world 页面。[在线演示](https://codepen.io/imjustaman/pen/abZajYg) 如果是通过 npm 安装,并希望配合 webpack 使用,请阅读下一节:[快速上手](/#/zh-CN/component/quickstart)。 ``` 然后我们在浏览器里点击安装后,就会看到下图的页面显示。Markdown已经成功渲染为在线文档了,并且代码也自带了高亮显示。 ![图片](https://static001.geekbang.org/resource/image/09/c3/09d16ddc1080b4d8d4b04b934bdyy3c3.png?wh=1920x1655) 然后我们需要在这个文档系统中支持Element3,首先执行下面的代码安装Element3: ```bash npm i element3 -D ``` 然后在项目根目录下的docs/.vuepress文件夹中新建文件clientAppEnhance.js,这是VuerPress的客户端扩展文件。我们导入了defineClientAppEnhance来返回客户端的扩展配置。这个函数中会传递Vue的实例App以及路由配置Router,我们使用app.use来全局注册Element3组件,就可以直接在Markdown中使用Element3的组件了。 ```javascript 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组件演示效果。 ```markdown ## Button 按钮 常用的操作按钮。 ### 基础用法 基础的按钮用法。 按钮 ```html 按钮 ``` ``` 然后进入docs/.vuepress/config.js中,新增侧边栏sidebar的配置之后,就可以看到下图的效果了。 ```javascript 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。 ```markdown :::demo 使用`type`、`plain`、`round`和`circle`属性来定义 Button 的样式。 ```html ``` ::: ``` 它能直接使用下面的:::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组件的解析就完成了。 ```javascript 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(``) 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 = `` } 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渲染的结果渲染在浏览器上。 ```javascript 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 ` ${description ? `
${md.render(description)}
` : ''} ` } return '
' } }) md.use(mdContainer, 'tip') md.use(mdContainer, 'warning') } ``` 然后我们就实现了demo-block组件。接下来我们新建DemoBlock.vue,在下面的代码中我们通过slot实现了组件的渲染结果和源码高亮的效果,至此我们就成功了实现了Markdown中源码演示的效果。 ```javascript ``` ## 总结 我们来总结一下今天学到的内容。 首先我们使用Vue官网文档的构建工具VuePress来搭建组件库文档,VuePress提供了很好的上手体验,Markdown中可以直接注册使用Vue组件,我们在.vuepress中可以扩展对Element3的支持。 如果我们定制需求更多一些,就需要自己解析Markdown并且实现对Vue组件的支持了,我们可以使用Markdown-it插件解析,支持Vue组件和代码高亮,这也是现在Element3文档的渲染方式。 ## 思考题 最后留给你一道思考题:现在很多组件库开始尝试使用Storybook来搭建组件库的文档,那么这个Storybook相比于我们实现的文档有什么特色呢? 欢迎你在评论区分享你的看法,也欢迎你把这节课的内容分享给你的同事和朋友们,我们下一讲再见!