Skip to content
This repository has been archived by the owner on Mar 4, 2020. It is now read-only.

feat: add strict type checks for components' props #1290

Merged
merged 54 commits into from
May 13, 2019

Conversation

kuzhelov
Copy link
Contributor

@kuzhelov kuzhelov commented May 6, 2019

Fixes #1263.

BREAKING CHANGES

Restricted type checks introduced by this PR may introduce breaking changes on the client side - however, most certain this will be an indication of problem in client's logic.

However, to mitigate potential migration problems, the following fallback option was introduced, to loose type checks to the same state they were working before.

Fallback option to loose type checks

In the exceptional cases, to mitigate migration risks on the client side, the fallback type was introduced as part of this PR. This type corresponds to the one that clients originally had for Stardust components before this update.

// re-export on client side
import { 
  Stardust_Button, 
  ButtonProps, 
  UNSAFE_TypedComponent, 
  UNSAFE_typed 
} from '@stardust-ui/react'

// either explicit cast
const Button = Stardust_Button as any as UNSAFE_TypedComponent<
  typeof Stardust_Button, 
  ButtonProps
>

// or simpler
const Button = UNSAFE_typed(Stardust_Button).withProps<ButtonProps>()

export Button

Description

Provides strict type checks for Stardust components' props.

Type checks introduced

Examples of type checks introduced are provided with Button component taken, which supports

  • circular: boolean prop
  • formEncType: string prop from the interface of intrinsic button element, which is rendered by default as Button's element type.

No 'as' prop defined (regular case)

<Button />

<Button circular />
<Button circular={13} />  // BAD, type mismatch (number instead of boolean)

<Button formEncType='...' />  // fine, as 'formEncType: string' is a prop of intrinsic 'button'
<Button formEncType />  // BAD, type mismatch (boolean instead of string)

<Button asdasdasd />  // BAD, unknown prop 'asdasdasd'

With 'as' prop defined

<Button as={'h1'} />

// fine, ANY prop is allowed in addition to Button's ones. 
// this is what we had before for all the cases of Stardust components' usage
<Button as={'h1'} asdasdasd />   

// checks for ButtonProps are preserved
<Button as={'h1'} circular={13} />   // BAD, type mismatch (number instead of boolean)

Type checks for as case were intentionally loosened, to avoid heap out-of-memory problem of TS compiler (more on that below). However, even with that provided type system was sufficient enough to capture all the same type problems in Stardust codebase that would be discovered by the strictest type-check solution, and generally is expected to prevent most of type problems on the client side.

Stardust problems discovered (just files list, for brevity)

  • docs/src/components/ComponentDoc/ComponentControls/ComponentControls.tsx
  • docs/src/components/ComponentDoc/ComponentDocSee.tsx
  • docs/src/components/ComponentDoc/ComponentExample/ComponentExample.tsx
  • docs/src/components/ComponentDoc/ComponentSidebar/ComponentSidebar.tsx
  • docs/src/components/ComponentDoc/ComponentSidebar/ComponentSidebarSection.tsx
  • docs/src/examples/components/ItemLayout/Types/ItemLayoutExampleSelection.shorthand.tsx
  • docs/src/examples/components/Portal/Types/PortalExample.shorthand.tsx
  • docs/src/examples/components/Portal/Types/PortalExample.tsx
  • docs/src/examples/components/Portal/Types/PortalExampleControlled.shorthand.tsx
  • docs/src/examples/components/Portal/Types/PortalExampleControlled.tsx
  • docs/src/examples/components/Portal/Types/PortalExampleFocusTrapped.shorthand.tsx
  • docs/src/examples/components/Portal/Types/PortalExampleFocusTrapped.tsx
  • docs/src/prototypes/AsyncShorthand/AsyncShorthand.tsx
  • docs/src/prototypes/IconViewer/index.tsx
  • docs/src/prototypes/chatPane/chatPaneContent.tsx
  • docs/src/prototypes/chatPane/services/messageFactoryMock.tsx
  • docs/src/views/PageNotFound.tsx
  • docs/src/views/QuickStart.tsx
  • packages/react/src/components/Attachment/Attachment.tsx
  • packages/react/src/components/Chat/ChatMessage.tsx
  • packages/react/src/components/Dropdown/DropdownItem.tsx
  • packages/react/src/components/Dropdown/DropdownSearchInput.tsx
  • packages/react/src/components/Provider/Provider.tsx
  • packages/react/src/components/Provider/ProviderBox.tsx
  • build/gulp/tasks/test-projects/cra/App.tsx
    • scaffolding file for create-react-app test project

Additional cleanup

  • obsolete ...WithDefault types were removed from: Divider, ItemLayout, Layout
  • types for components that doesn't support as prop by design were updated: Provider, Popup, Portal, PortalInner

Main type files changed

  • packages/react/src/lib/commonPropInterfaces.ts
  • packages/react/src/types.ts

Notes on the approach

The following things were taken into account in the solution provided.

Avoid 'heap out of memory' problem from TS Compiler

In order to prevent cause of this TS compiler's problem (which repros even for the latest to date, v3.4.5 of TS compiler), it was necessary to loose type checks for the cases when as prop is explicitly specified by client's code.

However, even with that provided type system was sufficient enough to capture all the same type problems in Stardust codebase that would be discovered by the strictest type-check solution, and generally is expected to prevent majority of type problems on the client side.

Introduced types don't cause 'heap out of memory' problem for TS compiler - as component type overloads do not cause deep recursion like the following one, while processed by TS engine:

image

Explicit prop type declarations in component types

Exported components are wrapped in withSafeTypeForAs type that requires type of component props to be explicitly provided in generic args:

export default withSafeTypeForAs<typeof Button, ButtonProps, 'button'>(Button)

While component prop types could be inferred, this approach was taken because of its huge performance benefits introduced to TS Server, as it helps to avoid unnecessary inference and significantly reduce load on TS server. With that, we are making things explicit only once while exporting component, and reap performance benefits of rapid TS checks every time it is used.

These are comparison metrics between 'solution with inference' and 'solution with no inference'

Metric With Props Inference With Explicit Props
reaction from IDE (its TS server) on key press while typing prop name 8-10s 0.5-1s
yarn build time 4.82 - 5 min 2.8-2.98 min

Non-related problems spotted

@kuzhelov kuzhelov changed the title [DO NOT REVIEW] add strict type checks for components props [DO NOT REVIEW] add strict type checks for components' props May 6, 2019
@codecov
Copy link

codecov bot commented May 7, 2019

Codecov Report

Merging #1290 into master will decrease coverage by 0.03%.
The diff coverage is 80%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1290      +/-   ##
==========================================
- Coverage   72.36%   72.32%   -0.04%     
==========================================
  Files         758      759       +1     
  Lines        5684     5692       +8     
  Branches     1663     1687      +24     
==========================================
+ Hits         4113     4117       +4     
- Misses       1565     1569       +4     
  Partials        6        6
Impacted Files Coverage Δ
packages/react/src/components/Header/Header.tsx 100% <ø> (ø) ⬆️
packages/react/src/components/Label/Label.tsx 95.23% <ø> (ø) ⬆️
packages/react/src/components/Flex/Flex.tsx 100% <ø> (ø) ⬆️
packages/react/src/components/Divider/Divider.tsx 100% <ø> (ø) ⬆️
packages/react/src/components/Flex/FlexItem.tsx 78.57% <ø> (ø) ⬆️
packages/react/src/components/Menu/MenuDivider.tsx 100% <ø> (ø) ⬆️
packages/react/src/components/Image/Image.tsx 100% <ø> (ø) ⬆️
packages/react/src/components/Embed/Embed.tsx 94.44% <ø> (ø) ⬆️
packages/react/src/components/Tree/TreeTitle.tsx 10% <ø> (ø) ⬆️
packages/react/src/components/Tree/Tree.tsx 55.17% <ø> (ø) ⬆️
... and 53 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update c9ec02c...cf7c415. Read the comment docs.

docs/src/app.tsx Outdated Show resolved Hide resolved
@layershifter
Copy link
Member

It's not critical, but it seems that autocomplete is not working 😭

not-work

@kuzhelov
Copy link
Contributor Author

It's not critical, but it seems that autocomplete is not working 😭

This doesn't work for VSCode at all for aria- attributes, thus would skip this problem as the one that is IDE-related. At the same time, type checks work correctly, and this is the most important thing there.

Copy link
Member

@layershifter layershifter left a comment

Choose a reason for hiding this comment

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

I don't want to block these changes because I see benefits from them.

However I am thinking that changes in run-time code to satisfy TypeScript is something evil 💣

@kuzhelov
Copy link
Contributor Author

However I am thinking that changes in run-time code to satisfy TypeScript is something evil

would want to avoid them as well, but for now, given that we have class components and there the only option are generics, as well as that it is impossible to introduce function overload semantics with regular TS types, I don't see any better approach there - and noop runtime tricks is the only way to deal with that.

Also, in case if solution will be found (introduced by TS), it will be quite easy for us to adopt it, as this type is applied consistently to all components, so we might be able to introduce necessary fixes to it.

{element}
</Provider>
)
return <Provider theme={newTheme}>{element}</Provider>
Copy link
Member

Choose a reason for hiding this comment

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

This breaks the possibility to render examples on transparent background:
image

Copy link
Contributor Author

Choose a reason for hiding this comment

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

got it - the thing is that this variables prop was missing from the Provider's API, and that has led me to confusion. Will address it

@kuzhelov kuzhelov merged commit bca7430 into master May 13, 2019
@delete-merged-branch delete-merged-branch bot deleted the feat/strict-component-types-alt branch May 13, 2019 11:35
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Props type safety
4 participants