Migrating from CoffeeScript to TypeScript
I recently finished migrating my app to TypeScript from CoffeeScript, and thought I’d share my story of woe, frustration, realization and finally triumph.
Background
I’ve been working on my app SweatRecord for the entirety of my career as a developer (little over two years at this point). Prior to this, my only official development experience was the Bitmaker Labs web developer bootcamp I enrolled in.
“A Little Bit of Knowledge is a Dangerous Thing”
Starting out, I had a conceptual understanding of OOP but no experience applying it to a front-end client. I was using Rails 4 with CoffeeScript in a very elemental fashion. All the code I wrote was contained in event handlers. All the clicks did all the things. But that approach ended up scaling rather poorly.
“Hmm, Maybe I Should Use Global Variables”
A few months in, I realized I couldn’t store all the data I wanted in the DOM and certainly couldn’t store all of the logic supporting page updates in the handlers. I then migrated my logic into global objects, which was an improvement. State data was now stored in those global objects and it was a big improvement over just using handlers.
“Remember Classes? Your Front-End isn’t an Exception.”
Later on, around August 2015 it occurred to me (was badgered by my best friend about it, TBH) that I needed to have a mobile client because fitness users can often be training in concrete block buildings with no reception. I had went through an iOS book before, but decided that I needed a cross-platform solution. After some quick research, I chose Xamarin.
As part of my training for Xamarin, I first devoured the excellent Adaptive Code via C# book. It had a strong focus on SOLID principles, and it occurred to me as I was reading through it that all of the challenges I had developing for the front-end were rooted in the fact that I had no classes. At all. It was the worst sort of spaghetti code with global objects with functions and data attached going everywhere.
I took a few weeks to replace my persistence layer, and then moved onto developing the mobile client. And then several months later…
I Have No Idea How This Works or Why
Several months after I had gotten the app to a late-alpha state, I realized that the business data model I had was incomplete. I had to make changes to the server as well. So I turned back to the server, updated the backend, and then turned to the front-end.
I had forgotten how the blasted thing worked. The entire solution was something I had to revisit, which was rather onerous because my documentation was thin, and RubyMine didn’t handle Coffeescript comments well.
People are probably going to move towards EcmaScript 6 because the CoffeeScript tooling is weak. — Some Dude at a Meetup
I heard that several months before but didn’t appreciate what it meant until this moment. I didn’t appreciate just how important tooling was until I worked with Visual Studio and saw the difference that both types and and documentation support brings to the developer experience.
What’s this TypeScript Thing I Keep Reading About?
A few months down the road, I started noticing people mentioning TypeScript. When RubyMine started supporting it, I decided I should give it a quick spin.
Everything had types, I no longer had to check the API docs for each library to see if I was using it right. It all made sense.
OK, Let’s Get This Migration Started
At that point, I was faced with the challenge of migrating about 17K LOC with several dozen classes in them. I did find some Gems that supported CoffeeScript conversions, but none of them seemed to be maintained. So I settled on semi-manual approach;
- Import the TypeScript definition files for the existing libraries (JQuery, Underscore, Velocity, etc). Create definition files for the libraries that didn’t have them.
- Create a definition file with all of the global variables my code depended on.
- Generate the Javascript for a class. I used the Js2Coffee library to compile the existing code.
- Convert the prototyped object into a TypeScript class and fix any compilation errors and type errors.
- Add type constraints to properties, parameters and method returns.
- Update the definition file with any types that hadn’t been converted.
- Run my feature tests to make sure I didn’t break nothing.
- Repeat Steps 3–7 for all CoffeeScript code in the project. It was easiest to start on the bottom of the type hierarchies and work my way up.
Ahh, Sweet, Sweet Order
As of a week or two ago, I was completely finished the conversion. I had banished CoffeeScript from my application altogether.
Next steps? Adding front-end unit tests using Jasmine and Karma. Before I get there, I need to deploy factories for all of my classes.