From 598212fc087394773ea558ffd3837a1906b26be9 Mon Sep 17 00:00:00 2001 From: Alexander Fedyashov Date: Mon, 11 Sep 2017 06:34:24 +0300 Subject: [PATCH] perf(docs): optimize ComponentProps (#2012) * perf(docs): optimize ComponentProps * fix(HOC): use PureComponent for pure() HOC * fix(ComponentProps): update `limit` value * feat(ComponentProps): make ComponentProps functional component, move toggleEnums logic down --- .../ComponentExample/ComponentExample.js | 6 +- .../ComponentProps/ComponentProps.js | 180 +++--------------- .../ComponentPropsDefaultValue.js | 13 ++ .../ComponentPropsDescription.js | 17 ++ .../ComponentProps/ComponentPropsEnum.js | 7 +- .../ComponentPropsFunctionSignature.js | 65 +++++++ .../ComponentProps/ComponentPropsHeader.js | 17 ++ .../ComponentProps/ComponentPropsName.js | 30 +++ .../ComponentProps/ComponentPropsRow.js | 55 ++++++ docs/app/HOC/pure.js | 10 +- gulp/plugins/gulp-react-docgen.js | 17 +- gulp/plugins/util/index.js | 1 + gulp/plugins/util/parseDefaultValue.js | 3 + gulp/plugins/util/parseDocBlock.js | 10 +- gulp/plugins/util/parseType.js | 7 +- 15 files changed, 264 insertions(+), 174 deletions(-) create mode 100644 docs/app/Components/ComponentDoc/ComponentProps/ComponentPropsDefaultValue.js create mode 100644 docs/app/Components/ComponentDoc/ComponentProps/ComponentPropsDescription.js create mode 100644 docs/app/Components/ComponentDoc/ComponentProps/ComponentPropsFunctionSignature.js create mode 100644 docs/app/Components/ComponentDoc/ComponentProps/ComponentPropsHeader.js create mode 100644 docs/app/Components/ComponentDoc/ComponentProps/ComponentPropsName.js create mode 100644 docs/app/Components/ComponentDoc/ComponentProps/ComponentPropsRow.js create mode 100644 gulp/plugins/util/parseDefaultValue.js diff --git a/docs/app/Components/ComponentDoc/ComponentExample/ComponentExample.js b/docs/app/Components/ComponentDoc/ComponentExample/ComponentExample.js index 54a05f986a..2ac3448878 100644 --- a/docs/app/Components/ComponentDoc/ComponentExample/ComponentExample.js +++ b/docs/app/Components/ComponentDoc/ComponentExample/ComponentExample.js @@ -388,15 +388,15 @@ class ComponentExample extends Component { onMouseLeave={this.handleMouseLeave} style={exampleStyle} > - - + + - + (tag.type.type === 'AllLiteral' ? 'any' : tag.type.name) +import ComponentPropsHeader from './ComponentPropsHeader' +import ComponentPropsRow from './ComponentPropsRow' /** * Displays a table of a Component's PropTypes. */ -export default class ComponentProps extends Component { - static propTypes = { - /** - * A single Component's prop info as generated by react-docgen. - * @type {object} Props info object where keys are prop names and values are prop definitions. - */ - props: PropTypes.object, - /** - * A single Component's meta info. - * @type {object} Meta info object where enum prop values are defined. - */ - meta: PropTypes.object, - } - - state = { - showEnumsFor: {}, - } - - toggleEnumsFor = prop => () => { - this.setState({ - showEnumsFor: { - ...this.state.showEnumsFor, - [prop]: !this.state.showEnumsFor[prop], - }, - }) - } - - renderName = item => {item.name} - - renderRequired = item => item.required && ( - } - content='Required' - size='tiny' - inverted - /> - ) - - renderDefaultValue = (item) => { - const defaultValue = _.get(item, 'defaultValue.value') - if (_.isNil(defaultValue)) return null - - return {defaultValue} - } - - renderFunctionSignature = (item) => { - const params = _.filter(item.tags, { title: 'param' }) - const returns = _.find(item.tags, { title: 'returns' }) - - // this doesn't look like a function propType doc block - // don't try to render a signature - if (_.isEmpty(params) && !returns) return - - const paramSignature = params - .map(param => `${param.name}: ${getTagType(param)}`) - // prevent object properties from showing as individual params - .filter(p => !_.includes(p, '.')) - .join(', ') - - const tagDescriptionRows = _.compact([...params, returns]).map((tag) => { - const name = tag.name || tag.title - return ( -
-
- {name} -
-
- {tag.description} -
-
- ) - }) - - return ( - {item.name}({paramSignature}){returns ? `: ${getTagType(returns)}` : ''}}> - {tagDescriptionRows} - - ) - } - - renderEnums = ({ name, type, value }) => { - const { showEnumsFor } = this.state - - if (type !== '{enum}' || !value) return - return ( - - ) - } - - renderRow = item => ( - - {this.renderName(item)}{this.renderRequired(item)} - {this.renderDefaultValue(item)} - {item.type} - - {item.description &&

{item.description}

} - {this.renderFunctionSignature(item)} - {this.renderEnums(item)} -
-
- ) - - render() { - const { props: propsDefinition } = this.props - - const content = _.sortBy(_.map(propsDefinition, (config, name) => { - const value = _.get(config, 'type.value') - let type = _.get(config, 'type.name') - if (type === 'union') { - type = _.map(value, val => val.name).join('|') - } - type = type && `{${type}}` - - const description = _.get(config, 'docBlock.description', '') - - return { - name, - type, - value, - tags: _.get(config, 'docBlock.tags'), - required: config.required, - defaultValue: config.defaultValue, - description: description && description.split('\n').map(l => ([l,
])), - } - }), 'name') - - return ( - - - - Name - Default - Type - Description - - - - {_.map(content, this.renderRow)} - -
- ) - } +const ComponentProps = ({ props: propsDefinition }) => ( + + + + {_.map(propsDefinition, item => )} + +
+) + +ComponentProps.propTypes = { + /** + * A single Component's prop info as generated by react-docgen. + * @type {object} Props info object where keys are prop names and values are prop definitions. + */ + props: PropTypes.object, + /** + * A single Component's meta info. + * @type {object} Meta info object where enum prop values are defined. + */ + meta: PropTypes.object, } + +export default ComponentProps diff --git a/docs/app/Components/ComponentDoc/ComponentProps/ComponentPropsDefaultValue.js b/docs/app/Components/ComponentDoc/ComponentProps/ComponentPropsDefaultValue.js new file mode 100644 index 0000000000..689ee154a1 --- /dev/null +++ b/docs/app/Components/ComponentDoc/ComponentProps/ComponentPropsDefaultValue.js @@ -0,0 +1,13 @@ +import _ from 'lodash' +import PropTypes from 'prop-types' +import React from 'react' + +import { pure } from 'docs/app/HOC' + +const ComponentPropsDefaultValue = ({ value }) => (_.isNil(value) ? null : {value}) + +ComponentPropsDefaultValue.propTypes = { + value: PropTypes.node, +} + +export default pure(ComponentPropsDefaultValue) diff --git a/docs/app/Components/ComponentDoc/ComponentProps/ComponentPropsDescription.js b/docs/app/Components/ComponentDoc/ComponentProps/ComponentPropsDescription.js new file mode 100644 index 0000000000..fca3df9b71 --- /dev/null +++ b/docs/app/Components/ComponentDoc/ComponentProps/ComponentPropsDescription.js @@ -0,0 +1,17 @@ +import _ from 'lodash' +import PropTypes from 'prop-types' +import React from 'react' + +import { pure } from 'docs/app/HOC' + +const ComponentPropsDescription = ({ description }) => (_.isNil(description) ? null : ( +

+ {_.map(description, line => [line,
])} +

+)) + +ComponentPropsDescription.propTypes = { + description: PropTypes.arrayOf(PropTypes.string), +} + +export default pure(ComponentPropsDescription) diff --git a/docs/app/Components/ComponentDoc/ComponentProps/ComponentPropsEnum.js b/docs/app/Components/ComponentDoc/ComponentProps/ComponentPropsEnum.js index e7c87f4dac..698e4633ac 100644 --- a/docs/app/Components/ComponentDoc/ComponentProps/ComponentPropsEnum.js +++ b/docs/app/Components/ComponentDoc/ComponentProps/ComponentPropsEnum.js @@ -7,7 +7,9 @@ import ComponentPropsExtra from './ComponentPropsExtra' import ComponentPropsToggle from './ComponentPropsEnumToggle' import ComponentPropsValue from './ComponentPropsEnumValue' -const ComponentPropsEnum = ({ limit, showAll, toggle, values }) => { +const ComponentPropsEnum = ({ limit, showAll, toggle, type, values }) => { + if (type !== 'enum' || !values) return null + const exceeds = values.length > limit const sliced = showAll ? values : _.slice(values, 0, limit) @@ -30,13 +32,14 @@ const ComponentPropsEnum = ({ limit, showAll, toggle, values }) => { } ComponentPropsEnum.defaultProps = { - limit: 10, + limit: 50, } ComponentPropsEnum.propTypes = { limit: PropTypes.number, showAll: PropTypes.bool, toggle: PropTypes.func, + type: PropTypes.string, values: PropTypes.array, } diff --git a/docs/app/Components/ComponentDoc/ComponentProps/ComponentPropsFunctionSignature.js b/docs/app/Components/ComponentDoc/ComponentProps/ComponentPropsFunctionSignature.js new file mode 100644 index 0000000000..0c818bbc7f --- /dev/null +++ b/docs/app/Components/ComponentDoc/ComponentProps/ComponentPropsFunctionSignature.js @@ -0,0 +1,65 @@ +import _ from 'lodash' +import PropTypes from 'prop-types' +import React from 'react' + +import { neverUpdate } from 'docs/app/HOC' +import ComponentPropsExtra from './ComponentPropsExtra' + +const descriptionStyle = { + flex: '5 5 0', + padding: '0.1em 0', +} + +const nameStyle = { + flex: '2 2 0', + padding: '0.1em 0', +} + +const rowStyle = { + display: 'flex', + flexDirection: 'row', +} + +const getTagType = tag => (tag.type.type === 'AllLiteral' ? 'any' : tag.type.name) + +const ComponentPropsFunctionSignature = ({ name, tags }) => { + const params = _.filter(tags, { title: 'param' }) + const returns = _.find(tags, { title: 'returns' }) + + // this doesn't look like a function propType doc block + // don't try to render a signature + if (_.isEmpty(params) && !returns) return null + + const paramSignature = params + .map(param => `${param.name}: ${getTagType(param)}`) + // prevent object properties from showing as individual params + .filter(p => !_.includes(p, '.')) + .join(', ') + + const tagDescriptionRows = _.compact([...params, returns]).map((tag) => { + const title = tag.name || tag.title + return ( +
+
+ {title} +
+
+ {tag.description} +
+
+ ) + }) + + return ( + {name}({paramSignature}){returns ? `: ${getTagType(returns)}` : ''}}> + {tagDescriptionRows} + + ) +} + +ComponentPropsFunctionSignature.propTypes = { + name: PropTypes.string, + tags: PropTypes.object, +} + +export default neverUpdate(ComponentPropsFunctionSignature) diff --git a/docs/app/Components/ComponentDoc/ComponentProps/ComponentPropsHeader.js b/docs/app/Components/ComponentDoc/ComponentProps/ComponentPropsHeader.js new file mode 100644 index 0000000000..abf98a9750 --- /dev/null +++ b/docs/app/Components/ComponentDoc/ComponentProps/ComponentPropsHeader.js @@ -0,0 +1,17 @@ +import React from 'react' +import { Table } from 'semantic-ui-react' + +import { neverUpdate } from 'docs/app/HOC' + +const ComponentPropsHeader = () => ( + + + Name + Default + Type + Description + + +) + +export default neverUpdate(ComponentPropsHeader) diff --git a/docs/app/Components/ComponentDoc/ComponentProps/ComponentPropsName.js b/docs/app/Components/ComponentDoc/ComponentProps/ComponentPropsName.js new file mode 100644 index 0000000000..1ab990d885 --- /dev/null +++ b/docs/app/Components/ComponentDoc/ComponentProps/ComponentPropsName.js @@ -0,0 +1,30 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Icon, Popup } from 'semantic-ui-react' + +import { pure } from 'docs/app/HOC' + +const popupStyle = { padding: '0.5em' } + +const ComponentPropsName = ({ name, required }) => ( +
+ {name} + {required && ( + } + /> + )} +
+) + +ComponentPropsName.propTypes = { + name: PropTypes.string, + required: PropTypes.bool, +} + +export default pure(ComponentPropsName) diff --git a/docs/app/Components/ComponentDoc/ComponentProps/ComponentPropsRow.js b/docs/app/Components/ComponentDoc/ComponentProps/ComponentPropsRow.js new file mode 100644 index 0000000000..f3b3333d34 --- /dev/null +++ b/docs/app/Components/ComponentDoc/ComponentProps/ComponentPropsRow.js @@ -0,0 +1,55 @@ +import PropTypes from 'prop-types' +import React, { Component } from 'react' +import { Table } from 'semantic-ui-react' + +import ComponentPropsDefaultValue from './ComponentPropsDefaultValue' +import ComponentPropsDescription from './ComponentPropsDescription' +import ComponentPropsEnum from './ComponentPropsEnum' +import ComponentPropsFunctionSignature from './ComponentPropsFunctionSignature' +import ComponentPropsName from './ComponentPropsName' + +export default class ComponentPropsRow extends Component { + static propTypes = { + defaultValue: PropTypes.string, + description: PropTypes.string, + name: PropTypes.string, + required: PropTypes.bool, + tags: PropTypes.object, + type: PropTypes.string, + value: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.arrayOf(PropTypes.string), + ]), + } + + state = {} + + toggleEnums = () => this.setState({ showEnums: !this.state.showEnums }) + + render() { + const { defaultValue, description, name, required, tags, type, value } = this.props + const { showEnums } = this.state + + return ( + + + + + + + + {`{${type}}`} + + + + + + + ) + } +} diff --git a/docs/app/HOC/pure.js b/docs/app/HOC/pure.js index f0d68ab18f..c4b3a615f1 100644 --- a/docs/app/HOC/pure.js +++ b/docs/app/HOC/pure.js @@ -1,12 +1,6 @@ -import React, { Component } from 'react' - -import { shallowEqual } from 'src/lib' - -const pure = ChildComponent => class extends Component { - shouldComponentUpdate(nextProps) { - return !shallowEqual(this.props, nextProps) - } +import React, { PureComponent } from 'react' +const pure = ChildComponent => class extends PureComponent { render() { return } diff --git a/gulp/plugins/gulp-react-docgen.js b/gulp/plugins/gulp-react-docgen.js index 6137530461..5f7e9c0bb0 100644 --- a/gulp/plugins/gulp-react-docgen.js +++ b/gulp/plugins/gulp-react-docgen.js @@ -4,7 +4,7 @@ import path from 'path' import { parse } from 'react-docgen' import through from 'through2' -import { parseDocBlock, parseType } from './util' +import { parseDefaultValue, parseDocBlock, parseType } from './util' export default (filename) => { const defaultFilename = 'docgenInfo.json' @@ -36,11 +36,20 @@ export default (filename) => { // replace prop `description` strings with a parsed doc block object and updated `type` _.each(parsed.props, (propDef, propName) => { - parsed.props[propName].docBlock = parseDocBlock(propDef.description) - parsed.props[propName].type = parseType(propDef) + const { description, tags } = parseDocBlock(propDef.description) + const { name, value } = parseType(propDef) - delete parsed.props[propName].description + parsed.props[propName] = { + ...propDef, + description, + tags, + value, + defaultValue: parseDefaultValue(propDef), + name: propName, + type: name, + } }) + parsed.props = _.sortBy(parsed.props, 'name') result[relativePath] = parsed diff --git a/gulp/plugins/util/index.js b/gulp/plugins/util/index.js index 44c429d990..97d3dfa8ac 100644 --- a/gulp/plugins/util/index.js +++ b/gulp/plugins/util/index.js @@ -1,2 +1,3 @@ +export parseDefaultValue from './parseDefaultValue' export parseDocBlock from './parseDocBlock' export parseType from './parseType' diff --git a/gulp/plugins/util/parseDefaultValue.js b/gulp/plugins/util/parseDefaultValue.js new file mode 100644 index 0000000000..7085d87da2 --- /dev/null +++ b/gulp/plugins/util/parseDefaultValue.js @@ -0,0 +1,3 @@ +import _ from 'lodash' + +export default propDef => _.get(propDef, 'defaultValue.value', undefined) diff --git a/gulp/plugins/util/parseDocBlock.js b/gulp/plugins/util/parseDocBlock.js index 6d2ae8e2db..b13e383517 100644 --- a/gulp/plugins/util/parseDocBlock.js +++ b/gulp/plugins/util/parseDocBlock.js @@ -1,3 +1,11 @@ import doctrine from 'doctrine' -export default docBlock => doctrine.parse(docBlock || '', { unwrap: true }) +export default (docBlock) => { + const { description = '', tags = [], ...rest } = doctrine.parse(docBlock || '', { unwrap: true }) + + return { + ...rest, + tags, + description: description.split('\n'), + } +} diff --git a/gulp/plugins/util/parseType.js b/gulp/plugins/util/parseType.js index e400d8f1b9..693cc0a54e 100644 --- a/gulp/plugins/util/parseType.js +++ b/gulp/plugins/util/parseType.js @@ -22,9 +22,12 @@ const parseEnum = (type) => { const parseUnion = (union) => { const { value } = union - const transformed = value.map(type => (type.name === 'enum' ? parseEnum(type) : type)) - return { ...union, value: transformed } + return { + ...union, + name: _.map(value, 'name').join('|'), + value: _.map(value, type => (type.name === 'enum' ? parseEnum(type) : type)), + } } const parsers = {