Common TypeScript Challenges
Lesson Description
The "Common TypeScript Challenges" Lesson is part of the full, Enterprise UI Development: Microfrontends, Testing, & Code Quality course featured in this preview video. Here's what you'd learn in this lesson:
Steve shares the challenges of TypeScript performance in large-scale applications. As projects grow, tools like ESLint, TypeScript, and Vite can slow down or fail, and upgrading TypeScript versions, especially to upcoming major releases, can improve performance. Reducing the TypeScript compiler workload by scoping type checking, avoiding barrel files, simplifying complex types, generating types with build tools, and explicitly declaring return types can drastically improve performance.
Transcript from the "Common TypeScript Challenges" Lesson
[00:00:00]
>> Steve Kinney: Kind of over the last year I've had the pleasure of building a whole bunch of different apps kind of from the ground up, and, you know, especially with some of the tooling today, you can build a fairly large app very quickly, which just means you start to run into all of those problems that would have previously taken you a year. You can now hit in about two months. And so I kind of kept a notebook by my desk, and all of the things where I still lost a day, sometimes more than a day of my life, became some of the framing of this next section, as well as having, again, built more than one large application from the ground up.
[00:00:44]
All the things that kind of sneak up on you eventually that never get covered in the kind of, you know, or they get covered in like sometimes a more introductory course, but at that time you're still just learning about TypeScript, you're still just learning about ESLint, you're still just learning about Tailwind, or you're still just learning about anything, and you're like that section at the end of like, and here's how you deal with scaling.
[00:01:06]
You're like, I'm just learning this thing that seems great, right? Some of this is kind of then going back and then revisiting those things that you kind of paid attention to, but now you're hurting, right? The first one that will usually bite me, it's a neck and neck race on what will bite me first, whether or not ESLint will. It's whether or not like Vitest, ESLint, or TypeScript will be the one that ruins my week first in a project.
[00:01:37]
I'm not going to say it's one or the other. It could be any one of the three at a given time, usually the same root cause of project got big, right? The more you have to do, the slower it gets, right, and the more memory it might take to do it. Now, one solution is something like Turborepo, and Turborepo will work really great until those things then become too, you know, you can kick the can down the road a little bit.
[00:02:09]
You know, you get to a point where you have two options. One is to deal with it. Two is to keep turning the money knob on like either your CI/CD or something along those lines. But then even if you do that, you get to the point where VS Code on your own computer is ready to call it, you know, and like all of a sudden you go to hover over something to go to definition, and it spins for a little bit before actually showing you the type or something along those lines.
[00:02:39]
Or you get that little error and it's like, nope, not doing this anymore. Like your TypeScript server has just crashed six times, we're going to leave it shut down now, and now you're back in the old days, right? And there's not a lot of great documentation on how to solve this. So what I did was I just took notes as I had to dig myself out of some of these holes, and now we're going to go through it together, and it's going to be great.
[00:03:06]
It goes without saying, and it goes without saying particularly right now, that like sometimes it's your fault or just the nature of things, and sometimes like the team that works on TypeScript makes improvements, right? And so like, it is worth, if you are far behind on a TypeScript version, it of course makes sense to update, right? And, you know, obviously it's a slightly larger jump sometimes if you're going from like TypeScript 4 to TypeScript 5.
[00:03:43]
In fairness, you've had multiple years to do that now, so I don't feel a lot of empathy for that. But it's worth it and also worth it to look at like, are you on TypeScript, you know, 5.3, maybe looking at TypeScript 5.7, you know, whatever the current version when you watch this is. And I think where this becomes particularly practical is you've got TypeScript 6, depending on the moment, either on the way or has arrived, and then already TypeScript 7 kind of over the horizon.
[00:04:17]
And what is TypeScript, and you're like, what is this, some kind of React Router thing where they're going to release six versions of it at the same time? No, TypeScript 7, I think, I believe, seeks to be the like Rust or Go. Everything else is getting rewritten in Rust, and for some reason TypeScript is getting rewritten in Go, which will be fast, but it's just hard to always remember because everything else is getting rewritten in Rust.
[00:04:45]
A Go version that will hopefully be wildly faster, and like these principles will still apply, you'll just have bought yourself a little bit more time. And so like, especially with the larger performance gains and all those things coming, it's like more than usual. Before it'd be like, oh, there was a little memory leak over there, we fixed it, right? A fundamental rewrite for the purposes of performance will probably be worth it for you, right?
[00:05:10]
And so like, you know, for those in the room right now or watching right now, obviously this holds true. But if you were watching it like, and you've heard that TypeScript 7 comes out, go do that, right? That is probably the first thing that will buy you a lot of gains, and then we can kind of go into some of these smaller tweaks as we go along to figure out stuff. So what are some of the culprits?
[00:05:37]
Basically, you know, TypeScript slows down under my golden rule of any kind of performance, which is doing stuff costs more than not doing stuff, right? And so the name of the game is to try to get TypeScript to do less, right, because the less it has to do, the faster it will do it, because not doing stuff is faster than doing stuff. So we have stuff like program size, and we can think about ways in which we would solve for that, which is, you know, I could say like have a smaller application.
[00:06:10]
You're like, neat, thank you. But we have already talked about breaking our application into parts, right? That was the entire theme of whether, you know, whether or not they're in their own repo, or whether or not they're in a monorepo, you know, breaking up into parts and having any given like type checking to do less, right? Or like, for instance, if currently your TypeScript configuration file is starting at the root of your project, but everything you care about is in the source directory, going down one level, you know, the one that doesn't have node_modules in it, right, is probably a great way to immediately shrink down the amount TypeScript has to check.
[00:06:55]
And there's ways to skip library checks and all that other stuff too, and we'll talk about some of that momentarily. But, you know, just kind of scoping it down and what you do and do not check. The other one is, you know, barrel files can be fun. A barrel file is, if you've ever had one of those in a directory, like let's say you had a source/lib/components, right, and instead of wanting to do importing from source/lib/components/button and source/lib/components/input and like have 9,000 imports every time you need to put together a form, you might be like, well, I'm going to put an index.ts in there and it's just going to export everything, right?
[00:07:43]
And most of the build tools today are somewhat good at tree shaking that out, right? But it probably can't fully code split it for you then either, right, because if two files, you know, if let's say you have 10 files in an index.ts and one component is grabbing four of them and the other one's grabbing another seven of them, that's usually one chunk, right, because if there's some overlap. So barrel imports seem very convenient, but they mean kind of like you are then importing everything and re-exporting it, which means you have now one giant chunk, right?
[00:08:21]
So it is also useful, and we'll look at some tools for how you can audit some of these things and see for yourself. And I will, I'm going to say this now, if you have a codebase on your computer that you work on, go find that, right, because we'll all kind of just individually look at some stuff and see, because it is very hard to make, I've tried to do this before, it's very hard to make an intentionally slow app.
[00:08:49]
I've unintentionally made many a slow app or many an insecure app or many a buggy app in my day. If you ever try to do it, incredibly difficult. And so we'll try it on one of our real codebases. The other one is type complexity, and this one has bitten me because at one point, especially through teaching a bunch of courses and stuff like that, I achieved, and I don't even mean this to gloat, because this is not a positive thing, I achieved a certain amount of TypeScript wizardry where I don't know even when it happened, all of a sudden I was writing those weird ternary types with generics and all sorts of stuff and it would work, right?
[00:09:33]
But the more complicated your types, the more work it has to do, right? And so like, sometimes that feels really great that you've made this incredible type for your component, but understand that that might have come at a cost, right? And sometimes, you know, the whole principle of like, do not repeat yourself, isn't worth it, right? And so one thing that I will do frequently these days is instead of making this magical algebraic type, I will have build tooling that just code generates, instead of, because I don't want to maintain, let's say I have a schema for every kind of database, or I don't want to maintain that, right?
[00:10:18]
But I could literally pull that data and then just have it spit out 83 types, right, that are not doing a bunch of intersection, union, ternary, if this then that logic, and that tends to be more performant. And a part of this too is if you have broken stuff, and we'll see this in a second, but I'm going to say it now because I think it's useful, which is if you find yourself in the position where you do have a monorepo or a bunch of small things, doing that kind of thing where you build the declaration file, right, so that TypeScript does not have to go search the entire codebase, try to figure out the types of everything based on how you wrote it, like what it normally does for you.
[00:10:57]
If you kind of generate the types as if you were distributing it as a JavaScript package, you'd be like, here's one file with everything you need, right? Again, that saves TypeScript a lot of time. So everything you can do to kind of simplify things, and a lot of times that is with build tooling, helps as well. Circular dependencies, I will like, don't have them, but no one does that on purpose. No one's like, I'm going to create a circular dependency on purpose, right?
[00:11:29]
But that's another thing to look out for. The other thing too is, you know, anytime you can help TypeScript to stop guessing, right? And TypeScript does a lot of amazing things out of the box that seem really compelling, right? If you had a function in TypeScript, and let's say it was just an add function, take a and b, they're both numbers, right, and it returns a plus b. TypeScript's super cool where it knows that the return value of that function is a number, because it looked at the inputs, it saw that you're adding together, and it knows that you are returning a number.
[00:12:03]
The problem is you just made it do that, you know what I mean? Versus if you had the function and you're like, here's a function that takes a and b as numbers, and the return type is a number. Now when you're using that function later, it does not have to go and deduce what the function did and what its return type is, because you stated it. The only time it's now deducing that is when it's looking at that small little function, right?
[00:12:28]
So figuring out all the places where you're making TypeScript do work, and we'll see the tooling for that in a moment, we're trying to build a mental framework right now, will all be the things that help you buy more time. Cool. So yeah, we're kind of looking at this again with the things that can go wrong and the possible solutions and how you might be able to tell, right? So the oversized program is pretty easy.
[00:12:59]
It's slow, right? You will probably notice that in just your impatience with CI/CD, right, or hovering over something in VS Code. By the time it's happening on your fancy computer, you know that you have a problem that needs to get solved, right? And so for instance, TypeScript will go through all of your node_modules and find all the type declarations, right? But, for instance, if you know that certain sections are only using a certain number of things, you can say, I'm going to tell you what types we're going to pull from node_modules.
[00:13:32]
You'll put in less than it would find otherwise, because if, let's say, you're using a library, and as we, you know, that joke about the weight of node_modules folder, the time-space continuum, where you pulled in five packages, but those packages pulled in 25 packages each, and those packages pulled in 10 packages each. Next thing you know, and you always hope that the security team never really understands how node_modules work because your life would end immediately if they wanted you to audit every dependency in your React app.
[00:14:04]
But limiting, you know, that type, and we'll see it in some actual code in a second, right? The barrel files feel nice, but you might want to pull out, you might want to get rid of them, right? And just direct, instead of having that one file that exports a whole bunch of other stuff, right, because now you're just kind of, to get that one type, you've made it now go look at a whole bunch of things, pull it all into the context and so on and so forth.
[00:14:23]
Thinking that you're some kind of TypeScript wizard will also do it. Maybe you should just repeat yourself, right, a little bit, because again, it has to then do all of that crazy stuff in order to figure out what the type was.
Learn Straight from the Experts Who Shape the Modern Web
- 250+In-depth Courses
- Industry Leading Experts
- 24Learning Paths
- Live Interactive Workshops