Migrate to TypeScript by typing a seam first
Migrating to TypeScript is less about the technical details and more about picking a place to start. A technique that I’ve seen work well is to pick a seam.
In many apps, this seam is utility code. Consider this JavaScript codebase:
export const fullName = (first, last) => [first, last].join(' ');import {fullName} from './src/utils';
export const App = () => { return ( <> <p>Hello {fullName('Jake', 'Worth')}!</p> <p>Hello {fullName('Josh')}!</p> </> );};This code is broken because fullName expects two names, but our second call
only sends one. These are the kinds of problems that type-safety can catch.
Let’s see how. First, we convert our utility file to TypeScript.
export const fullName = (first, last) => [first, last].join(' ');Without doing anything else, your text editor should start complaining in that file:
Parameter 'first' implicitly has an 'any' type,but a better type may be inferred from usage. (tsserver 7044)
Parameter 'last' implicitly has an 'any' type,but a better type may be inferred from usage. (tsserver 7044)TypeScript doesn’t like that first and last are not explicitly typed. So, we
type them as strings.
export const fullName = (first: string, last: string) => [first, last].join(' ');Inferred types are smart enough to know that this function must return a string.
If this was all we did, we’ve added value, because fullName is better
documented. And, if we call it in this file without passing both arguments, we’d
get a type error.
The next step is to convert the caller to TypeScript TSX:
import {fullName} from './src/utils';
export const App = () => { return ( <> <p>Hello {fullName('Jake', 'Worth')}!</p> <p>Hello {fullName('Josh')}!</p> </> );};Hovering on the second call is the real payoff:
Expected 2 arguments, but got 1.
Related information:
* strings.ts#1,41: An argument for 'last' was not provided.
(tsserver 2554)This bug is especially tricky because it isn’t a JavaScript runtime exception. The malformed “Josh” construction doesn’t break, but it’s incorrect.
> fullName("Josh")'Josh 'This is not a “full name” (the function is a lie) and contains an extra space. That space shows up in the HTML with an extra space between “Josh” and “!”.
<p>Hello Josh !</p>TypeScript can catch this; JavaScript reasonably can’t.
This technique might not work when you have widely reused utilities. And it might not be efficient enough when you have bugs based on production data that you hope to address immediately with TypeScript.
It’s one approach that has helped me move past “Analysis Paralysis” and start getting TypeScript code into the codebase. Keep adding seams and expanding from your seams until you have a comfortable amount of type-safety.
- Migrating from JavaScript — TypeScript handbook