Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Support TSX files and create diagrams for React components #37

Open
bheklilr opened this issue May 22, 2019 · 4 comments
Open

Comments

@bheklilr
Copy link

bheklilr commented May 22, 2019

Opening this issue to request support for TSX files and to create class diagrams for React components.

Main goals

  • Be capable of parsing TSX files to extract normal typescript constructs like types, interfaces, and classes
  • Recognize when a class or a function is a React component and produce different output for those
  • Optionally extract the props and state types for a React component and include them in the output

Caveats:

  1. React components do not have to be named, and are often exported as a default const arrow function:
export default (props: SomePropsType) => {
    return <div />
}

For these, I think it would make sense to use the file name as the component's name, unless the filename is "index.tsx", in which case the containing folder's name should be used. This should cover 99.9% of use cases.

  1. If a component is in the form const ComponentName = (props: SomePropsType) => <div />;, then the const variable's name should be used as the component name.

  2. With react hooks, it is not possible to determine a hooks-based component's state. We can just ignore the state in these cases. If better tooling arises in the future for this from the react team (or a 3rd party), this could be revisited

export default () => {
    // Not easy at all to infer these types, especially with custom hooks
    // that wrap multiple calls to useState
    const [counter, setCounter] = useState(0);

    return (
        <div>
            <h1>Count: {counter}</h1>
            <button onClick={() => setCounter(counter + 1)}>+</button>
            <button onClick={() => setCounter(counter - 1)}>-</button>
        </div>
    );
}

Example

Input:

interface FCProps {
    text: string
}
function FuncComp(props: FCProps) {
    return <span>{props.text}</span>;
}

interface ArrowProps {
    title: string;
    value: number;
}
const ArrowComp = (props: ArrowProps) => {
    return (
        <div>
            <FuncComp text={props.title} />
            <span>Value: {props.value}</span>
        </div>
    );
}

const NoPropsComp = () => {
    return (
        <div>
            <span>I don't have any props</span>
        </div>
    );
}

interface ClassProps {
    title: string;
}

interface ClassState {
    value: number;
}

// Props and state types can be left out here, but it has to be
// given if they're being used.
class ClassComp extends React.Component<ClassProps, ClassState> {
    state = {
        value: 0,
    };

    public render() {
        return (
            <div>
                <ArrowComp
                    title={this.props.title}
                    value={this.state.value} 
                />
                <NoPropsComp />
                <button onClick={this.onIncrement}>+</button>
                <button onClick={this.onDecrement}>-</button>
            </div>
        );
    }

    private onIncrement = () => {
        this.setState(prevState => ({ value: prevState.value + 1 }));
    }

    private onDecrement = () => {
        this.setState(prevState => ({ value: prevState.value - 1 }));
    }
}

Output:

@startuml
class FuncComp <function> {
  ..Props..
  text: string
}
class ArrowComp <arrow> {
  ..Props..
  title: string
  value: number
}
class NoPropsComp <arrow> {
  ..Props..
}
class ClassComp <class> {
  ..Props..
  title: string
  ..State..
  value: number
}

ClassComp <-- ArrowComp
ClassComp <-- NoPropsComp
ArrowComp <-- FuncComp
@enduml

image

Not addressed

  1. React Context - it may be possible to extract the React context type from class components that use them, as it's specified as
class MyComp extends React.Component {
    contextType: React.ContextType<typeof MyContext>
}

but that seems out of scope for this initial issue, and becomes more complicated with hooks.

  1. Component diagrams could become incredibly large. It is typical for React projects to have a lot of components defined in them. Maybe support a --max-component-depth option or something to work around it?

  2. This would ignore any components that are made using React.createElement, but no one really does that anymore.

  3. Doesn't address how to handle 3rd party components. I'd say ignore them. 3rd party components are often written in JS and shipped with .d.ts files. This would be very difficult to support, and probably wouldn't be very useful anyway.

@bafolts
Copy link
Owner

bafolts commented May 23, 2019

Files with a .tsx extension shouldn't take much work to get working. I will look into that part of this feature request first.

@jdmairs
Copy link

jdmairs commented Apr 3, 2020

Did the tsx file extension get implemented? I don't see it in the README.md

@apecatikov
Copy link

Is this project still maintained?

@bafolts
Copy link
Owner

bafolts commented Oct 9, 2020

I was dusting it off last night as someone brought up a suggestion. I got the dependencies up to date. Plan to start tackling some of these issues which are straight forward.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants