在编写 React UI
组件时为了方便开发者使用组件,我们经常会使用文档系统来自动生成组件文档和定义文档。开源中比较出名的是 storybook
,还有 react-styleguidist
。
动机
然而某些情况下会遇到需要将文档系统嵌入到其它系统中的情况,如组件依赖于某些环境,则需要将文档系统嵌入到该环境中。为此,需要一个能够适用于各种环境的文档系统。
为此设计师将文档系统分为了 3 块(compiler
为凑数用 🤦♂️,其实可以不用拆出来,只是 fork 后还是保持了独立):
- 文档生成器
gen
:通过读取组件的定义,生成组件的注释、props
、method
等文档,并整合组件的markdown
文档 - 组件预览编辑
live
:通过组件文档中的code
,生成组件的预览,并可实时编辑代码 - 运行时
js
转译器compiler
:为了能够将代码转换为组件,需要在运行时将代码进行编译才能执行 - 文档展示
doc
:读取组件的定义文档、markdown
文档数据,解析markdown
展示文档界面,并将code
片段按照配置生成实时编辑块
gen
gen
可通过 cli
调用:
npx recodo-gen build -p ./components/ 复制代码
gen
主要用于数据抓取,会使用 react-docgen
爬取组件定义,生成定义数据 info.json
,包装这层主要是为了控制生成的数据结构,方便其它包使用。
除此之外,gen
还会读取源码中的 markdown
文件,生成 doc.json
文件。
所有组件生成数据会汇总在 index.js
中。
比如如下的组件定义:
components └── Button ├── Button.tsx ├── README.md └── index.tsx 复制代码
Button.tsx
import React, { ReactNode, HTMLAttributes } from 'react'; const Button = ({ size, style, ...rest }: { /** content */ children: ReactNode; /** size of the button */ size: 'sm' | 'md' | 'lg'; } & HTMLAttributes<HTMLButtonElement>) => { return <button {...rest} style={{ lineHeight: { sm: 24, md: 30, lg: 36 }[size] + 'px', ...style }} />; }; export default Button; 复制代码
执行 gen build -p components
生成的数据如下:
index.js
const infoMap = { Button: require('./Button.info.json') }; const docMap = { Button: require('./Button.doc.json') }; module.exports = { infoMap, docMap }; 复制代码
Button.doc.json
{ "README": { "path": "Button/README.md", "name": "README", "info": "" } } 复制代码
Button.info.json
{ "Button": { "path": "Button/Button.tsx", "name": "Button", "info": { "description": "", "displayName": "Button", "methods": [], "props": { "children": { "required": true, "tsType": { "name": "ReactNode" }, "description": { "description": "content", "tags": [] } }, "size": { "required": true, "tsType": { "name": "union", "raw": "'sm' | 'md' | 'lg'", "elements": [ { "name": "literal", "value": "'sm'" }, { "name": "literal", "value": "'md'" }, { "name": "literal", "value": "'lg'" } ] }, "description": { "description": "size of the button", "tags": [] } } } } } } 复制代码
此外还包含以下参数:
参数 | 说明 | 格式 |
--help | Show help | [boolean] |
--version | Show version number | [boolean] |
-p, --componentPath | Path for find components | [string] [required] |
-t, --targetPath | Path for place build files | [string] [default: "recodo-gen-output"] |
-b, --babelrc | Path for custom babelrc file | [string] |
-c, --componentRegExp | RegExp for match component file | [string] [default: "^[^/\]+(/|\)[A-Z][a-za-z_-]*.(j|t)s(x)?$"] |
-d, --docRegExp | RegExp for match doc file | [string] [default: "^[^/\]+(/|\)[A-Z][a-za-z_-]*.md(x)?$"] |
-r, --resolver | Choose type of resolver | [string] [choices] "findExportedComponentDefinition", "findAllComponentDefinitions", "findAllExportedComponentDefinitions" |
除了 build
外还提供了 watch
命令,用于监听源码变化,自动生成文档数据。
此外还可使用 node
直接引入调用,方便融入其它工具中:
const path = require('path'); const recodoGen = require('@ucloud-fe/recodo-gen'); module.exports = ({ cachePath, rootPath, callback }) => { recodoGen .build({ componentPath: rootPath, targetPath: cachePath, babelrc: path.resolve(__dirname, '../config/.babelrc.js'), callback: callback }) .catch(err => { console.error(err); }); }; 复制代码
compiler
compiler
用于在运行时转移源码,主要用于实时预览 React
组件。源码从 buble fork
而来,没直接使用的原因是 buble
不支持 import
,所以 fork
后自行增加了 import
、export
的支持。
如下代码:
import React from 'react'; const Hello = () => { return <div>Hello world</div>; }; export default Hello; 复制代码
通过 compiler
的 transform
会被转译为:
var React = require('react').__esModule ? require('react').default : require('react'); var Hello = function () { return React.createElement('div', null, 'Hello world'); }; module.exports = Hello; 复制代码
从而能够直接在浏览器运行。
live
live
设计和之前说的 react-live
一致,都是为了实现实时编辑预览组件效果。没有直接使用的原因是 react-live
不支持 import
,并且支持的语法写出来和自己想象的单文件代码相差较远,所以照着 react-live
的设计重新使用 ts
实现了一遍。
live
内部使用 compiler
来做运行时转移,原理基本等同 react-live
,不了解的可以看之前写的关于 react-live
的源码解析。
doc
doc
主要作用是渲染 gen
所生成的文档数据,会将 props
定义渲染为表格、文档 markdown
解析渲染、markdown
中的代码块通过 react-live
渲染为实时预览编辑器。