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.
Stories and Strategies from Converting to TypeScript
AI Generated Video Summary
This Talk discusses the process of converting to TypeScript at CodeCademy. It emphasizes the importance of encouraging adoption and knowledge sharing within the team. The Talk also highlights the seamless integration of TypeScript into the existing infrastructure and build system. The strategy for converting to TypeScript involved using dedicated pull requests and automated tools. The speaker shares tips on automating changes, setting up a styling guide, and celebrating victories. Resources for learning TypeScript are also mentioned.
1. Introduction to Converting to TypeScript
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 code academy, previously Microsoft, and I'm a cat dad. 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.
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 code academy, 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 are going to talk about code academy 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. There wasn't a big topic or knowledge point on the team. The team itself was pretty small. It 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 is some switch you want to make, say a TypeScript, you want people to feel it. They should be excited in their bones.
2. Encouraging Adoption of TypeScript
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.
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 at the very beginning was us advertising it as part of the bug acquisition funnel, as we call it, or the antifunnel. Not drawn to scale as there is no reliable scale here. No one part of this, pyrethod 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 wanted to get better at, stability, not having bugs and annoying quirks. TypeScript is one of the core features of TypeScript, and it helps you find bugs early.
3. Ramping up the Team and Knowledge Sharing
Once we decided to do the thing, we needed to ramp up the team. We identified two archetypes: area experts and cheerleaders. Area experts can introduce best practices, explain TypeScript, and save the team from obvious pitfalls. Cheerleaders, even with less experience, can act as guinea pigs and help spread the new technology. It's recommended to have at least one or two cheerleaders per team area and at least two area experts for validation and idea exchange.
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 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 in 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. For 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.
4. Knowledge Sharing and Code Changes
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 groundbags, which your team might call chalk talks or lunch talks or some such presentations.
After that, we have 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 my experiences. And generalization was a useful one. At least for me, if I haven't written a language yet or I haven't used a framework, I don't really understand it until I do. So I scheduled at least one pairing session with every frontend-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. I would try to do that now that the team is above 50-60. That would be really painful. But at the time, it was useful and I think it was one of the more pressing and salient ways to get people onboarded to TypeScript. 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. And 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 into the sun. That's really easy to break and really hard to adjust for merge conflicts. So 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 through. We also just didn't change a lot of people with it. No one was disrupted by it.
5. Introducing TypeScript into the Infrastructure
We were able to introduce TypeScript seamlessly into our infrastructure, as most of the tools we used already had built-in support or easy integration. Babel, our test runner and production builder, had a TypeScript preset that worked like magic. ESLint, which I'm familiar with, also worked great with TypeScript. And the best part was that Prettier supported TypeScript out of the box without any configuration needed.
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 Roepack, respectively, each had 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, Printier doesn't even have a configuration step that we needed to use. It just auto-magically supports TypeScript out of the box. That was my favorite thing. We didn't have to do anything for that. It was great.
6. Introducing TypeScript into the Build System
We introduced TypeScript into the build system with ease of adoption in mind. We only used TypeScript as a syntax and build time checker, without disrupting engineers with legacy code. While we didn't enable the no implicit any setting initially, we later converted files into null implicit any. However, we did enable strict null checks from the start.
On the bright side, we did have the ability easily to turn on strict null checks from the start. Very simple concept, not considering null and undefined to be assignable to other types. And it was very useful. So yay, we got that from the start. Beautiful.
7. Converting to TypeScript Strategy and Tools
We used dedicated pull requests to convert to TypeScript, focusing on specific areas and involving engineers based on ownership. These pull requests provided a great opportunity for learning and knowledge sharing. I also developed a script to check the language of old commits, which helped us track the conversion progress. The conversion took 259 days, prioritizing comfort over speed. One of my favorite tools, Typestep, automatically applies conversions across files, making the process more efficient. It's open source and can generate TypeScript representations of prop types.
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 something to 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 helped 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 syntax with pull request comments, we're adding a Type here because, etc. Then when people would review the pull requests, 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. As you can see, over time, it prints out the percentage growth which was pretty nifty. We started the conversion on April 2, 2019, the day after our April 1 joke release which was a fully functional law code course which is a programming language based off of Internet use and cats, one of my favorites. The conversion ended up taking us about 259 days until December 17th which is fine. Had we wanted to, we could have prioritized moving faster but we wanted to optimize for speed. Sorry, for slowness, comfort over speed which is something I highly recommend. Don't assume you have to get to Tashcript immediately. It's likely you can convert some of your code base, high value areas, sooner as a way to ramped up for later.
My favorite technical part of it was typestep, 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 properties 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. 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 can probably give a full-length talk, 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 type is, it's a 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.
8. Automating Changes and Setting up Styling Guide
So again, a lot trickier, but still doable. Definitely automate everything and anything whenever possible. Set up a styling guide and learn preemptive questions. Don't convert and change at the same time. TypeScript engineers have this phobia of writing the word any because they think it makes the code less good.
So 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 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. 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 styling guide and learning what questions people would ask preemptively. Questions like, should you use an interface or type? How do you declare React props? What does x common confusing error mean? That stuff is gonna get asked. It's gonna 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 gonna 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 Code Academy user in 2019 and we broke this 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 of TypeScript is a hard thing to review and it's especially hard if you also introduce run time 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, save it for later. Resist the urge. Just be 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 the 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. Maybe you just have to any cast, like 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, doggos, thunks, it becomes really hard to type out.
9. Final Thoughts and Resources
It took us literal months of work to get on the same typings version for Redux due to interconnected dependencies. We still any cast a lot of our Redux code in TypeScript. Celebrate your victories and make a big deal out of converting to TypeScript. Check out TypeScriptland.org and CodeCademy.com for resources on TypeScript. Thank you for listening!
It took us literal months of work in the background to get just on the same typings version for a 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 a 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. And it 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 for 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. It can help guide you as a new implementer, 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 joshuakagleberg. Thanks.