We’re going through a similar process at my company - how do we refactor the decade old startup-style JS without 1) spending a year rewriting everything from scratch generating little business value and potentially introducing regressions in the process 2) continuing to build stuff on that shaky house of cards. It looks like the solution we’ve kind of settled on has also been TypeScript. At this point it’s probably sub-5% of our prod code that’s TS source, and it’s daunting given how much we have to change and how many years it will take at our current pace, but it is the middle ground moderate way and probably the right way to approach things like this.
In my experience old JS code suffers from lack of structure and discipline much more than type safety. Can you provide an example where that isn't the case in your codebase?
Have you looked into Flow? This is the type of project it’s suited for (gradual typing). Facebook were in a very similar position when they created it. You can add a Flow pragma to a file and immediately get useful feedback based on Flow’s type inference.
In theory Flow has a stronger inference engine than TypeScript, which means it's more suited to incremental adoption.
TypeScript is based on an AST; which means it's inferences are limited. For example it requires you to type the parameters to a function. If the return type is derived from untyped parameters then TypeScript falls back to typing the return type as 'any'.
Flow maps the flow (hence the name) of types throughout an application, which means that it can derive the signature of a function based on the types that are passed into it. That should mean that it's a little easier to add to an existing project.
TypeScript can do type refinements based on the flow (e.g. a null check refines a type with null to one without) but can't do what you just showed. Are there any tools that can emit the inferred types to source code? I just now got a vision of using flow style type inference to gradually augment a project with either flow or TS types with very little work.
Flow has an API that can expose its inferences. There are Flow to TypeScript conversion tools that might bake the types into definitions.
Ironically, we’re considering switching to TypeScript and in my team the lack of inference is seen as an advantage. It forces people to consider their types as part of the design of their interfaces.