
Migrating 300k LOC from Flow to TypeScript - rgoldfinger
https://medium.com/tech-quizlet/now-or-never-migrating-300k-loc-from-flow-to-typescript-at-quizlet-d3bae5830a1
======
z1mm32m4n
The important parts here are (1) that they remained metrics driven and (2)
that they focused on repeatability.

1\. Both TypeScript and Flow are gradual type systems. They both explicitly
allow escaping the safety and coverage of a type system, either to allow
easier interop with existing JavaScript packages or to allow existing
JavaScript codebases to incrementally adopt types. So there are always holes
in which errors are reported, regardless of whether they had to silence some
explicitly or not. By measuring the extent, they have a yardstick against
which they can drive further type adoption.

2\. Repeatability is the only way to do large code migrations. Write the steps
in a script, run it, find the bugs in the script, fix the script, and repeat.
With 300k lines of code no doubt they have dozens or hundreds of people
committing daily. So a script is the only way to sneak in during off-hours,
get something in that doesn’t race to conflict with someone else, and land the
change. A long running, manually crafter branch here would not work—it would
effectively operate as a fork of the codebase for the entire duration of the
migration, with all the downsides that a fork entails.

Kudos to the team; this is a very impressive feat!

------
truth_seeker
I really like the way TypeScript approaches Dynamic world of JS ecosystem with
various advance types.

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

Intersection Types - An intersection type combines multiple types into one.

Union Types - A union type describes a value that can be one of several types

Type Guards - A type guard is some expression that performs a runtime check
that guarantees the type in some scope.

Nullable types - compiler level safety for "can be a null " implication of any
type

Type alias - as the name suggests

String literal types - Union type of few string values

Numeric literal types - Union type of few numeric values

Enum Member Types - enum members have types when every member is literal-
initialized

Discriminated Unions - You can combine singleton types, union types, type
guards, and type aliases

Index Types - you can get the compiler to check code that uses dynamic
property names

Mapped Types - Transforms each property in the old type in the same way to
create new type.

Conditional Types - selects one of two possible types based on a condition
expressed as a type relationship test

~~~
orta
Nice overview! I plan on building a larger version of this into the docs
eventually

[https://github.com/microsoft/TypeScript/issues/31983#issueco...](https://github.com/microsoft/TypeScript/issues/31983#issuecomment-503667997)

------
munchor
I recently underwent a very similar experience at MemSQL[1] but for only 30K
LOC. The pain points that led us away from Flow to TypeScript seem to be the
same as the ones as Quizlet's .

Overall, I'm very happy to see more and more teams move to TypeScript as a
direct consequence of its Babel 7 support. TypeScript makes more sense as a
pure type checker and not as a full blown transpiler.

It's also interesting to see that their type coverage increased from 66% to
86% (ours increased from 88% to 96%). This pattern is probably because of
better third party type definitions.

I still prefer Flow's Type Inference and philosophy over TypeScript's, but in
practice TypeScript is much better pretty much any use case I can think of.

[1]: [https://davidgomes.com/porting-30k-lines-of-code-from-
flow-t...](https://davidgomes.com/porting-30k-lines-of-code-from-flow-to-
typescript/)

~~~
vicapow
There is not a 1-1 metric for "coverage" across these two systems. For
exmaple, TS believes this code is "covered" but Flow knows it's not safe:

type A = { readonly prop: number }; type B = { prop: number };

function func(f: B) { f.prop = 20; return f; }

const a: A = { prop: 10 }; func(a); // no error?

------
paulddraper
Similar decisions to what we made at Lucidchart 2 years ago for migrating 600
KLOC of Closure to TypeScript. [1]

(Closure is a venerable JavaScript type system by Google using JSDoc-like
annotations.)

Stop the world migration, automated scripts, manual fixes, lots of ts-
ignore/any. And thoroughly worth it.

[1]
[https://www.lucidchart.com/techblog/2017/11/16/converting-60...](https://www.lucidchart.com/techblog/2017/11/16/converting-600k-lines-
to-typescript-in-72-hours/)

~~~
vosper
How do you feel about Typescript 2 years after switching? Anything that you
wished you known before, that you've had to learn on the way?

~~~
paulddraper
The largest change was actually going from Closure modules (namespaces) to
ES6-style imports.

TypeScript just keep getting better and better. I am looking forward to better
Google Bazel support for TypeScript to help with monorepos.

------
vimslayer
There have been a few posts like this, and the discussion always seems to
focus on plain JavaScript vs TypeScript. I'd like to see some talk about Flow
vs TypeScript instead. We have what I'd call a medium-sized project (~30k loc)
written with Flow and TypeScript's superior tooling is indeed attractive
enough that we too have been thinking about making the switch.

Having used both, there are a few features in Flow that I'd miss. I think
classes being nominal is a good approach and the ability to declare new
nominal types is useful. Otherwise Flow's type system is structural, like
TypeScript's. Function parameter type inference is also very nice feature, so
I can make simple module- or function-local helper functions without needing
to specify the parameter types explicitly. Having a syntax for specifying
parameter variance allows for a more sound type system in some areas.

Performance has historically been pretty bad but it's been getting better with
every release. The tooling has also seen some improvements after Facebook
nuked their own Nuclide-editor project and moved on to endorsing Language
Server Protocol and VSCode. It's still far, _far_ behind TypeScript, though.
It's also a bit worrying that the community seems to be getting smaller, not
bigger, with posts like this popping up.

~~~
vimslayer
A few more random thoughts that popped into my head:

Flow's Windows support is pretty buggy. We have some team members who prefer
Windows as their dev machine and it looks... painful.

Flow supports the upcoming `?.` operator and overall they seem more open to
introducing features that are in a development phase and might be
changed/deprecated in the future. I guess it's a matter of perspective if
that's good or bad, but man I'm going to be sad if we end up making the change
and I have to convert all those nice and clean `foo?.bar?.baz` chains into
some unreadable multiline monstrosity or calls to some random getter library.

Flow's exact object types [1] are really useful and have prevented actual bugs
when used with optional properties. TypeScript's concept of "freshness" [2]
does prevent many if not most of these bugs though.

[1]: [https://flow.org/en/docs/types/objects/#toc-exact-object-
typ...](https://flow.org/en/docs/types/objects/#toc-exact-object-types)

[2]:
[https://basarat.gitbooks.io/typescript/docs/types/freshness....](https://basarat.gitbooks.io/typescript/docs/types/freshness.html)

~~~
WorldMaker
> Flow supports the upcoming `?.` operator and overall they seem more open to
> introducing features that are in a development phase

It's funny because Typescript started out by trying to get ahead of a lot of
features and "settling down" to trying to stick to (mostly) only TC39 Stage 3+
features has been a maturation that has been good for the language. It's
interesting that Flow seems to be moving the other direction.

A benefit to making it easier to pipeline Typescript inside Babel is that it
is no longer entirely on Typescript to transpile every possible wishlist
language proposal. This is also why a lot of people preferred Flow because you
could just slot Flow checking before or after certain transforms. Typescript
is getting better about fitting into the middle of a Babel transform stack so
that you might have some basic preprocessing steps of Stage 2 or earlier or
non-standards track things that Babel supports before type checking.

(The Optional Chaining proposal for `?.` is currently in Stage 2 in TC39.
Indications seem to be that at least some of the Typescript devs are ready to
champion that feature in the very minute it hits Stage 3.)

~~~
robmcm
Can’t wait for the, “Elvis operator” to land. Going to remove so much code!

------
jillesvangurp
I recently spent some time introducing typescript to a javascript code base.
My background is mostly backend/JVM, so I wasn't the perfect person to be
doing this obviously. But as there was nobody else and I needed to own this
stuff, I put in the work and got things done.

My impressions are mostly positive in the sense that if you are doing JS, you
are better off doing TS. The tooling is great and you can start with simple
changes that almost immediately start giving clear benefits in terms of typing
related warnings and other improvements. Typescript is minimally intrusive in
the sense that all js is valid ts and you can gradually improve it by adding
type annotations and addressing warnings.

The benefits are a vastly improved safety net at a very minimal cost, smarter
tools, and more confidence that a given piece of code will not break with some
entirely preventable error (like an NPE). Excluding whole categories of bugs
is a good thing. For new code, I consider it a no brainer. Most new projects
seem to default to using typescript so I guess more people have come to the
same conclusion. Lots of upsides, no real downsides that I know off. You'd
have to be pretty stubborn to opt out at this point. For older code that you
still care about, migrating is a good investment as well: you will improve the
code. Any other code you probably need to get rid off anyway.

That being said, I believe typescript is merely a gateway drug. Typescript is
great and you definitely should use the strict mode. But even in the strict
mode it still inherits a lot of cruft from javascript and this makes life
needlessly hard. Other languages don't have this problem and the progression
from the js/ts ecosystem to other things is very obvious with some younger
full stack engineers I've known for a few years. Go and Rust seem particularly
popular lately. And even Kotlin seems to be well liked (big fan myself).

Better still, a lot of these languages are coming to the browser (via WASM or
transpilation). Inevitably, some projects will start moving away from
defaulting to the js/ts ecosystem for frontend work. Right now you'd be a very
early adopter but things are improving rapidly and there are some early
adopters. I played a bit with kotlin-js recently and while it still has lots
of rough edges, it actually works quite well. Not quite a drop-in replacement
for typescript just yet but most of that is related to consuming javascript
from kotlin; which I'd argue is short term needed but long term undesirable
and likely to become less important as native kotlin frameworks emerge (e.g.
kvision is a popular one). Tip, parcel recently added kotlin support; works
without extra configuration even.

~~~
deckard1
The problem with JavaScript is that it's a terrible example of a dynamically
typed language.

> The benefits are a vastly improved safety net at a very minimal cost,
> smarter tools

That's entirely subjective. As far as tooling, I get the impression from my
coworkers and what I've seen online that people that love TypeScript love big
bloated IDEs. They love having their IDE tell them what to do. And,
personally, I find their usage of IDEs to be a crutch that prevents them from
transitioning from a plateau of mediocrity to being a great developer. They
don't understand the code. They merely throw things together that match the
types that their IDE tells them, like LEGOs. Then they spend hours trying to
figure out why their code doesn't work.

As far as the cost goes, my experience has been vastly different than yours.
TypeScript is killing my organization. For no real benefit. The warnings it
produces are for theoretic bugs rather than _actual_ bugs. The warnings
TypeScript produces are incomprehensible to most developers. And when they
finally quiet TypeScript down, they don't realize they _have the wrong types_!
No one seems to fully realize how easy it is to get TypeScript wrong. Your
code gets littered with ts-ignore and "any" types and suddenly you have a
mountain of tech debt. Code becomes ugly and difficult to read with type
annotations. It's hard to imagine anyone actually winning in this war of
attrition between clean, simple, readable code and TypeScript.

If you're doing TypeScript and having an easy time of it, you're most likely
_doing it wrong_. TypeScript is incredibly nuanced.

Management loves TypeScript though. It's a shield for responsibility. When
bugs happen (and they still will with TypeScript), they can point to
TypeScript as something they tried. They didn't fail the organization, their
technology failed _them_.

~~~
CuriouslyC
I don't want to have to remember the properties of every object, or the
arguments to every function, or a myriad of other things. I could be using
that time and energy to work on other things that are more important.

~~~
deckard1
It's not about knowing the interface to a blackbox. It's about semantics. It's
about knowing what an object or function is _doing_.

> I could be using that time and energy to work on other things that are more
> important.

As a developer, it's your job to know how your code works. There really isn't
anything more important than that.

------
cryptica
I've been using TypeScript for over 1 year after 10 years of JavaScript and I
really don't like it. It slows me and the team down and creates more problems
than it solves. It doesn't even ensure type safety because there is no runtime
type validation for JSON objects received from the API. It's disturbing that
so few people can see how useless it is. To me, it's extremely obvious.

TypeScript is a hack of epic proportions and every so often the reality rears
its ugly head in the form of failed source mapping, version compatibility
issues, unexpected types during runtime, poor architectural decisions aimed at
pleasing the compiler instead of fulfilling project goals.

TypeScript is a very poor way to model real-world systems because it
incorrectly assumes that real-world entities have a fixed type schema. This is
obviously wrong. Real-world objects change over time and most things cannot be
categorised clearly. A tadpole turns into a frog and learns the ability to
walk. Some people are disabled and cannot walk. Some cars have 6 wheels. There
is no fixed type schema for anything in the real world; it is filled with
anomalies so why should we design systems to model such unrealistic objects?
Why not force the developers to account for as many cases and schemas as
possible, it's our job!

~~~
jmull
People should not be downvoting this.

There's a weird impulse out there to try to pretend the limitations of static
type-checking don't exist... namely, that it's _static_. That is, it's build-
time checking and doesn't provide guarantees at runtime.

For tightly coupled systems were you control all tiers, you can stretch the
value of static type checking by ensuring that changes to back, middle, and
front all roll out together. But obviously, you're seriously limiting the
scalability of your product and agility of your releases when you do this (you
need a command-and-control dev communication structure). If you want static
type checking to make guarantees beyond the local component, this is a major
architectural commitment. To maximize the "guarantees" of static type-
checking, it will affect the topology of your entire product, including the
release process. (How much downtime is acceptable per release? What
limitations are you willing to accept in terms of distributed scalability? Is
it acceptable that user sessions become invalid for a release and how will you
handle the UX for this?)

Not saying you shouldn't do it, but go in with your eyes open.

Now, static tools certainly have their uses. But:

(1) type-checking is just one limited case. e.g., go get eslint, turn on
almost all the rules, and work with that for a few weeks. After you get over
the initial shock, you'll get a lot of benefit from from it and it will last
for years.

(2) it doesn't help you with anything external to the source file that's not
tightly coupled through some additional control mechanism. (Note that you pay
a price for control mechanisms.) So, it's a small solution to small problems.
Quite nice. But limited without paying an additional price, which may be quite
expensive depending on the other goals of your system.

I don't agree with the previous poster's assertion that it's a useless hack.
But I understand why one might feel that way. The utility of static type-
checking seems to be greatly oversold.

~~~
stickfigure
I don't get this argument at all.

If you look at a typical call stack - in any language - going from the
frontend all the way to the database, there will be hundreds of lines. Two,
maybe three of those will be remote jumps.

Every one of those calls is an opportunity for a contract mismatch. Even if
static type checking misses a half-percent of your calls, it's still working
to enforce 99.5% of your contracts. The value of that is hard to oversell.

~~~
jmull
There’s nothing wrong with static type checking, It’s generally good and will
prevent certain kinds of problems with out the cost of unit tests. I’m all for
static checking generally, not just for types.

My point was to point out the limitations. I might be wrong, but people seem
to often ignore these limitations. This oversells the benefits and leads to
frustrated people like the one whose comment I originally responded to.

99.5% is not nothing, but not something you can live with in most systems so
you are probably going to need something more. Also, you are going to be a lot
happier if the vast majority of those hundreds of lines in your cross-system
call stack are completely agnostic to the types of your problem domain and you
want to be careful to minimize the coupling between components living on
different sides of the jumps you mention. You can do that, of course, with
static types, but it takes more care and intentional design and you really
need to acknowledge the issues before you can get it reasonably right.

------
lacker
My only real pain point with TypeScript so far is with a codebase where some
parts run in Node, some parts run in the browser, and some parts are libraries
that can be imported by either. The best solution I have found is to separate
out all TS code into three directories and have multiple tsconfig, but it
often doesn't work nicely with the rest of the ecosystem.

------
iLemming
I've been reading the comments, and sorry, I can't help it. Every time I read
about (or try) some trendy thing in JS/Front-end world, I feel irritated and
sad. Every single "big promise" hype that supposed to help us to "fix" issues
with front-end development adds more complexity and frustration. jQuery was
awesome, then "okay", then become bad, then later really, really bad. Same
with Backbone. Same with Angular and Web components. Then Coffescript,
Livescript, GorillaScript, IcedCoffeeScript and other "x-cripts", Dart,
Typescript and Flow, Traceur and Babel. Then Grunt, Gulp, Browserify, WebPack.
React came, and we got Flux, Redux, Redux Saga, MobX. And then some stuff
that's actually not too bad for whatever reasons gets mostly ignored, e.g.
Meteor, Ember, Cycle.js.

I think fundamentally something deeply wrong with all that, and I don't know
when and how we're going actually to fix it. After years of trying, I finally
decided enough is enough and started looking for deferentially alternative
solutions. I like Purescript and Elm. ReasonML looks promising. Currently, I'm
using Clojurescript. It is not a silver bullet, but it is an astonishingly
practical choice. I know - it won't work for everyone. The majority would
dismiss it almost immediately (mostly for the wrong reasons).

I'm not advocating for it. I'm just saying that I feel Typescript is not that
magical pill that makes everything better. If you follow @garybernhardt on
Twitter, I think he may feel the same.

~~~
tekkk
So you have seen the JS world progressing and feel sad about that they haven't
just found out the "best way" from the beginning? I think it's marvelous how
fast JS moves and outdated solutions are phased out in new projects for better
and more reliable methods (although I admit some JS developers pick the wrong
tools for wrong reasons). The act of having to constantly learn and think
about a better way of doing things has at least kept me sharp and meticulously
pondering the pros and cons of each way of doing things.

Sure the churn can be discouraging and if you wanted to be able to write the
same way with same tools to retirement I wonder why you'd even consider doing
frontend development. I am quite happy with the tools I use (Typescript,
React, Mobx, StyledComponents) and have felt no need to change for something
else. They are tools I am very productive with, and all the developers I've
worked with have been able to get quickly on track with using them. No huge
mental leap from JS to TS as with functional languages like Clojure or Elm.

Although it's my opinion that, JS itself should be eventually be phased out
for something better for majority of frontend software. It just has too much
old baggage so it would be best to just switch for something new completely.
However, there's something mysteriously intriguing about JS in that allows so
great flexibility, in good and bad, that not just any language can replace it.
But nowadays I don't do much complaining about JS stuff anymore. I have found
my sweet spot and I'm mostly just busy getting stuff done and there's things
far worse in programming than how "bad JS is".

------
orta
Somewhat hijacking this thread to say that if you have ideas for improvements
to TypeScript's documentation or website, we're currently asking for ideas:

[https://github.com/microsoft/TypeScript/issues/31983](https://github.com/microsoft/TypeScript/issues/31983)

------
sdegutis
I've been a big fan of TypeScript for a few years, but lately I've been
Considering Removing Typescript[1] from my project, because it seems to have
just as many drawbacks as benefits, and the freshness of pure JavaScript is
starting to look appealing again.

But I haven't heard anyone take this perspective and I'd be really curious to
hear people's thoughts on this idea.

[1] [https://sdegutis.com/2019-06-20-considering-removing-
typescr...](https://sdegutis.com/2019-06-20-considering-removing-typescript)

~~~
chaostheory
> Browsers don’t support it, so it adds an extra build step. Type-completion
> has gotten slower the bigger our project gets.

This is something a faster computer can mitigate. Also I would compare the
time you spend guessing what parameters actually are in vanilla JS vs the
seconds you lose waiting for a Typescript build. Silicon time is much cheaper
than carbon time.

I don't understand how anyone doesn't use Typescript for large projects.
Javascript is just too lenient. It doesn't even complain when you're not
matching a method signature that you call; you can't even catch these ninja
errors let alone explicitly know they dropped on you. There's a lot of value
when you have a system that adds more checks and validation before runtime.

~~~
sdegutis
That's the general consensus and how I mostly feel too. But I'm starting to
feel like the drawbacks might possibly be outweighing the benefits.

~~~
chaostheory
If we were talking about Ruby or Python, you'd have a stronger point. However
Typescript's benefits far outweigh its drawbacks in light of Javascript's
excessive lenience. Since, you're complaining about types, have you made good
use of Typescript's interfaces and class inheritance? They're pretty powerful
and even more flexible than something like Java.

~~~
sdegutis
Yes, I use TypeScript types very idiomatically and have received very positive
feedback on my TypeScript code. But just running `tsc` takes >10 seconds on a
relatively small project, and IDE support is becoming proportionately slow,
e.g. hovering over anything to show its type often takes 5-10 seconds before
VS Code will actually have something to show me. It's becoming painful, and
that feature which I once heavily relied on has become nearly useless to me.

~~~
chaostheory
Is the time spent compiling drastically more than the time spent debugging
code because of the lack of types? A few seconds of compiling is because than
the potential hours wasted on debugging. Besides, you can mitigate compile
times by getting a faster computer. Silicon time is still way cheaper than
carbon time.

~~~
sdegutis
It's not just "seconds wasted in compiling". It's having my IDE say
"thinking..." for 3-10 seconds _every time I want to see what type something
is_. Or having create-react-app load for 5-15 seconds, _every time I save any
file_ before letting my browser update (even if I press refresh manually),
because it's compiling. And when this happens on a minute-by-minute basis, it
drastically slows down development time.

------
masonic
Similar story porting a similar volume of Flow code (MemSQL):

[https://news.ycombinator.com/item?id=18906405](https://news.ycombinator.com/item?id=18906405)

------
turadg
Great planning and tooling work by our Web Infra team at Quizlet (Roger,
Karoun, Jonathan) and all the product engineers.

We're closely following this discussion so if you have any questions on some
of the specifics we're happy to answer!

------
bytematic
I converted my work codebase to TS and it provided a lot more benefits than
just types, including a greater understanding of the architecture. I was able
to convert about ~400 files in around a week without any sort of plugins

