NFL Loves Codemods: Migrating a Monolith
NFL ♥ Codemods: Migrating a Monolith - NFL Engineers - Medium
You may have heard that we started using React in production over a year ago at the NFL. If you’ve been using it too then you know a lot has changed in that time: React itself went from version 0 to 15 (not really, but really), React Router introduced dynamic routing, inline styles entered our collective good graces after many years in the doghouse, and the React ecosystem in general grew by leaps and bounds. To say it’s been an exciting year would be an understatement.Of course, that excitement hasn’t come without a few headaches. The NFL web team maintains nine React projects in one monolithic repository. As much as we want to keep them all shiny and up to date, it just isn’t feasible (or sane) to comb through that much code every time there’s a new syntax for refs or a new routing paradigm.The hardest part is knowing when to let go (of your code)Which means that when we decide to go for it, we do it all at once, like ripping off a band-aid. We recently packaged about six months worth of minor updates into one exhaustive migration. Most of the changes were small and repetitive, like modifying every <Link> tag to support the latest React Router, or massaging all of our styles a bit as we crossed over from Stilr to Radium. The point is, the updates we had to make weren’t large or complex, but they were everywhere.Enter react-codemodNot long after we started quietly panicking, we came across a project by Facebook called react-codemod. Every so often React comes out with breaking changes in its API, and react-codemod is a series of scripts that go through your code for you and make the necessary updates (the technology behind it is outside the scope of this article, but it’s pretty awesome).Using react-codemod as a guide, we began writing our own scripts to kickstart the migration process. They all follow the same basic structure: parse your code into an abstract syntax tree, edit the tree, and print out the new code.Simple, right?Baby StepsWe started off small: take something that could be fixed with a simple find/replace, but handle it via codemod. The first item we chose to tackle was a self-inflicted wound. We had open-sourced one of our libraries (yay!) so all of the imports had to change (boo) from @nfl/gridiron/addons to react-helmet.import Helmet from “@nfl/gridiron/addons” —> import Helmet from “react-helmet”Stepping It UpOnce we had the basics down, it was time to step our game upOnce we had the first few scripts in place, it was time to kick things up a notch. ast-types, the technology behind react-codemod, gave us control over every aspect of our code, from method calls to ES7 decorators to JSX tags and attributes. Each new discovery came with a sigh of relief, since more automation up front meant less grunt work later.CSS classNames to inline stylesAs I mentioned, we switched to Radium for styling, which meant moving from CSS to inline styles. When it came to applying those styles, the most common use case involved switching out the className attribute for style and using the spread operator to create a style object:// before (using the classNames library to combine classes)<input className={classNames(styles.one, styles.two)} />// after<input style={{...styles.one, ...styles.two}} />And the code:Upgrading React RouterWe also upgraded React Router, which meant getting rid of named routes and switching over to an internal route helper.// before<Link to=”superbowl” params={linkParams}>{game.year}</Link>// after: route.for() is the internal helper we created<Link to={route.for(“superbowl”, linkParams)}>{game.year}</Link>Taking It FurtherThe last item we had to address stemmed from a central piece of our new architecture: jspm. When you import something in Node, you aren’t required to be too specific. You can point to a file, a directory, or an external module, and Node will go through a bunch of steps to figure out what you want to import. This is fine if you’re using a javascript bundling tool like Webpack, but jspm loads every file individually via HTTP. For us, this meant changing all of our imports from ambiguous to explicit.It would have taken forever to go through all of the imports in our code by hand, but codemod made it easy. We just grabbed all imports coming from our own /src directory (i.e. not node packages) and then used the resolve module to determine the full path to the file. Problem solved!Throughout this process we also found a number of “alias” index files, which did nothing but import and export a sibling. They had been there for the sake of those ambiguous imports, but now we could remove code and computational overhead by getting rid of them. You can think of the imports as a linked list of A → B → C. We needed to remove B, and point straight from A to C.You NEVER go more than two imports deepThis involved following our imports down a couple levels. Specifically, any time we came across an imported file named index.js, we then scanned the contents of that file. If it was an alias, we would resolve the file that it was importing, point to that instead, and mark the index file for deletion. Generally a codemod script will treat each individual file in isolation, but this particular task forced us to reach out into the surrounding code for the information we needed. You can check out the full script but here is the test for alias files.This code cleaned up 2700 imports. Not bad, right?Cleaning UpTo get rid of those extra files we brought in a tool called shelljs which, as its name would imply, brings all your favorite shell commands into javascript land. This was useful for removing those extra index files and handling a couple other tidying-up tasks, like renaming our .jsx files to .js for the sake of uniformity.And with that, our migration was complete!The AftermathA photo of me after all of this was overMigrating our codebase was a long process (even the article about it is long, sorry), and in the end automation only got us so far. Certain complex tasks required human intervention, and other adjustments were so trivial that scripting them didn’t make sense from a pragmatic standpoint.Still, the vast majority of the work lay in a sweet spot somewhere in the middle, and with these new automation tools at our disposal we’ll be able to keep our stack more up-to-date than ever. Gone are the days of waiting to deal with all migration issues at once; we can now handle things in a more modular way as each situation arises (if the term wasn’t already taken I would dub this the “continuous integration” approach).We’ve just scratched the surface of what can be done with codemod and ast-types. We used them to migrate existing code, but there’s no reason they can’t go through your code and stub out unit tests customized to your preferences, or combine with eslint to clean up your code as you write it. Maybe you just need a script to switch out all your images with pictures of Leonardo DiCaprio (actually that one’s done, DM me if you want it). What I’m getting at is that the list goes on and on, and we’re excited to see where else we can take this newfound toolset.Our codemod project can be viewed here, we encourage you to check it out!Images property of (in order) Paramount Pictures, New Line Cinema, Warner Bros. Pictures, 20th Century FoxI tried to stick Leo in this picture too but they wouldn’t let me