【我要做开源】Vue DevUI开源指南05:给Vue3组件库添加VitePress文档系统

avatar
前端组件库 @华为

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

最近在与村长老师一起做直播,给大家分享vue devui开源组件库的建设,前面三期以 tree 组件为栗子🌰,介绍了如何设计和开发Vue组件:

  1. Vue DevUI开源指南01:提交我的第一次pr
  2. Vue DevUI开源指南02:实现一个能渲染多层节点的Tree组件
  3. Vue DevUI开源指南03:如何给 tree 组件增加展开/收起功能

从上次开始给大家分享组件库工程化相关的内容,上一次直播的内容比较简单,直播完也有小伙伴说:

好像说了,又好像没说

看来这道开胃菜大家觉得不过瘾,所以我们这一期继续深入,给大家分享组件库建设中非常重要的文档系统的搭建。

后续的直播也会分成两条线:

  1. 一条是组件的设计和实现
  2. 另一条是组件库的工程化

欢迎大家持续关注~

上一期内容回顾

上一期给大家分享了如何使用vite搭建一个支持ts/jsx的vue3组件库工程,并接上了咱们第一次直播编写的jsx风格的tree组件,内容不多,算是咱们组件库工程化的一个开场。

有了基础工程,就可以往里面不断丰富组件啦,这时我们会发现一个问题,写好的组件,在哪里看效果呢?

我们还缺少一个展示组件demo/api的文档系统,这个文档系统至少需要有以下模块/功能:

  • 需要一个左侧菜单,用来展示我们有哪些组件
  • 点击左侧菜单中的组件,可以展示这个组件的基本信息、Demo、API文档

技术选型

由于我们使用的是Vite构建工具,因此需要选择一款与其配套的文档系统。

当从vue的生态上来说,需要决策的是选择VuePress还是VitePress,最终我们选择VitePress,主要原因是:

  • VitePress是基于Vite的,而VuePress是基于Webpack的(也可以通过配置bundler: '@vuepress/bundler-vite'改成Vite构建)
  • VitePress更快更轻量,极易上手

参考:

v2.vuepress.vuejs.org/zh/guide/#%…

添加VitePress文档

安装vitepress:

yarn add -D vitepress

创建第一个文档:

mkdir docs && echo '# Hello VitePress' > docs/index.md

增加脚本命令:

{
  "scripts": {
    "docs:dev": "vitepress dev docs",
    "docs:build": "vitepress build docs",
    "docs:serve": "vitepress serve docs"
  }
}

本地启动:

yarn docs:dev

浏览器访问看效果:

http://localhost:3000/

这是VitePress默认的效果。

参考:

vitepress.vuejs.org/guide/getti…

配置VitePress

我们需要实现以下功能:

  • 需要一个左侧菜单,用来展示我们有哪些组件
  • 点击左侧菜单中的组件,可以展示这个组件的基本信息、Demo、API文档

配置sidebar

VitePress有一个配置文件,里面有一个themeConfig/sidebar配置,可以配置左侧菜单。

docs/.vitepress/config.ts

const sidebar = {
  '/': [
    { text: '快速开始', link: '/' },
    {
      text: '通用',
      children: [
        { text: 'Button 按钮', link: '/components/button/' },
      ]
    },
    {
      text: '导航',
    },
    {
      text: '反馈',
    },
    {
      text: '数据录入',
    },
    {
      text: '数据展示',
    },
    {
      text: '布局',
    },
  ]
}

const config = {
  themeConfig: {
    sidebar,
  }
}

export default config

点击左侧菜单的Button,看下效果:

http://localhost:3000/components/button/

页面404了

创建Button组件的文档

404的原因是我们没有创建相应的文档

docs/component/button/index.md

先随便写一些内容

# Button 按钮

在看下效果:

在md中增加vue组件

VitePress的一大好处是可以直接在md中写vue组件,VitePress会将其渲染出来。

docs/components/button/index.md

# Button 按钮

<d-button></d-button>

编写一个Button组件

devui/button/src/button.tsx

const Button = () => <div>Button 按钮</div>

export default Button

引入Button组件

docs/.vitepress/theme/index.ts

import Theme from 'vitepress/dist/client/theme-default'
import Button from '../../../devui/button/src/button'

export default {
  ...Theme,
  enhanceApp({ app }) {
    app.component('d-button', Button)
  }
}

报错:React is not defined

这个报错是不是非常熟悉?没错!上一次直播我们也遇到过,相信聪明的你一定知道是怎么回事,也知道怎么解决啦~

Uncaught (in promise) ReferenceError: React is not defined
    at Button (button.tsx:1)

引入jsx插件

安装jsx插件:

yarn add -D @vitejs/plugin-vue-jsx

docs/vite.config.ts中引入jsx插件:

import { defineConfig } from 'vite'
import vueJsx from '@vitejs/plugin-vue-jsx'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vueJsx()]
})

组件显示出来了!

demo代码展开/收起

这部分内容由wailen同学分享。

引入背景

早期进行组件开发的时候,demo展示部分需要用```包裹展示的代码块,另外组件的显示也需要同样的代码,所以同样的代码写了两遍,非常的不“银杏”。比如下面这样

<!-- index.md -->

  <!-- 以下是显示组件部分 -->
  <d-icon name="emoji"><d-icon>
  <d-icon name="emoji" color="#3dcca6"><d-icon>

  <!-- 以下是暴露到文档的代码块 -->
  <!-- 需要再次重复写同样的代码 -->
  ```html
  <d-icon name="emoji"><d-icon>
  <d-icon name="emoji" color="#3dcca6"><d-icon>
 ```

所以在网上看看有没有现成的方案了, 毕竟

 

但是vitepressvue3当时解决方案还不太多,所以也不抱太多希望。

不过最终很幸运的发现了两款支持vitepress的demo展示插件vitepress-theme-demoblockvitepress-for-component。但最后调研发现vitepress-for-component是 fork 自 vitepress的脚手架,并不单单是一个插件了,不好集成到devui中,最后就确定了vitepress-theme-demoblock

插件的使用

安装

npm install -D vitepress-theme-demoblock
yarn add -D vitepress-theme-demoblock

注入插件

首先我们得知道vitepress关于markdown的拓展规则,vitepress 使用markdown-it作为 markdown 渲染器,具体可以查看

我们得将插件在vitepressconfig.js中注册,如下面这样:

module.exports = {
  markdown: {
    config: (md) => {
      // 这里可以使用 markdown-it 插件,vitepress-theme-demoblock就是基于此开发的
      const { demoBlockPlugin } = require('vitepress-theme-demoblock')
      md.use(demoBlockPlugin)
    }
  }
}

demoBlockPlugin插件的核心

demoBlockPlugin是插件的核心方法,函数内部注册了三个方法,如下:

//  vitepress-theme-demoblock/demoblock/index.js
const demoBlockPlugin = (md, options = {}) => {
  md.use(blockPlugin, options)
  md.use(codePlugin, options)
  md.use(renderPlugin, options)
}
  • blockPlugin: blockPlugin的作用是根据markdown-it-container插件获取到的md文档所有内容转换成的AST树,从树中获取到被语法:::demo 包裹的部分,输出成大概如下的字符串格式。
// content就是需要渲染组件的代码内容
`<demo sourceCode="${content}">
  <!--vue-demo:${content}:vue-demo-->
</demo>`
  • codePlugin: codePlugin作用是生成描述部分description和代码展示部分highlight,使用了vue的具名插槽渲染。其中description可以满足md语法。比如下面这样使用。
:::demo 使用`size`、`style`属性来定义 Card 的样式。
    ```vue
    <template>
      <div class="card-wrap">
        <div class="card">{{ title }}</div>
      </div>
    </template>
    ```
:::

最终生成的效果如图所示

红色部分就是description,黑色部分对应highlight

  • renderPlugin:顾名思义渲染函数,将上面blockPlugin函数返回的<!--vue-demo:${content}:vue-demo-->部分,通过正则表达式的形式获取到需要输出的template, script, style内容,最终渲染出来。

devui将所有docs/.vitepress/config.js中的配置项都单独抽离了逻辑,在阅读项目源码的时候需要注意下。

注入主题与插件的组件

vitepress的主题拓展可以查看

我们得在docs/.vitepress/theme/index.ts中注册vitepress-theme-demoblock插件的demo组件,如下面这样

// 主题样式
import 'vitepress-theme-demoblock/theme/styles/index.css'
// 插件的组件,主要是demo组件
import { registerComponents } from './register-components.js'

export default {
  enhanceApp({ app }) {
    registerComponents(app)
  }
}

那么上面引入的register-components.js怎么来的呢?这个文件其实不需要我们创建,可以使用脚本自动创建该文件,这样的好处是所有需要的插件组件都不需要我们手动进行注册,全部在插件内部就能确定,未来插件有任何改动都不需要修改这里。该文件长这样

// register-components.js
import Demo from 'vitepress-theme-demoblock/components/Demo.vue'
import DemoBlock from 'vitepress-theme-demoblock/components/DemoBlock.vue'
export function registerComponents(app) {
  app.component('Demo', Demo)
  app.component('DemoBlock', DemoBlock)
}

自动生成该文件可以使用如下命令

// package.json
"scripts": {
  "register:components": "vitepress-rc"
}
yarn register:components

至此,vitepress-theme-demoblock的就在vitepress中注册成功了

使用与效果

注册好插件过后,我们只需要在demo展示文件index.md中使用固定语法包裹代码就能自动生成组件demo以及代码块

    :::demo 使用`sm`,`''`,`lg`来定义`Search`基本类型

    ```vue
    <template>
      <div>
        Small
        <d-search size="sm" autoFocus style="width: 200px" :delay="1000"></d-search>
        Middle
        <d-search style="width: 200px" isKeyupSearch></d-search>
        Large
        <d-search iconPosition="left" size="lg" style="width: 200px"></d-search>
        Disabled
        <d-search disabled style="width: 200px"></d-search>
      </div>
    </template>
    ```
    :::

生成的效果如下

效果非常棒~

遇到的问题

开源库的协议

在提议使用了vitepress-theme-demoblock后,有一天Kagol找到了我

李姐李姐,毕竟得保护知识产权嘛。于是乎,我就去插件库看了一下,在此之前,我一直认为的开源协议是这样的:

// package.json
{
  "name": "vue-devui",
  "version": "0.0.1",
  "license": "MIT",
  "dependencies": {},
}

嗯,源码中的package.json有个licenseMIT,ok,fine。于是乎我立马回复大佬

于是便安安心心的学习去了。

结果突然有一天,Kagol又找到了我,告诉我插件没有开源协议,并告诉我开源协议要是这样的

定了定神,于是乎,又去了解了开源协议,原来开源库必须得定义好LICENSE文件,原来协议分好几种:

  • MIT: 最为宽松的协议,意味着对所有人免费,并且可以任意处置,包括使用,复制,修改,合并,发表,分发,再授权,或者销售。唯一的限制是,软件中必须包含上述版权和许可提示
  • BSD: 需要包含一份版权提示和免责声明之外,没有任何限制
  • GNU GPL: 保证了所有开发者的权利,同时为使用者提供了足够的复制,分发,修改的权利,可自由复制,可自由分发,可用来盈利,可自由修改
  • GNU LGPL: 对产品所保留的权利比 GPL 少,因为 GPL 要求,使用了 GPL 代码的产品必须也使用 GPL 协议,开发者不允许将 GPL 代码用于商业产品。LGPL 绕过了这一限制
  • Apache: 除了为用户提供版权许可之外,还有专利许可,对于那些涉及专利内容的开发者而言,该协议最适合

devui就是使用了MIT协议,所以我们可以放心大胆的使用,当然,vitepress-theme-demoblock也在跟开发作者邮件沟通过后,补加上了MIT协议。小伙伴们平时在使用第三方库的时候也需要注意下是否有开源协议哦。

插件的bug

最开始使用vitepress-theme-demoblock时,还是会碰到一些坑,比如下面这样

会发现组件部分已经超出了容器。本身是一个小问题,所以在向插件提了pr过后就修改好了(跟devui提pr一样的流程)。

我们在使用插件时,碰到了插件本身的问题,不妨研究一下插件,自己修改过后提pr也是一种不错的体验。后面小伙伴们也陆陆续续提了一些bug,该插件的作者也非常积极的帮忙解决了,非常棒。

直播

组件库建设的系列文章也有相应的直播,欢迎大家关注村长的直播间: live.bilibili.com/22531545

也欢迎看往期的录播视频:

  1. 【我要做开源】华为大佬亲授,Vue DevUI开源指南01:提交我的第一次pr
  2. 【我要做开源】华为大佬亲授,Vue DevUI开源指南02:做一个有模有样的Tree组件
  3. 【我要做开源】华为大佬亲授,Vue DevUI开源指南03:学会“单测”才会有安全感!完成Tree组件!
  4. 【我要做开源】华为大佬亲授,Vue DevUI开源指南04:组件库工程化建设之项目初始化、jsx支持

欢迎参与devui开源项目

我们 DevUI 团队有多个开源项目,现在都在招募contributor,欢迎大家一起参与开源中来!(感兴趣的小伙伴可以添加DevUI小助手的微信:devui-official,将你拉到我们的核心开发群)

DevUI官网:devui.design/