Static type checking in JS

Originally published May 3, 2019·Tagged javascript, tooling, static-analysis, react, web-development

I'm interested in using TypeScript for work. IDE integration with [VS Code][vs-code] is fantastic, plus running type-checking at build time prevents a [whole category of bugs][type-errors]. However, we're invested heavily in the [Babel](https://babeljs.io) ecosystem at this point. In addition, I don't want to add a feature to our codebases if it's going to ruin someone's productivity in the future. [vs-code]: https://code.visualstudio.com/ [type-errors]: https://en.wikipedia.org/wiki/Type_system#Type_errors My initial thought was "I want to integrate the TypeScript compiler into our apps." This turned out to be the wrong approach; it would be a ton of work to do so, and I'd likely create maintenance debt along the way. Instead, I listed out my goals: 1. Run static type checking at compile time. 1. Get static type hints in my IDE while I'm coding. 1. Allow incremental adoption. In other words, if I update a project to allow type checking, I should not have to make any code changes — all changes should be to the configuration. 1. I shouldn't have to make changes to the default `create-react-app` configuration to implement type checking. ## First attempt: piggyback on ESLint I'm already using ESLint for static analysis. There's a plugin for [Prettier][prettier]; why wouldn't there be for TypeScript? After putting in a fair bit of research, I started to feel like I was hitting a wall. I found [typescript-eslint] fairly quickly. However, that project requires you to use the [`@typescript-eslint/parser`][typescript-eslint] plugin, which would collide with `create-react-app` and its usage of `babel-parser`. [prettier]: https://prettier.io/ [typescript-eslint]: https://typescript-eslint.io/ [typescript-eslint-parser]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/parser ## Success: separate build step My dreams of adding type checking into the existing tooling didn't pan out. I still thought I might be able to "layer" type checking on top of what was already there, without having to swap out the compiler. This ended up going much more smoothly than I'd anticipated! Once I'd added the right packages, I renamed one of my `.jsx` files to `.tsx`, and my IDE immediately rewarded me with some squiggly red underlines! > (aside) Packages: `npm install --save-dev @types/jest @types/node @types/react @types/react-dom tslint typescript` For `tsconfig.json`, I mostly accepted the `create-react-app` defaults, except I set `"strict": false` since strict checking generally costs me more time than it saves. ```js // Header: tsconfig.json { "compilerOptions": { "target": "es5", "lib": [ "dom", "dom.iterable", "esnext" ], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "strict": false, "forceConsistentCasingInFileNames": true, "module": "esnext", "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "preserve" }, "include": [ "src" ] } ``` ## Lessons learned Goals are best achieved if they're stated as a list of wants, not a particular plan. If a genie could have added the TypeScript compiler to my project, I'd have wasted a lot of time in interoperability issues in the future. What I really wanted, embodied in the four points listed above, was something simpler, and something that took me less than a day to complete. No genies required. ✨