Skip to content

Conversation

@ghost
Copy link

@ghost ghost commented Mar 18, 2021

Resolves #709
Impact: major
Type: feature

Issue

DX Suffers without TypeScript

Solution

Add strict TypeScript support

Breaking changes

None, though I've changed the code style a little to use functional components, added Prettier to the mix and disabled some ESLint rules in TypeScript files. If we're going to discuss code style now would be a good time. Setting some established patterns now will help others who plan to perform a TypeScript conversion as they fork their Storefronts.

Configuration

Configure VSCode to use Prettier for TypeScript files by opening the Command Palette (Cmd+Shift+P), opening the Settings JSON file and adding:

"editor.formatOnSave": true,
"[typescript]": {
  "editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
  "editor.defaultFormatter": "esbenp.prettier-vscode"
},

Then run ext install esbenp.prettier-vscode to install it.

Testing

  1. Checkout this branch
  2. Run yarn to install dependencies
  3. Run the app
  4. Confirm header looks the same as before
  5. Run yarn type-check from the command line
  6. Confirm there are exactly 0 type issues
  7. Review Header.tsx
  8. Confirm it a functional component written in type-script
  9. Open the file in VSCode
  10. Confirm no ESLint issues appear in the file
  11. Observe the TODO item for Link component
  12. Attempt to add a MUI component anywhere in the JSX before it's imported
  13. Observe VSCode IntelliSense is working and the import statement is added automatically

/cc @janus-reith @nnnnat

@@ -0,0 +1 @@
declare module "@reactioncommerce/components/ShopLogo/v1";
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we need this?

Copy link
Author

Choose a reason for hiding this comment

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

This prevents a type error using modules from the component library. This will eventually become a list of items as they are used from the lib, each added when it becomes used. Not the most straight-forward thing but to me sane until there's a better approach (I haven't found one yet).

Screen Shot 2021-04-01 at 6 46 02 PM

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ok yes I was also expecting this to become a larger list at some point and therefore wondered if we couldn't avoid that somehow. But IMHO not a blocker, something that could easily be refactored later on.

viewer: any;
}

const Header: FC<HeaderProps> = ({ classes, shop, uiStore }) => {
Copy link
Collaborator

@janus-reith janus-reith Mar 23, 2021

Choose a reason for hiding this comment

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

I'd like to discuss the general style we adopt here:
Typescript could infer these, should we utilize that and reduce boilerplate, or do we want to be explicit all the time?
Also, FC seems to add no value so we could use const Header = ({ classes, shop, uiStore }: HeaderProps) which I would prefer personally.
I don't want to pick on this specific one as it's just an implementation example, but it's style will potentially be adopted in other places aswell.

Copy link
Author

Choose a reason for hiding this comment

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

My thinking behind use of Type Parameters is to identify type immediately when looking at a function without having parse my eye parse over an argument and possible default params before getting to it, e.g.

const Header = ({
  classes: { toolbar, title, controls, appBar },
  shop: { name = "cmVhY3Rpb24vc2hvcDo5TVNZOEZ4ZFI2dTJaOHVNdg==" },
  uiStore,
}: HeaderProps) => {

Add an explicit return type, assign default args or define the types inline and it personally starts to give me the bug eyes even after Prettier has gone to work on it, and still suffers from the eye scanning problem I mentioned:

const Header = ({
  classes: { toolbar, title, controls, appBar },
  shop: { name = "cmVhY3Rpb24vc2hvcDo5TVNZOEZ4ZFI2dTJaOHVNdg==" },
  uiStore,
}: {
  shop: {
    name: string;
  };
  uiStore: {
    toggleMenuDrawerOpen: Function;
  };
  viewer: any;
}) => {

Then there's the moment where you want to return null inside an element—likely with a ternary expression—and are required to add children to the type definition and you'll still get an error until you choose the correct return type (which FC already does for us).

Type 'Element | null' is not assignable to type 'HeaderProps'.
  Type 'null' is not assignable to type 'HeaderProps'.ts(2322)

The biggest disadvantage I see of using FC has to do with potentially adding children where we're not dealing with a react component, in which case I prefer the grammar you supplied.

That said it seems as though others prefer omitting FC to save a little boilerplate and I'm cool with that so long as we can agree on a way forward to get the migration started and let the style grow out of the refactored code before adding any opinion.

Thoughts?

Copy link
Author

Choose a reason for hiding this comment

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

For the record, here's the number of times we're returning null in component definitions which can be used as a rough proxy for how often the above error will occur as people are coding: https://github.com/reactioncommerce/example-storefront/search?q=%22return+null%22&type=code

Copy link
Collaborator

Choose a reason for hiding this comment

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

Thanks for taking the time to explain your thoughts on this!
I'm happy to keep it this way.

Longer-term if we deal with passing more data types from the api (which hopefully we won't all type by hand within components anyway and just use codegen) we can still the what patterns fits best, so I'm negating my original statement, not a blocker and nothing that needs to be set in stone right now.

@janus-reith
Copy link
Collaborator

Thanks for getting this started @balibebas!

@ghost
Copy link
Author

ghost commented Mar 31, 2021

@janus-reith Just saw your code review. I'll loop back to this soon.

Copy link
Author

@ghost ghost left a comment

Choose a reason for hiding this comment

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

@janus-reith all set. once you've considered my responses I'm comfortable moving forward with either grammar for the function signatures so we can get this code merged.

@Akarshit Akarshit self-requested a review April 1, 2021 14:31
@Akarshit
Copy link
Contributor

Akarshit commented Apr 1, 2021

Let me know if this looks good, and I can get it merged.

package.json Outdated
"rules": {
"comma-dangle": "off",
"react/prop-types": "off",
"quote-props": "off"
Copy link
Author

@ghost ghost Apr 1, 2021

Choose a reason for hiding this comment

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

Two more eslint rules worth disabling now as prettier takes care of it:

"react/jsx-max-props-per-line": "off",
"max-len": "off"

I'll plan to add these as I incorporate review feedback. 🙏🏼

@ghost ghost requested a review from janus-reith April 5, 2021 07:03
tsconfig.json Outdated
@@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "es5",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should we use "es5" here?
The next's typescript example uses target es next: https://github.com/vercel/next.js/blob/canary/examples/with-typescript/tsconfig.json
and next's itself uses ES2017: https://github.com/vercel/next.js/blob/canary/packages/next/tsconfig.json

TBH I'm unsure what the actual effect of different values here is, since by my understanding, nextjs would not really compile typescript, but typecheck on build and then leverage babel and @babel/preset-typescript to just strip out type definitions.

Copy link
Author

Choose a reason for hiding this comment

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

Seems reasonable to shift forward. I suspect it'll reduce bundle sizes by removing boilerplate for things like generator functions where async could be used.

Copy link
Collaborator

@janus-reith janus-reith left a comment

Choose a reason for hiding this comment

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

Looks good to me, thanks @balibebas!

I'd like to reconsider what target we set in tsconfig, but approving for now since my by understanding, impact of that property is rather low for us anyways.

Next step might be introducing https://github.com/dotansimha/graphql-code-generator to derive most types from our api schema. By leveraging the typescript-react-apollo plugin and defining some operations (out of the gql files we have already) we can even auto-generate typed apollo hooks for Querys and Mutations and save us a lot of work.

Signed-off-by: Josh Habdas <jhabdas@protonmail.com>
@ghost
Copy link
Author

ghost commented Apr 5, 2021

@Akarshit we're all set once ci checks have passed. thanks @janus-reith for your feedback and review

@nnnnat
Copy link
Contributor

nnnnat commented Apr 6, 2021

LGTM! If @janus-reith and @Akarshit are good with this let merge

@Akarshit
Copy link
Contributor

Akarshit commented Apr 7, 2021

I will give this one a last look tomorrow and then merge it.

@Akarshit
Copy link
Contributor

Akarshit commented Apr 8, 2021

I didn't get much time these days, but I will definitely get this done tomorrow.

@ghost
Copy link
Author

ghost commented Apr 9, 2021

Backing down a change made during review to prevent non-blocking test warning given the TypeScript target esnext isn't fully supported by Node 12, the current engine running in the docker container. es2019 works fine for Node 12.

Prevents warning on Jest test runs:

ts-jest[config] (WARN) There is a mismatch between your NodeJs version v12.14.1 and your TypeScript target ESNext. This might lead to some unexpected errors when running tests with `ts-jest`. To fix this, you can check https://github.com/microsoft/TypeScript/wiki/Node-Target-Mapping

See Node target mappings here: https://github.com/microsoft/TypeScript/wiki/Node-Target-Mapping

@Akarshit
Copy link
Contributor

Akarshit commented Apr 9, 2021

@balibebas Hey! looks like to forgot to do the sign-off for the last commit...

Signed-off-by: Josh Habdas <jhabdas@protonmail.com>
@ghost
Copy link
Author

ghost commented Apr 9, 2021

@balibebas Hey! looks like to forgot to do the sign-off for the last commit...

Nice catch. Done

@Akarshit Akarshit merged commit 78681a4 into reactioncommerce:trunk Apr 12, 2021
@Akarshit
Copy link
Contributor

@balibebas Hey! Sorry it was the weekend so I couldn't get to this one. Looks really good to me. Thanks a lot 🎉

@ghost ghost deleted the tscftw branch April 13, 2021 10:33
@rc-publisher
Copy link
Collaborator

🎉 This PR is included in version 4.1.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@rc-publisher rc-publisher added the released Applied automatically by semantic-release label Jun 23, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

released Applied automatically by semantic-release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Begin TypeScript Migration?

4 participants