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

TypeScript all the things #5150

Merged
merged 10 commits into from
May 19, 2020
Merged

TypeScript all the things #5150

merged 10 commits into from
May 19, 2020

Conversation

akx
Copy link
Collaborator

@akx akx commented Apr 27, 2020

To follow up on #5148, this PR starts to convert src/ to TypeScript.

I grabbed the TS/eslint configuration from react-overlays, loosened it up a little for development purposes. A little Python script (enclosed) merged .js and .d.ts files into .tsx files, and I then began working in the dim light of while true; yarn tsc --noEmit 2>&1 >tmp.log; clear; head -n50 tmp.log; sleep 5; end...

23:56, 27 April

  • some helper functions and such, used by multiple components, have either been strictly typed or any-typed to make things easier for the time being
  • a small part of components pass tsc checks,
    • ⚠️ but the as="element" polymorphism is very likely lost on them right now. I'm having a somewhat hard time wrapping my head around the typings required to make that work in a strict enough way; it'll likely require some deeper type magic than I'm accustomed to so far! (Any ideas? 🤔)
    • in the same vein, assigning propTypes onto these components requires an ugly as any thing 🤢 – I expect there to be no as anys when we're collectively finished with this...

Anyway! It's a start, and judging by stricter typing finding us #5149, I think it's worth it 😄

22:59, 5 May

Okay, the test suites pass now!

  • There are lots of as anys, as unknown as Xs and some // @ts-ignores.
  • There's a handful of TODO comments, mostly for types and such that weren't quite documented.
  • I haven't checked whether all propTypes declarations match all *Props interfaces.
  • The as polymorphism is now implemented with a type ...; I'm not sure if that's the right way, but it seems to work and type-checks alright.
  • build seems to result in something sane, but I'm not sure if it's equivalent to what was there before.
  • The typing for Card.Body etc. feels evil (it's where I couldn't think of any other ways to do things but that as unknown as X thing).
  • Components created with createWithBsPrefix are utterly poorly typed. :(
  • The generated .d.ts files look pretty horrible, since they include all of react's builtin HTML element names...

@jquense
Copy link
Member

jquense commented Apr 28, 2020

the as typing is pretty tricky. The pattern i've found that works best is to not try and type as as part of the component definition, but declare it separately and cast the component to it. The Form components show the right pattern for that. Something like:

export interface FormControlProps {
  ...
}

declare interface FormControl
  extends BsPrefixRefForwardingComponent<'input', FormControlProps> {
  Feedback: typeof Feedback;
}

const RBFormControl: FormControl = React.forwardRef((props: FormControlProps, ref: Ref) => { 

  return (...)
})

What happens is that inside the FormControl Props is just the defined FormControlProps, which is what you want, you can only rely on props you know exist. But a user of FormControl is getting the correct type that will respect their as prop

@akx
Copy link
Collaborator Author

akx commented Apr 28, 2020

the as typing is pretty tricky. The pattern i've found that works best is to not try and type as as part of the component definition, but declare it separately and cast the component to it. The Form components show the right pattern for that.

Cheers for the pointer. I'll look into that :)

@akx akx force-pushed the all-ts branch 3 times, most recently from ebebac8 to c847d7a Compare May 5, 2020 19:58
@akx akx changed the title Heavily WIP: TypeScript all the things TypeScript all the things May 5, 2020
@akx akx force-pushed the all-ts branch 3 times, most recently from 975954a to 895e1bc Compare May 5, 2020 20:56
@akx
Copy link
Collaborator Author

akx commented May 5, 2020

@jquense et al.: any idea why Travis isn't building anymore?

@akx akx marked this pull request as ready for review May 5, 2020 21:01
Copy link
Member

@jquense jquense left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did a real quick pass and got as far as the FormCheck. Looking great! I'm not sure what is going on with travis ... @bpas247 you know?

const actual = element.style[prop];
element.dataset[prop] = actual;
css(element, {
[prop]: `${parseFloat(css(element, prop)) + adjust}px`,
[prop]: `${parseFloat(css(element, prop as any)) + adjust}px`,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the T isn't doing a much here if we have to cast to any right below yeah?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It kind of is... T allows us to read element.style safely, but css(...) uses a different typing, so... maybe some sort of intersection type between keyof CSSStyleDeclaration and keyof CSS.Properties?

src/BootstrapModalManager.tsx Outdated Show resolved Hide resolved
const [direction, setDirection] = useState('next');

const [isSliding, setIsSliding] = useState(false);
const [renderedActiveIndex, setRenderedActiveIndex] = useState(activeIndex);
const [renderedActiveIndex, setRenderedActiveIndex] = useState<number>(
activeIndex || 0,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a little concerned with the coercing to 0 having unintended consequences

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess, yeah, but there are places doing arithmetic like renderedActiveIndex - 1 without checking whether it is might happen to be undefined... What do you think, fix those to assume things or..?

{(state, innerProps) =>
React.cloneElement(children, {
{(state, innerProps) => {
return React.cloneElement(children as any, {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can children be typed as React.ReactElement to avoid this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not without refactoring Collapse into an FC. ClassComponents' props are typed like

readonly props: Readonly<P> & Readonly<{ children?: ReactNode }>;

src/DropdownButton.tsx Outdated Show resolved Hide resolved
menuProps.alignRight = alignEnd;
(menuProps as any).show = show;
(menuProps as any).close = close;
(menuProps as any).alignRight = alignEnd;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to cast this to a more correct type vs casting to any below

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I suppose, but how do we know the props of the custom Component? 🤔

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't, but that's not important here, the Component must accept these props, e.g. the interface for whatever Component is, is "accepts at least these props". That said i think that's less important than just correctly typing menuProps so that it's safe to use here not just so Component knows it.

src/Feedback.tsx Outdated Show resolved Hide resolved
@bpas247
Copy link
Member

bpas247 commented May 6, 2020

@jquense Looks like the last build seems to be failing due to the prettier step, although I'm not sure why it's stopped kicking off for those new commits after (also strange that GitHub actions isn't kicking off at all here).

@bpas247
Copy link
Member

bpas247 commented May 6, 2020

Update: This might be due to how many times the git history has been re-written for this PR (especially considering the last build references a commit that doesn't exist anymore), I did notice that there is a few merge conflicts so hopefully the CI builds will kick off again with a merge commit

@akx
Copy link
Collaborator Author

akx commented May 7, 2020

@bpas247 Rebasing on origin/master seemed to do the trick. :)

@mxschmitt mxschmitt mentioned this pull request May 12, 2020
@mxschmitt mxschmitt requested a review from jquense May 12, 2020 21:02
merge_ts.py Outdated Show resolved Hide resolved
package.json Outdated Show resolved Hide resolved
Copy link
Member

@mxschmitt mxschmitt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great work! I would merge this probably soon and get rid of type any/as unknown in further iterations.
How big is the confidence in general, that this breaks existing projects @jquense / other maintainers?
Probably since mostly types were added and the tests are passing the risk is not that big in my mind. Maybe bundling / publishing / types issues.

Copy link
Member

@jquense jquense left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking great. I think one thing we should change is probably move the bsPrefix type from the as component type helper and add it to each set of props manually, like we did with proptypes. That would help ensure that the type helpers don't get misused just to add the bs* props

menuProps.alignRight = alignEnd;
(menuProps as any).show = show;
(menuProps as any).close = close;
(menuProps as any).alignRight = alignEnd;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't, but that's not important here, the Component must accept these props, e.g. the interface for whatever Component is, is "accepts at least these props". That said i think that's less important than just correctly typing menuProps so that it's safe to use here not just so Component knows it.

src/DropdownMenu.tsx Outdated Show resolved Hide resolved
src/DropdownItem.tsx Outdated Show resolved Hide resolved
src/DropdownButton.tsx Outdated Show resolved Hide resolved
src/DropdownItem.tsx Show resolved Hide resolved
src/DropdownToggle.tsx Outdated Show resolved Hide resolved
Component: 'figure',
});
}) as unknown) as Figure;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

usually we deal with this by using Object.assign which handles adjusting the type

merge_ts.py Outdated Show resolved Hide resolved
simple-types-test.tsx Outdated Show resolved Hide resolved
type?: 'valid' | 'invalid';
}

type Feedback = BsPrefixRefForwardingComponent<'div', FeedbackProps>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: it's a little confusing, maybe, to have two things e.g. both named Feedback here? maybe we can just inline the type def?

src/NavbarCollapse.tsx Outdated Show resolved Hide resolved
src/Switch.tsx Outdated Show resolved Hide resolved
src/AbstractNav.tsx Outdated Show resolved Hide resolved
>
{children}
</Component>
</SelectableContext.Provider>
</AccordionContext.Provider>
);
});
}) as unknown) as Accordion;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a better way of typing this?

src/AccordionContext.tsx Outdated Show resolved Hide resolved
src/Accordion.tsx Outdated Show resolved Hide resolved
@mxschmitt
Copy link
Member

Hey @akx, would be awesome if you could go over the comments if you have time, because once this is merged, we can start with making it Bootstrap 5 compatible.

@akx akx force-pushed the all-ts branch 3 times, most recently from ad47589 to 4428e9d Compare May 17, 2020 15:14
@akx
Copy link
Collaborator Author

akx commented May 17, 2020

Okay, rebased on origin/master again and upgraded some dependencies required, addressed most if not all review comments...

@mxschmitt: Thank you! I also agree this should be merged sooner rather than later. There will likely be some breakage for both TS and JS consumers since the typing likely isn't 100% watertight, but it won't get fixed without merging this... Also, I think the Gatsby site build is definitely broken right now, and I don't quite have enough Gatsby-fu to look into it.

@jquense: Yeah, there actually is a BsPrefixAndClassNameOnlyProps type now, but that's one unwieldy name! :)

@taion: I was actually surprised TS allows you to name a variable the same as a type, but apparently so! Those confusions could be fixed up in a later PR, yeah?

@jquense, @bpas247: Re the as unknown as any hacks, I think those could be replaced with Object.assigns in a subsequent PR.

@jquense
Copy link
Member

jquense commented May 18, 2020

I think we should merge this into a typescript branch here, for followups and so @akx doesn't have to keep it up to date with master himself. I'm concerned that pushing this right to master will leave us in a bad spot releasing, when i'd like to test this out and do a prerelease for folks to try before we push it out to latest, given the general risk involved. also we need to fix the docs

@jquense jquense changed the base branch from master to typescript May 18, 2020 17:49
@jquense
Copy link
Member

jquense commented May 18, 2020

I changed the base! we can merge once the yarn.lock conflict is fixed

@akx
Copy link
Collaborator Author

akx commented May 19, 2020

I changed the base! we can merge once the yarn.lock conflict is fixed

@jquense Rebased & fixed conflicts!

@jquense jquense merged commit 179fec9 into react-bootstrap:typescript May 19, 2020
@jquense
Copy link
Member

jquense commented May 19, 2020

awesome work @akx Thanks a ton!

@akx
Copy link
Collaborator Author

akx commented May 19, 2020

By the way, I happened upon this TS implementation of ref forwarding: https://github.com/reach/reach-ui/blob/52d721cd643d1a6d24c7253ab24e114be262d092/packages/utils/src/index.tsx#L193-L217

Not sure if it's safer or less so than ours though :)

@akx akx mentioned this pull request May 27, 2020
jquense added a commit that referenced this pull request Jul 6, 2020
* TypeScript all the things (#5150)

* Add Typescript tooling via react-overlays

* Add @types/ via akx/autotypes

* Mostly mechanically merge JavaScript and TypeScript files

* Fix typings in all TS files

* BootstrapModalManager: add types + fix marging -> margin typo

* Amend test and build configuration

* Address some review comments

* Address more review comments

* Move simple-types-test into tests/ and fix things to have it typecheck

* Update dev deps for e.g. Eslint 7+ compat & fix type issues

* chore: fix types build output (#5204)

* Publish v1.1.0-rc.0

* fix up

* lint

* fix defaults

Co-authored-by: Aarni Koskela <akx@iki.fi>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants