From f65e662beb61d6dbcf221bfe7bf353685243acd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Oliveira?= Date: Wed, 22 May 2024 16:07:29 -0300 Subject: [PATCH 01/18] Introducing TypeScript best practices --- _includes/markdown/Typescript.md | 73 ++++++++++++++++++++++++++++++++ javascript.md | 2 + typescript.md | 11 +++++ 3 files changed, 86 insertions(+) create mode 100644 _includes/markdown/Typescript.md create mode 100644 typescript.md diff --git a/_includes/markdown/Typescript.md b/_includes/markdown/Typescript.md new file mode 100644 index 00000000..d41c0b2d --- /dev/null +++ b/_includes/markdown/Typescript.md @@ -0,0 +1,73 @@ +The purpose of this document is to help guide Engineers at the usage of TypeScript at 10up projects. The goal here is not to teach TypeScript but rather explain 10up recommendation on when to use TypeScript alongside general recommendations. + +## Why TypeScript + +We believe that: +1. TypeScript can make engineers more confident +2. TypeScript makes refactoring code easier +3. TypeScript helps engineers unfamiliar with the codebase + +Therefore we are big fans of TypeScript at 10up and we strive to use whenever it makes sense. + +## When to use TypeScript + +At 10up we require that all projects considered "JavaScript-First" to make use of TypeScript. Generally speaking Standard WordPress projects (including block development) would not fall under "JavaScript-First" projects. + +If you are building a project using Next.js and/or 10up's [HeadstartWP framework](https://headstartwp.10up.com/), Sanity and any other headless CMS'es powered by a React front-end then we require TypeScript unless there are other cicunstances that makes using just JavaScript a better choice. + +The main reason we don't require TypeScript for standard WordPress projects at the moment, is because TS support for block development is still very limited, largely driven by community types packages that don't always match the actual types for the `@wordpress` packages. + +## How to use TypeScript + +In an ideal world every engineer would be fully capable in writing all sorts of complex TypeScript types, however we understand that it takes time to master a language like TypeScript, specially in a few scenarios that requires writing more complex types. Therefore, while we use a reasonable strict `tsconfig.json` we are flexible in practice. + +We want the TypeScript to always be valid and issue no errors at compilation, but we allow a few escape hatches should engineers get stuck with a particular typing problem. We follow the principles below when writing TypeScript: + +### 1. Never ship code that has compilation issues + +Depending on the tooling you're using (e.g Next.js) it is possible to disable type checking and still compile invalid TS code as long as the underlying JS code is valid. While the runtime behavior might still be working as expected this not a recommended practice and generally would just make engineers to not have trust in the type system. If you are facing an challenge typing something it is better to use some of the escape hatches documented below instead of just ignoring TS errors. + +### 2. Favor efficiency instead of strict typing + +In ideal world all engineers would be capable of typing everything properly from the get go. However we know that is not the case and engineers needs time and practice to get there. For that reason we favor efficiency over strict typing. + +Our philosophy is that over time engineers will organically get more familiar with TypeScript and stricness will come as a result. Code reviews are also a place where engineers might get quick feedback that would allow them to more efficiently adress typing issues instead of sinking a lot of time during initial development trying to figure it out on their own. + +When facing such typing challenges we recommend the following strategies as a escape hatch: +#### 1. The `as unkown as ExpectedType` type cast + +When truggling with a type that is not matching the expected type, one of the escape hatches we recommend is to forcebly type cast the type to the expected type: + +```ts +// force the variable to have the type `ExpectedType` +const withExpectedType = myValueThatWithWrongType as unkown as ExpectedType; +``` + +While this usually indicates that there's something wrong with types or even the code itself, this would at least provide type checking for the subsequent lines of code. This strategy for instance, is much better than just typing as any. + +#### 2. The `any` type. + +Another strategy is to just type your variable with `any` which essentially disables type checking for that variable and while it is a more drastic measure it should be an easy way to unblock work until engineers have more time to come back and try to get the types right. + +Note: we recommend setting the `noImplicitAny` setting to `true` in `tsconfig.json` which forces explicitly setting a type as any. This makes it easier to identify code that is relying on any and would also make catching the escape hatch in code reviews which is a great opportunity to get feedback and potentially get the types right. + +#### 3. The `// @ts-expect-error` comment + +Latsly, if nothing else worked to unblock the code, TypeScript allows disregarding errors on a given line by just telling the compiler that an error is expected on that line. This is usually the least effective option as it would only solve the problem for a single line. However, if nothing else works, this is the last and most drastic escape hatch. + +While we'd like that none of these escape hatches to be used, it would be unrealistic to expect all engineers to have types 100% correct on every project all the time. We believe that these recomendations provides a path to address them organically during the life-cycle of the project. + +## TypeScript and CI + +We recommend that all projects run the typescript compiler to check type safety at the CI level. Every Pull Request/Merge Request should run `tsc --noEmit` on CI to ensure there are no compilation issues before merging the branch. + +This is a great strategy to avoid builds failing due to type errors. With Next.js for instance, you might not realise you have a TS compilation issue until you actually run a build, therefore to avoid merging code that produces failing builds we recommend always ensuring that the code has no TypeScript compilation issues before merging. + +## Recommended `tsconfig.json` settings + +This is not a full list but we highlight some recommended settings here. + +1. `noFallthroughCasesInSwitch: true`- [Definition](https://www.typescriptlang.org/tsconfig/#noFallthroughCasesInSwitch) - ensures you are not skipping anything in a switch statement, and always at least provide a default option. +2. `noImplicitAny: true` - [Definition](https://www.typescriptlang.org/tsconfig/#noImplicitAny) - ensures you define `any` explicitly, rather than implicitly assume it. This makes it a lot easier to find all code relying on any for future refactoring. +3. `strictNullChecks: true` - [Definition](https://www.typescriptlang.org/tsconfig/#strictNullChecks) - forces handling both `undefined` and `null` +4. `noUnusedParameters: true` - [Definition](https://www.typescriptlang.org/tsconfig/#noUnusedParameters) - does not allow unused parameters to remain in code \ No newline at end of file diff --git a/javascript.md b/javascript.md index 24e4a3e0..42b27efe 100644 --- a/javascript.md +++ b/javascript.md @@ -20,6 +20,8 @@ subnav: tag: libraries - title: React slug: react + - title: TypeScript + slug: typescript - title: Node.js slug: nodejs updated: 10 Dec 2018 diff --git a/typescript.md b/typescript.md new file mode 100644 index 00000000..25c2ccad --- /dev/null +++ b/typescript.md @@ -0,0 +1,11 @@ +--- +page: javascript +title: TypeScript +layout: default +updated: 22 May 2024 +--- + +
+ {% capture typescript %}{% include markdown/Typescript.md %}{% endcapture %} + {{ typescript | markdownify }} +
From 4056895f65187d1d700fbda8e8b5f10ac21f0eb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Oliveira?= Date: Thu, 23 May 2024 07:06:15 -0300 Subject: [PATCH 02/18] prroofreading --- .vscode/settings.json | 8 ++++++++ _includes/markdown/Typescript.md | 33 ++++++++++++++++---------------- 2 files changed, 24 insertions(+), 17 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..e04a1e91 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "grammarly.selectors": [ + { + "language": "markdown", + "scheme": "file" + } + ] +} \ No newline at end of file diff --git a/_includes/markdown/Typescript.md b/_includes/markdown/Typescript.md index d41c0b2d..cf69c910 100644 --- a/_includes/markdown/Typescript.md +++ b/_includes/markdown/Typescript.md @@ -1,4 +1,4 @@ -The purpose of this document is to help guide Engineers at the usage of TypeScript at 10up projects. The goal here is not to teach TypeScript but rather explain 10up recommendation on when to use TypeScript alongside general recommendations. +The purpose of this document is to help guide Engineers in the usage of TypeScript at 10up projects. The goal here is not to teach TypeScript but rather explain the 10up stance on when to use TypeScript alongside general recommendations. ## Why TypeScript @@ -7,43 +7,42 @@ We believe that: 2. TypeScript makes refactoring code easier 3. TypeScript helps engineers unfamiliar with the codebase -Therefore we are big fans of TypeScript at 10up and we strive to use whenever it makes sense. +Therefore we are big fans of TypeScript at 10up and we strive to use it whenever it makes sense. ## When to use TypeScript At 10up we require that all projects considered "JavaScript-First" to make use of TypeScript. Generally speaking Standard WordPress projects (including block development) would not fall under "JavaScript-First" projects. -If you are building a project using Next.js and/or 10up's [HeadstartWP framework](https://headstartwp.10up.com/), Sanity and any other headless CMS'es powered by a React front-end then we require TypeScript unless there are other cicunstances that makes using just JavaScript a better choice. +If you are building a project using Next.js and/or 10up's [HeadstartWP framework](https://headstartwp.10up.com/), Sanity and any other headless CMS'es powered by a React front-end then we require TypeScript unless other circumstances make using just JavaScript a better choice. -The main reason we don't require TypeScript for standard WordPress projects at the moment, is because TS support for block development is still very limited, largely driven by community types packages that don't always match the actual types for the `@wordpress` packages. +The main reason we don't require TypeScript for standard WordPress projects at the moment is that TypeScript support for block development is still very limited, largely driven by community types packages that don't always match the actual types for the `@wordpress` packages. ## How to use TypeScript -In an ideal world every engineer would be fully capable in writing all sorts of complex TypeScript types, however we understand that it takes time to master a language like TypeScript, specially in a few scenarios that requires writing more complex types. Therefore, while we use a reasonable strict `tsconfig.json` we are flexible in practice. +In an ideal world, every engineer would be fully capable of writing all sorts of complex TypeScript types, however, we understand that it takes time to master a language like TypeScript, especially in a few scenarios that require writing more complex types. Therefore, while we use a reasonably strict `tsconfig.json`` we are flexible in practice. We want the TypeScript to always be valid and issue no errors at compilation, but we allow a few escape hatches should engineers get stuck with a particular typing problem. We follow the principles below when writing TypeScript: ### 1. Never ship code that has compilation issues -Depending on the tooling you're using (e.g Next.js) it is possible to disable type checking and still compile invalid TS code as long as the underlying JS code is valid. While the runtime behavior might still be working as expected this not a recommended practice and generally would just make engineers to not have trust in the type system. If you are facing an challenge typing something it is better to use some of the escape hatches documented below instead of just ignoring TS errors. +Depending on the tooling you're using (e.g. Next.js) it is possible to disable type checking and still compile invalid TS code as long as the underlying JS code is valid. While the runtime behavior might still be working as expected this is not a recommended practice and generally would just make engineers not have trust in the type system. If you are facing a challenge typing something it is better to use some of the escape hatches documented below instead of just ignoring TS errors. ### 2. Favor efficiency instead of strict typing +In an ideal world, all engineers would be capable of typing everything properly from the get-go. However, we know that is not the case and engineers need time and practice to get there. For that reason, we favor efficiency over strict typing. -In ideal world all engineers would be capable of typing everything properly from the get go. However we know that is not the case and engineers needs time and practice to get there. For that reason we favor efficiency over strict typing. +Our philosophy is that over time engineers will organically get more familiar with TypeScript and strictness will come as a result. Code reviews are also a place where engineers might get quick feedback that would allow them to more efficiently address typing issues instead of wasting a lot of time during initial development trying to figure it out on their own. -Our philosophy is that over time engineers will organically get more familiar with TypeScript and stricness will come as a result. Code reviews are also a place where engineers might get quick feedback that would allow them to more efficiently adress typing issues instead of sinking a lot of time during initial development trying to figure it out on their own. +When facing such typing challenges we recommend the following strategies as an escape hatch: +#### 1. The `as unknown as ExpectedType` type cast -When facing such typing challenges we recommend the following strategies as a escape hatch: -#### 1. The `as unkown as ExpectedType` type cast - -When truggling with a type that is not matching the expected type, one of the escape hatches we recommend is to forcebly type cast the type to the expected type: +When struggling with a type that does not match the expected type, one of the escape hatches we recommend is to forcibly typecast the type to the expected type: ```ts // force the variable to have the type `ExpectedType` -const withExpectedType = myValueThatWithWrongType as unkown as ExpectedType; +const withExpectedType = myValueThatWithWrongType as unknown as ExpectedType; ``` -While this usually indicates that there's something wrong with types or even the code itself, this would at least provide type checking for the subsequent lines of code. This strategy for instance, is much better than just typing as any. +While this usually indicates that there's something wrong with types or even the code itself, this would at least provide type checking for the subsequent lines of code. This strategy, for instance, is much better than just typing as any. #### 2. The `any` type. @@ -53,15 +52,15 @@ Note: we recommend setting the `noImplicitAny` setting to `true` in `tsconfig.js #### 3. The `// @ts-expect-error` comment -Latsly, if nothing else worked to unblock the code, TypeScript allows disregarding errors on a given line by just telling the compiler that an error is expected on that line. This is usually the least effective option as it would only solve the problem for a single line. However, if nothing else works, this is the last and most drastic escape hatch. +Lastly, if nothing else worked to unblock the code, TypeScript allows disregarding errors on a given line by just telling the compiler that an error is expected on that line. This is usually the least effective option as it would only solve the problem for a single line. However, if nothing else works, this is the last and most drastic escape hatch. -While we'd like that none of these escape hatches to be used, it would be unrealistic to expect all engineers to have types 100% correct on every project all the time. We believe that these recomendations provides a path to address them organically during the life-cycle of the project. +While we'd like none of these escape hatches to be used, it would be unrealistic to expect all engineers to have types 100% correct on every project all the time. We believe that these recommendations provide a path to address them organically during the life-cycle of the project. ## TypeScript and CI We recommend that all projects run the typescript compiler to check type safety at the CI level. Every Pull Request/Merge Request should run `tsc --noEmit` on CI to ensure there are no compilation issues before merging the branch. -This is a great strategy to avoid builds failing due to type errors. With Next.js for instance, you might not realise you have a TS compilation issue until you actually run a build, therefore to avoid merging code that produces failing builds we recommend always ensuring that the code has no TypeScript compilation issues before merging. +This is a great strategy to avoid builds failing due to type errors. With Next.js for instance, you might not realize you have a TS compilation issue until a build is executed, therefore to avoid merging code that produces failing builds we recommend always ensuring that the code has no TypeScript compilation issues before merging. ## Recommended `tsconfig.json` settings From 97d1c5bf108e9be85a25a117e02e3ad97cfd93c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Oliveira?= Date: Thu, 23 May 2024 07:07:25 -0300 Subject: [PATCH 03/18] remove .vscode folder --- .vscode/settings.json | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index e04a1e91..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "grammarly.selectors": [ - { - "language": "markdown", - "scheme": "file" - } - ] -} \ No newline at end of file From 33799f891b69c53fb0291d64a65a626c5a132c6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Oliveira?= Date: Mon, 27 May 2024 13:45:52 -0300 Subject: [PATCH 04/18] Add ts and react section --- _includes/markdown/Typescript.md | 110 ++++++++++++++++++++++++++++++- 1 file changed, 109 insertions(+), 1 deletion(-) diff --git a/_includes/markdown/Typescript.md b/_includes/markdown/Typescript.md index cf69c910..62d902f2 100644 --- a/_includes/markdown/Typescript.md +++ b/_includes/markdown/Typescript.md @@ -69,4 +69,112 @@ This is not a full list but we highlight some recommended settings here. 1. `noFallthroughCasesInSwitch: true`- [Definition](https://www.typescriptlang.org/tsconfig/#noFallthroughCasesInSwitch) - ensures you are not skipping anything in a switch statement, and always at least provide a default option. 2. `noImplicitAny: true` - [Definition](https://www.typescriptlang.org/tsconfig/#noImplicitAny) - ensures you define `any` explicitly, rather than implicitly assume it. This makes it a lot easier to find all code relying on any for future refactoring. 3. `strictNullChecks: true` - [Definition](https://www.typescriptlang.org/tsconfig/#strictNullChecks) - forces handling both `undefined` and `null` -4. `noUnusedParameters: true` - [Definition](https://www.typescriptlang.org/tsconfig/#noUnusedParameters) - does not allow unused parameters to remain in code \ No newline at end of file +4. `noUnusedParameters: true` - [Definition](https://www.typescriptlang.org/tsconfig/#noUnusedParameters) - does not allow unused parameters to remain in code + +## TypeScript and React + +### Use `type` for React Props + +While `interface` and `type` are mostly interchangeable, prefer `type` over `interfaces` unless you expect the props to be the base for other components's props. + +```ts +type BlocksProps = { + html: string; +}; + +export const Blocks = ({ html }: BlocksProps) => { + // render blocks +} +``` + +### Use function default arguments instead of `defaultProps` + +`defaultProps` has been deprecated in React 19, therefore prefer using function default arguments. + +```ts +type MyComponentProps = { + title?: strinmg; +}; + +export const MyComponent = ({ title = 'Default Title' }: MyComponentsProps) => { + // ... +} +``` + +Make sure the eslint rule `react/require-default-props` is set up to look for `defaultArguments` e.g `'react/require-default-props': ['error', { functions: 'defaultArguments' }]`; + +### Prefer explicitly declaring `children` instead of using `PropsWithChildren` + +`PropsWithChildren` will make `children` optional, but sometimes you do want to be very explicit and make it required. Therefore we generally recommend declaring `children` explicitly. + +```ts +type LayoutProps = { + children: React.Node +}; + +// Layout has passed a children prop +const Layout = (props: LayoutProps) { + return ( +
+ {children} +
+ ); +} +``` + +### Avoid prop spreading + +Prop spreading is when you simply forward all props to another component e.g: + +```ts +// AVOID DOING THIS +const PostProps = { + title: string, + category: string, + link: string + // other props +}; + +const Post = (props: PostProps) => { + return ( +
+ +
+ ); +} +``` + +While there might be use cases where you need to use them, it's generally better to avoid them and pass props explicitly. In the example above it would be better to just pass a `post` object as a single prop. + +Prop spreading is especially dangerous when prop spreading into native DOM elements. So make sure to not ignore TypeScript errors and make sure your types are set up correctly so that TS can catch forwarded props incompatible with the target component/element. + +### Colocate types with React components + +For the most part, the React Component types should be colocated with the React component, meaning that it should be in the same file that the React component is written. Only hoist types to avoid circular dependencies and when you expect them to be reused/shared across many components/files. + +### Use `@types` packages if necessary + +If the library you're using does not ship types, check if there is type information available for that package in the [DefinatelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped) repo. + +### Global types definitions + +**Do not** put the application types in the global scope just to avoid importing them. You may only create global type definitions when needing to extend global types (e.g `window` object). + +To extend global types create a `global.d.ts` file and include them in your `tsconfig.json` in the `include` config option. Here's an example for a Next.js project: + +```json + "include": [ + "next-env.d.ts", + "src/global.d.ts", + "**/*.ts", + "**/*.tsx" + ], +``` + +```ts +// globals.d.ts +interface Window { + // add any methods or props added by third party scripts/libraries + custom_prop: number; +} +``` From 6d5ab940961f72fbb4cc8cb75d07b7b8ce56bd10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Oliveira?= Date: Mon, 27 May 2024 13:46:46 -0300 Subject: [PATCH 05/18] fix code snippet --- _includes/markdown/Typescript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_includes/markdown/Typescript.md b/_includes/markdown/Typescript.md index 62d902f2..1b498c00 100644 --- a/_includes/markdown/Typescript.md +++ b/_includes/markdown/Typescript.md @@ -138,7 +138,7 @@ const PostProps = { const Post = (props: PostProps) => { return (
- +
); } From 24e3111391043b67077f1a1e972ec400cf2f3a6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Oliveira?= Date: Mon, 27 May 2024 19:29:58 -0300 Subject: [PATCH 06/18] add recommended tsconfig.json --- _includes/markdown/Typescript.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/_includes/markdown/Typescript.md b/_includes/markdown/Typescript.md index 1b498c00..b477d9ec 100644 --- a/_includes/markdown/Typescript.md +++ b/_includes/markdown/Typescript.md @@ -33,6 +33,7 @@ In an ideal world, all engineers would be capable of typing everything properly Our philosophy is that over time engineers will organically get more familiar with TypeScript and strictness will come as a result. Code reviews are also a place where engineers might get quick feedback that would allow them to more efficiently address typing issues instead of wasting a lot of time during initial development trying to figure it out on their own. When facing such typing challenges we recommend the following strategies as an escape hatch: + #### 1. The `as unknown as ExpectedType` type cast When struggling with a type that does not match the expected type, one of the escape hatches we recommend is to forcibly typecast the type to the expected type: @@ -71,6 +72,8 @@ This is not a full list but we highlight some recommended settings here. 3. `strictNullChecks: true` - [Definition](https://www.typescriptlang.org/tsconfig/#strictNullChecks) - forces handling both `undefined` and `null` 4. `noUnusedParameters: true` - [Definition](https://www.typescriptlang.org/tsconfig/#noUnusedParameters) - does not allow unused parameters to remain in code +We also have a recommended [tsconfig.json](https://github.com/10up/headstartwp/blob/develop/projects/wp-nextjs/tsconfig.json) for Next.js projects. + ## TypeScript and React ### Use `type` for React Props From 47a9b9958991f1e9cebb7299d317b14c2d285304 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Oliveira?= Date: Thu, 30 May 2024 09:21:29 -0300 Subject: [PATCH 07/18] Adding tools sections --- _includes/markdown/Typescript.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/_includes/markdown/Typescript.md b/_includes/markdown/Typescript.md index b477d9ec..35814f07 100644 --- a/_includes/markdown/Typescript.md +++ b/_includes/markdown/Typescript.md @@ -19,7 +19,7 @@ The main reason we don't require TypeScript for standard WordPress projects at t ## How to use TypeScript -In an ideal world, every engineer would be fully capable of writing all sorts of complex TypeScript types, however, we understand that it takes time to master a language like TypeScript, especially in a few scenarios that require writing more complex types. Therefore, while we use a reasonably strict `tsconfig.json`` we are flexible in practice. +In an ideal world, every engineer would be fully capable of writing all sorts of complex TypeScript types, however, we understand that it takes time to master a language like TypeScript, especially in a few scenarios that require writing more complex types. Therefore, while we use a reasonably strict `tsconfig.json` we are flexible in practice. We want the TypeScript to always be valid and issue no errors at compilation, but we allow a few escape hatches should engineers get stuck with a particular typing problem. We follow the principles below when writing TypeScript: @@ -57,6 +57,11 @@ Lastly, if nothing else worked to unblock the code, TypeScript allows disregardi While we'd like none of these escape hatches to be used, it would be unrealistic to expect all engineers to have types 100% correct on every project all the time. We believe that these recommendations provide a path to address them organically during the life-cycle of the project. +## Tools + +- [TypeScript Error Translator](https://ts-error-translator.vercel.app/): Lets you drop any TypeScript error message and gives you a human-readable explanation in plain English. +- [pretty-ts-errors] (https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors): A VSCode extension that translaters TypeScript errors in a human-readable way right into VSCode. + ## TypeScript and CI We recommend that all projects run the typescript compiler to check type safety at the CI level. Every Pull Request/Merge Request should run `tsc --noEmit` on CI to ensure there are no compilation issues before merging the branch. From 655c872da0aa90a6825dc395c3021e8d2ef2aab2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Andr=C3=A9?= Date: Thu, 30 May 2024 19:59:52 -0300 Subject: [PATCH 08/18] Update _includes/markdown/Typescript.md Co-authored-by: Kaspars Zarinovs --- _includes/markdown/Typescript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_includes/markdown/Typescript.md b/_includes/markdown/Typescript.md index 35814f07..26253dd1 100644 --- a/_includes/markdown/Typescript.md +++ b/_includes/markdown/Typescript.md @@ -101,7 +101,7 @@ export const Blocks = ({ html }: BlocksProps) => { ```ts type MyComponentProps = { - title?: strinmg; + title?: string; }; export const MyComponent = ({ title = 'Default Title' }: MyComponentsProps) => { From 70ef156f6d9b7db041eb925f96c6cf228b94769a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Andr=C3=A9?= Date: Fri, 31 May 2024 14:03:53 -0300 Subject: [PATCH 09/18] addressing feedback --- _includes/markdown/Typescript.md | 59 +++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/_includes/markdown/Typescript.md b/_includes/markdown/Typescript.md index 26253dd1..1aaa5ba7 100644 --- a/_includes/markdown/Typescript.md +++ b/_includes/markdown/Typescript.md @@ -6,33 +6,41 @@ We believe that: 1. TypeScript can make engineers more confident 2. TypeScript makes refactoring code easier 3. TypeScript helps engineers unfamiliar with the codebase +4. TypeScript helps catch certain issues earlier Therefore we are big fans of TypeScript at 10up and we strive to use it whenever it makes sense. +We also recgonize that the industry has largely adopted TypeScript as the defacto standard for writing JavaScript apps. + ## When to use TypeScript At 10up we require that all projects considered "JavaScript-First" to make use of TypeScript. Generally speaking Standard WordPress projects (including block development) would not fall under "JavaScript-First" projects. -If you are building a project using Next.js and/or 10up's [HeadstartWP framework](https://headstartwp.10up.com/), Sanity and any other headless CMS'es powered by a React front-end then we require TypeScript unless other circumstances make using just JavaScript a better choice. +If you are building a project using Next.js and/or 10up's [HeadstartWP framework](https://headstartwp.10up.com/), Sanity and any other headless CMS'es powered by a React front-end then we require TypeScript unless other circumstances make using JavaScript a better choice. -The main reason we don't require TypeScript for standard WordPress projects at the moment is that TypeScript support for block development is still very limited, largely driven by community types packages that don't always match the actual types for the `@wordpress` packages. +The main reason we don't require TypeScript for standard WordPress projects is that TypeScript support for block development is still very limited, largely driven by community types packages that don't always match the actual types for the `@wordpress` packages. ## How to use TypeScript -In an ideal world, every engineer would be fully capable of writing all sorts of complex TypeScript types, however, we understand that it takes time to master a language like TypeScript, especially in a few scenarios that require writing more complex types. Therefore, while we use a reasonably strict `tsconfig.json` we are flexible in practice. +In an ideal world, no one would struggle with TypeScript, however, we understand that it takes time to master a language like TypeScript, especially in a few scenarios that require writing more complex types. Therefore, while we use a reasonably strict `tsconfig.json` we are flexible in practice. + +We also don't want engineers looking for "perfection" when it comes to writing complex types. TypeScript has a very powerfull type system and it allows for amazing things to be done statically at compilation level, however, we should recognize that most projects will suffice with simpler, easy-to-use types. Unless there's a good reason (e.g building a reusable package or library), engineers should strive to not write overly complex types. -We want the TypeScript to always be valid and issue no errors at compilation, but we allow a few escape hatches should engineers get stuck with a particular typing problem. We follow the principles below when writing TypeScript: +Lastly, we want TypeScript to always be valid and issue no errors at compilation, but we allow a few escape hatches should engineers get stuck with a particular typing problem. We follow the principles below when writing TypeScript: ### 1. Never ship code that has compilation issues Depending on the tooling you're using (e.g. Next.js) it is possible to disable type checking and still compile invalid TS code as long as the underlying JS code is valid. While the runtime behavior might still be working as expected this is not a recommended practice and generally would just make engineers not have trust in the type system. If you are facing a challenge typing something it is better to use some of the escape hatches documented below instead of just ignoring TS errors. ### 2. Favor efficiency instead of strict typing + In an ideal world, all engineers would be capable of typing everything properly from the get-go. However, we know that is not the case and engineers need time and practice to get there. For that reason, we favor efficiency over strict typing. +By "efficiency" we mean engineers recognizing and time-boxing the time spent trying to type something properly and not letting that block the development of the task at hand. We prefer to ship non-strict typed code instead of sinking a lot of time on it. + Our philosophy is that over time engineers will organically get more familiar with TypeScript and strictness will come as a result. Code reviews are also a place where engineers might get quick feedback that would allow them to more efficiently address typing issues instead of wasting a lot of time during initial development trying to figure it out on their own. -When facing such typing challenges we recommend the following strategies as an escape hatch: +To favor efficiency, we recommend the following "escape hatches": #### 1. The `as unknown as ExpectedType` type cast @@ -55,12 +63,18 @@ Note: we recommend setting the `noImplicitAny` setting to `true` in `tsconfig.js Lastly, if nothing else worked to unblock the code, TypeScript allows disregarding errors on a given line by just telling the compiler that an error is expected on that line. This is usually the least effective option as it would only solve the problem for a single line. However, if nothing else works, this is the last and most drastic escape hatch. +When using any of these escape hatches, please leave a `TODO` comment with a short description of the issue: + +```js +// TODO(nicholaiso): the types coming from the package do not seem correct +``` + While we'd like none of these escape hatches to be used, it would be unrealistic to expect all engineers to have types 100% correct on every project all the time. We believe that these recommendations provide a path to address them organically during the life-cycle of the project. ## Tools - [TypeScript Error Translator](https://ts-error-translator.vercel.app/): Lets you drop any TypeScript error message and gives you a human-readable explanation in plain English. -- [pretty-ts-errors] (https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors): A VSCode extension that translaters TypeScript errors in a human-readable way right into VSCode. +- [pretty-ts-errors](https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors): A VSCode extension that translaters TypeScript errors in a human-readable way right into VSCode. ## TypeScript and CI @@ -85,21 +99,40 @@ We also have a recommended [tsconfig.json](https://github.com/10up/headstartwp/b While `interface` and `type` are mostly interchangeable, prefer `type` over `interfaces` unless you expect the props to be the base for other components's props. -```ts +```tsx type BlocksProps = { html: string; }; -export const Blocks = ({ html }: BlocksProps) => { +export const Blocks: React.FC = ({ html }) => { // render blocks } ``` +### Use `React.FC` to type React Components + +When typing React components prefer using `React.FC`. While in the past it was problematic because of implicitly accepting `children` as an optional prop, since React 18 it [no longer does that](https://www.totaltypescript.com/you-can-stop-hating-react-fc). Using React.FC ensures your component is correctly typed, including its return type. + +```tsx +import * as React from 'react'; + +type MyComponentProps = { + /* */ +}; + +export const MyComponent: React.FC = (props) => { + // component logic +} +``` +**Note**: If using React < 17, be aware that `children` will be explicitly set for all components using `React.FC`. This can cause some TS errors when upgrading React to 18+, if a component is passed `children` but does not explicitly declare it as a prop. + +The [React TypeScript Cheatsheet](https://react-typescript-cheatsheet.netlify.app/) is a good resource to check how to type specific things in React (e.g Forms, Events etc). + ### Use function default arguments instead of `defaultProps` `defaultProps` has been deprecated in React 19, therefore prefer using function default arguments. -```ts +```tsx type MyComponentProps = { title?: string; }; @@ -115,12 +148,12 @@ Make sure the eslint rule `react/require-default-props` is set up to look for `d `PropsWithChildren` will make `children` optional, but sometimes you do want to be very explicit and make it required. Therefore we generally recommend declaring `children` explicitly. -```ts +```tsx type LayoutProps = { - children: React.Node + children: React.Node; }; -// Layout has passed a children prop +// If Layout is not passed a children, TS will catch the error const Layout = (props: LayoutProps) { return (
@@ -134,7 +167,7 @@ const Layout = (props: LayoutProps) { Prop spreading is when you simply forward all props to another component e.g: -```ts +```tsx // AVOID DOING THIS const PostProps = { title: string, From 8b59eec76f659d727bb277836f3e8161975fcd76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Andr=C3=A9?= Date: Mon, 3 Jun 2024 15:49:38 -0300 Subject: [PATCH 10/18] Update _includes/markdown/Typescript.md Co-authored-by: Antonio Laguna --- _includes/markdown/Typescript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_includes/markdown/Typescript.md b/_includes/markdown/Typescript.md index 1aaa5ba7..73d6621d 100644 --- a/_includes/markdown/Typescript.md +++ b/_includes/markdown/Typescript.md @@ -10,7 +10,7 @@ We believe that: Therefore we are big fans of TypeScript at 10up and we strive to use it whenever it makes sense. -We also recgonize that the industry has largely adopted TypeScript as the defacto standard for writing JavaScript apps. +We also recognize that the industry has largely adopted TypeScript as the de facto standard for writing JavaScript apps. ## When to use TypeScript From 23fde2d67b77e9dbe20c9a3a0cbce5c511efb1fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Andr=C3=A9?= Date: Mon, 3 Jun 2024 15:49:49 -0300 Subject: [PATCH 11/18] Update _includes/markdown/Typescript.md Co-authored-by: Antonio Laguna --- _includes/markdown/Typescript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_includes/markdown/Typescript.md b/_includes/markdown/Typescript.md index 73d6621d..0e9e400b 100644 --- a/_includes/markdown/Typescript.md +++ b/_includes/markdown/Typescript.md @@ -24,7 +24,7 @@ The main reason we don't require TypeScript for standard WordPress projects is t In an ideal world, no one would struggle with TypeScript, however, we understand that it takes time to master a language like TypeScript, especially in a few scenarios that require writing more complex types. Therefore, while we use a reasonably strict `tsconfig.json` we are flexible in practice. -We also don't want engineers looking for "perfection" when it comes to writing complex types. TypeScript has a very powerfull type system and it allows for amazing things to be done statically at compilation level, however, we should recognize that most projects will suffice with simpler, easy-to-use types. Unless there's a good reason (e.g building a reusable package or library), engineers should strive to not write overly complex types. +We also don't want engineers looking for "perfection" when it comes to writing complex types. TypeScript has a very powerful type system and it allows for amazing things to be done statically at the compilation level, however, we should recognize that most projects will suffice with simpler, easy-to-use types. Unless there's a good reason (e.g. building a reusable package or library), engineers should strive to not write overly complex types. Lastly, we want TypeScript to always be valid and issue no errors at compilation, but we allow a few escape hatches should engineers get stuck with a particular typing problem. We follow the principles below when writing TypeScript: From b7eaafd89bd2c850ecc1ac4b9bd9361a75977afa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Andr=C3=A9?= Date: Mon, 3 Jun 2024 15:50:05 -0300 Subject: [PATCH 12/18] Update _includes/markdown/Typescript.md Co-authored-by: Antonio Laguna --- _includes/markdown/Typescript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_includes/markdown/Typescript.md b/_includes/markdown/Typescript.md index 0e9e400b..d76d85be 100644 --- a/_includes/markdown/Typescript.md +++ b/_includes/markdown/Typescript.md @@ -36,7 +36,7 @@ Depending on the tooling you're using (e.g. Next.js) it is possible to disable t In an ideal world, all engineers would be capable of typing everything properly from the get-go. However, we know that is not the case and engineers need time and practice to get there. For that reason, we favor efficiency over strict typing. -By "efficiency" we mean engineers recognizing and time-boxing the time spent trying to type something properly and not letting that block the development of the task at hand. We prefer to ship non-strict typed code instead of sinking a lot of time on it. +By "efficiency" we mean engineers recognizing and time-boxing the time spent trying to type something properly and not letting that block the development of the task at hand. We prefer to ship non-strict typed code instead of spending a lot of time on it. Our philosophy is that over time engineers will organically get more familiar with TypeScript and strictness will come as a result. Code reviews are also a place where engineers might get quick feedback that would allow them to more efficiently address typing issues instead of wasting a lot of time during initial development trying to figure it out on their own. From 7845b74547f0f0a91a8e6a913ff06ba17dffef04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Andr=C3=A9?= Date: Mon, 3 Jun 2024 15:50:17 -0300 Subject: [PATCH 13/18] Update _includes/markdown/Typescript.md Co-authored-by: Antonio Laguna --- _includes/markdown/Typescript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_includes/markdown/Typescript.md b/_includes/markdown/Typescript.md index d76d85be..333aa94c 100644 --- a/_includes/markdown/Typescript.md +++ b/_includes/markdown/Typescript.md @@ -126,7 +126,7 @@ export const MyComponent: React.FC = (props) => { ``` **Note**: If using React < 17, be aware that `children` will be explicitly set for all components using `React.FC`. This can cause some TS errors when upgrading React to 18+, if a component is passed `children` but does not explicitly declare it as a prop. -The [React TypeScript Cheatsheet](https://react-typescript-cheatsheet.netlify.app/) is a good resource to check how to type specific things in React (e.g Forms, Events etc). +The [React TypeScript Cheatsheet](https://react-typescript-cheatsheet.netlify.app/) is a good resource to check how to type specific things in React (e.g. Forms, Events etc). ### Use function default arguments instead of `defaultProps` From 9075ea37e1c21be5fcf0b009b674522e00854e40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Andr=C3=A9?= Date: Mon, 3 Jun 2024 15:50:27 -0300 Subject: [PATCH 14/18] Update _includes/markdown/Typescript.md Co-authored-by: Antonio Laguna --- _includes/markdown/Typescript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_includes/markdown/Typescript.md b/_includes/markdown/Typescript.md index 333aa94c..214c0080 100644 --- a/_includes/markdown/Typescript.md +++ b/_includes/markdown/Typescript.md @@ -142,7 +142,7 @@ export const MyComponent = ({ title = 'Default Title' }: MyComponentsProps) => { } ``` -Make sure the eslint rule `react/require-default-props` is set up to look for `defaultArguments` e.g `'react/require-default-props': ['error', { functions: 'defaultArguments' }]`; +Make sure the ESLint rule `react/require-default-props` is set up to look for `defaultArguments` e.g `'react/require-default-props': ['error', { functions: 'defaultArguments' }]`; ### Prefer explicitly declaring `children` instead of using `PropsWithChildren` From 986e34b2a7f2d38856756fb1a8519b77018fe30f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Andr=C3=A9?= Date: Mon, 3 Jun 2024 15:50:33 -0300 Subject: [PATCH 15/18] Update _includes/markdown/Typescript.md Co-authored-by: Antonio Laguna --- _includes/markdown/Typescript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_includes/markdown/Typescript.md b/_includes/markdown/Typescript.md index 214c0080..f8598f09 100644 --- a/_includes/markdown/Typescript.md +++ b/_includes/markdown/Typescript.md @@ -199,7 +199,7 @@ If the library you're using does not ship types, check if there is type informat ### Global types definitions -**Do not** put the application types in the global scope just to avoid importing them. You may only create global type definitions when needing to extend global types (e.g `window` object). +**Do not** put the application types in the global scope just to avoid importing them. You may only create global type definitions when needing to extend global types (e.g. `window` object). To extend global types create a `global.d.ts` file and include them in your `tsconfig.json` in the `include` config option. Here's an example for a Next.js project: From 019b4146a364f44ebc724cfe08e963034c100457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Andr=C3=A9?= Date: Mon, 3 Jun 2024 15:50:42 -0300 Subject: [PATCH 16/18] Update _includes/markdown/Typescript.md Co-authored-by: Antonio Laguna --- _includes/markdown/Typescript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_includes/markdown/Typescript.md b/_includes/markdown/Typescript.md index f8598f09..58fea4a9 100644 --- a/_includes/markdown/Typescript.md +++ b/_includes/markdown/Typescript.md @@ -16,7 +16,7 @@ We also recognize that the industry has largely adopted TypeScript as the de fac At 10up we require that all projects considered "JavaScript-First" to make use of TypeScript. Generally speaking Standard WordPress projects (including block development) would not fall under "JavaScript-First" projects. -If you are building a project using Next.js and/or 10up's [HeadstartWP framework](https://headstartwp.10up.com/), Sanity and any other headless CMS'es powered by a React front-end then we require TypeScript unless other circumstances make using JavaScript a better choice. +If you are building a project using Next.js and/or 10up's [HeadstartWP framework](https://headstartwp.10up.com/), Sanity and any other headless CMSs' powered by a React front-end then we require TypeScript unless other circumstances make using JavaScript a better choice. The main reason we don't require TypeScript for standard WordPress projects is that TypeScript support for block development is still very limited, largely driven by community types packages that don't always match the actual types for the `@wordpress` packages. From 992e83c6004aae085745cf278e0fec01d48df692 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Andr=C3=A9?= Date: Thu, 6 Jun 2024 06:30:49 -0300 Subject: [PATCH 17/18] Update _includes/markdown/Typescript.md Co-authored-by: Evan Mattson --- _includes/markdown/Typescript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_includes/markdown/Typescript.md b/_includes/markdown/Typescript.md index 58fea4a9..0e67e6d2 100644 --- a/_includes/markdown/Typescript.md +++ b/_includes/markdown/Typescript.md @@ -179,7 +179,7 @@ const PostProps = { const Post = (props: PostProps) => { return (
- +
); } From 3563bde68d9b272ff8012a53b964325cf39b3e6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Andr=C3=A9?= Date: Thu, 6 Jun 2024 06:31:38 -0300 Subject: [PATCH 18/18] Apply suggestions from code review Co-authored-by: Evan Mattson --- _includes/markdown/Typescript.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/_includes/markdown/Typescript.md b/_includes/markdown/Typescript.md index 0e67e6d2..49553d82 100644 --- a/_includes/markdown/Typescript.md +++ b/_includes/markdown/Typescript.md @@ -8,7 +8,7 @@ We believe that: 3. TypeScript helps engineers unfamiliar with the codebase 4. TypeScript helps catch certain issues earlier -Therefore we are big fans of TypeScript at 10up and we strive to use it whenever it makes sense. +Therefore, we are big fans of TypeScript at 10up and we strive to use it whenever it makes sense. We also recognize that the industry has largely adopted TypeScript as the de facto standard for writing JavaScript apps. @@ -74,7 +74,7 @@ While we'd like none of these escape hatches to be used, it would be unrealistic ## Tools - [TypeScript Error Translator](https://ts-error-translator.vercel.app/): Lets you drop any TypeScript error message and gives you a human-readable explanation in plain English. -- [pretty-ts-errors](https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors): A VSCode extension that translaters TypeScript errors in a human-readable way right into VSCode. +- [pretty-ts-errors](https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors): A VSCode extension that translates TypeScript errors in a human-readable way right into VSCode. ## TypeScript and CI @@ -97,7 +97,7 @@ We also have a recommended [tsconfig.json](https://github.com/10up/headstartwp/b ### Use `type` for React Props -While `interface` and `type` are mostly interchangeable, prefer `type` over `interfaces` unless you expect the props to be the base for other components's props. +While `interface` and `type` are mostly interchangeable, prefer `type` over `interfaces` unless you expect the props to be the base for other components' props. ```tsx type BlocksProps = { @@ -154,7 +154,7 @@ type LayoutProps = { }; // If Layout is not passed a children, TS will catch the error -const Layout = (props: LayoutProps) { +const Layout = (props: LayoutProps) => { return (
{children} @@ -189,13 +189,13 @@ While there might be use cases where you need to use them, it's generally better Prop spreading is especially dangerous when prop spreading into native DOM elements. So make sure to not ignore TypeScript errors and make sure your types are set up correctly so that TS can catch forwarded props incompatible with the target component/element. -### Colocate types with React components +### Co-locate types with React components -For the most part, the React Component types should be colocated with the React component, meaning that it should be in the same file that the React component is written. Only hoist types to avoid circular dependencies and when you expect them to be reused/shared across many components/files. +For the most part, the React Component types should be co-located with the React component, meaning that it should be in the same file that the React component is written. Only hoist types to avoid circular dependencies and when you expect them to be reused/shared across many components/files. ### Use `@types` packages if necessary -If the library you're using does not ship types, check if there is type information available for that package in the [DefinatelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped) repo. +If the library you're using does not ship types, check if there is type information available for that package in the [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped) repo. ### Global types definitions @@ -204,13 +204,14 @@ If the library you're using does not ship types, check if there is type informat To extend global types create a `global.d.ts` file and include them in your `tsconfig.json` in the `include` config option. Here's an example for a Next.js project: ```json - "include": [ +{ + "include": [ "next-env.d.ts", "src/global.d.ts", "**/*.ts", "**/*.tsx" ], -``` +} ```ts // globals.d.ts