使用react-docgen自動生成組件文檔

做者: 凹凸曼 - 朱飛飛react

背景

最近在接到一個開發 React 組件庫的需求,組件庫在開發過程當中,剛寫完一個組件打算給同事用,同事立馬來了個靈魂拷問「啊?這個組件怎麼用」。emmm,我尋思直接告訴它下一次又忘了,仍是老老實實寫個文檔吧。git

文檔寫到一半,@#%#¥……#@麻煩死了。這麼多組件,每一個組件都須要有對應的文檔,寫起來太耗時了,手寫文檔比寫個組件還麻煩。爲了能快點完(xia)成(ban)任(hui)務(jia)。因而研究下那些優秀的組件庫究竟是怎麼作的,看了下Quark夸克組件庫的文檔生成,大受啓發,如下內容是講講關於如何優雅地偷懶並把組件文檔都作好的。github

爲何要自動生成文檔

聊這個事情以前,咱們先看看文檔但願長什麼樣子npm

組件文檔

組件文檔須要什麼內容babel

  • 提供組件的介紹說明
  • 提供組件的屬性列表 propTypes
  • 提供組件調用的案例 usage
  • 提供組件調用的演示案例/源碼

若是要把這些內容都經過 markdown 去寫,寫完耗費的時間可能比作一個簡單的組件還多,爲了把更多的精力投入到開發更優質的組件當中,咱們須要文檔生成自動化markdown

文檔自動化後能爲咱們帶來什麼?工具

  • 統一文檔格式,抹平不一樣開發者寫文檔的格式差別
  • 節省寫文檔的時間來作更多有意(tou)義(lan)的事情

咱們拿一個小案例來嘗試一下優化

react-docgen

開始進入正題,先簡單介紹下文檔自動生成的主角 react-docgen ,官方對於它的介紹是這樣的:ui

react-docgen 是一個 CLI 和工具箱,可幫助從 React 組件中提取信息並從中生成文檔。它使用 ast 類型和@ babel / parser 將源解析爲 AST,並提供處理此 AST 的方法以提取所需的信息。輸出/返回值是一個 JSON blob / JavaScript 對象。

簡單來講就是:它能提取組件的相關信息this

安裝

用 yarn 或 npm 安裝模塊:

yarn add react-docgen --dev

npm install --save-dev react-docgen
關於它的 API 能夠參考官方文檔 https://www.npmjs.com/package/react-docgen

偷偷再分享一個高級版的 react-styleguidist https://github.com/styleguidist/react-styleguidist

例子

咱們先寫一我的物的組件,裏面包含 姓名愛好事件回調

// ./Persion/index.jsx

import React, { Component } from 'react'
import PropTypes from 'prop-types'

/**
* 人物組件
* @description 這是關於人物組件的描述內容
* @class Persion
* @extends {Component}
*/
class Persion extends Component {
  /**
   * 處理睡覺的回調
   * @param {string} name 姓名
   */
  handleSleep = (name) => {
    console.log(`${name} 開始睡覺`)
    this.props.onSleep()
  }
  render() {
    const { name, hobbies } = this.props
    return (
      <div onClick={this.handleSleep.bind(this, name)}>
        <p>姓名:{name}</p>
        <p>愛好:{hobbies.join(',')}</p>
      </div>
    )
  }
}
Persion.propTypes = {
  /**
   * 姓名
   */
  name: PropTypes.string.isRequired,
  /**
   * 愛好
   */
  hobbies: PropTypes.array,
  /**
   * 睡覺的事件回調
   */
  onSleep: PropTypes.func
}
Persion.defaultProps = {
  name: '張三',
  hobbies: ['睡覺', '打王者']
}
export default Persion

咱們定義了一我的物的組件,在組件類註釋中描述了組件的基本信息, 同時在propTypesdefaultTypes中也對組件的屬性參數進行了定義和屬性註釋

組件的基本信息都寫的差很少了,那麼咱們先開始使用react-docgen去提取組件的相關信息。

// ./docgen.js

const path = require('path')
const fs = require('fs-extra')
const reactDocs = require('react-docgen')
const prettier = require('prettier')

// 讀取文件內容
const content = fs.readFileSync(path.resolve('./Persion/index.jsx'), 'utf-8')
// 提取組件信息
const componentInfo = reactDocs.parse(content)
// 打印信息
console.log(componentInfo)

這裏寫了一個簡單的讀取文件和解析的過程,並把提取到的信息打印出來,如下是組件信息提取後的內容 componentInfo

{
    "description":"
        人物組件
        @description 這是關於人物組件的描述內容
        @class Persion
        @extends {Component}"
     ,
    "displayName":"Persion",
    "methods":[
        {
            "name":"handleSleep",
            "docblock":"
                處理睡覺的回調
                @param name 姓名
            ",
            "modifiers":[

            ],
            "params":[
                {
                    "name":"name",
                    "description":"姓名",
                    "type":{
                        "name":"string"
                    },
                    "optional":false
                }
            ],
            "returns":null,
            "description":"處理睡覺的回調"
        }
    ],
    "props":{
        "name":{
            "type":{
                "name":"string"
            },
            "required":false,
            "description":"姓名",
            "defaultValue":{
                "value":"'張三'",
                "computed":false
            }
        },
        "hobbies":{
            "type":{
                "name":"array"
            },
            "required":false,
            "description":"愛好",
            "defaultValue":{
                "value":"['睡覺', '打王者']",
                "computed":false
            }
        },
        "onSleep":{
            "type":{
                "name":"func"
            },
            "required":false,
            "description":"睡覺的事件回調"
        }
    }
}

關於 react-docgen 提取的信息中,解釋下下面幾個參數

  • displayName 組件名稱
  • description 組件的類註釋
  • methods 組件定義的方法
  • props 組件的屬性參數

其中這裏的props是咱們組件文檔的核心內容,在提取的內容中,已經涵蓋了屬性的 屬性名、屬性描述、類型、默認值、是否必傳。這些內容知足咱們閱讀組件文檔所須要的屬性信息。

有了所需的componentInfo信息以後,下一步咱們須要把它轉換成 markdown (至於爲何要用 markdown 我就不解釋了 8)

// ./docgen.js

// 生成markdown文檔
fs.writeFileSync(path.resolve('./Persion/index.md'), commentToMarkDown(componentInfo))

// 把react-docgen提取的信息轉換成markdown格式
function commentToMarkDown(componentInfo) {
  let { props } = componentInfo
  const markdownInfo = renderMarkDown(props)
  // 使用prettier美化格式
  const content = prettier.format(markdownInfo, {
    parser: 'markdown'
  })
  return content
}
function renderMarkDown(props) {
  return `## 參數 Props
  | 屬性 |  類型 | 默認值 | 必填 | 描述 |
  | --- | --- | --- | --- | ---|
  ${Object.keys(props)
    .map((key) => renderProp(key, props[key]))
    .join('')}
  `
}

function getType(type) {
  const handler = {
    enum: (type) =>
      type.value.map((item) => item.value.replace(/'/g, '')).join(' \\| '),
    union: (type) => type.value.map((item) => item.name).join(' \\| ')
  }
  if (typeof handler[type.name] === 'function') {
    return handler[type.name](type).replace(/\|/g, '')
  } else {
    return type.name.replace(/\|/g, '')
  }
}

// 渲染1行屬性
function renderProp(
  name,
  { type = { name: '-' }, defaultValue = { value: '-' }, required, description }
) {
  return `| ${name} | ${getType(type)} | ${defaultValue.value.replace(
    /\|/g,
    '<span>|</span>'
  )} | ${required ? '✓' : '✗'} |  ${description || '-'} |
  `
}

上面的轉換 markdown 的代碼其實作的事情比較少,主要是如下幾個步驟

  1. 遍歷props對象中的每一個屬性,
  2. 解析屬性prop,提取屬性名類型默認值必填描述、生成對應的 markdown 表格行。
  3. 生成 markdown 內容,經過prettier美化 markdown 代碼。

通過轉換後最終生成咱們這個 markdown 的文件

## 參數 Props

| 屬性    | 類型   | 默認值             | 必填 | 描述           |
| ------- | ------ | ------------------ | ---- | -------------- |
| name    | string | '張三'             | ✗    | 姓名           |
| hobbies | array  | ['睡覺', '打王者'] | ✗    | 愛好           |
| onSleep | func   | -                  | ✗    | 睡覺的事件回調 |

拓展優化

這個案例只簡單講述瞭如何解析props並生成 markdown 的參數 Props模塊的流程,在現實項目中,以上流程還有不少能夠優化的空間,咱們還能夠經過不少自定義規則進行各類騷操做。

好比咱們不但願把參數的數據屬性(name、hobbies)和回調屬性(onSleep)都放到同一個 Props 表格中,咱們但願能夠進行屬性上的分類。

在屬性描述的註釋中,咱們能夠經過 @xx (或者 ¥%#@^!【】……你喜歡就好)進行不一樣的描述定義和分類,最終在屬性解析的步驟中進行信息的深度的拆分解析分類,生成更加複雜多元的文檔。

通過一些改造後,咱們經過在註釋中添加不一樣規則的定義描述,獲得更優雅美觀的文檔模塊

Persion.propTypes = {
  /**
   * @text 姓名
   * @category data
   */
  name: PropTypes.string.isRequired,
  /**
   * @text 愛好
   * @category data
   */
  hobbies: PropTypes.array,
  /**
   * @text 睡覺的事件回調
   * @category event
   */
  onSleep: PropTypes.func
}
## 數據 Data

| 屬性    | 類型   | 默認值             | 必填 | 描述 |
| ------- | ------ | ------------------ | ---- | ---- |
| name    | string | '張三'             | ✗    | 姓名 |
| hobbies | array  | ['睡覺', '打王者'] | ✗    | 愛好 |

## 事件 Event

| 屬性    | 類型 | 默認值 | 必填 | 描述           |
| ------- | ---- | ------ | ---- | -------------- |
| onSleep | func | -      | ✗    | 睡覺的事件回調 |

固然還有不少好比description或者methods等均可以進行不一樣的解析並生成對應的markdown模塊,數據信息提取出來了,其實最終怎麼進行ast解析取決自身的具體業務要求。

小結

在平常開發的過程當中,咱們除了組件的代碼編寫外,還有不少流程上、邊角上的工做須要作,這些事情每每都比較瑣碎又必需要作。咱們多借助工具去解決咱們的工做中那些零星簡單的任務,從而達到高(jiu)效(xiang)完(kuai)成(dian)工(xia)做(ban)的目標。開發者都是懶惰的(可能只有我??),否則怎麼會有這麼多自動化的產物呢~


參考資料:
[1] react-docgen 倉庫文檔 https://github.com/reactjs/react-docgen#readme

歡迎關注凹凸實驗室博客:aotu.io

或者關注凹凸實驗室公衆號(AOTULabs),不定時推送文章。

相關文章
相關標籤/搜索