By Sean Barag, Senior Software Developer
Splitshot allows us to write new features with TypeScript’s most strict settings without having to modify any of our existing CoffeeScript code. Bridging that gap gives us significantly more confidence in the stability of our code. Simply put: we’re able to keep our coffee(script) but sleep a little better at night.
The Before Times
Pushing the Limits
We maintained that codebase through several large overhauls, including a migration to the custom, open-source data-binding library that Zachary Cava talked about last year. But eventually, we began to run up to the limits of large CoffeeScript applications — we had over 600 CoffeeScript files at one point:
- CoffeeScript’s existential checks (aka the safe navigation operator) were extremely handy, but didn’t force us to handle the resulting null values
- Modifying a function in file A that called a function in file B required both files to be open to ensure the call signature was correct
Now late in 2016, TypeScript’s compile-time guarantees were gaining popularity in the JS community. We’d been interested in getting some of our code written in TypeScript, but we quickly realized a limitation imposed by our existing CoffeeScript source.
TypeScript can provide its most useful guarantees by enabling the — strict family of arguments, which require type information for non-TypeScript code being called. The TypeScript compiler didn’t have any information about our CoffeeScript files, so it could only help us when TypeScript files called code from other TypeScript files. With 600 CoffeeScript files in existence, hand-writing declarations for those classes would require either a “stop the world” approach — where all code is locked while we hand-write declarations — or a “please make sure you update the declarations” approach — where we all eventually forget to update them. Neither is particularly attractive, especially while attempting to launch a major UX redesign.
“Is this a dumb idea?”
“Is this a dumb idea? I feel like this is a dumb idea.”
I spoiled that in the title I guess, but that’s exactly what we did. “splitshot,” the tool we wrote to handle this, attempts to understand what each CoffeeScript file exports via module.exports and generates a declaration file for each one automatically.
Aside: why “splitshot”?
Hulu’s LivingRoom team is based in Seattle, a city that prides itself on coffee culture. A splitshot from a coffee shop is a modification to any espresso-based drink in which one of the two shots is pulled from decaffeinated espresso. Just like this tool, they let you keep your coffee(script) but sleep a little better at night :)
Concessions, but not the “get your popcorn here” kind
A few complications immediately came up while attempting to generate strict-mode declarations:
- Function arguments can be multiple types or even excluded
- Null is sometimes allowed, but sometimes not
- CoffeeScript’s default value support makes this all a little more tedious!
- Most properties and class members are declared as any
- All functions return any
- All function arguments are optional
- All function parameters are of type any
While that slightly diminishes the type-checking abilities of the compiler, it allowed autocomplete in any editor with a TypeScript plugin, and forced all TypeScript to TypeScript interactions to be as strict as possible. We didn’t gain any new confidence about the CoffeeScript we had, but we did gain a lot of confidence in the consumers of that code. Since these declarations are automatically generated for every new build, we’re able to detect potentially breaking changes just by compiling our application.
Hulu has been using splitshot for our production app for just about two years. It strives to be a transitional tool to help decrease a codebase’s reliance of CoffeeScript while encouraging type-safe TypeScript code. To that end, it’s been a resounding success!
- -strict has been enabled for all of our builds over that period, and we’ve noticed a sharp downturn in invalid property access bugs. (We’re also actively replacing some of our most core CoffeeScript modules with native TypeScript implementations, but that’s a blog post for another day!)
In the meantime, @hulu/splitshot is available for installation via NPM, and the source is available on Github. We welcome GitHub issues for any bugs you might find and would be happy to talk about this kind of migration in the comments below.
If you’re interested in working on projects like these and powering play at Hulu, see our current job openings here.