strothj / react-docgen-typescript-loader

Webpack loader to generate docgen information from Typescript React components.
Other
360 stars 47 forks source link

Docs Not Displayed or in STORYBOOK_REACT_CLASS (TS / Styled-Components) #26

Closed nicholaai closed 6 years ago

nicholaai commented 6 years ago

Hi guys, I'm having an issue getting PropTypes to render.

I'm using Styled Components - TypeScript - Storybook - Storybook info add-on - react-docgen-typescript-loader.

I Have done a good amount of googling, but I haven't been able to fix my issue. I've dug into my node_modules file for react-docgen-typescript-loader and here's what i've found.

Here's my code (Please excuse copy/paste formatting)

(.storybook and src is in root)

./storybook/decorators/webpack.config.js

const path = require("path");
const include = path.resolve(__dirname, '../');

module.exports = {
    resolve: {
        extensions: ['.ts', '.tsx', '.js', '.json']
    },
    module: {
        rules: [
            {
                test: /\.(ts|tsx)$/,
                use: [
                    'ts-loader',
                    {
                        loader: 'react-docgen-typescript-loader',
                        options: {
                            skipPropsWithoutDoc: false,
                            setDisplayName: true,   
                        }
                    }
                ],
                exclude: /node_modules/,
                include
            }
        ]
    }
};

./storybook/decorators/CenterDecorator.tsx

import * as React from 'react';

import { RenderFunction } from '@storybook/react';

const CenterDecorator = (story: RenderFunction) => (
    <div style={{ textAlign: 'center' }}>{story()}</div>
);

export default CenterDecorator;

src/Box/Box.tsx

import { color, fontSize, space, width } from 'styled-system';
import styled from '../styled-components';
import ISpaceProps from '../system-types';

export interface IBox extends ISpaceProps {
    /** testing */
    dude?: any;
    /** background color */
    bg?: string | string[];
    children?: any;
    color?: string | string[];
    fontSize?: number | number[];
    css?: any;
    width?: string | number | string[] | number[];
}

/**
 * Testing
 */
const Box = styled.div<IBox>`
    ${color}
    ${fontSize}
    ${p => p.css}
    ${space}
    ${width}
`;

Box.displayName = 'Box';

export default Box;

Using my Box component in a story like so

import * as React from 'react';

import { withInfo } from '@storybook/addon-info';
import { storiesOf } from '@storybook/react';
import { Box } from '../index';
import theme from '../theme';

// tslint:disable-next-line
console.log('window.STORYBOOK_REACT_CLASSES', window);

storiesOf('Box', module)
    .add(
        'Component Summary',
        withInfo({
            inline: true,
            text: 'Replacement for a basic div'
        })(() => <Box p={3}>Hello</Box>)
    )
    .add('Basic', () => <Box>Hello</Box>)
    .add('Half Width', () => (
        <Box width={1 / 2} bg={theme.colors.darkBlue} color="white">
            Hello
        </Box>
    ));

Placing a console log in loader.js in my node_modules

 // Configure parser using settings provided to loader.
    // See: node_modules/react-docgen-typescript/lib/parser.d.ts
    var parser = parser_js_1.withDefaultConfig(parserOptions);
    if (options.tsconfigPath) {
        parser = parser_js_1.withCustomConfig(options.tsconfigPath, parserOptions);
    }
    else if (options.compilerOptions) {
        parser = parser_js_1.withCompilerOptions(options.compilerOptions, parserOptions);
    }
    var componentDocs = parser.parse(this.resourcePath);

    console.log(' component docs ', componentDocs);

    // Return amended source code if there is docgen information available.
    if (componentDocs.length) {
        return generateDocgenCodeBlock_1.default({
            filename: this.resourcePath,
            source: source,
            componentDocs: componentDocs,
            docgenCollectionName: options.docgenCollectionName,
            setDisplayName: options.setDisplayName,
        });
    }

Gives me the following for componentDocs for Box:

(note: displayName is set to 'StyledComponentClass' and not 'Box'. My interface props are defined at the end of the object)

resource path /Users/nicholas/Development/[route-name-filler]/src/Box/Box.tsx
 component docs  [ { description: '',
    displayName: 'StyledComponentClass',
    props:
     { ref: [Object],
       key: [Object],
       defaultChecked: [Object],
       defaultValue: [Object],
       suppressContentEditableWarning: [Object],
       suppressHydrationWarning: [Object],
       accessKey: [Object],
       className: [Object],
       contentEditable: [Object],
       contextMenu: [Object],
       dir: [Object],
       draggable: [Object],
       hidden: [Object],
       id: [Object],
       lang: [Object],
       placeholder: [Object],
       slot: [Object],
       spellCheck: [Object],
       style: [Object],
       tabIndex: [Object],
       title: [Object],
       inputMode: [Object],
       is: [Object],
       radioGroup: [Object],
       role: [Object],
       about: [Object],
       datatype: [Object],
       inlist: [Object],
       prefix: [Object],
       property: [Object],
       resource: [Object],
       typeof: [Object],
       vocab: [Object],
       autoCapitalize: [Object],
       autoCorrect: [Object],
       autoSave: [Object],
       color: [Object],
       itemProp: [Object],
       itemScope: [Object],
       itemType: [Object],
       itemID: [Object],
       itemRef: [Object],
       results: [Object],
       security: [Object],
       unselectable: [Object],
       'aria-activedescendant': [Object],
       'aria-atomic': [Object],
       'aria-autocomplete': [Object],
       'aria-busy': [Object],
       'aria-checked': [Object],
       'aria-colcount': [Object],
       'aria-colindex': [Object],
       'aria-colspan': [Object],
       'aria-controls': [Object],
       'aria-current': [Object],
       'aria-describedby': [Object],
       'aria-details': [Object],
       'aria-disabled': [Object],
       'aria-dropeffect': [Object],
       'aria-errormessage': [Object],
       'aria-expanded': [Object],
       'aria-flowto': [Object],
       'aria-grabbed': [Object],
       'aria-haspopup': [Object],
       'aria-hidden': [Object],
       'aria-invalid': [Object],
       'aria-keyshortcuts': [Object],
       'aria-label': [Object],
       'aria-labelledby': [Object],
       'aria-level': [Object],
       'aria-live': [Object],
       'aria-modal': [Object],
       'aria-multiline': [Object],
       'aria-multiselectable': [Object],
       'aria-orientation': [Object],
       'aria-owns': [Object],
       'aria-placeholder': [Object],
       'aria-posinset': [Object],
       'aria-pressed': [Object],
       'aria-readonly': [Object],
       'aria-relevant': [Object],
       'aria-required': [Object],
       'aria-roledescription': [Object],
       'aria-rowcount': [Object],
       'aria-rowindex': [Object],
       'aria-rowspan': [Object],
       'aria-selected': [Object],
       'aria-setsize': [Object],
       'aria-sort': [Object],
       'aria-valuemax': [Object],
       'aria-valuemin': [Object],
       'aria-valuenow': [Object],
       'aria-valuetext': [Object],
       dangerouslySetInnerHTML: [Object],
       onCopy: [Object],
       onCopyCapture: [Object],
       onCut: [Object],
       onCutCapture: [Object],
       onPaste: [Object],
       onPasteCapture: [Object],
       onCompositionEnd: [Object],
       onCompositionEndCapture: [Object],
       onCompositionStart: [Object],
       onCompositionStartCapture: [Object],
       onCompositionUpdate: [Object],
       onCompositionUpdateCapture: [Object],
       onFocus: [Object],
       onFocusCapture: [Object],
       onBlur: [Object],
       onBlurCapture: [Object],
       onChange: [Object],
       onChangeCapture: [Object],
       onInput: [Object],
       onInputCapture: [Object],
       onReset: [Object],
       onResetCapture: [Object],
       onSubmit: [Object],
       onSubmitCapture: [Object],
       onInvalid: [Object],
       onInvalidCapture: [Object],
       onLoad: [Object],
       onLoadCapture: [Object],
       onError: [Object],
       onErrorCapture: [Object],
       onKeyDown: [Object],
       onKeyDownCapture: [Object],
       onKeyPress: [Object],
       onKeyPressCapture: [Object],
       onKeyUp: [Object],
       onKeyUpCapture: [Object],
       onAbort: [Object],
       onAbortCapture: [Object],
       onCanPlay: [Object],
       onCanPlayCapture: [Object],
       onCanPlayThrough: [Object],
       onCanPlayThroughCapture: [Object],
       onDurationChange: [Object],
       onDurationChangeCapture: [Object],
       onEmptied: [Object],
       onEmptiedCapture: [Object],
       onEncrypted: [Object],
       onEncryptedCapture: [Object],
       onEnded: [Object],
       onEndedCapture: [Object],
       onLoadedData: [Object],
       onLoadedDataCapture: [Object],
       onLoadedMetadata: [Object],
       onLoadedMetadataCapture: [Object],
       onLoadStart: [Object],
       onLoadStartCapture: [Object],
       onPause: [Object],
       onPauseCapture: [Object],
       onPlay: [Object],
       onPlayCapture: [Object],
       onPlaying: [Object],
       onPlayingCapture: [Object],
       onProgress: [Object],
       onProgressCapture: [Object],
       onRateChange: [Object],
       onRateChangeCapture: [Object],
       onSeeked: [Object],
       onSeekedCapture: [Object],
       onSeeking: [Object],
       onSeekingCapture: [Object],
       onStalled: [Object],
       onStalledCapture: [Object],
       onSuspend: [Object],
       onSuspendCapture: [Object],
       onTimeUpdate: [Object],
       onTimeUpdateCapture: [Object],
       onVolumeChange: [Object],
       onVolumeChangeCapture: [Object],
       onWaiting: [Object],
       onWaitingCapture: [Object],
       onClick: [Object],
       onClickCapture: [Object],
       onContextMenu: [Object],
       onContextMenuCapture: [Object],
       onDoubleClick: [Object],
       onDoubleClickCapture: [Object],
       onDrag: [Object],
       onDragCapture: [Object],
       onDragEnd: [Object],
       onDragEndCapture: [Object],
       onDragEnter: [Object],
       onDragEnterCapture: [Object],
       onDragExit: [Object],
       onDragExitCapture: [Object],
       onDragLeave: [Object],
       onDragLeaveCapture: [Object],
       onDragOver: [Object],
       onDragOverCapture: [Object],
       onDragStart: [Object],
       onDragStartCapture: [Object],
       onDrop: [Object],
       onDropCapture: [Object],
       onMouseDown: [Object],
       onMouseDownCapture: [Object],
       onMouseEnter: [Object],
       onMouseLeave: [Object],
       onMouseMove: [Object],
       onMouseMoveCapture: [Object],
       onMouseOut: [Object],
       onMouseOutCapture: [Object],
       onMouseOver: [Object],
       onMouseOverCapture: [Object],
       onMouseUp: [Object],
       onMouseUpCapture: [Object],
       onSelect: [Object],
       onSelectCapture: [Object],
       onTouchCancel: [Object],
       onTouchCancelCapture: [Object],
       onTouchEnd: [Object],
       onTouchEndCapture: [Object],
       onTouchMove: [Object],
       onTouchMoveCapture: [Object],
       onTouchStart: [Object],
       onTouchStartCapture: [Object],
       onPointerDown: [Object],
       onPointerDownCapture: [Object],
       onPointerMove: [Object],
       onPointerMoveCapture: [Object],
       onPointerUp: [Object],
       onPointerUpCapture: [Object],
       onPointerCancel: [Object],
       onPointerCancelCapture: [Object],
       onPointerEnter: [Object],
       onPointerEnterCapture: [Object],
       onPointerLeave: [Object],
       onPointerLeaveCapture: [Object],
       onPointerOver: [Object],
       onPointerOverCapture: [Object],
       onPointerOut: [Object],
       onPointerOutCapture: [Object],
       onGotPointerCapture: [Object],
       onGotPointerCaptureCapture: [Object],
       onLostPointerCapture: [Object],
       onLostPointerCaptureCapture: [Object],
       onScroll: [Object],
       onScrollCapture: [Object],
       onWheel: [Object],
       onWheelCapture: [Object],
       onAnimationStart: [Object],
       onAnimationStartCapture: [Object],
       onAnimationEnd: [Object],
       onAnimationEndCapture: [Object],
       onAnimationIteration: [Object],
       onAnimationIterationCapture: [Object],
       onTransitionEnd: [Object],
       onTransitionEndCapture: [Object],
       dude: [Object],
       bg: [Object],
       fontSize: [Object],
       css: [Object],
       width: [Object],
       m: [Object],
       mt: [Object],
       mr: [Object],
       mb: [Object],
       ml: [Object],
       mx: [Object],
       my: [Object],
       p: [Object],
       pt: [Object],
       pr: [Object],
       pb: [Object],
       pl: [Object],
       px: [Object],
       py: [Object],
       theme: [Object],
       innerRef: [Object] } } ]

Using .addDecorator(CenterDecorator) like so:

import * as React from 'react';

import { storiesOf } from '@storybook/react';
import CenterDecorator from '../../.storybook/decorators/CenterDecorator';
import { Button } from '../index';

storiesOf('Button', module)
    .addDecorator(CenterDecorator)
    .add('Blue', () => <Button>yo</Button>)
    .add('Red', () => <Button size="large">yo</Button>);

Results in CenterDecorator being added to window.STORYBOOK_REACT_CLASS. None of the components show up in the object, only CenterDecorator

I placed a console.log screen shot 2018-09-12 at 4 56 08 pm

If I remove the .addDecorator(CenterDecorator) from the code above, then the CenterDecorator is no longer appears in window.STORYBOOK_REACT_CLASS

Also, none of the other component docs are anywhere to be found.

Not having an issue with the other info pieces like Story Source rendering.

Any idea what I'm doing wrong here? Thank you

nicholaai commented 6 years ago

I've made a bit of progress in addressing this issue.

Expanded my webpack config to exclude the stories and filter out props (copy-paste indent sucks, sorry)

{
                        loader: 'react-docgen-typescript-loader',
                        options: {
                            skipPropsWithoutDoc: false,
                            excludes: ["\\.stories.tsx$"],
                            propFilter: function(prop) {
                                if (prop.parent == null) {
                                    return true;
                                }

                                return prop.parent.fileName.indexOf('node_modules/@types/react') < 0;
                            }
                        }
                    }

Within loader.js I experimented with hardcoding in my component displayName after running the parse function

var componentDocs = parser.parse(this.resourcePath);
 if (componentDocs[0]) {
        componentDocs[0].displayName = 'Box';
    }

This resulted in the Box props being rendered along with being added to window.STORYBOOK_REACT_CLASS

screen shot 2018-09-12 at 9 59 59 pm

screen shot 2018-09-12 at 9 57 44 pm

So, it seems like there is an issue with var parser_js_1 = require("react-docgen-typescript/lib/parser.js"); and displayNames. Any thoughts on that?

nicholaai commented 6 years ago

Found an associated issue https://github.com/styleguidist/react-docgen-typescript/issues/82

switched from

const Box = styled.div<IBox>`
    ${color}
    ${fontSize}
    ${p => p.css}
    ${space}
    ${width}
`;

Box.displayName = 'Box';

export default Box;

to

export const Box = styled.div<IBox>`
    ${color}
    ${fontSize}
    ${p => p.css}
    ${space}
    ${width}
`;

Box.displayName = 'Box';

And i got it working. That seems to be a workaround. PR https://github.com/styleguidist/react-docgen-typescript/pull/87 was supposed to fix this

"ogic, essentially, is if displayName is extracted from stateless or functional component, then use that. otherwise fall back to previous logic without changes."

which leads me to believe it should work (maybe i'm wrong?). i think perhaps styled-components is causing the issue here.

strothj commented 6 years ago

Hello,

Thanks for the detailed report. I won’t be able to dig in until Sunday, possibly Saturday due to some current obligations.

You might want to try disabling the loader option to set display names automatically.

The styled component display names I think may be actually “styled(Box)”. I will need to look into that.

So setting an explicit display name to the component in code may work with that option disabled.

The info addon I think tries to match the display name to the story name.