Automatic generation of component documents using react docgen

Time:2020-11-21

Automatic generation of component documents using react docgen

Author: attapulgite Zhu Feifei

background

Recently, I received a request to develop the react component library. During the development process of the component library, I just finished writing a component to be used by my colleagues. My colleague immediately asked me, “ah? How to use this component. Emmm, I thought I’d just tell it I’d forget it next time, so I’d better write a document honestly.

In the middle of the document, @ ාාාාාාාාාාාාාාා #@It’s too much trouble. With so many components, each component needs to have a corresponding document, which is too time-consuming to write, and it is more troublesome to write a handwritten document than to write a component. In order to finish the task quickly. So study thoseExcellent component libraryHow to do it? Have a lookQuarkThe document generation of quark component library is greatly inspired. The following is about how to be elegant and lazy and do a good job of component documents.

Why generate documents automatically

Before we talk about this, let’s take a look at what the document looks like

Automatic generation of component documents using react docgen

What is required for component documentation

  • Provides an introduction to the components
  • Provides a list of properties for the componentpropTypes
  • Provides a case for component invocationusage
  • Provide demonstration case / source code of component call

If we want to pass all these contentsmarkdownIt may take more time to write than to make a simple component. In order to put more energy into developing better components, we need toDocument generation automation

What can document automation bring us?

  • unifiedDocument format, to smooth the difference of document format written by different developers
  • saveTime to write documents to do more intentional things

Let’s take a small case to try

react-docgen

Start to enter the main topic, first of all, a brief introduction to the protagonist of automatic document generationreact-docgenThe official introduction to it is as follows:

React docgen is a CLI and toolkit that helps extract information from react components and generate documents from them. It uses ast type and @ Babel / parser to parse the source to ast, and provides a method to process the AST to extract the required information. The output / return value is a JSON blob / JavaScript object.

In short, it can extract information about components

install

Install the module with yarn or NPM:

yarn add react-docgen --dev

npm install --save-dev react-docgen

For its API, please refer to the official documentation https://www.npmjs.com/package/react-docgen

Share an advanced version ofreact-styleguidist https://github.com/styleguidist/react-styleguidist

example

Let’s first write a character component that containsfull namehobbyevent callbacks

// ./Persion/index.jsx

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

/**
*Character component
*@ Description This is the description of the character component
* @class Persion
* @extends {Component}
*/
class Persion extends Component {
  /**
   *Handle sleep callback
   *@ param {string} name
   */
  handleSleep = (name) => {
    console.log ('${name} starts sleeping')
    this.props.onSleep()
  }
  render() {
    const { name, hobbies } = this.props
    return (
      <div onClick={this.handleSleep.bind(this, name)}>
        <p>Name: {name}</p>
        <p>Hobbies:{ hobbies.join (',')}</p>
      </div>
    )
  }
}
Persion.propTypes = {
  /**
   *Name
   */
  name: PropTypes.string.isRequired,
  /**
   *Hobbies
   */
  hobbies: PropTypes.array,
  /**
   *Sleep event callback
   */
  onSleep: PropTypes.func
}
Persion.defaultProps = {
  Name: 'Zhang San',
  Hobbies: ['sleep ','king beater']
}
export default Persion

We define a character component in the componentClass annotationThe basic information of the component is described inpropTypesanddefaultTypesThe attribute parameters of components are also defined inProperty comments

The basic information of the component is almost written. Let’s use it firstreact-docgenTo extract the relevant information of components.

// ./docgen.js

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

//Read file contents
const content = fs.readFileSync(path.resolve('./Persion/index.jsx'), 'utf-8')
//Extract component information
const componentInfo = reactDocs.parse(content)
//Print information
console.log(componentInfo)

Here we write a simple process of reading the file and parsing, and print the extracted information. The following is the content of the extracted component informationcomponentInfo

{
    "description":"
        Character component
        @description 这是关于Character component的描述内容
        @class Persion
        @extends {Component}"
     ,
    "displayName":"Persion",
    "methods":[
        {
            "name":"handleSleep",
            "docblock":"
                Handle sleep callback
                @Param name
            ",
            "modifiers":[

            ],
            "params":[
                {
                    "name":"name",
                    "Description": "name,",
                    "type":{
                        "name":"string"
                    },
                    "optional":false
                }
            ],
            "returns":null,
            "description":"Handle sleep callback"
        }
    ],
    "props":{
        "name":{
            "type":{
                "name":"string"
            },
            "required":false,
            "Description": "name,",
            "defaultValue":{
                "Value": "'zhang San '",
                "computed":false
            }
        },
        "hobbies":{
            "type":{
                "name":"array"
            },
            "required":false,
            "Description": "description":,
            "defaultValue":{
                "Value": "['sleeping ','king beating'],
                "computed":false
            }
        },
        "onSleep":{
            "type":{
                "name":"func"
            },
            "required":false,
            "Description": "sleep event callback"
        }
    }
}

For the information extracted by react docgen, explain the following parameters

  • displayNameComponent name
  • descriptionClass annotation for component
  • methodsMethod of component definition
  • propsProperty parameters of the component

One of them is herepropsIs the core content of our component document. In the extracted content, theAttribute name, property description, type, default value, whether to pass。 These contents satisfy the attribute information we need to read the component document.

You have what you needcomponentInfoAfter information, the next step is to convert it intomarkdown(as for why markdown is used, I will not explain 8)

// ./docgen.js

//Generate markdown document
fs.writeFileSync(path.resolve('./Persion/index.md'), commentToMarkDown(componentInfo))

//The information extracted by react docgen is transformed into markdown format
function commentToMarkDown(componentInfo) {
  let { props } = componentInfo
  const markdownInfo = renderMarkDown(props)
  //Using prettier to beautify the format
  const content = prettier.format(markdownInfo, {
    parser: 'markdown'
  })
  return content
}
function renderMarkDown(props) {
  Return ` parameter props
  |Attribute | type | default value | required | description|
  | --- | --- | --- | --- | ---|
  ${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, '')
  }
}

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

In fact, the code for converting markdown does less, mainly the following steps

  1. ergodicpropsFor each attribute in the,
  2. Parsing propertiesprop, extractProperty nametypeDefault valueRequireddescribeGenerate the corresponding markdown table row.
  3. Generate markdown content byprettierBeautify markdown code.

After the transformation, the final file of our markdown is generated

##Parameter props

|Attribute | type | default value | required | description|
| ------- | ------ | ------------------ | ---- | -------------- |
|Name | string |'zhang San '| name|
|Hobbies | array | hobbies|
|Event callback of onsleep | func | - |✗| sleep|

Expansion and optimization

This case only tells us how to analyze itpropsAnd generate markdown’sParameter propsModule process, in real projects, there is a lot of space to optimize the above process, we can also use a lot of custom rules for a variety of operations.

For example, we don’t want to change theData properties(name, hobbies) andCallback properties(onsleep) are placed in the same props table. We hope to classify the attributes.

In the annotation of the attribute description, we can use @ XX (or ¥% # @ ^!] Finally, in the step of attribute resolution, the information is deeply split, analyzed and classified to generate more complex and diverse documents.

After some modifications, we have adopted theThe definition description of different rules is added in the noteTo get a more elegant and beautiful document module

Persion.propTypes = {
  /**
   *@ text name
   * @category data
   */
  name: PropTypes.string.isRequired,
  /**
   *@ text hobbies
   * @category data
   */
  hobbies: PropTypes.array,
  /**
   *@ text sleep event callback
   * @category event
   */
  onSleep: PropTypes.func
}
##Data

|Attribute | type | default value | required | description|
| ------- | ------ | ------------------ | ---- | ---- |
|Name | string |'zhang San '| name|
|Hobbies | array | hobbies|

##Event

|Attribute | type | default value | required | description|
| ------- | ---- | ------ | ---- | -------------- |
|Event callback of onsleep | func | - |✗| sleep|

A lot of them, of coursedescriptionperhapsmethodsAll of them can be parsed and generatedMarkdown moduleData information has been extracted. In fact, how to do itastResolution depends on the specific business requirements.

Summary

In the process of daily development, in addition to the code writing of components, we also have a lot of work to do in process and corner, which are often trivial and must be done. We use tools to solve the sporadic and simple tasks in our work, so as to achieve the goal of high efficiency. Developers are lazy (maybe just me?) Otherwise, how could there be so many automated products


reference material:
[1] React docgen warehouse document https://github.com/reactjs/react-docgen#readme


Welcome to the bump lab blog: aotu.io

Or focus on the official account of AOTULabs.