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

Add support for Typescript to carbon/react #12222

Merged
merged 13 commits into from
Nov 1, 2022

Conversation

mbarrer
Copy link
Contributor

@mbarrer mbarrer commented Oct 4, 2022

Closes #11587
Closes #12654
Closes #12655
Closes #12656
Closes #12657

This PR adds support for compiling typescript files in the carbon/react package allowing us to begin converting components to typescript. As a proof of concept, Checkbox.js and all its imports have now been converted to typescript

Changelog

New

  • @babel/preset-typescript added to rollup and storybook builds to transpile ts

    • Generates .js files to the output directory
  • @typescript-eslint/eslint-plugin and @typescript-eslint/parser added for linting

  • @rollup/plugin-typescript being used to perform typechecking on ts files

    • This plugin will only output .d.ts files to the output directory, it does not handle generating .js (using emitDeclarationOnly: true compiler option)
  • Added TS extensions and @babel/preset-typescript to Jest config

  • Added types/common.d.ts with a newer typing for PolymorphicProps (components that use 'as' props to control the root node. additional explaination)

  • added types/globals.d.ts as a place to declare workspace globals

Changed

  • @types/react and @types/prop-types have been moved to resolutions and pinned to prevent pulling in types for react 18
  • Text.js, Checkbox.js and all their related components have been converted to TS

Testing / Reviewing

These changes can be tested and verified by running a fresh yarn, yarn build and yarn storybook. Verify that the behavior in both the Checkbox and Text stories remain unchanged

@mbarrer mbarrer requested a review from a team as a code owner October 4, 2022 14:16
@netlify
Copy link

netlify bot commented Oct 4, 2022

Deploy Preview for carbon-components-react ready!

Built without sensitive environment variables

Name Link
🔨 Latest commit 1b090eb
🔍 Latest deploy log https://app.netlify.com/sites/carbon-components-react/deploys/636040b71afc4b0009f68980
😎 Deploy Preview https://deploy-preview-12222--carbon-components-react.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site settings.

@netlify
Copy link

netlify bot commented Oct 4, 2022

Deploy Preview for carbon-elements ready!

Name Link
🔨 Latest commit 1b090eb
🔍 Latest deploy log https://app.netlify.com/sites/carbon-elements/deploys/636040b7682f360008d0a040
😎 Deploy Preview https://deploy-preview-12222--carbon-elements.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site settings.

Copy link
Member

@tay1orjones tay1orjones left a comment

Choose a reason for hiding this comment

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

Thanks for this! Here's a few baseline thoughts from my first pass through.

package.json Show resolved Hide resolved
@@ -92,6 +92,9 @@ module.exports = {
},
framework: '@storybook/react',
stories,
typescript: {
reactDocgen: 'react-docgen', // Favor docgen from prop-types instead of TS interfaces
Copy link
Member

Choose a reason for hiding this comment

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

I was going to ask about this. I agree and think we should favor the docgen from prop-types for now. Maybe once things are fully converted we could evaluate using the TS interfaces instead.

/**
* Specify a title for the <label> node for the Checkbox
*/
title: PropTypes.string,
Copy link
Member

Choose a reason for hiding this comment

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

This one doesn't have a type supplied in the interface above. Is that intentional?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Correct this is intentional. The reason is because since we are doing the spread of ...others on the input element I have decided to have the interface extend all the native props of an HTMLInputElement using React.InputHTMLAttributes<HTMLInputElement>. We omit some native props that we are overriding/providing stricter types for

Comment on lines 11 to 18
export interface TextDirectionContextData {
direction?: TextDir;
getTextDirection?: MutableRefObject<GetTextDirection | undefined>;
}

export const TextDirectionContext = createContext<TextDirectionContextData>({});
export const useTextDirectionContext = () =>
useContext<TextDirectionContextData>(TextDirectionContext);
Copy link
Member

Choose a reason for hiding this comment

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

IIUC the change here from the original file is largely because we need to have typings for everything that is placed within the context. Is that correct? It's a big stylistic change that we'll need to make for any place we use context. Tabs in particular could be a complicated refactor. Without this change does compilation fail, or is it just a warning?

I'm not sure if initializing the context to an empty object instead of null has any side effects. I can't immediately think of any though.

Copy link
Contributor Author

@mbarrer mbarrer Oct 6, 2022

Choose a reason for hiding this comment

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

Yes this is correct, the refactor here was so that we can have all the types for the context in one place. The addition of the useTextDirectionContext was to provide a convenience method for the useContext so we didnt have to manually pass the typing to context every time. The empty object instantiation here is from a pattern we use over in fusion-ui, since this context isnt really used too widely I was able to replace the null here with just an empty object. For other contexts that have wider usages, we can probably just incorporate null as a valid type for the contexts to prevent bigger refactors

Copy link
Contributor

Choose a reason for hiding this comment

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

If we want to go more conservatively here, we could "not" do types for the context and then just use any here (or something similar).

If the goal is to provide types externally for carbon/react, then this conversion of the context doesn't actually help that goal (I'm assuming this context isn't exposed externally).

I know it's tempting to type everything that's encountered along the way, but that's likely not in the best interest of the Carbon System Squad right now until the team gets more comfortable developing in typescript.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

A type is required for the useContext otherwise when its used we get an object with an any type and have no idea what properties are valid for this context

Copy link
Contributor

Choose a reason for hiding this comment

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

I definitely get that, and in a totally typescript product I'd 100% agree this is the correct approach, but that's unfortunately not the state of carbon/react ...yet!

My argument is that the lack of typing on that context is no worse than what is currently present in the JS codebase. The existing code functions as-is without types. I definitely believe that converting all the things is a great eventual goal, but I don't believe that now is necessarily the right time for that.

In a general sense, what the System squad has the capacity to take on at this point maintenance and new-development-wise is the introduction of type interfaces at the externally-exposed component level, but not anything beyond that at this time. That's why I'm erring more on the side of caution with respect to the scope of things we convert to typescript out of the gate.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Im a little confused, are you suggesting that we not convert this file to TS at all or just remove the interface and change it to any? Im not sure how the addition of the interface is going to cause confusion in the JS code, I would actually think that it helps set the expectations on what we are able to access and set. Are we concerned that a developer might not necessarily know that they have to add a prop to the interface if we wanted to add something new to a context?

Copy link
Contributor

@erifsx erifsx Oct 7, 2022

Choose a reason for hiding this comment

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

@jdharvey-ibm Are you saying you'd rather

export interface TextDirectionContextData {
  direction?: TextDir;
  getTextDirection?: MutableRefObject<GetTextDirection | undefined>;
  [key: string]: any;
}

So they can pass in additional unknown arguments?

Copy link
Contributor

Choose a reason for hiding this comment

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

@mbarrer sorry I missed your comment otherwise I would have responded sooner!

I'd recommend we don't convert any files to TS unless they are an exported component and are part of the public interface of the carbon/react package. In this case, that would mean leaving the TextDirectionContext file as a JS file and asserting the type of that context in places where it is used as any or something else that's equally basic.

You're 100% correct that it's ambiguous what properties exist on the context and that it is (generally speaking) an improvement to the codebase to assign types to that context and other non-component files.

But with that said, the goal of this particular work item is to get component types to the community and to get them out there as quickly as possible. Thereby unblocking the transition to v11 and all its goodies. Making changes to any non-external-component file is useful, but it unfortunately doesn't expedite that goal.

Once we get the initial typings done, a great next step would be to start improving on these sorts of things to make the internals more bullet-proof.

Copy link
Contributor

@erifsx erifsx left a comment

Choose a reason for hiding this comment

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

This looks great to me!

Copy link
Member

@tay1orjones tay1orjones left a comment

Choose a reason for hiding this comment

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

Thanks for making those changes!

@kodiakhq
Copy link
Contributor

kodiakhq bot commented Oct 28, 2022

This PR currently has a merge conflict. Please resolve this and then re-add the status: ready to merge 🎉 label.

@gi
Copy link
Contributor

gi commented Nov 3, 2022

With this merged, will the team start or like help converting component files to Typescript?

@tay1orjones
Copy link
Member

@gi sorry for the delayed response - yes! I've begun organizing an umbrella issue #12513 containing all the pieces needed for this so we can track overall status on component-by-component basis.

I'll be putting out a formal call for contributions soon. We're still ironing out some kinks, #12600

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Archived in project
7 participants