Skip to content

Commit

Permalink
Merge pull request #7 from OriR/improve-extensibility
Browse files Browse the repository at this point in the history
Improve extensibility
  • Loading branch information
OriR authored Feb 2, 2019
2 parents 878641c + 71a36d0 commit 5f01cac
Show file tree
Hide file tree
Showing 16 changed files with 3,082 additions and 221 deletions.
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pids
lib-cov

# Coverage directory used by tools like istanbul
coverage
coverage.html

# nyc test coverage
.nyc_output
Expand All @@ -37,4 +37,5 @@ jspm_packages
.node_repl_history

# Webstorm
.idea
.idea
.vscode
6 changes: 6 additions & 0 deletions .labrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
coverage: true,
threshold: 100,
globals: '__core-js_shared__',
shuffle: true
};
1 change: 1 addition & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodejs 10.15.0
67 changes: 21 additions & 46 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ const fs = require('fs');
const reactDocgen = require('react-docgen');
const ReactDocGenMarkdownRenderer = require('react-docgen-markdown-renderer');
const componentPath = path.absolute(path.join(__.dirname, 'components/MyComponent.js'));
const renderer = new ReactDocGenMarkdownRenderer({
componentsBasePath: __.dirname
});
const renderer = new ReactDocGenMarkdownRenderer(/* constructor options object */);

fs.readFile(componentPath, (error, content) => {
const documentationPath = path.basename(componentPath, path.extname(componentPath)) + renderer.extension;
Expand All @@ -32,17 +30,20 @@ fs.readFile(componentPath, (error, content) => {
});
```

By default `react-docgen-markdown-renderer` will use `process.cwd()` as the `componentsBasePath`.</br>
#### constructor
**options.componentsBasePath `String`**
This property is optional - defaults to `process.cwd()`.</br>
Represents the base directory where all of your components are stored locally.</br>
It's used as the text for the markdown link at the top of the file - if not specified the link won't appear.

#### options
##### componentsBasePath `String`
**options.remoteComponentsBasePath `String`**
This property is optional.</br>
Represents the base directory where all of your components live.</br>
It's used when creating a markdown link at the top of the file.
Represents the base directory where all of your components are stored remotely.</br>
It's used as the base url for markdown link at the top of the file - if not specified the link won't appear.

##### template `String`
`react-docgen-markdown-renderer` uses [Handlebarsjs](http://handlebarsjs.com) to generate the markdown template.</br>
Which means that you can use all the partials and helpers that `react-docgen-markdown-renderer` defines in your own template!</br>
**options.template `TemplateObject`**
This property is optional - uses the default template if not specified.</br>
A template should be an object constrcuted through the `template` method coming from `react-docgen-renderer-template`.
The default template is
```javascript
const defaultTemplate = `
Expand Down Expand Up @@ -80,35 +81,8 @@ prop | type | default | required | description
`;
```

#### Creating your own template
As you can see from the default template you have access to several objects within your template.</br>
##### `componentName // String`
The name of the component that is being documented.
##### `srcLink // String`
The relative path to the component (based on the `componentsBasePath`).
##### `description // String`
The description given to that component.
##### `props // Object[#]<Prop>`
A hash-map of the flattened props this component exposes.</br>
The key is the flattened name of the prop.</br>
each `Prop` can have a description, a required flag and a defaultValue. The type is inferred with the helper `typePartial` like so `{{> (typePartial this) this}}`.
##### `composes // Array<Component>`
An array of components that the current component composes.</br>
It has the same structure as the original react-docgen AST plus a property named `componentName`.
##### `isMissingComposes // Boolean`
Whether or not there are composes that are missing from the composes array.
</br></br>

`react-docgen-markdown-renderer` also comes with some useful partials and helpers if you'll want to take advantage of.
##### `typePartial`
This needs to be used in order to render the type of the prop - travels all the way down to the the leaf nodes to determine the exact type for each flattened prop.
##### `typeObject`
This returns the actual type object of a type, whether it has a `type` property or just a `name`.
##### all React.PropTypes
All `React.PropTypes` have their own partials that know how to render the type given the relevant type object.

#### Example
##### input
### Example
#### input
```javascript
/**
* This is an example component.
Expand Down Expand Up @@ -181,22 +155,23 @@ MyComponent.propTypes = {
])
};
```
##### output
#### output
<img width="828" alt="Example markdown output" src="https://cloud.githubusercontent.com/assets/2384068/22395353/0622d310-e544-11e6-855c-bb61b5ca46f6.png">

### FAQ
##### What is this weird type notation?
#### What is this weird type notation?
Well, I wanted to create a table for all of my props. Which means that I can't easily nest the components according to their actual structure.</br>
So this notation is helping define the needed types in a flattened manner.
* `[]` - An `arrayOf` notation.
* `.` - A `shape` notation.
* `[#]` -An `objectOf` notation.
* `[#]` - An `objectOf` notation.
* `<{number}>` - A `union` notation, where the `number` indicates the index of the option in the union.

In case of `arrayOf`, `objectOf` and `oneOfType` there also exists the internal type of each value which is noted with `<>`.
##### I want to create my own renderer
#### I want to create my own renderer
This is not as hard as it sounds, but there are some things that you have to know.</br>
A renderer has an `extension` property and a `render(file, doc, composes) => String` function.</br>
Once you have these two you're basically done.</br></br>
A renderer has an `extension` property, a `render(file, doc, composes) => String` and `compile(options) => void` (as described above) functions.</br>
Once you have these you're basically done.</br></br>
`react-docgen-markdown-renderer` expects a `react-docgen` documentation object which helps populate the template above.</br>
It's highly recommended that you use it as well, but note that it doesn't flatten the props by default.
Since you're writing your own renderer you won't have access to all the partials and helpers defined here, but you have the freedom to create your own!</br></br>
Expand Down
166 changes: 0 additions & 166 deletions index.js

This file was deleted.

66 changes: 66 additions & 0 deletions lib/defaultTemplate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@

const os = require('os');
const { template, type } = require('react-docgen-renderer-template');

const generatePropsTable = (props, getType) => {
const entries = Object.entries(props);
if (entries.length === 0) return 'This component does not have any props.';

let propsTableHeader = `prop | type | default | required | description
---- | :----: | :-------: | :--------: | -----------
`;
return propsTableHeader + entries.map(([propName, propValue]) => `**${propName}** | \`${getType(propValue.type)}\` | ${propValue.defaultValue ? `\`${propValue.defaultValue}\`` : '' } | ${propValue.required ? ':white_check_mark:' : ':x:' } | ${propValue.description ? propValue.description : ''}`).join(os.EOL);
};

const templateCreator = template({
unknown: 'Unknown',
func: 'Function',
array: 'Array',
object: 'Object',
string: 'String',
number: 'Number',
bool: 'Boolean',
node: 'ReactNode',
element: 'ReactElement',
symbol: 'Symbol',
any: '*',
custom: '(custom validator)',
shape: 'Shape',
arrayOf: type`Array[]<${({ context, getType }) => getType(context.type.value) }>`,
objectOf: type`Object[#]<${({ context, getType }) => getType(context.type.value) }>`,
instanceOf: type`${({ context }) => context.type.value}`,
enum: type`Enum(${({ context, getType }) => context.type.value.map(value => getType(value)).join(', ')})`,
union: type`Union<${({ context, getType }) => context.type.value.map(value => getType(value)).join('\\|')}>`
});

const templateObject = templateCreator`## ${({ context }) => context.componentName}${({ context }) => {
let headerValue = '';
if (context.srcLinkUrl) {
headerValue = `${os.EOL}From [\`${context.srcLink}\`](${context.srcLinkUrl})`;
}
if (context.description) {
headerValue += os.EOL + os.EOL + context.description;
}
headerValue += os.EOL;
return headerValue;
}}
${({ context, getType }) => generatePropsTable(context.props, getType)}
${({ context }) => context.isMissingComposes
? `*Some or all of the composed components are missing from the list below because a documentation couldn't be generated for them.
See the source code of the component for more information.*`
: ''}${({ context, getType }) => context.composes.length > 0
? `
${context.componentName} gets more \`propTypes\` from these composed components
${context.composes.map(component =>
`#### ${component.componentName}
${generatePropsTable(component.props, getType)}`
).join(os.EOL + os.EOL)}`
: '' }
`;

module.exports = templateObject;
Loading

0 comments on commit 5f01cac

Please sign in to comment.