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

Upgrade Typescript to 4.5.3 to match Kibana #5591

Merged
merged 18 commits into from
Feb 7, 2022
Merged

Conversation

cee-chen
Copy link
Member

@cee-chen cee-chen commented Feb 2, 2022

Summary

Kibana is currently pinned at Typescript 4.5.3 (elastic/kibana#120812), so we should upgrade EUI to match (which also allows us to upgrade react-docgen-typescript to 2.x+, as their 2.x+ requires typescript 4.3+).

Checklist

  • Props have proper autodocs and playground toggles
  • Checked for breaking changes and labeled appropriately
  • A changelog entry exists and is marked appropriately

- to match Kibana
Property 'MSInputMethodContext' does not exist on type 'Window & typeof globalThis'
Error: 'onClick' is specified more than once, so this usage will be overwritten.
Errors:
- Initializer provides no value for this binding element and the binding element has no default value.
- Binding element '_onClick' implicitly has an 'any' type.

Fixes:
- Fix primary type issue, which was using `...` instead of `|| {}`
- Remove unnecessary `_onClick` cast
- Optional switch to optional chaining instead of && check
Error:
- This condition will always return true since this function is always defined. Did you mean to call it instead?

Fix:
- [EuiPinnableListGroup] Remove unnecessary check, pinnable serves that purpose
- [EuiOverlayMask] Remove unnecessary check, there's already an early return at the beginning of the block
- [extra] DRY out portalTarget var - it's being used interchangably with overlayMaskNode.current
Errors:
- Object is of type 'unknown'.
- Type 'unknown' is not assignable to type 'EuiMarkdownParseError | null'

see https://stackoverflow.com/questions/68240884/error-object-inside-catch-is-of-type-unkown

We could optionally disable `useUnknownInCatchVariables`, but I think it also does make sense to check for a valid Error instance if we're going to dive into it.
package.json Show resolved Hide resolved
Comment on lines +172 to +175
const error: Error =
e instanceof Error
? { name: e.name, message: e.message }
: { name: 'Unexpected error', message: String(e) };
Copy link
Member Author

@cee-chen cee-chen Feb 2, 2022

Choose a reason for hiding this comment

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

Obligatory https://stackoverflow.com/a/68257472/4294462: In TS 4.4+ caught errors default to unknown instead of any. We could disable this via useUnknownInCatchVariables in tsconfig.json, but honestly I think it's a legit change and it's worth checking for a valid Error instance since something like throw 'foo' is an allowed error. LMK if you have other thoughts however!

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe check for 'message' in e - possibly with a type guard to catch some non-error-but-still-compatible objects?

Copy link
Member Author

@cee-chen cee-chen Feb 2, 2022

Choose a reason for hiding this comment

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

hm, I'd be more tempted to JSON.stringify() a custom error object with a message key - that way the user can always get the full error no matter what. This seems like it might be worth DRYing out a helper that checks for instanceof Error and tries to return a valid Error type if not. WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

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

Probably too engineery at that point, and leaving as-is makes more sense? 🙂

Copy link
Member Author

Choose a reason for hiding this comment

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

Fair enough! We can keep it in our back pockets for later if we add more try/catches

@kibanamachine
Copy link

Preview documentation changes for this PR: https://eui.elastic.co/pr_5591/

- it appears to have changed as of the recent upgrade, this keeps all props that output `ReactElement` the same as before
- Was previously working, with, e.g. `number | SomeStringEnum`, now needs a config to restore functionality
@kibanamachine
Copy link

Preview documentation changes for this PR: https://eui.elastic.co/pr_5591/

- it's causing too many other unintended side effects (like turning `|`s into `,`s. There's probably no working around `number | SomeStringEnum` without stating the enum, it'll have to be a props doc changing going forward
@kibanamachine
Copy link

Preview documentation changes for this PR: https://eui.elastic.co/pr_5591/

- enum no longer appears to be extracted by react-docgen
@kibanamachine
Copy link

Preview documentation changes for this PR: https://eui.elastic.co/pr_5591/

- appears to have also changed/shortened since the upgrade
@kibanamachine
Copy link

Preview documentation changes for this PR: https://eui.elastic.co/pr_5591/

It appears as
```
string | number | boolean | {} | ReactElement | ReactNodeArray | ReactPortal
```
in some places, and
```
string | number | boolean | {} | ReactElement | ReactNodeArray | ReactPortal | ({} & string) | (ReactElement<...> & string) | (ReactNodeArray & string) | (ReactPortal & string)
```
in others - this regex should capture both scenarios
- string enums within objects no longer appear to be expanded
@kibanamachine
Copy link

Preview documentation changes for this PR: https://eui.elastic.co/pr_5591/

- EuiButtonDisplay was bogarting the docgenInfo for the file, apparently - moving its declaration below EuiButton and giving EuiButton its a displayName seems to be required to get its docgenInfo working
@cee-chen
Copy link
Member Author

cee-chen commented Feb 4, 2022

OK - I've checked the auto generated props table for all pages/components in our docs 💦 TL;DR: Not all props types changed, but the ones that did were generally for the better/were more concise. Here's a list of bigger changes:

  1. In general the upgrade seems to have caused less extraction/expansion for string enums in unions or objects. In some cases this is a significant improvement, for example EuiDataGrid:
Before:

((props: EuiDataGridCellValueElementProps) => ReactElement
<any, string | 
((props: any) => ReactElement
<any, string | ... | 
(new (props: any) => Component
<any, any, any>)>) | 
(new (props: any) => Component
<...>)>) | 
(new (props: EuiDataGridCellValueElementProps) => Component
<...>) | ((props: EuiDataGridCellValueEleme...

After:

JSXElementConstructor<EuiDataGridCellValueElementProps> | 
((props: EuiDataGridCellValueElementProps) => ReactNode)

And another example:

Before:

string | number | boolean | {} | ReactElement<any, string | 
((props: any) => ReactElement
<any, string | ... | 
(new (props: any) => Component
<any, any, any>)> | null) | 
(new (props: any) => Component
<...>)> | ... 4 more ... | undefined

After:

ReactNode | EuiDataGridToolBarAdditionalControlsOptions

However, in other cases the extra brevity unfortunately leads to less clear docs, e.g. in EuiBasicTable:

Before:
{ field: keyof T; direction: "asc" | "desc"; }

After:
{ field: keyof T; direction: Direction; }

or in EuiFlyout:

Before:
number | "xs" | "s" | "m" | "l" | "xl"

After:
number | EuiBreakpointSize
  1. Interestingly enough, in the case of numeric enums it appears the props docs now do a better job of expanding them (see EuiLoadingContent:
Before:
LineRange

After:
1, 2, 3, 4, 8, 5, 6, 7, 9, 10
  1. It also appears certain Foo<> types are no longer being expanded. For example, some event callbacks:
Before:
(event: MouseEvent<HTMLButtonElement, MouseEvent>) => void

After:
MouseEventHandler<HTMLButtonElement>

However, in other cases like Ref<> this is a readability improvement:

Before:
((instance: HTMLButtonElement) => void)
 | RefObject<HTMLButtonElement> | 
((instance: HTMLAnchorElement) => void)
 | RefObject<...>

After:
Ref<HTMLButtonElement> | Ref<HTMLAnchorElement>
  1. Interestingly enough any CSSProperties in our props became more verbose with an extra *Property<> wrapper. Take this example for CSSProperties['height']:
Before
string | number

After
HeightProperty<string | number>
  1. Omit<> types now prints as-is instead of attempting to Pick<> the non-omitted keys. For example in EuiKeyPadMenu:
Before:
Partial<Pick<EuiToolTipProps, "id" | "children" | "position" | "className" | "onMouseOut" | "display" | "anchorClassName">>

After:
Partial<Omit<EuiToolTipProps, "title" | "content" | "delay">>
  1. Another change I noticed with the upgrade was that certain type references with generics are now correctly expanded, for example EuiSideNav:
Before:
EuiSideNavProps<T>["className"]
EuiSideNavProps<T>["renderItem"]

After:
string
RenderItem<T>
  1. The props table now correctly handles React.* types without them needing to be destructured. Examples can be found in EuiTreeView:
Before:
any

After:
React.ReactElement
React.ReactNode
React.Ref<>
React.MouseEvent
  1. Also worth mentioning, some default values now appear to be properly rendering where they weren't before (e.g. in EuiMarkdownEditor and EuiProvider) which is great and a noted bugfix in react-docgen-typescript's release notes.

@kibanamachine
Copy link

Preview documentation changes for this PR: https://eui.elastic.co/pr_5591/

/>
);
};
EuiButton.displayName = 'EuiButton';
Copy link
Member Author

Choose a reason for hiding this comment

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

The rundown on this "fix" for EuiButton's props tables - without dede73b, EuiButton's props table errors with docgenInfo is undefined:

This was the only component with this issue that I saw while I was checking every docs page. EuiButtonEmpty, EuiButtonIcon, and EuiButtonGroup did not have this issue. EuiSkipLink had almost the exact same typing (ExclusiveUnion for anchor or button refs) and did not have this issue - a heavy hint that the problem did not lie with the actual types of EuiButton.

Eventually, thanks to Chandler's SUPER helpful tips earlier on how to debug scripts/babel/react-docgen-typescript, I was able to figure out that docgenInfo was, for some reason, only being generated for the internal EuiButtonDisplay component also declared within the same file. So I experimented with temporarily moving it out of the file, and boom. EuiButton's props table started working again.

I could have moved EuiButtonDisplay out to its own button_display file (and still can, if people prefer) but opted for the fix with the least diffs. For whatever reason, if I moved EuiButtonDisplay down on the page and also gave EuiButton a displayName, that was enough for react-docgen-typescript to look at EuiButton first and ignore EuiButtonDisplay. 🙈

EDIT: Aha, after digging more through react-docgen-typescripts, it looks like this is a known issue on their end that was introduced in 2.1.0: styleguidist/react-docgen-typescript#395

@chandlerprall any thoughts on whether we should document this limitation for now until react-docgen-typescript has a fix?

Copy link
Member Author

@cee-chen cee-chen Feb 4, 2022

Choose a reason for hiding this comment

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

I updated our component development wiki: 97a5cd8

With all that, I think this PR is ready to go if we're OK with all of the above type changes, and if someone else could take a quick second QA pass through this PR's staging server to check that there are no other obviously broken prop tables.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ouch, that's a rough bug. Thanks for updating the wiki page, should be enough to help track this and point people to if/when needed.

- Add warning about displayName bug found in elastic@dede73b#r799165839

- useful links to our custom react-docgen config/filters + react's docs

- lint misc typos/grammar
@kibanamachine
Copy link

Preview documentation changes for this PR: https://eui.elastic.co/pr_5591/

Copy link
Contributor

@thompsongl thompsongl left a comment

Choose a reason for hiding this comment

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

This all looks great! Quickly looked through all the props tables, mostly to ensure no errors.

Not all props types changed, but the ones that did were generally for the better/were more concise

Agreed!

However, in other cases the extra brevity unfortunately leads to less clear docs

I think we're ok to make per-case judgment calls on which enum style to use 👍

Also worth mentioning, some default values now appear to be properly rendering where they weren't before

This is probably my favorite improvement

any thoughts on whether we should document this limitation for now until react-docgen-typescript has a fix?

I'm good with the documentation addition. It's rare that we're declaring multiple components in a file, let alone multiple with displayName declarations.

Copy link
Contributor

@chandlerprall chandlerprall left a comment

Choose a reason for hiding this comment

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

Changes LGTM, didn't try to break anything

@cee-chen
Copy link
Member Author

cee-chen commented Feb 7, 2022

Woohoo! Thanks y'all. Merging.

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

Successfully merging this pull request may close these issues.

4 participants