Автоматизация документации React с помощью React-Docgen ⚛⚙️📚
В моем непрекращающемся стремлении сделать документацию максимально простой и безболезненной, я начал экспериментировать с библиотекой команды Facebook под названием react-docgen.
Это интерфейс командной строки и API, которые считывают файлы компонентов React, собирают всю документацию, оставленную в комментариях, и выкачивают объект со всеми документами и реквизитами:
{
"props": {
"foo": {
"type": {
"name": "number"
},
"required": false,
"description": "Description of prop \"foo\".",
"defaultValue": {
"value": "42",
"computed": false
}
},
"bar": {
"type": {
"name": "custom"
},
"required": false,
"description": "Description of prop \"bar\" (a custom validation function).",
"defaultValue": {
"value": "21",
"computed": false
}
}
},
"description": "General component description."
}
Некоторые системы проектирования и фреймворки используют react-docgen как часть процесса создания документации. Обычно вы берете объект, выводимый CLI, и сохраняете его в файл JSON для использования в других скриптах / инструментах. Отлично подходит для создания файлов содержимого / Markdown с использованием SSG (генераторов статических сайтов), таких как GatsbyJS, Jekyll или Slate.
Я протестировал два способа использования react-docgen для документирования кода React. Моей целью было использовать GatsbyJS в качестве генератора статических сайтов и создать сайт документации на основе данных интерфейса командной строки.
Если вас интересует более нестандартное решение, попробуйте react-styleguidist. Он использует response-docgen для создания одностраничной документации по всем компонентам.
Приступим ⚡️
Процесс
Я описал базовый процесс выше: мы хотим запустить скрипт, который использует response-docgen API, и сгенерировать файл JSON с документами React, который мы можем использовать в процессе сборки сайта.
Давайте сначала попробуем это 👇
Инструментальный скрипт + вывод Gatsby
Процесс довольно прост:
- Создайте скрипт, который захватывает все компоненты и использует API-интерфейс response-docgen для синтаксического анализа файлов в JSON (сохраняется как выходной файл -
components.json
) - GatsbyJS
gatsby-node.js
скрипт для анализа JSON и создания страниц.
Сначала делаем скрипт сборки:
const fs = require("fs"); const path = require("path"); const reactDocs = require("react-docgen");
// The React components to load const componentFolder = "./src/components/";
// Where the JSON file ends up const componentJsonPath = "./docs/components.json";
const componentDataArray = [];
function pushComponent(component) { componentDataArray.push(component); }
function createComponentFile() { const componentJsonArray = JSON.stringify(componentDataArray, null, 2); fs.writeFile(componentJsonPath, componentJsonArray, "utf8", (err, data) => { if (err) { throw err; } console.log("Created component file"); }); }
/** * Use React-Docgen to parse the loaded component * into JS object of props, comments * * @param {File} component * @param {String} filename */ function parseComponent(component, filename) { const componentInfo = reactDocs.parse(component); const splitIndex = filename.indexOf("/src/"); const shortname = filename.substring(splitIndex + 4);
componentInfo.filename = shortname;
pushComponent(componentInfo); }
/** * Loads a component file, then runs parsing callback * @param {String} file * @param {Promise} resolve */ function loadComponent(file, resolve) { fs.readFile(file, (err, data) => { if (err) { throw err; }
// Parse the component into JS object resolve(parseComponent(data, file)); }); }
/** * Explores recursively a directory and returns all the filepaths and folderpaths in the callback. * * @see http://stackoverflow.com/a/5827895/4241030 * @param {String} dir * @param {Function} done */ function filewalker(dir, done) { let results = [];
fs.readdir(dir, async (err, list) => { if (err) return done(err);
let pending = list.length;
if (!pending) return done(null, results);
list.forEach(file => { file = path.resolve(dir, file);
fs.stat(file, async (err, stat) => { // If directory, execute a recursive call if (stat && stat.isDirectory()) { filewalker(file, (err, res) => { results = results.concat(res); if (!--pending) done(null, results); }); } else { // Check if is a Javascript file // And not a story or test if ( file.endsWith(".js") && !file.endsWith(".story.js") && !file.endsWith(".test.js") ) { await new Promise(resolve => { loadComponent(file, resolve); }); await results.push(file); } if (!--pending) done(null, results); } }); }); }); }
filewalker(componentFolder, (err, data) => { if (err) { throw err; }
createComponentFile(); });
Мы используем функцию firewalker()
, которую я нашел на Github, которая загружает папку и «просматривает» каждый файл. Когда мы просматриваем каждый файл, мы проверяем, является ли он JS-файлом (а не тестовым или JS-файлом Storybook), а затем запускаем функцию loadComponent()
, которая является оболочкой для Node API для загрузки файлов.
Как только файл компонента действительно загружен, мы запускаем parseComponent()
функцию, которая фактически запускает response-docgen для нашего файла. Наконец, мы «помещаем» сгенерированные данные документов в массив. После загрузки всех файлов наша firewalker()
функция имеет обратный вызов, который запускает функцию createComponentFile()
, которая выводит фактический файл JSON.
Используя скрипт
Теперь мы можем сгенерировать JSON-файл всех наших компонентов внутри /src/components/
, запустив скрипт в интерфейсе командной строки Node:
node generate-documentation.js
GatsbyJS поддерживает использование файлов JSON в качестве источника для создания сайтов с использованием gatsby-transformer-json. Когда мы загружаем сгенерированный файл, он преобразует JSON в узел GraphQL, который мы можем запросить.
Чистый Гэтсби
Последний метод работает, но он казался громоздким и подверженным ошибкам из-за необходимости полагаться на отдельный скрипт инструментов. После небольшого исследования я обнаружил второй, более интегрированный способ справиться с этим.
- Используйте плагин response-docgen для Gatsby, который захватывает все компоненты из определенной папки, генерирует JSON и запускает для него конечную точку GraphQL.
Мы устанавливаем плагин и добавляем его в нашу конфигурацию Gatsby (вместе с источником файловой системы, который импортирует наши компоненты):
gatsby-config.js:
module.exports = {
plugins: [
{
resolve: `gatsby-source-filesystem`,
options: {
name: `components`,
// Location of your React components
path: `../src/components/`,
},
},
// "Transforms" our "source" of React/JS files with the react-docgen CLI
// and creates a GraphQL node with the output
`gatsby-transformer-react-docgen`,
],
}
А затем отобразить наши данные так же просто, как запрос GraphQL:
import React, { Component } from 'react' import { graphql } from 'gatsby' import Layout from '../components/layout'
import PropsTable from '../components/propstable'
export default class ComponentPage extends Component { render() { const { children, data, tableOfContents } = this.props console.log('mdx', data.mdx) console.log('component metadata', data.componentMetadata) return ( <Layout> <div className="content"> {children} <h1>{data.componentMetadata.displayName}</h1> <p>{data.componentMetadata.docblock}</p> <h2 style={{ marginTop: '2rem' }}>Props:</h2> <PropsTable propMetaData={data.componentMetadata.childrenComponentProp} /> </div> </Layout> ) } }
export const pageQuery = graphql` query($name: String!) { componentMetadata(displayName: { eq: $name }) { id displayName docblock doclets childrenComponentProp { name docblock required parentType { name } type { value } defaultValue { value computed } } composes } } `
Бонус: стол реквизита
Чтобы отобразить все данные из наших реквизитов (описание, значение по умолчанию, требуется ли это? И т. Д.), Мы создаем компонент, который принимает наши реквизиты из интерфейса командной строки react-docgen и выводит таблицу. Я нашел это на Github и изменил его для работы с Gatsby-версией response-docgen:
import React, { Component } from 'react' import PropTypes from 'prop-types'
/** * Table for React props generated by react-docgen * * @see https://github.com/episodeyang/react-component-props-table/ */ const PropsTable = props => { let { className = '', propMetaData = [], ..._props } = props if (className) className += ' component-props-table' return ( <table className={className} {..._props}> <thead> <tr> <th>Prop Name</th> <th>Type</th> <th>Is Required</th> <th>Default Value</th> <th>Description</th> </tr> </thead> <tbody> {Object.keys(propMetaData).map(key => { const prop = propMetaData[key] return ( <tr key={key}> <td style={{ color: 'rgb(17, 147, 154)' }}>{prop.name}</td> <td>{prop.parentType ? prop.parentType.name : ''}</td> {prop.required ? ( <td style={{ color: 'rgb(255, 76, 34)' }}>required</td> ) : ( <td style={{ color: '#c6c6c6' }}>optional</td> )} {prop.defaultValue ? ( <td style={{ color: 'rgb(236, 171, 32)' }}> {prop.defaultValue.value} </td> ) : ( <td style={{ color: '#c6c6c6' }}>none</td> )} {prop.docblock ? <td>{prop.docblock}</td> : <td />} </tr> ) })} </tbody> </table> ) }
PropsTable.propTypes = { /** this is the `metadata.props` field of what metadata you get from the react-docgen-loader. */ propMetaData: PropTypes.object, } PropsTable.defaultProps = { propMetaData: {}, }
export default PropsTable
Намного более эффективно, поскольку он запускает response-docgen при сборке, вместо того, чтобы требовать от нас запускать скрипт отдельно (или подключать его к нашему процессу сборки).
Также импортирует документацию как конечную точку GraphQL, что позволяет нам запрашивать данные - вместо жесткого импорта (потенциально гигантского) файла JSON - или с помощью плагина gatsby-transformer-json
(который не форматирует данные для GraphQL, а также специальный плагин react-docgen).
Вы можете скачать финальную версию чистого Гэтсби на Github здесь: Gatsby Documentation Starter.
Получение документации
Я надеюсь, что это помогло вам понять процесс, лежащий в основе документации или, в частности, документации по компонентам React. Использование интерфейса командной строки или API-интерфейса react-docgen делает процесс таким же простым, как загрузка файлов компонентов и их передача через библиотеку, выкачивая структурированные данные, идеально подходящие для интерфейсов внешнего интерфейса.
Существует множество готовых решений, но всегда полезно понимать, как они работают (демистификация магии ✨), особенно если вы хотите создать что-то новое и свежее (пока только фреймворки).
Привет,
Рё
Ссылки: