A few months ago, I wrote about our adventures building a brand new project using React. Our existing SparkPost app was built many moons ago in the ancient framework known as AngularJS. In that earlier post, I said (and I quote), “So what about that 50,000-line Angular app? We won’t be migrating it to React, at least not directly.” Well, we’re migrating it to React. Directly.

It wasn’t that I lied, but let’s say I was a victim of my own lofty idealism. Shortly after that article was published, I started in on my plan to improve the Angular app. After almost two weeks of struggling to get Bower out of the project, I was immediately rethinking all of my ideals and life choices. I’d tried one thing and it was hard. It was time to leave ideals behind.

The Pivot

In all seriousness, we considered a couple of other wild ideas like writing React inside of Angular or migrating everything one page at a time. Ultimately we realized that the fastest and safest way to rebuild our entire app in a brand new framework was to do exactly what I’d publicly ridiculed: direct migration, i.e. a parallel rebuild. The big question was would SparkPost get behind this big idea?

The Pitch

Before I go on, I should explain something very clearly. The chance to completely rewrite your existing app from scratch is a rare occurrence in the world of engineering. If you come away from this article thinking, “I want to totally rewrite OUR app!”, you should know that you’re going to need a Very Good Pitch (and some flexible decision makers willing to carve you a good chunk of time).

That said, there’s tremendous value in the chance to rethink and rebuild your entire user interface as a singular, consistent unit instead of continuing to stack more cards on top of a shakier and shakier house. SparkPost got on board with the rebuild and we started in on a plan.

The Plan

So how do you rebuild an entire web application?

Step 1: Catalog and build a solid foundation of reusable components, design tools, and critical features like authentication.

Step 2: Draw the rest of the [censored] owl.

To be honest, our original plan was only slightly more fleshed out than that, and we’ve shifted and adapted it a lot along the way. Here are a few of the pieces we’ve been most happy about and that have helped us prepare for maximum owl-drawing.

  1. Focus: Our entire application team has always been “full-stack”, bouncing from back end to front end and back. As we’ve grown we’ve recognized a need for a core group of folks who can completely focus on a project like this front-end rebuild. That kind of focus helps us maximize efficiency and create the best foundation possible. It also allows us to be a  bridge to the rest of the team about how this new app is built and, eventually, how they can contribute.
  2. Modular design: Jon Ambas, our front-end engineer and design lead (and my main partner in the early days of dreaming up this plan), has been all-in on creating a fully-featured component library to make our work in React a lot simpler and more visually consistent. We now use Matchbox throughout the app and rarely have to think about CSS for anything except custom corner cases.
  3. Openness: Client-side code is already public when it runs in your browser. Therefore, I saw no reason to keep the code private in a repository.  We don’t expect anyone will use this code but us (please don’t). It’s already been nice to be able to point to examples of things we’re doing and link directly to the code in GitHub.
  4. Testing and Monitoring: This has been a big goal of the rebuild work we’re doing. Our Selenium-driven functional tests in Angular were taking hours to run and constantly failing until we finally turned them off completely. In the new app, we’re writing lots of tests (👋 snapshots) but we decided early on that we’ll never be able to predict every bug with test cases. For that reason, we’re also investing in real-time error logging and monitoring. No more waiting for a customer to report an error introduced in a deploy that went out 8 hours ago. Fast stable tests supplemented by error monitoring is our new way forward.
  5. “Preserve and improve” as a redesign strategy: In other words, we’ve preserved the architecture of the app. Leaving most things generally in the places you’d expect to find them. Avoid incredibly drastic changes that would require lots of approvals, user testing, etc. We’ve also taken every opportunity we’ve had to make easy, obvious improvements as we rebuild each part of the app. The result so far feels like an incredibly fresh coat of paint on a familiar base. Which will hopefully help us when we roll the new app out to our existing customers.
  6. Rollout: Incremental rollout, to be specific, is the last big part of our very big plan and probably the least-well-defined at the moment. We know we want to incrementally roll the app out to users (randomly and/or by allowing users to opt-in) so we can test out how it works for real use-cases before we unleash it on everyone. We’re still looking at options for this, but getting the rollout right will be one of the most important parts of our plan overall.

The Point

The takeaway here is that you should be skeptical of anything I say, especially if it sounds good or idealistic. When it comes to something like application development, being practical is almost always better than being pure.

Happy owl-drawing!

Jason Rhodes

Here at SparkPost we have a “single-page JavaScript app” that consists of about 50,000 lines of early 2015-era Angular 1.x code spread across more than 400 files. It’s not a small app. And as you’d expect with almost any language or framework over a period of 2 years, we’ve gotten pretty familiar with Angular’s good, bad and ugly sides. Angular 2, having been released as “Final” in September of last year, would seem like a pretty natural fit for us. But the title of this post has already given it away: we’re most likely not upgrading to Angular 2.

Why not Angular 2? Mostly because of a migration path that makes a strong case for considering almost anything else and maybe somewhat because of TypeScript, but if I’m being honest it’s mostly because it’s nice to try new things. We’re not an agency with a new greenfield project kicking off every few weeks or months where we can test out the latest pre-alpha releases of our favorite cleverly-named JavaScript libraries. Fifty thousand lines of code changes slowly. But that’s when the “tools app” showed up.

A rare greenfield project

Our team was asked to build a set of email tools that wouldn’t live inside of our existing app. These “hardcore email tools” help developers with deep cut email setup—the kind of stuff we already take care of for SparkPost customers —so we wanted them to have their own space out from behind our login. Suddenly, we had a place to explore something new [cue harp music].

We came up with some important criteria for what we’d use to build this new app:

  • It needed to be easy to learn
  • It needed to be fast to build
  • It needed to be something we could build in the open
  • It needed to not be Angular
  • It needed to probably just be React

After considering these criteria carefully and thoughtfully as a team, we came to a surprising decision to give React a try. At the time, I was the leading React expert on our team by way of having completed one Udemy course on the subject, so I began to throw something together.

Some things we accidentally did right

We already had a small part of the app designed and built. It’s hard to underestimate the value of a designed, styled, and approved working prototype of even just a small part of your app. The time that could have been spent arguing over button placement and wording was replaced with figuring out how to get a React app off the ground.

Speaking of which, we used Create React App. Think “html5boilerplate for React apps,” or maybe “Ember for React apps.” CRA gives you a working React starting point complete with all the dependencies (literally, it might download all of the dependencies) and with a working baseline Webpack configuration. Again, this let us focus on what we were actually building. And when you’re ready, CRA lets you “eject” and take control of the whole setup. It’s fantastic and you should use it.

You should also find a designer who can do both. And by both I mean design and understand React. I know this is a very unfair thing to suggest because it really seems to be incredibly hard to find, but we found one of these magical unicorns and they’ve been invaluable. (I even looked up “invaluable” just now to confirm that it does mean really freaking valuable.) If you can, make it a priority to hire this kind of person for your team. (And thanks for being awesome, Jon.)

We also made a decision early on to build the app using only setState / local state, i.e. no Flux, no Redux, etc. We eventually added Redux—another topic for another time—but starting with a simple React app made it much easier to onboard new developers who were getting up to speed with a lot of things at once. Not to mention, waiting on Flux also lets you decide if you really need it at all.

A few other things I’d recommend based on our first-timer experience:

  • Use Jest for your testing. Jest comes with Create React App and despite being 100% Mocha/Chai across all of our other projects, it was too hard for us to deny how great Jest is. Notably, the amazing Jest CLI and Snapshot testing have both been especially useful additions for us.
  • Use the dev tools. There are ones for React (Chrome, Firefox) and ones specifically for Redux if you use it. They’re free, they work great, and they’re incredibly useful.
  • Find a group of people you trust, ask them for advice, and do what they say. I’m fortunate to have friends in our local meetup group (CharmCityJs) and in the NYC JavaScript community (BoroJS), both with active Slack teams. Being able to ask “what do people use for x?” has been a huge help because really, you just need to pick something. Trusting someone else is as good a reason as any.

Fifty thousand lines of code changes slowly

So what about that 50,000-line Angular app? We won’t be migrating it to React, at least not directly, and it can’t really survive as an Angular 1.x app forever, either. But here’s an interesting thing I noticed as I was getting familiar with React: in some ways, it’s not that much different than Angular. Here’s an Angular 1.5+ component:

If you pretend the template string is some JSX and it gets returned from that controller’s render method, you basically have a React component (at least structurally). So instead of trying to haul 400 files’ worth of old-school, big-controller Angular code into a new framework, our plan is to focus on the patterns. Specifically, the patterns of “small, focused components” and “unidirectional data-flow”. I’ll talk more about that second part in a later post about our adventures with Redux, but refactoring our giant controllers into small Angular components has two big advantages:

  1. React isn’t forever. Any large-app rewrite/refactor is going to take a while, and if you haven’t noticed, the JavaScript ecosystem moves pretty quickly. By focusing on refactoring our existing app to use better patterns, we get it prepared to migrate to whatever happens to be the best solution at the time, when we’re finally in better shape to make that move.
  2. Iterative, incremental development is dangerous. One of my favorite images of how “agile development” should work is a drawing by Henrik Kniberg from a Spotify presentation, explaining how to be iterative in a productive way. You’ve probably seen it before:
react agile illustration
Source: http://blog.crisp.se/wp-content/uploads/2016/01/Making-sense-of-MVP-.jpg

If we spend 6 to 9 months or more trying to rewrite the app in React and don’t succeed, run out of time, or have the work shelved for other priorities, we end up with nothing useful at all. But with the refactor-first plan, the worst thing we end up with is a better, more maintainable Angular app. In other words, it’s an easy decision.

Angular, React, Kumbaya

No lie, we had a lot of fun building our new tools app in React/Redux. It’s a great library with a fantastic ecosystem and a lot of good patterns. But to be honest, our Angular app already works, and that’s fine. If you’re maintaining a big legacy app, remember:

  • Find small greenfield projects where you can build something with new tools.
  • Focus on patterns, and figure out how you can incorporate those patterns into your legacy app without having to rewrite the whole thing.

As I mentioned before, we built this in the open, so feel free to check out the code as well as the live app itself. If you’re coming from an Angular app, I’ve written up a bunch of notes about learning React that may be helpful for you, too. If this post was interesting to you for any reason at all, check back often as we continue to write more about our adventures with Angular, React, and front-end development. If you have any questions or if there’s anything else specific you’d like to hear about, let us know!

–Jason Rhodes