TypeScript ain’t exactly new, but it’s a bit new to us. Robert was the most knowledgeable about TypeScript on the team and felt like it could be valuable for us. What does that mean though? Where would we use TypeScript? What blockers were there? What does it actually help with? The implementation hasn’t been trivial, so has it been worth it? Will it be worth it? Robert, Chris, and Stephen discuss.
[Radio channel adjustment]
Announcer: Today, on CodePen Radio.
Chris Coyier: Hey, everybody. CodePen Radio #391. Maybe we'll just call it TypeScript because that's what we're going to talk about. Not a particularly new technology, but new to CodePen. Thanks, Robert.
Robert Kieffer: You're so welcome.
Robert: Apologies in advance.
Stephen Shaw: Thanks is in quotes. Yeah.
Chris: And that's Mr. Shaw. How are you doing, Stephen?
Stephen: Hey. How's it going?
Chris: Good. That's the thing. We, for the first time ever, do have -- I guess you could consider it production. I don't know. Whatever. There's some TypeScript in the CodePen code base. Whether those things have made it all the way to production, I think they have because I think some of our componentry is in it. So, TypeScript in production!
Chris: On CodePen. That's what we're going to talk about. It's tempting to put some of us on the pro-TypeScript side and some of us on anti- and let us duke it out. But as it turns out, I think all three of us have some--
You know Robert, I think, can argue either side because I think you've mentioned, say four years ago, you were kind of anti-.
Robert: Kind of? Oh, I hated it.
Chris: You hated it, and then came around to it, so we'll get to that story. Stephen has certainly felt the pain of our implementation of it, as we all have, because it wasn't just one of those decisions like, "Hey, let's turn on TypeScript. Oh, the syntax is so funky, but we can get over it."
It's not really that. I think all of us here are pretty smart about syntax and could battle our way through that. It was more of a dev ops battle. [Laughter]
Chris: We can get into all that. Then we might as well, for people listening that are just like, "Why would you bother? What is it?" that kind of thing. We don't need to spend too much time on what it is.
Chris: First of all, Robert, why did you even try to convince CodePen that we should travel down this road knowing that we have zero TypeScript in...?
Stephen: Why did you do this to us, Robert?
Chris: Why did you?
Robert: Why?! Why?! Well, my journey with TypeScript started -- it really started probably two years ago when I was switching away from being a vanilla JS guy who had coded in VIM for years. I had sort of this epiphany of, like, I'm an aging developer. I probably need to brush up on my modern stack, my modern tech stack, so I switched to VS Code.
At the same time, I was like, "You know I want to check out this TypeScript thing that I've heard so much about and probably complained about even more." So, I--
Chris: Wow! So, you did it just because you were like, "I'm going to bone up."
Robert: Well, yeah. Yeah, exactly, and so I had a project, the NPM Graph website, which some people may be familiar with, that I had written in all JS, and I ported that to TypeScript. It was a pretty good experience. The thing that really made an impression on me was just how profound a difference it made in terms of my awareness of all the little shortcuts and places where my code was sloppy, and I never really thought about it.
Like I said, I've been doing JS for a long time, and I'd like to think I'm a pretty good programmer. I know my way around the codebase. It really showed me just how wrong I was about that.
The way I describe it (and the way it's been described to me) is that TypeScript gives you guardrails. As you're writing code, it's constantly surfacing stuff to you where you're like, "Oh, yeah, right. That probably could be a string or a number, and I've got to actually figure out how I want to treat that if I get an unexpected value in there."
That was sort of the background that I came to CodePen with. I actually have another project that I started in TypeScript and that I've been working on. And so, I've done enough with TypeScript to be comfortable with the language and to have a sense for how the vanilla JS experience and the TypeScript experience really differ from one another. I think once you've done that, you become sort of sensitized to just all the ways in which TypeScript really does keep you on track as far as the quality of your code.
Chris: Okay, so you convert a codebase to TypeScript. At some point, I think I've heard you say that. You're like, "Holy crap. I bet I found a dozen, two dozen literal bugs that just disappeared."
Robert: Yeah, that you just weren't even aware of.
Chris: I've been skeptical of that. That was one of my things where I'm like, "How many bugs in the history of CodePen got logged in GitHub that turned out to be a type error?" It's like, what, two or something? I'm like, "Oh, let's add this complicated crap to our stack for some theoretical types problem that doesn't exist." What would you say to that?
Robert: Well, I think the first response is it's a lot more than you think. As you go through the codebase, you'll be like, "Oh, I probably need to be thinking about this. Why is VS Code complaining about the fact that I'm trying to add two values together and one of them might be undefined?"
You sort of untangle why does VS Code think this value might be undefined. Then, sure enough, three call sites away, there's a way that undefined can get injected into that code path. You're just like, "Oh, I had no idea."
Chris: I see. I see. I might not even attribute that to a type problem. Maybe my understand of a type problem is too narrow. Not like, "You tried to add two strings together, but you meant integer," or something. Almost cheesy TypeScript 101 like that, but just a possible undefined, that probably is a bug that we've had tons of.
Robert: Yeah, and it's one of those things where you might be getting away with it. Maybe you've just never had a call path that's really triggered that particular problem. It's a bit of a landmine just waiting to happen, and so there's a lot of that sort of stuff that, for me at least, tends to get surfaced.
Chris: Cool. And so, when you look at CodePen, you say, "This is a big enough, important enough codebase with enough people working on it that the value is there. There is a cost to TypeScript, but it's worth paying."
Robert: I think so. Yeah. We can talk about this more, but I think, in team environments, it really comes into its own.
Chris: Okay. It fixes types. The other one that I always think about, because it seems a little more up my alley for whatever reason, is two-pronged. One is that it helps you code in a way, or as you are literally typing out a function or calling a function, the VS Code experience (which is notoriously super great at TypeScript) is almost helping you fill it out, like, "Oh, I see you're trying to call this function. Let me assist you."
Stephen: The intellisense of it kind of grabs those type definitions, right?
Chris: Yeah. That's just cool.
Robert: Yeah. I think there's really two sides to TypeScript. The first is it makes your code better, and the second is it provides a very high-quality form of documentation that you don't otherwise get.
Chris: Cool. Yeah, I look forward to taking advantage of that and more. We should get into our baby steps into it a little bit, I think. Wouldn't that be nice to know?
One thing we didn't do is just decide that every JSX file in our entire code base, we're just going to make TSX and then commit that to master. We did do that.
First, we had to set it up, and we have a couple of Next.js instances in our codebase, and those are trivially easy to make TypeScript. That you really can just change the file. If the TXS file is within the next universe, it's just fine. It just knows what to do, it makes it part of the bundle, and it knows how to process it and all that. That part was easy, right? [Laughter] -ish?
Robert: Yeah. The next side, I think it's a little hard to answer that question for CodePen because those Next apps are actually built on top of some shared code. We have these packages directories that are Yarn workspaces that are shared not just with the Next apps but also other parts of the codebase. That part of things gets a little bit more complicated.
Within Next, yeah, it was great. You basically just tweak your TS config file.
Chris: Right. But CodePen, as we have talked about on this podcast, is a mono repo. One of the reasons it's a mono repo is because we have these Next apps, but they live side-by-side.
What also lives side-by-side is a bunch of shared stuff. Pretty much meaning a pattern library, but also our constants and our utilities. We have all kinds of fancy stuff outside the root next level. Those are the things that we also want and would benefit from being TypeScript.
Chris: TSX has no idea what to do with that.
Robert: When I started looking at, okay, how do we actually sort of bring TypeScript to the table within CodePen, the Next apps were pretty simple. We have the packages, the shared codebase, but there's also we've got Cloudflare workers, and we've got Lambda functions. We've got a processors directory off to the side, which is kind of its own thing.
Chris: Yeah, and our whole Ruby on Rails app.
Robert: Yeah, and the Rails app, which is its own beast. I think that for me was probably the most challenging part of all this. There were just so many different contexts in which TypeScript needs to live.
Chris: And you can't just decide, "Oh, you know what? The Rails app, screw it. We're not going to deal with TypeScript there." We don't have that luxury because if you want to make your componentry TypeScript, those get pulled in by the Rails app, and so it has no choice. It has to deal with it.
Robert: Right and, at the end of the day, TypeScript is a compilation step in your build process. It means your source files, they can live, like you have a foo.js file and it can get compiled into a foo.ts.
Robert: I'm sorry. You have a foo.ts file that gets compiled into a foo.js file right next to it or you can put it in a directory, but either way, you have a different set of files that you have to be building on.
Chris: Yeah. If you think of it like Sass or whatever else. Right?
Robert: Anywhere that you make assumptions in your build process, any scripts you have, any deploy scripts, any of that stuff that makes assumptions about where code lives, all that stuff breaks. [Laughter] And you have to fix it.
Stephen: We tried to avoid having build steps for of these Yarn works/fix packages for that reason. Number one, we didn't want to deal with esbuild and configuring all the compilation stuff, setting up watchers and all this for our dev environment.
We didn't have anything set up for almost all of those packages. Now introducing TSC into that, we then had to go back and make sure that we're referencing dist instead of the source folder (and all those little pain points).
Chris: Now we do have a watcher, but just one. [Laughter]
Robert: Two. We've got two. [Laughter]
Chris: Ah, damn it! [Laughter]
Stephen: Oh, yeah, that was a hurdle we had to get over. What was the point behind the two watchers?
Robert: Well, we've got two watchers. We're actually running TSC. We're not running esbuild or SWC or any of the other types of TypeScript compilers.
Chris: On purpose because we want it to complain about types, which something like esbuild would not, right?
Robert: Right, so we have a watcher that's running TSC in watch mode, which is fine. Great. Any JS files, any TS files, it compiles those. They get put into the dist directory and life is great.
What TypeScript does not do, what TSC does not do is deal with any static assets. So, if you have CSS files or images or anything else that may need to be copied alongside that source, then you're kind of on your own for that. So, we have another watcher, which is in development. We use the FS Watch utility to run our sync to synchronize static assets across both directories.
Chris: FS Watch is a Node thing, isn't it? What about this one?
Robert: It's a Mac OS thing. We're all running Mac OS on our development boxes, and so it just watches for changes.
Chris: Then it calls Rsync, which is a Ruby thing, isn't it?
Robert: Rsync is a Linux thing that goes back to the dawn of time. It'll be on pretty much every box you develop on.
Chris: Yeah. Yeah. I remember that. Do you remember your TypeScript watcher was working great, then I'd be like, "It doesn't work on mine"? You're like, "Oh, it's because I wrote--" There are a couple of assumptions in there that turned out weren't on everybody's machine in the entire world.
Chris: Oh, my god. [Laughter] I feel like all those "Oh, my god" that I'm saying right now are symbols of multiple days of time of mostly--
Robert: [Laughter] One thing this surfaced really clearly was all the assumptions I made about how my build environment was just like everybody else's and when in fact it wasn't.
Chris: Yeah, and a lot of times at CodePen, we don't like that, and we solve that through running everything in a docker.
Stephen: Even then--
Chris: Some stuff just has to. Yeah, even then it's a little tricky. But some stuff lives outside, and sometimes we put stuff out-outside the dockers on purpose just for ease of life a little bit.
Chris: Having to be like, "Oh, you have to run this command from in the docker," means that we need a little special tmux panel for it and whatever. You can't just do it from the bottom of your VS Code or whatever, like you kind of want to a lot of times.
Robert: It seems to be working. We're still dealing with some interesting issues. I won't bore people by going into the details, but we've got a complex environment and two or three levels of caching and various tools. [Laughter] That's its own particular--
Chris: Yeah. Kind of. I think we're so close. In fact, we're not even close anymore. We're there. We have some of this stuff in production.
I feel like in two, three weeks from now, it'll be a lot smoother. We're still in this place that's like, "When you switch branches, make sure to restart the watcher," or delete the next cache, or whatever. We haven't homed in on the absolute perfect way to never experience any errors with it.
But, hey, we got some types now, and there's been three, four PRs, I'd say, that have leaned into using TypeScript. And not just changing the file extension and making it pass, but actually producing some types and that type of thing. That's what we're most excited about.
Even, Stephen, just before this call, we were having a little conversation about what should be a type and what shouldn't and where do you put these common types. Do you have to import types, or do you just let it be magical? All these fun, new conversations about technology.
Robert: Right. In the interest of making types the most useful and reusable throughout our application. That's a bridge we haven't fully crossed yet.
Chris: Right now, because I haven't actually written the type yet, it feels like, "Oh, we have these new things on CodePen. We're going to make this master type called widget or whatever. Every time any component needs it, we'll just pass around the widget type." That seems very magical and cool to me right now, but I haven't actually written any code like that yet, so we'll just see how magical that feels.
Stephen: I won't say it won't feel that magical, but there's definitely some real substance to the value there. I was thinking about what are the concrete examples of how TypeScript really helps, and I mean this isn't directly relevant to, I think, what you're talking about, but one of the things that I've found really useful is when you're making a request to a JSON API of some sort. If you're just using vanilla JS with an XML HTTP request or fetch object, what you get back is a wad of data. Then that data gets passed around, and it goes through your code.
God forbid that data should change shape. It'll change shape, but you have no way of knowing where your code is going to break and where you've made assumptions about what properties are on there.
If you have a type associated with that data, that wad of data you get back, then if the API changes, you can change that type and TypeScript will tell you, like, "Hey, here are all the places in your code where you're making assumptions about the shape of that data." That was one of the big wins for me early on with TypeScript.
Robert: Yeah, that's one thing I haven't seen played out too much yet with our type checking because our app is so dynamic that the static analysis side of TypeScript, I'm wondering how it's going to help in those instances where we do have these dynamic renders coming from the API data that TypeScript isn't necessarily aware of because it's not happening at runtime. We had some discussion about this with types and prop types in the React components.
Chris: Oh, we didn't even talk about prop types. [Laughter]
Chris: Let's do it. Types vs. prop types. It's near and dear to my heart because, at one point, somebody noticed that we turned off our -- there's like an ES Lint rule that gives you red squiggles in VS Code when you don't have the right prop types in a file. I think we were just annoyed by it.
When you're in the heat of the moment and you're working on a component, I do find it a little obnoxious that it's like, "Oh, the size attribute. That's not declared at the bottom of your file with a little dot string. Dot is required." Meh. Get out of here. I don't care.
It was bitching enough that we just turned it off, I think, and nobody could remember who did it or why. I'm like, don't worry, guys. I'll turn it back on, and I'll add every prop type in our whole code base," [laughter] which turned out to be 500 files or something. It was a huge pain in the ass to get done.
Stephen: That was a huge PR.
Chris: Yeah, dude. That was a mess. That was a thing. So, now along comes TypeScript. Robert is saying -- and it's not just you -- is kind of like, in a perfect world kind of thing, you really don't need prop types in your React components if you have good TypeScript. They're not exactly the same, but they're kind of the same.
Robert: Yeah. Actually, one question I have, because I haven't done much with prop types, like React prop types, do those actually do runtime checks or is it--?
Stephen: Right, yeah.
Chris: They only do runtime checks.
Stephen: Right. It's only runtime, so whenever you're actually rendering the application out and you pass in something that's an invalid prop type, you'll get warnings and errors and different things like that.
Robert: Yeah. That's really interesting because prop types and TypeScript are fundamentally dealing with the same thing of, like, make sure your data is the right shape. But they do it at two very different times.
Actually, I think you and I, Chris, when we were working on one of your first TypeScript PRs, was like, "Hey, just delete the prop types because you've defined the types in the signature now."
Robert: Then we were like, "Oh, actually, that might have been a mistake," because any vanilla JS code that uses that component isn't going to have the static type check that TypeScript provides.
Chris: Yeah. Exactly. We're going to leave the prop types in until we're at - I don't know - 90%, 100% TypeScript.
Robert: 98% TypeScript, or whatever.
Chris: Let's call it 98%. Yeah.
Stephen: Well, even then, it goes back to how dynamic our application is. Are we getting the benefit of TypeScript when we're rendering the page completely off API data? Do we still see that benefit? I don't know how TypeScript or TSC--
Chris: No, it'll never be runtime.
Chris: Right? Runtime meaning do you see errors in the console of your browser as you're browsing around the site? A lot of times, that's how we find out about prop-type errors. It's the only time you find out about prop-type errors. And that's going to be gone in TypeScript land.
But you catch it earlier. Don't they call that shift left. They call that shifting left in programming.
Robert: You have this dynamic stuff floating around. How dynamic is it really? Somebody somewhere knows what the shape of that data is. Unless it's really dynamically built, you should be able to describe somewhere, like, this is what we expect this data to be (to look like).
CodePen is a really a big user of GraphQL, which is not something I have worked with TypeScript on. I'm kind of assuming that somebody somewhere has figured out how to deal with TypeScript and the intersection of TypeScript and GraphQL, but that's sort of an unexplored realm for us at this point in time.
Stephen: That's one thing I was looking at, the prop types versus just the types. Surely other people have these same kind of concerns and there's probably tooling out there for it. But in all my digging, all the resources for that are years out of date. There's just no discussion on it.
But I'm like, they're not solving the same problem but they contain the same data. Can't we just pass that information back and forth? Isn't there something that can convert types to prop types, and then we get the benefit of both without having to write it manually both times?
Robert: I don't know. I had sort of a similar problem with another project I was working on at CodePen. I've got TypeScript types, but we're using Go on the server, and we want to basically validate data in the Go world but based on the TypeScript types. Like you, I had Googled around. I eventually found an NPM module that can generate JSON schema files from TypeScript types.
Chris: I remember you working on that. That was weird.
Robert: Yeah, which is actually really cool. When it works, it's really cool.
Robert: But there's a couple of big caveats to that. It's doable, but there are a few asterisks there.
Chris: Yeah. You know one thing I like about all this conceptually, just because I've been trying to explore more of our stack and be more useful across more errors, which has meant a lot of API work. It's meant Go work. It's meant database work, et cetera. Our PostgreSQL database, which we've switched to in the last couple of years -- not that MySQL wasn't -- it's also type. But think of the column types in PostgreSQL. They're typed. You have to type them. You have to say what type a column is in PostgreSQL. There's no alternative, really. You know?
Okay, then we build these APIs to ask for data from them. We built those APIs that connect to this database and pull data out in Go, and Go is typed, like extremely typed. Very mad at me all the time about types.
Chris: Then we feed that data. The API that we're choosing to build is GraphQL. All those queries and such, they are typed. GraphQL is typed, period. You have to. It has to be typed.
But you know. Too late. TypeScript now.
Robert: Yeah. God, I hope it's too late to go back.
Robert: One thing that's been interesting for me about this is or I think that's unique to CodePen is just the philosophy that I think we all come to the table with when it comes to this sort of change, and specifically with regard to TypeScript. I'm not a TypeScript fanboy by any means. I had a huge chip on my shoulder about Microsoft and anything that came from them (for years). But it's really sold me on this.
At the same time, I'm also experienced enough to know this is a big change and I definitely did not expect people at CodePen to be like, "Oh, this is phenomenal. We should totally do this."
But to everybody's credit, I think everybody at CodePen has had this very sort of pragmatic and tolerant approach to this. I would say this hasn't been a big, planned effort. It's more of just like I had TypeScript. I added it in a couple little places in our codebase that were nice and standalone. And over the last year or so, I think it's just been kind of building to where it's more and more, like, "Okay, it's time to start getting serious about this."
Chris: Yeah. Yeah. Yeah.
Robert: That's my view. Stephen, I don't know how you feel about it. [Laughter] I'm still not sure I know how you feel about it. [Laughter]
Stephen: At this point, I'm like, "Take it or leave it." I understand the theoretical benefit of it. I just haven't been in it enough to actually be like, "Oh, that really saved our bacon there from just some obnoxious error."
I'm getting into it more. Honestly, I'm still just learning the TypeScript syntax and everything to just be able to write it more easily. Man, it's still obnoxious to look at. I don't think I'll ever get over that even just being familiar with it. There's just so many--
Chris: I agree. It does look ugly.
Stephen: --colons and ... things that it's, ah... That doesn't feel like the right place to put it. That doesn't look good.
Robert: I remember that feeling.
Robert: Especially, "It doesn't look right." You do get used to it. To Microsoft or VS Code (whosever credit), the inferred typing that they do helps a lot.
Way back in the day, I worked with Java and Objective C, two very typed languages, and I developed kind of an allergic reaction to type languages as a result because they were so hard to work with and the types were so prevalent in your code. With VS Code and TypeScript, you only really put types where you actually absolutely have to have them. Everything else is pretty much just the way it was before. It's ugly to look at, but it could be a lot worse. [Laughter]
Robert: As the English units of measurement are to metric.
Robert: Well, at the time I came up with that analogy, it was more about how America is in this weird, like, we're English units except for all the ways in which we're metric, which is basically everything that gets imported into this country. And so, ask any car mechanic how many tools they have, and it's like, well, they've got an entire set for English and an entire set of metric.
Chris: Yeah. Tell me about it. Fricken' -- what gets me are the hex wrenches. You've got to have a hundred of both.
Robert: And so, we're in this process, as we're switching over, where we've got the worse of both worlds going at the moment. [Laughter] That's really where that analogy came from.
Robert: You're welcome. [Laughter]
Stephen: Our speed limit signs have kilometers and miles per hour.
Chris: Oh, my gosh.
Robert: Yeah, exactly.
Chris: Yeah. We have plenty of stuff in this app that is not all the way converted to one thing, and I feel like it's normal and not that bad. I'm hoping that this is one of those things too. Stopping to absolutely complete a conversion from one thing to the other sometimes is useful and sometimes is a waste of time.
Again, we're this tiny little team. We're not Spotify over here who can sit around and do nothing and make a billion, zillion dollars - or whatever. We have to be pretty eye on the prize at finishing some larger projects.
Robert: I take it Spotify is not a sponsor.
Chris: No. They're great and whatever, and they might disagree, but it's like you're a highly profitable company and your little technology decisions aren't the only factor in you existing tomorrow.
Stephen: Listen to CodePen Radio on Spotify or wherever podcasts--
Chris: We probably are on Spotify. Thanks, Spotify. Good job with everything. Congrats on buying Remix, or whatever you did.
Okay, everybody. Thanks, Robert. Thanks, Stephen Shaw. Good conversation. Maybe we'll update you in a year or something and proudly tell you that we're now 74% TypeScript.
Robert: I'll be coming back here hat in hand apologizing.
Chris: We should have used Flow.
Chris: All right. See ya later.
Robert: All right. Take care.
[Radio channel adjustment]