|
| 1 | +--- |
| 2 | +title: Adding Typescript to your Gatbsy application |
| 3 | +date: "2020-05-10" |
| 4 | +author: franleplant |
| 5 | +description: "Docker has become a widely used |
| 6 | + technology and chances are you are going to have to deal with it eventually, at least superficially, in your Front End |
| 7 | + career. Let's cover the basic concepts and day to day useful commands you will likely use when dealing with Docker." |
| 8 | +tags: |
| 9 | + - gatsbyjs |
| 10 | + - javascript |
| 11 | + - react |
| 12 | + - typescript |
| 13 | + - graphql |
| 14 | +--- |
| 15 | + |
| 16 | +## Intro |
| 17 | + |
| 18 | +I have been using Typescript for the past 4 years in different production |
| 19 | +projects in different companies and although this post is not meant to be |
| 20 | +a _why typescript?_ post let me tell you briefly why I love typescript |
| 21 | +and why I do think that most medium to largse size Javascript applications |
| 22 | +would benefit from using Typescript, in no particular order: |
| 23 | + |
| 24 | +1- automatically up-to-date and synchronized code documentation in the form of types. |
| 25 | +2- new code verification tool added to your pool (in javascript apps this is mostly made of different kinds of tests and lints) |
| 26 | +3- better development experience: better autocompletion and api discovery |
| 27 | +4- type checker guided hassle free refactoring |
| 28 | +5- improve the mind set of developers so that they think more about data structures and their semantic meaning inside the code (as oposed to just have a bunch of unamed and uncategorized objects which is something that tends to happen in bigger javascript apps). |
| 29 | + |
| 30 | +I plan to talk a lot more about this in a follow up post so that is why I wont focus to much |
| 31 | +explaining these points, but please note that most of these come from real world experiences |
| 32 | +handling large production code bases in both Javascript and Typescript, and there is an abysm between the too. |
| 33 | + |
| 34 | +Additionally, from a business standpoint we can also enumerate derived benefits: |
| 35 | + |
| 36 | +- more productivity: items 1, 3 and 4 |
| 37 | +- higher quality: items 2, 4 and 5 |
| 38 | + |
| 39 | +Having these in mind we are going to cover how to integrate Typescript to Gatsby, |
| 40 | +considering that Gatbsy is a framework that can accomodote larger production applications that |
| 41 | +will benefit the most out of this. |
| 42 | + |
| 43 | +The process is rather simple but I want to cover some paint points and tips and tricks. |
| 44 | + |
| 45 | +Note: This blog is actually using Typescript. |
| 46 | + |
| 47 | +## How to use Typescript in your Gatbsy.js site. |
| 48 | + |
| 49 | +We are going to install two plugins: |
| 50 | + |
| 51 | +- [gatsby-plugin-typescript](https://www.gatsbyjs.org/packages/gatsby-plugin-typescript/) to support typescript compilation |
| 52 | +- [gatsby-plugin-graphql-codegen](https://www.gatsbyjs.org/packages/gatsby-plugin-graphql-codegen/) to automatically generate Typescript types out of the underlying graphql data model that Gatsby uses. |
| 53 | + |
| 54 | +After following the instructions you will have most of the tools, the rest is going to be |
| 55 | +related to 1) configuration and 2) migrating js files. |
| 56 | + |
| 57 | +## Configuration |
| 58 | + |
| 59 | +### Typescript |
| 60 | + |
| 61 | +You will need to create a `tsconfig.json` at the root of your project configuring the |
| 62 | +typescript compiler. This can vary a lot depending on your taste but here's the one we are using: |
| 63 | + |
| 64 | +```json |
| 65 | +{ |
| 66 | + "compilerOptions": { |
| 67 | + "target": "esnext", |
| 68 | + "module": "commonjs", |
| 69 | + "jsx": "preserve", |
| 70 | + "strict": true, |
| 71 | + "noImplicitAny": true, |
| 72 | + "strictNullChecks": false, |
| 73 | + "strictFunctionTypes": true, |
| 74 | + "noImplicitThis": true, |
| 75 | + "alwaysStrict": true, |
| 76 | + "noUnusedLocals": true, |
| 77 | + "noUnusedParameters": true, |
| 78 | + "noImplicitReturns": true, |
| 79 | + "noFallthroughCasesInSwitch": true, |
| 80 | + "allowSyntheticDefaultImports": true, |
| 81 | + "esModuleInterop": true |
| 82 | + }, |
| 83 | + "include": ["src/**/*"] |
| 84 | +} |
| 85 | +``` |
| 86 | + |
| 87 | +I wont cover all the options but I do want to cover `strictNullChecks`. |
| 88 | +I found that enabling this option to be a big pain when convining it with the GraphQL codegen tool. |
| 89 | +Most of the automatically generated types from the queries you make around your codebase |
| 90 | +will have a lot of optionals and `Maybe` making it super verbose and sometimes super |
| 91 | +awkard to handle all these in a strict way. |
| 92 | + |
| 93 | +I usually try to use `strictNullChecks: true` for applications because it makes the typechecking |
| 94 | +to provide even more guarantees about your code, but in this particular application I recommend |
| 95 | +you avoid it altogether. |
| 96 | + |
| 97 | +At this stage you might want to `allowJs: true` to avoid having to migrate _all_ your codebase |
| 98 | +at once. But you should probably migrate some of the core files just to verify the setup. |
| 99 | + |
| 100 | +The process is rather simple, any `.js` file that jas `JSX` inside it should be renamed to `.tsx`, |
| 101 | +if not, it should be rename to `.ts`. |
| 102 | + |
| 103 | +Let the compiler tell you what it expects from those freshly migrated files (see next section): spoiler alert, |
| 104 | +it will ask for props definitions and perhaps some dependencies type definitions. |
| 105 | + |
| 106 | +### `declarations.d.ts` |
| 107 | + |
| 108 | +In most Typescript codebases we have some global types or some untyped modules to declare, |
| 109 | +that is why you most likely will need `src/declaration.d.ts`. Let's see a bair minimum one: |
| 110 | + |
| 111 | +```typescript |
| 112 | +// Make typescript aware of this |
| 113 | +// global Gatbsy variable |
| 114 | +declare var __PATH_PREFIX__: string |
| 115 | + |
| 116 | +// Declare that this modules are untyped |
| 117 | +declare module "typography-theme-github" |
| 118 | +declare module "vfile-message" |
| 119 | +// This makes importing svg images |
| 120 | +// typecheck. You might need |
| 121 | +// to declare other types of files |
| 122 | +// used in your codebase |
| 123 | +declare module "*.svg" |
| 124 | +``` |
| 125 | +
|
| 126 | +And then you should import this file from your `gatsby-browser.js` which |
| 127 | +is the main entry point of the browser application: |
| 128 | +
|
| 129 | +```javascript |
| 130 | +import "./src/declarations.d.ts" |
| 131 | +``` |
| 132 | + |
| 133 | +### Typechecking |
| 134 | + |
| 135 | +As the typescript plugin docs state, by default it wont run typechecks on your code, it |
| 136 | +will only strip out any type information and typescript sintax and compile it down to Javascript. |
| 137 | + |
| 138 | +For enabling typechecking you will need to create an npm script: |
| 139 | + |
| 140 | +```json |
| 141 | +"typecheck": "tsc --noEmit", |
| 142 | +``` |
| 143 | + |
| 144 | +`tsc` is the Typescript Compiler and `noEmit` tells it that we only want to typecheck. |
| 145 | + |
| 146 | +I also call this script from my `test` script because I want to always pass this verification, |
| 147 | +which as one of the reasons I had to add it. |
| 148 | + |
| 149 | +You will probably need to install type definitions for some libraries you might be using by |
| 150 | +`yarn add --dev @types/my-lib` if they exist or simply declaring them as untyped. |
| 151 | +When typechecking your code base the typescript compiler will let you know what to do |
| 152 | +pretty acurately in each case. |
| 153 | + |
| 154 | +**NOTE**: I configured this in March 2020 and I had some troubles with Gatbsy type definitions, in |
| 155 | +order to fix it I had to add this to my `package.json#resolutions` field: |
| 156 | + |
| 157 | +```json |
| 158 | +"resolutions": { |
| 159 | + "unified": "7.1.0", |
| 160 | + "vfile": "4.0.3", |
| 161 | + "@types/vfile": "4.0.0" |
| 162 | +}, |
| 163 | +``` |
| 164 | + |
| 165 | +If you see a type error relating these libraries this might be fix for you. |
| 166 | + |
| 167 | +### gatsby-plugin-typescript |
| 168 | + |
| 169 | +I did not made any special configurations to this plugin. |
| 170 | + |
| 171 | +### gatsby-plugin-graphql-codegen |
| 172 | + |
| 173 | +Here is where things become more complicated. |
| 174 | + |
| 175 | +The main data layer of Gatbsy is Grapqhl and most of the pages |
| 176 | +and some components will acess that data layer and that data will |
| 177 | +flow through props and context down the component tree. |
| 178 | + |
| 179 | +So it is reasonable to expect to somehow have thata data model |
| 180 | +and the queries we make out of it to by statically typed. |
| 181 | + |
| 182 | +You can of course write these types by hand but it will be much better |
| 183 | +to have a tool automatically generate those files from the latest queries |
| 184 | +and GraphQL data model. Enter: `gatsby-plugin-graphql-codegen`. |
| 185 | + |
| 186 | +This is how I recommend you configure it: |
| 187 | + |
| 188 | +```typescript |
| 189 | +{ |
| 190 | + resolve: `gatsby-plugin-graphql-codegen`, |
| 191 | + options: { |
| 192 | + codegenConfig: { |
| 193 | + // optional if you prefix, as I do, |
| 194 | + // all your types with I |
| 195 | + typesPrefix: "I", |
| 196 | + avoidOptionals: true, |
| 197 | + }, |
| 198 | + }, |
| 199 | +} |
| 200 | +``` |
| 201 | + |
| 202 | +The `avoidOptionals` is important because the codegen tools generate |
| 203 | +most of the types with the following shape: |
| 204 | + |
| 205 | +```typescript |
| 206 | +export type SomeGeneratedType = { |
| 207 | + attribute?: Maybe<Type> |
| 208 | +} |
| 209 | +``` |
| 210 | +
|
| 211 | +This evaluates to something like `Type | null | undefined`, so you basically have three potential types instead |
| 212 | +of just the two you actually epect: `Type | null` which is what `Maybe<Type>` resolves to. |
| 213 | +
|
| 214 | +So this will reduce some of the more awkard errors when passing props. |
| 215 | +
|
| 216 | +Additionally, it is better to create your own types instead of trying to reuse the types that the |
| 217 | +codegen genrates. |
| 218 | +
|
| 219 | +For example in this blog we have an `data/authors.yaml`. |
| 220 | +The codegen generates this type |
| 221 | +
|
| 222 | +```typescript |
| 223 | +export type IAuthorYaml = INode & { |
| 224 | + id: Scalars["ID"] |
| 225 | + parent: Maybe<INode> |
| 226 | + children: Array<INode> |
| 227 | + internal: IInternal |
| 228 | + bio: Maybe<Scalars["String"]> |
| 229 | + profilepicture: Maybe<IFile> |
| 230 | + twitter: Maybe<Scalars["String"]> |
| 231 | + github: Maybe<Scalars["String"]> |
| 232 | +} |
| 233 | +``` |
| 234 | +
|
| 235 | +but the pages that make the query might generate different variantes of this type. Sometime shaving |
| 236 | +some fields off sometimes others and in particular the profilepicture filed whihc maps to an image |
| 237 | +might be altered in several ways like asking for a sharp image fluid. |
| 238 | +
|
| 239 | +IN that case this type wont be useful at all. |
| 240 | +INstead what I have done is to manually write a concrete `IAuthor` type |
| 241 | +that I defined which is very similar to this but only has the "domain" attributes I need |
| 242 | +
|
| 243 | +```typescript |
| 244 | +export interface IAuthor { |
| 245 | + id: string |
| 246 | + bio: string |
| 247 | + twitter: string |
| 248 | + github: string |
| 249 | + profilepicture?: any |
| 250 | +} |
| 251 | +``` |
| 252 | + |
| 253 | +and in the case of providing a particular image flui I can extend that type adhoc like this |
| 254 | + |
| 255 | +```typescript |
| 256 | +interface IAuthorWithProfilePic extends IAuthor { |
| 257 | + profilepicture: { |
| 258 | + childImageSharp: { |
| 259 | + fluid: IGatsbyImageSharpFluidFragment |
| 260 | + } |
| 261 | + } |
| 262 | +} |
| 263 | +``` |
| 264 | + |
| 265 | +This will force you a litte bit to always query the author with all the fields except `profilepicture` which |
| 266 | +is a rather simple cost to pay to have your codebase have greater type safety guarantees. |
| 267 | + |
| 268 | +Note that in the latter case `author.profilepicture` will have all the type information about |
| 269 | +what Gatbsy does with the image, with a properly configured IDE, you will be able to see how |
| 270 | +this object is made without leaving your dev tools and googling it (in this aprticular case it |
| 271 | +is actually not easy to find exactly how this object looks like) |
| 272 | + |
| 273 | +### How to incorporate typechecking to your `npm run test` |
| 274 | + |
| 275 | +The only important considerationg here is that you need to codegen |
| 276 | +your GraphQL types before being able to typecheck your codebase: |
| 277 | + |
| 278 | +```json |
| 279 | +{ |
| 280 | + "test": "npm run build && npm run typecheck" |
| 281 | +} |
| 282 | +``` |
| 283 | + |
| 284 | +And of course you can mix this with lints, prettier and actual unit or integration tests. |
0 commit comments