Stories and Strategies from Converting to TypeScript

Bookmark

TypeScript is great, but migrating an existing app to it can be a pain. Codecademy took multiple existing React apps and converted them to TypeScript. We'll cover how to make those kinds of conversions successful from both cultural and technical standpoints.



Transcription


Hello, and welcome to stories and strategies from converting to typescript with me, Josh Goldberg. To start off, hi, I'm from upstate New York. I'm a front-end developer on the web platform team at Codecademy, previously Microsoft, and I'm a cat dad. My wife and I have a few cats. They're very cute. All the stuff in this presentation is available on my website under the slides link under this talk at joshuakgoldberg.com. This presentation does include several looped animated images. If those are distracting for you, I would recommend viewing them in PowerPoint, which allows you to pause them. As for an agenda, first we're going to talk about Codecademy in 2019, before we made the jump to typescript, how we made that decision to jump to typescript, some of the techniques we used for knowledge sharing on the team, technical details of what we did in order to make that jump, and then some of the learnings we got at the end throughout the process, things that you might be able to use in your conversions, I hope. Good stuff. Starting off, where was Codecademy before typescript? Let's take a step back all the way to early 2019, a simpler time. We had a main front end app, which at the time consisted of about 2,000 files of react and Redux, a few more files in a separate design system, which itself was converted to typescript as a proof of concept, and most team members had really only vaguely heard of typescript. It wasn't a big topic or knowledge point on the team. The team itself was pretty small, was only around 20, maybe 30 engineers at tops. Most of the people didn't really have typescript experience, and there was, as with any engineering team, ongoing work around features and bug fixes. So how did we do it? How did we make that switch to typescript? First, we made the decision that we wanted to do it in the first place. And when there's a will, there's a way, but there has to be a will. Any architectural shift should have the informed support of its constituents. I think a lot of people, especially those who are new to a team, make the mistake of immediately trying to jump to conclusions and push an agenda, send out proposals, which a lot of the time is a mistake, just to do that immediately. It's a good idea to soak in the experiences of being a developer on the team, talk to people, get a feel for what the real issues are, and then use that to inform your decision making around what to push and how. Don't get me wrong. I'm not trying to devalue coming into a team with a fresh perspective and trying to get people to understand and listen to that perspective. That's great. That's commendable. Teams should absolutely be open to you coming in with fresh ideas. But those fresh ideas and perspectives are much more likely to succeed if you validate them with those around you, if you can convince people of their validity. So don't be a brat. Definitely talk to people before trying to push them to do things. If you do want to push people to do things, I highly recommend make some kind of hype train for it. If there's some switch you want to make, say in typescript, you want people to feel it. They should be excited in their bones. This is awesome. This is going to make my life happier and better. The code is going to work. It's going to be awesome. I want to do it. That's the feeling you want to encapsulate with perhaps some of the larger decisions you want to push on the team. Part of the way we did it was by encouraging people to think about how typescript helps their existing goals. Always a good idea. My favorite image from the evangelism phase of our typescript version, the very beginning, was us advertising it as part of the bug acquisition funnel, as we call it, or the anti-funnel. Not drawn to scale, as there is no reliable scale here. No one part of this, Pyre thought or peer review or whatnot, can truly prevent all bugs. But put together, they can help reduce bugs and crashes on the sites. That was a big part of the team charter at the beginning of 2019. Something we really wanted to get better at. Stability, not having bugs or annoying quirks. And typescript was really easy to maneuver into that. Because that's one of the core features of typescript. That it helps you find bugs early. So once we decided to do the thing, we needed to ramp up the team. Knowledge sharing, as they call it. This was especially valuable even before we made the decision where we wanted people to understand what it meant so they could meaningfully contribute to the conversation. We identified roughly two rough archetypes of people on the team. First off, you had area experts. Someone like me, who's already really excited about and knowledgeable with typescript. I may say so. They're the type of person you can use to clue the team into the technology, to introduce best practices, explain it to others, set up the right learning materials. They're the person who can save the team from themselves. And that you can stop the team from making the really obvious pitfalls that you know from having done it before. Also useful and very valuable on the team are cheerleaders. People who might not have as much experience with, say, typescript, but are still really excited about it and want to push it and agree with the push. These are the people who can act as guinea pigs, who will try it out. Your early adopters or beta testers, as they call them. They can help spread it among the team much more effectively than you, because there are a lot of them, oftentimes, if the decision is exciting and new. So I recommend try to get at least one or two cheerleaders per team area so they can spread the joy of the new thing amongst the team area. At least in Codecademy and many other companies I've seen, product teams tend to talk with each other a lot. So if you can get at least one person into that mix, that's very useful for introducing a new technology. Other area experts, at least two is useful, because if you only have one, they don't have a peer who can really validate what they're doing, at least from prior experience. So it's useful to have at least two who can bounce ideas off each other. Part of the knowledge sharing for us, once we identified area owners and cheerleaders, was internal presentations, because no one does anything until there's a talk explaining the general thing, the spirit of the team's movement to it. We held two brown bags, which your team might call chalk talks or lunch talks or some such presentations. The first was static analysis in javascript. It was a general introduction to the concept of taking a look at the file without running it and figuring out what's wrong or right with it. It honestly didn't even talk about typescript that much. It was mostly about existing tools we already were using to get people into the right frame of mind, pretty early, I guess, mostly. After that, we had the really exciting talk when I was happy about typescript, which was specifically about what typescript is and what the features it provides that we would use would look like. We also participated in the team's existing lightning talks, which were a series of a few people talking every week about five minutes of whatever, random topics. These were particularly useful for the more esoteric topics for typescript that might be interesting or cute, but not entirely useful for most people trying to decide to use it or onboard it. Presentations are really useful because people can look at them after the fact and kind of rehearse and go over the stuff. The most valuable, I think, individual interactions we had were pairing with people because no one really learns anything until they're forced to do it, at least in my experiences. An overgeneralization, perhaps, was certainly a useful one, at least for me. If I haven't written a language yet or if I haven't used a framework, I don't really understand it until I do. I scheduled at least one pairing session with every front-end focused member of the team, which was at the time about a dozen, a little more. These sessions, we would pair off who was coding in typescript and who was supervising. It did not scale. If I were to try to do that now, now that the team's above 50, 60, that would be really painful. But at the time, it was really useful. I think it was one of the more pressing and salient ways to get people onboarded to typescript. I highly recommend, especially if you can scale it out with more area owners. As for the technical details, the literal code changes we made, we kept to as few as possible. This is a strategy I would highly recommend for any typescript conversions. You don't want to start off with a multi-thousand file PR that converts everything under the sun. That's really easy to break and really hard to adjust for merge conflicts. We started off with just converting the minimum infrastructure integrations with just a few files converting to TS or TSX, just to make sure it would work. This was good for a couple of reasons. To start, the initial PR was not disruptive to the team at all. We didn't have to pause deployments or anything to get it through. We also just didn't change a lot of people with it. No one was disrupted by it. We were able to wait to introduce typescript to people until the time was right, until we could pair with them or they were already more comfortable with the ideas. And the infrastructure honestly wasn't that big of a shift in this pull request. Pretty much everything we used already had a basic typescript inclusion, either built in or something you can get online pretty easily. Our test runner and our production builder, Justin Repak, respectively, each had a dependency on Babel for compiling code. And Babel has a really nice typescript preset, Babel preset typescript. So we just went with that and it worked magically. ESLint has typescript ESLint, a project that I sometimes contribute to, so I was pretty familiar with. And that also worked great. ESLint runs take a little longer for us because we bring in the type checker and that's fine. It still all works. Amusingly, Prettier doesn't even have a configuration step that we needed to use. It just automagically supports typescript out of the box. That was my favorite thing. We didn't have to do anything for that. It was great. Once we introduced typescript into the build system, we prioritized ease of adoption rather than sudden, quick, get everything into typescript. So we tried not to be too disruptive to people. We explicitly only used typescript as a syntax and as a build time checker. We didn't involve it in checking javascript code or existing things that weren't typescript. We didn't want to disrupt engineers with messages around legacy code that wasn't yet meant to be typescript. So there was no need for us to check JS, as the compiler option is called. We also didn't turn on the very useful no implicit any compiler setting, unfortunately. It's just hard in a mixed typescript javascript code base. If a file imports from a javascript file and you want to convert it to typescript, then you have to convert all its dependencies sometimes in order to get those proper types set up. And that's just a pain. We ended up using a similar process later on to eventually convert files piecemeal into no plus any. On the bright side, we did have the ability easily to turn on strict null checks from the start. They're pretty simple concepts, not considering null and undefined to be assignable to other types. And it was very useful. So yeah, we got that from the start. Beautiful. As we went over time, we took a strategy of dedicated pull requests for the most part to convert to typescript. Occasionally a team member would get excited and convert some of the typescript as part of an existing PR. But for the most time, it was just convert area to typescript. That allowed us to target specific areas and focus those pull requests just on typescript, which made it a little more clear what the changes were. In most cases, or many cases, few to none actual runtime changes. And it helps us understand which engineers to pull in based off of areas of ownership. This was also a great opportunity for learning for most of the team. Where in each of these pull requests, we explained all the new, potentially confusing even pieces of syntax with pull request comments. We're adding a type here because et cetera. And then when people would review the pull request, they would almost instinctively have to read these comments, which was quite nice. So highly recommend dedicated pull requests as a post pairing way of helping knowledge share. Later on, I wrote a quick little script that would run git checkout on each of our old commits and check out all the JS files versus the TS files to see which ones were which language. And you could see over time, print out the percentage growth, which was pretty nifty. We started the conversion on April 2nd, 2019, the day after our first April 1st joke release, which was a fully functional log code course, which is a programming language based off of internet muse and cats, one of my favorites. The conversion ended up actually taking us about 259 days until December 17th, which is fine. Had we wanted to, we could have prioritized moving faster, but we really wanted to optimize for speed, which is sorry, for slowness, for comfort over speed, which is something I'd highly recommend. Don't just assume you have to get to touch script immediately. It's very likely that you can convert some of your code base, the high value areas sooner as a way to get your team ramped up for later things. My favorite technical part of it was type stat to be honest, which is a tool I wrote that automatically applies deducible or obvious conversions in mass, so across many files when possible. For example, if a react component declares its prop types with the prop types package, you can sometimes most of the time automatically create a typescript type or interface for that just by looking at the file. It's kind of involved as a tool. It's still early stage, got a lot of bugs, but it's open source. I highly recommend looking at it if you want to use something like it for your types of conversions. It took a lot of hours to write, but it's saving us a lot of time to this day for our conversions, so I'm pretty pleased with it. I could probably give a full-length talk on it, but we don't have the time or the focus of this one. This is an example, though, to reiterate. If you use prop types, we could read your file in, write a script that parses the text into understanding what that prop types is, its list of props, and for each of them what the type is, and then you could print out back to the file the typescript representation of that. It's a little trickier for components that don't declare their own prop types. You have to use the type checker to find all the references to the component to see what are the types and names of all the props provided across all those references and then combine them together, which is something TypeStat does. It's, again, a lot trickier, but still doable. So think of the edge cases and observe TypeStat if you're interested. Anyway, we learned a lot, and I was very pleased with the process, so I'd like to share that with you now, if that's okay. To start, definitely automate everything and anything whenever possible, whether it's a really complex setup like TypeStat or just a quick little bash or a Node script you write to do the obvious 70% of the work. We relied on this for the vast majority of the changes. My favorite PRs, the most smooth ones, were generated with TypeStat for 80% to 90% of the work, and then we touched them up by hand just to make them correct, double check. Really, I mean, why would you do stuff when you can write a script to do it for you? Also just from working with engineers, I highly recommend setting up a style guide and learning what questions people would ask preemptively. Things like, should you use an interface or type? How do you declare react props? What does X common confusing error mean? That stuff is going to get asked. It's going to get asked repeatedly and angrily and early on. So be proactive, talk to people ahead of time and figure out what is the stuff that you really need to cover that they're going to ask you repeatedly. A style guide is a great way to answer a lot of these questions and have an FAQ section at the end. We definitely learned the hard way, and sorry if you are a Codecademy user in 2019 and we broke the site or added a bug for you. Don't convert and change at the same time. Any large sweeping PR that changes a whole area to typescript is a hard thing to review, and it's especially hard if you also introduce runtime changes to it. So I highly recommend keeping those separate. If you see a change you really want to make for typescript, just jot it down and save it for later. Resist the urge. Even just compassion for your code review or someone who now has to go through your PR and QA changes in addition to perhaps even learning typescript. That's a hard ask. A lot of engineers, especially around typescript, don't like taking shortcuts. They don't like intellectual shortcuts. I should say they don't like doing things the wrong way. typescript engineers have this phobia of writing the word any because they think it makes the code less good. Sometimes you just have to any cast, you know? We were using Redux, which is fine. Redux on its own is a beautiful, very type system compatible system. But when you have all these integrations like we do, Redux actions, stagos, sunks, it becomes really hard to type out. It took us literal months of work in the background to get just on the same typing's version for Redux because of all the weird interconnected dependencies. And we're still any casting a lot of our Redux code in typescript because we just don't have the time or interest in getting it 100% right. So just any cast. It's fine. The choice between any casting and not converting to typescript should be pretty clear that you just any cast and save it for later. Opinions. Lastly, definitely celebrate your victories. We spent a little office budget on this cake and it was a very fun little morale thing of, oh, we did something great. We converted to typescript. We finished. This was back in December. It helps signify to the team when you do something cute and memorable at the end. I highly recommend make a big deal out of it. If the hype train, as you might have called it, finishes its trip, celebrate it landing into the station or some analogy. Ironically, I think the cake was actually vanilla at the end. Anyway, a few resources that might be useful for you. The TypeScriptland.org website, shout out to our friend Orta, is really good. It has a lot of great docs, explains a lot of good stuff around typescript, can help guide you as a new implementer, as an experienced user, as a converter. We actually ended up writing a course on typescript, which I helped co-lead in the inception, CodeCademy.com, learn typescript. I highly recommend. I'm a big fan. And then each of the integrations that I mentioned, Babelius Lynch's webpack has its own guide to using typescript because it's so popular. So, highly recommend going to those. Anyway, thank you for listening to me. I have been answering questions in the chat as they come up. And you can always tweet at me or see my contact info on my site, both of which are Joshua K. Goldberg. Thanks. Have a great rest of the conference. And many thanks to the conference organizers.
20 min
14 May, 2021

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

Workshops on related topic