
Migrating a 10,000-line legacy JavaScript codebase to TypeScript - zjiekai
http://www.pgbovine.net/migrating-legacy-codebase-to-typescript.htm
======
ghh
This is kind of a random Typescript tip, but when migrating regular
Javascript, there is an alternative to adding the type 'any' to every object
to 'silence' the compiler.

That is to introduce a preliminary type definition. Instead of:

    
    
      const oldVar: any = { field: 1, ... }
      function foo(bar: any) { ... }
    

You can write:

    
    
      declare type OldVarType = any
    
      const oldVar: OldVarType = { field: 1, ... }
      function foo(bar: OldVarType) { ... }
    

This way, you can signal that it's not just any kind of any, but a particular
kind of any, which is now trackable in your codebase.

When you're ready, you can gradually update the OldVarType declaration and
solve the compiler type check warnings from there. Union types [1] can be
quite useful then too.

[1] [https://www.typescriptlang.org/docs/handbook/advanced-
types....](https://www.typescriptlang.org/docs/handbook/advanced-types.html)

~~~
peteretep
Ah, that's a neat idea. I wonder if there's any easy way to then scan your
code for type aliases that are `any` to make sure you've caught all of those.

~~~
wldcordeiro
Common prefix or suffix? I know in the RxJS community it's not uncommon to
suffix an Observable variable like `fooBar$`

~~~
smt88
I think that would be more trouble than it's worth. Since "any" is a keyword,
you wouldn't even need regex to search for it. Something like this would work:

" = any"

This would _only_ show types that are aliases of the "any" type. As a side
note, the following would create a compiler error ("cannot find name 'any'")
because types are metadata and can't be used as program data.

const something = any;

------
kevan
I'm about halfway through migrating a ~30kloc ES5 codebase to ES6 with a
transpiler (Babel), linter (ESLint), and fast test suite (Mocha) [1]. Once all
files are going through the transpiler I'll probably add TypeScript or Flow.
We already had RequireJS in place so the conversion from AMD to ES6 module
format has been pretty straightforward, but I'm continually amazed with every
file I convert I usually find a couple real bugs with the linter. The most
common have been:

* Undefined variables referenced in a seldom-traveled conditional branch.

* Leaked globals due to missing `var` keyword.

Every bug I find is immediate feedback that this is a worthwhile use of our
time.

[1] [https://medium.com/@kevanahlquist/evolution-of-javascript-
at...](https://medium.com/@kevanahlquist/evolution-of-javascript-at-
bluestem-285e4fc5ca26#.f9mn885tm)

~~~
muglug
I can't recommend flow highly enough for gradually typing existing code (as it
requires far fewer code changes than TypeScript). Not sure what your timelines
are like, but it takes no time at all to set flow up, and you might find it
makes the task of migrating a little more varied.

~~~
a-saleh
How does flow stand now?

I have tried to use it ~6 months ago and the tooling felt kind-of wonky and I
didn't feel like maintaining the interface files if so much of it seemed to be
in flux.

~~~
tomduncalf
Based on a quick play recently, I'd say still slightly wonky. Nuclide has
improved a lot but autocomplete etc. is noticeably sluggish compared to the
atom-typescript plugin. On my travel 12" Macbook, Atom with Nuclide it's too
slow to comfortably use and the Sublime plugin for Flow seems to be quite
buggy, whereas the Sublime TS plugin is pretty solid.

In terms of type definitions available for third party libraries, Typescript
is way ahead. I also think the documentation and community around Typescript
is stronger, and will only get more so with the move to use TS as the default
in Angular 2. Also the stability is great and the roadmap looks promising.

Nothing against Flow, I think it is great and definitely has some advantages
(and anything which adds types to JS is a winner in my book!), but it feels
slightly more like an internal Facebook tool that happens to be released to
the public versus Typescript, which is more of a release quality product.

But yeah, either is great improvement over plain JS and I'd definitely
consider Flow for a personal project, but in a bigger company/team environment
I think the stability, tooling and community of Typescript would make me push
for that.

------
rkwz
I curious how using Typescript makes the project more "structured" compared to
using plain JS.

Typescript just gives you static types and classes. I can understand "classes"
make the code more modular and thereby more structured, but can't you already
do that using ES5 "class"? Types help in catching errors early and can help in
documenting the interface of the methods etc.

Also, if the other problem was isolating the scope of modules and dependency
injection, it could be easily done using Requirejs and all the modules could
be concatenated during build time. Webpack is a cool tool, but given the
nature of its documentation, the maintenance would be really costly in the
long run. Requirejs is just more established and satisfies all the author's
requirements.

It would've been really helpful if the author had included additional
reasoning behind the choices made.

~~~
WayneBro
The types are what give it structure. Types are the structure. When you create
a type, you are saying in concrete terms "this is exactly how things happen
and nothing else".

This is _very_ useful information for a large project. Think of it like a map.
You don't need one for your house, but you need one to walk cross country. Or
like NASA procedural manuals for something normal people don't need a guide
for...like an astronaut taking a shower or something.

~~~
rkwz
I don't understand.

> Types are the structure. When you create a type, you are saying in concrete
> terms "this is exactly how things happen and nothing else".

Types make the interface concrete - which is better for compiler/interpreter
(catch bugs) and better for developers (easy to understand instead of
guesswork)

But when I think about "structure", It is usually about how the codebase is
organized. How different modules interact with each other and is the flow of
the code easily understandable. This could be easily achieved without Types.

Maybe it's just my interpretation of the term "structure".

~~~
pixie_
It is pretty much impossible to have any sort of inferred structure from plain
javascript. The reason you can use super powerful tools to refactor, rename,
and find all references across files in typescript is because the IDE can
build a 100% accurate data model of your code base when it's written in
typescript.

~~~
chc
You can do that kind of stuff in dynamic languages like JavaScript, Python and
Ruby. Check out JetBrains' editors.

~~~
DCoder
To do things like that in a dynamic language, you need to either infer types
somehow, or rely on metadata like JSDoc, beyond the language itself, and that
sucks – if another developer works on the project with a dumber IDE, their
code will probably lack such metadata or it will not be entirely correct.

I have been using IntelliJ daily for the last five years for PHP, JS and TS
development. For PHP, I write typehints and PHPDoc wherever possible to help
the IDE make sense of things. For JS, I tried doing the same with jsdoc but it
didn't work out, and I have decided to avoid any automated refactoring or even
autocomplete, it's far too fragile. For TS, using the types in my code makes
the IDE smart and reliable, although IntelliJ's support for type inference in
TS is still far behind VSCode.

Special mention here goes to Magento developers, whose PHPDoc, 98% of the
time, is either lacking or outright incorrect (referencing classes that don't
exist – incredibly frustrating).

------
Dolores12
TLDR: rename files to .ts, download type definitions for external libraries,
silence TypeScript compiler warnings using 'any'

~~~
chrismbarr
Maybe initially, but that `any` type really defeats the purpose of having
types in the first place. Where I work we have several 100% TS apps and we've
banned the `any` type except for very rare exceptions. It's always better to
define a type so that it can be easily used elsewhere, and it also helps
prevent typos.

~~~
k__
for data objects (no methods) you could at least use Object instead of any,
this should prevent anyone from calling methods on it.

------
nudpiedo
By using haxe he could have started now transpiling some of those 10k lines
also to the python backend just for free plus all the additional prepocessor
and type checks that are common for typescript and haxe.

I don't really understand why people jumps to typescript and not haxe, which
in my opinion has more strategic advantages and warranties (with very few
exceptions).

Is perhaps just a marketing topic?

~~~
smt88
You're talking about two different things. Haxe is a different language, while
TS is a superset of JS (all JS is valid TS).

JS -> Haxe requires rewriting 10k lines before the program will run.

JS -> TS requires changing the file names, doing some imports, and adding
"any" in a few places. The program will then run, and type information can be
added gradually.

~~~
nudpiedo
I do not see that much of differences between Haxe and JS, mostly because Haxe
is a superset of EcmaScript. However the process is not that automatic because
TypeScript is more permissive I would say (though there is already a convertor
from typescript to haxe: ts2hx).

But that said, I am not really that much experienced with TypeScript.

~~~
smt88
According to Wikipedia, Haxe is not a superset of ES:

"Haxe is similar to ECMAScript, although almost no ECMAScript code will run on
Haxe without modifications."

------
AnkhMorporkian
I've always wondered if you could create a runtime inferrer of types for these
sorts of projects. As in, attach something that observes function calls and
sees over the course of some amount of time what types are passed into them,
and have them make a best guess of what arguments are what types. While of
course it couldn't be perfect, it could save a lot of grunt work.

I made a quick version of this idea in Python 3.4, where it was much easier
since I could modify the AST on import, but I never quite got it where I
wanted it and sort of lost passion.

~~~
andrewfong
You could probably do 80% of the inference statically as well. For instance,
given something like this:

    
    
      function incrX(obj) {
        obj.x += 1
      }
    

We can infer incrX expects an object with property x, which is probably an
integer. And therefore, a call like `incrX({x: "hello"})` is probably
incorrect. In fact, I think this is how type inference in languages like OCaml
works.

That said, I do wish statically-typed languages generally had runtime type
checking built in. For instance, I can tell Typescript what type of response
we're expecting to get from an API call, but that doesn't mean the compiled
Typescript will warn me if the API call ends up with a different type.

~~~
masklinn
> We can infer incrX expects an object with property x, which is probably an
> integer. And therefore, a call like `incrX({x: "hello"})` is probably
> incorrect. In fact, I think this is how type inference in languages like
> OCaml works.

Except OCaml's typing is much stricter than JS's, in JS `obj.x += 1` is
perfectly valid if `x` is a string. In fact due to all the runtime type
conversion protocols you can have pretty much anything as `x` and have `obj.x
+= 1` succeed.

So that requires a strict break and separation with JS semantics and treating
JS (or whatever) as an implementation detail and "assembly", which is the
opposite of Typescript's purpose (it's more of an Elm or Purescript or
ocaml_in_js thing)

> That said, I do wish statically-typed languages generally had runtime type
> checking built in.

Statically typed languages with lots of holes (at the type-system level) like
Java or C# do have runtime checks. Those with less holes like OCaml or Haskell
don't as the only way to get the "wrong" types at runtime is to have wilfully
undermined the type system in which case the developer is on the hook for
making sure they do that correctly.

> For instance, I can tell Typescript what type of response we're expecting to
> get from an API call, but that doesn't mean the compiled Typescript will
> warn me if the API call ends up with a different type.

That would require TypeScript having its own runtime rather than compiling to
regular Javascript, or it would require that TypeScript inject a fuckton of
type checks which would make the output orders of magnitude slower (both from
the overhead of javascript-level type-checking and from the increase in code
size and decrease in JIT optimisation opportunities).

~~~
dllthomas
To expand, with regard to (GHC) Haskell: you can perform run-time type checks
with the Typeable typeclass, and you can forgo compile-time checks in favor of
runtime checks with Data.Dynamic. You can also replace compile-time detected
type errors with "if this is executed, explode", with the -fdefer-type-errors
flag. And of course, in any typed language you can apply sufficiently general
types that you need to perform manual checks for safety.

------
partycoder
A machine is faster and more reliable than a human at comparing. Matching
types for verification is one of those things.

------
mark_l_watson
Nice article, and I am going to give webpack a try. I took an eDX class in
Typescript a year ago and really like the language but decided modern
JavaScript was getting "good enough." I regret that decision sometimes because
Tyoescript really is better IMHO.

~~~
CuriouslyC
Typescript is ridiculously easy to learn. The only hard part is getting your
typings/build/environment set up, the actual semantics of the language are a
breeze.

There are some subtle nuances to structural typing and interface definitions
if you want to write really slick code, but for the most part you can get by
without them.

------
alphaomegacode
Lots of intelligent replies here (like much of HN); having a Java & C/C++
background, I picked up Javascript some yrs ago for web work.

Now, I don't know how/if to move to Typescript or ES6? And any
resources/books/vids you all highly recommend? (I built some corp stuff in
Angular 1 but guess I'll have to move to TS for Ang 2 or learn React?)

Many commenters in this thread seem experienced in TS so thanks in advance for
sharing any advice

------
balls187
> Since globals defined in different JavaScript files share a common
> namespace, I often found it convenient to reference globals...I knew all
> along that these were bad habits

This line resonated with me. In my first CS course (Intro to programming), I
learned about why using globals can be problematic.

A good reminder that engineering is all about trade-offs. Sacrifice future
maintainability for present day productivity.

------
mizzao
I feel that academic code is generally more prone to bad-smell pressure than
industry code, since people generally produce for papers and deadlines and
"just get it working for now." It's good to see that it's possible to produce
some semblance of order in the constant grind.

~~~
jventura
Right, and "just get it working for now" never happens in non-academic
projects.. :)

~~~
mizzao
I don't know, but outside of large CS systems projects, I haven't seen much
incentive to "build things right", especially from the higher-ups.

