bafolts / tplant

Typescript to plantuml
https://segmentationfaults.com/tplant/default.html
GNU General Public License v3.0
267 stars 34 forks source link

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

Open bheklilr opened 5 years ago

bheklilr commented 5 years ago

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

Main goals

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 commented 5 years ago

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 commented 4 years ago

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

apecatikov commented 3 years ago

Is this project still maintained?

bafolts commented 3 years ago

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.