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 refactor #959

Merged
merged 29 commits into from
Jan 15, 2021
Merged

TypeScript refactor #959

merged 29 commits into from
Jan 15, 2021

Conversation

colebemis
Copy link
Contributor

@colebemis colebemis commented Jan 12, 2021

Based off of @BinaryMuse's original TypeScript PR: #845

Summary

This PR lays the groundwork for an incremental TypeScript refactor of Primer React components.

Impact

From #953:

Refactoring our codebase in TypeScript will make our library less bug-prone by enforcing type safety in the implementation of our components, reduce the number of issues we face with the manual maintenance of our index.d.ts file, and make upstreaming components from other projects at GitHub written in TS much much faster allowing us to greatly improve our contributor experience.

Technical details

Here are the infrastructure changes that were needed to support TypeScript:

Compilation (Babel)

I configured Babel to use the @babel/preset-typescript preset, which allows Babel to compile .ts and .tsx files.

Linting (ESLint)

I configured ESLint to used the @babel/eslint-parser parser, which allows ESLint to parse .ts and .tsx files.

Testing (Jest)

I installed Jest type declarations (@types/jest), which provides type annotations in the test files.

Documentation (Gatsby)

I configured the documentation site to use gatsby-plugin-typescript, which allows us to import .ts and .tsx files into documentation site files.

Type checking and type declarations

I added a yarn dist:types script to type check and generate type declaration files (.d.ts) for every TypeScript file.

How to migrate a component to TypeScript

The migration process for each component may vary, but it will likely follow these high-level steps:

1. Change the file extension from .js to .tsx

src/Box.js → src/Box.tsx

2. Add type annotations to the component file

Here are type annotations for the Box component:

  // src/Box.tsx
+ const Box = styled.div<CommonProps & FlexProps & LayoutProps & SxProps>`
    ${COMMON}
    ${FLEX}
    ${LAYOUT}
    ${sx}
  `
+ export type BoxProps = React.ComponentProps<typeof Box>
  export default Box

Most Primer React components will follow a similar pattern. Each component should export a ___Props type in addition to the default export.

ℹ️ Note: After doing some research on types vs interfaces, I propose we use types (instead of interfaces) for all component props. In addition to the reasons discussed in this article, types allow us to use React.ComponentProps<> to generate prop type definitions for styled components.

Question: How can we programmatically enforce that every component exports a ___Props type?

3. Change the file extension of tests

src/__tests__/Box.js → src/__tests__/Box.tsx

Next steps

Short term

  • Rewrite all existing components in TypeScript

Long term (after TypeScript refactor is complete)

  • Remove manually maintained index.d.ts
  • Remove propTypes
  • Generate prop documentation using react-docgen-typescript

@vercel
Copy link

vercel bot commented Jan 12, 2021

This pull request is being automatically deployed with Vercel (learn more).
To see the status of your deployment, click below or on the icon next to each commit.

🔍 Inspect: https://vercel.com/primer/primer-components/dv9vvn1wh
✅ Preview: https://primer-components-git-colebemis-ts-refactor.primer.now.sh

@vercel vercel bot temporarily deployed to Preview January 12, 2021 20:49 Inactive
@vercel vercel bot temporarily deployed to Preview January 12, 2021 21:19 Inactive
@vercel vercel bot temporarily deployed to Preview January 12, 2021 22:46 Inactive
src/utils/types.ts Outdated Show resolved Hide resolved
@vercel vercel bot temporarily deployed to Preview January 12, 2021 23:03 Inactive
Copy link
Contributor

@dmarcey dmarcey left a comment

Choose a reason for hiding this comment

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

Wow, @colebemis this is looking great! I like the incremental approach, so that we don't have to have a single, massive migration PR and can hopefully fan the work out a bit.

I left a few comments in-line and also have a few questions / suggestions here.

  • Is the idea that we will eventually get rid of the index.d.ts file and just replace it with a top-level index.ts that exports all of our components / interfaces? If so, maybe we don't need to worry about

❓ Question: How can we programmatically enforce that every component exports a ___Props type?
too much? Because that index.ts would hopefully be a pretty straight-forward
export Box, {BoxProps from './Box';
and we could probably spot issues fairly quickly?
Another thought is somehow using some sort of doc auto-generation based on the *Props for the doc site. If these were somehow set up to target the *Props then it would ensure that they existed and were exported. I know this is kind of hand-wavy as I don't know exactly how to do this, but it was just a thought!

  • I wonder if converting all of the tests to TS sooner rather than later would be a beneficial incremental step? Now that we have the infrastructure, it seems like it would be safe and straight-forward, and also might help anticipate any trickier gotchas we might encounter along the way.

Overall this is amazing and I'm super excited to see this work!

src/constants.ts Outdated Show resolved Hide resolved
Comment on lines +24 to 25
export type BoxProps = React.ComponentProps<typeof Box>
export default Box
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it worth re-considering using default exports as well? It is probably a breaking change if anyone was consuming directly from @primer/components/lib/Box or something like that, but if we're going to be making other breaking changes, it might be a fair time to consider it.

I'm not really aware of the main pros/cons of default exports, and it seems like in other code at GitHub we're inconsistent on this already, so maybe it's not even worth looking at 😅

For example, https://github.com/github/details-dialog-element/blob/main/src/index.ts uses a default export, and web components in the dotcom codebase itself seem to be about 50/50.

Just thought I'd mention it to see if it sparks thoughts for others.

Copy link
Contributor

Choose a reason for hiding this comment

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

Generally I prefer not to use default exports. Here is a good article about it: https://basarat.gitbook.io/typescript/main-1/defaultisbad

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm open to moving away from default exports for our components. A few thoughts:

  • If we decide to remove default exports, I'd prefer to do that in a separate PR that updates all the exports of all the components at once. This would allow consumers to update all their imports at once.
  • How do we ensure consistency across the codebase? Is there an existing linting rule for this?

Copy link
Contributor

Choose a reason for hiding this comment

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

package.json Outdated Show resolved Hide resolved
src/constants.ts Outdated Show resolved Hide resolved
src/sx.ts Show resolved Hide resolved
@vercel vercel bot temporarily deployed to Preview January 14, 2021 19:51 Inactive
@vercel vercel bot temporarily deployed to Preview January 14, 2021 20:44 Inactive
@vercel vercel bot temporarily deployed to Preview January 14, 2021 21:15 Inactive
Copy link
Contributor

@dmarcey dmarcey left a comment

Choose a reason for hiding this comment

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

Great job, @colebemis ! Thanks for responding to all of the feedback!
I'm also on board with making the export default changes a separate scope of work, even if we possibly wrap it up in a similar major version release?

I think this is good to 🚢 !

Copy link

@emplums emplums left a comment

Choose a reason for hiding this comment

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

Approving but lets make sure to resolve the TS errors we were seeing in Memex before merging :)

One question: will changing interfaces to types cause breaking changes in consuming applications? I know sometimes in Memex they import things like SelectMenuProps directly so they can extend them

Copy link
Contributor

@T-Hugs T-Hugs left a comment

Choose a reason for hiding this comment

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

This is awesome - long time coming! Great job!

@colebemis
Copy link
Contributor Author

@emplums and I found that the location of the Box import statement in index.d.ts was causing type errors in the Memex codebase. Specifically, importing before the module declaration was causing issues:

image
image

However, moving the import inside the module declaration causes an error in the Primer React components codebase:

image

@T-Hugs @dmarcey Any ideas about why this might be happening and how to fix it?

@vercel vercel bot temporarily deployed to Preview January 15, 2021 17:06 Inactive
@T-Hugs
Copy link
Contributor

T-Hugs commented Jan 15, 2021

My recommendation is to revert to shipping exclusively the manually-created index.d.ts until the full TypeScript conversion is complete, assuming that will be a relatively fast-follow.

@vercel vercel bot temporarily deployed to Preview January 15, 2021 17:09 Inactive
@colebemis colebemis merged commit c42474f into main Jan 15, 2021
This was referenced Jan 15, 2021
@colebemis colebemis deleted the colebemis/ts-refactor branch October 29, 2021 00:44
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.

4 participants