Faster TypeScript builds with --isolatedDeclarations

Spanish audio is available in the player settings
Rate this content

Type-checking a TypeScript codebase can be slow, especially for monorepos containing lots of projects that each need to use the type checker to generate type declaration files. In this talk, we introduce — for the very first time — a new TypeScript feature we are working on called “Isolated Declarations” that allows DTS files to be generated without using the type checker at all! This opens the door to faster declaration generation in TypeScript itself, as well as in external tools written in other languages such as ESBuild and swc. You'll see how to use this new option, and maybe (just maybe) you’ll be convinced about the benefits of explicit return types! Most importantly, we will show how Isolated Declarations enables parallel builds to spread work across your CPU cores to significantly improve the build speed of your TypeScript projects.

24 min
21 Sep, 2023

Video Summary and Transcription

This talk discusses the performance issues in TypeScript builds and introduces a new feature called isolated declarations. By running the compiler in parallel and using isolated modules, significant performance gains can be achieved. Isolated declarations improve build speed, compatibility with other tools, and require developers to write types in code. This feature has the potential to further increase performance and may be available in TypeScript soon.

Available in Español

1. Introduction to TypeScript Builds

Short description:

Hello, everyone. Welcome to my talk, Speeding up TypeScript builds. I'm going to talk about build performance, especially in monorepos. We're developing an upcoming feature in collaboration with Bloomberg, Google, and Microsoft. The motivation for this feature is the complaints about build performance in TypeScript. We tried running the compiler in parallel, which resulted in significant performance gains for a 23-project mono repo.

Hello, everyone. Welcome to my talk, Speeding up TypeScript builds. My name is Tiziano Cernico-Adragomir. I'm a software engineer in Bloomberg in the TypeScript JavaScript infrastructure team, and I'm also a TypeScript compiler contributor.

And today I want to talk about build performance, especially build performance in monorepos. And we're going to talk about an upcoming feature of TypeScript that we've been developing in collaboration with Bloomberg, Google, and Microsoft. You can check out this feature on our public GitHub repo on our fork of TypeScript. But we're going to talk about it today as well.

So what is the motivation for this feature? Well, if you spend any amount of time on Twitter, you will definitely find people that complain about built-in performance in TypeScript. And these complaints are not unwarranted. Unfortunately, type checking can be slow, especially if you have a lot of code in a lot of projects in a model repo. So the question is, what can we do about it? Well, we could optimize the compiler, and the TypeScript team have definitely been doing that. If you check the release notes for the last several versions, you will see that every version brings new optimizations to the compiler. And that is all great work. We could also rewrite the compiler, and there's efforts out there that are trying to do this. We're excited to see where those go as well. But we tried a different approach. We thought about running things in parallel, because a lot of hardware today is multi-core, people have a lot of cores on their desktops and their laptops. And it would be great if we could take advantage of those to actually make things faster for people when they build their projects. So let's run the compiler in parallel. Let's see what performance gains we could get. What we did in Silent Bloomer, we took one of our own mono repos. It was a 23-project mono repo. It has over 100,000 lines of TypeScript code and a very deep dependency graph, so you can think something like this, lots of projects with lots of dependencies. And we tried to see what gains we could get from running the type checker in parallel for this mono repo. We took an initial baseline using TypeScript's built-in composite project support, and we got build times from about 60 seconds to 30, depending on hardware. Now, TypeScript-B, which is the composite project support in TypeScript, is actually single-threaded. It will not try to build anything in parallel, so all of these projects are type checked individually in order of their dependencies. So let's see what we could do if we try to run the compiler in parallel. What we did was we ran a separate instance of the compiler for each project.

2. Optimizing Parallelism and Dependency Management

Short description:

We batched the projects into workers with shared caches. However, projects are not independent, so we have to wait for dependencies to finish building. This limits parallelism, allowing only a few projects to be built simultaneously.

Now, we didn't do this in a naive way. There were some optimizations that we did do. For example, we batched all of the projects into several workers, and each one of these workers had a shared file cache and a shared source file syntax tree cache, which are already optimizations that TypeScript does for its composite project support anyway. So we were just trying to emulate that.

Now, the problem we immediately ran into is that projects are not independent. So we can't just run everything in parallel. We need to actually wait for dependencies to finish building. So, for example, for the diagram I showed before of dependencies, we would get an execution that looks something like this. And we can already see that we have a great limit on parallelism and there's only two projects that run in parallel at the same time. So only two projects get type checked in parallel. And even in our sample monorepo with 23 projects, we would only get about four to build in parallel.

3. Increasing Parallelism and Isolated Modules

Short description:

On a laptop with 12 cores, we only use some of the computing power, leaving CPU cores idle. Running things in parallel results in a 10-20% improvement, but not a dramatic one. TypeScript performs type checking, declaration emit, and JavaScript emit for each project. Unblocking a project's type checking phase requires available declarations of its dependencies. We depend on declaration emit even though it doesn't remove the need for type checking. TypeScript's isolated modules mode allows JavaScript emit without type checking dependency, making it a purely syntactic process.

Now, what does this actually mean? So, for example, on a laptop with 12 cores, which is what we see here in this picture, we only use some of that computing power. We can see that even while the build runs, there's a lot of CPU power that is still being left unutilized. A lot of CPU cores that are just sitting there idle. So, that's definitely not a great start for running things in parallel.

Let's see sometimes what kind of improvements could we get if we did this? And the answer is that we definitely will get improvements, but they're just not that great. I mean, there's about a 10-20% improvement that we get from running things in parallel using this approach, which is again better than what we had, but it's not something that is going to be dramatic.

So, how can we increase the parallelism available? How can we run more things in parallel? Well, for that, we first of all need to understand exactly what it is TypeScript actually does, and what it does is three things. It does type checking, it does declaration emit, and it does JavaScript emit. Now, these things are not perfectly independent, namely declaration emit depends on the result of type checking. But for each one of our projects, TypeScript will do all of these three things. So, inside the build for each of our projects, all of these three things have to happen.

Now, there is one key insight here, namely that what is needed to unblock a project from starting its type checking phase is that it needs to have available all of the declarations for its dependencies. So, declaration emit is what is actually needed for the dependencies. We don't really care if the dependency has finished type checking if we can get its declarations. So, the actual dependency queue would look more like this, right? Each type checking phase depends on the declaration emit of its dependencies, not necessarily the type checking. That's an interesting insight, but as long as we still have this dependency between type checking and declaration emit, the fact that this is the case, the fact that we depend on the declaration emit doesn't really help us because we need to do the type checking anyway.

Could we remove this dependency? Well, for that, we should first of all look at why JavaScript emit is so independent from type checking. How is that? We actually even have tools that do JavaScript emit without the need for a type checker. They do it completely independently of TypeScript. How is this enabled? Well, a few years back, TypeScript added a new mode to TypeScript called isolated modules. So, what exactly does isolated modules do? Well, it ensures that each file can be transpiled from TypeScript to JavaScript individually. It basically removes any dependency on type checking from JavaScript emit. So this means that JavaScript emit becomes a purely syntactic process. Tools still need to understand TypeScript syntax. So, every time we get new syntax in TypeScript, all tools that do JavaScript emit need to understand this new syntax. But the advantage is that they don't actually need to understand all the semantic rules of TypeScript. They just need to understand the syntax enough to be able to remove it. So, basically, when we do JavaScript emit, we start out with a TypeScript file, and what happens inside a JavaScript emitter is that all the type annotations are removed, right? And this is a very simple process. It's not hard to build such a transformation. At least it's not hard compared to actually building a full typechecker.

4. Cutting Dependency and Isolated Declarations

Short description:

We can cut the dependency between declaration emit and typechecking by specifying all types in the TypeScript file. This would allow us to unblock dependencies sooner. TypeScript introduces the new feature called isolated declarations, where type annotations are added to values that make it into declaration files. The type checker is not involved in declaration emit to ensure it remains a purely syntactic transformation.

So could we use a similar approach to cut this dependency from between the declaration emit and typechecking? And for that, we have to look a little bit at what exactly declaration emit entails. So for the most part, it's also a subtractive process where we just cut things from the TypeScript file, except we're not cutting the types anymore. We're cutting the implementation. We're cutting the, for example, the initializer here, we're cutting the function body, we're cutting the constructor body. So all of these just go away in declaration files.

However, there is one exception, namely in this case, the return type of this function, because if the developer did not specify the return type, then the declaration emit needs to reach into the type checker and find out what the return type of this function will be. And this is why type checking is required for declaration emit. If everybody were to write all types in the TypeScript file, then we wouldn't actually need the type checker for declaration emit. So this is one way where we could cut the dependency if we specify all types, and this would allow us to unblock dependencies sooner.

So there are already lint rules out there that could enforce this, but it would definitely be used to have something built into the actual compile that would be a guarantee for other tools as to what annotations are necessary. So we decided to add a new flag to TypeScript that we called isolated declarations. So isolated declaration is the new feature that we are implementing. And let's have a look at what this feature would mean. So our first idea was that we should add type annotations to all values that make it into declaration files. Now, this mostly means exported values, although there are some indirect ways that values can make it into declaration files as well, even if they're not explicitly exported.

Now we did make some exceptions. In some cases, values can be trivially inferred, and maybe we can allow those without having explicit type annotations. But what is for certain is that the type checker itself cannot be involved in declaration admin, because we want declaration admin to be purely a syntactic transformation where we're just cutting out implementation, and we don't have to reach into the type checker to ask about type information from the source code.

5. Type Annotation and Inference

Short description:

You don't have to type annotate everything unless you opt into it. If you're happy with your TypeScript builds, you can keep your project as is. Our initial thinking was to specify type annotations on everything, but that proved to be difficult for large projects. We went through three phases of inference, but encountered problems. Our code would require duplicate annotations for literals and object literals. However, we made the declaration smarter by inferring types for number and literal types, and extracting information from the parameter list for functions.

So the next logical question is, okay, do I have to type annotate everything? I mean, I want to point out that you don't have to do anything unless you opt into it. If you're happy with your TypeScript builds, if you're happy with the way TypeScript was and with your build times, you can keep your project as is. Nobody's forcing you to adopt this. If your dependencies adopt this, you won't really care because you're only consuming their declaration file, so it won't really be something that everybody has to opt into.

So our initial thinking however was that yes, we will need to specify type annotations on everything. Turns out this makes converting large projects a pain and it's not the best developer experience, especially since looking at some of these expressions, you could figure out very easily that yes, indeed, the type of something is a number. So we went through three phases. Basically, the first one was where we thought that no we wouldn't do any inference in this mode. The second one was where we thought that maybe some trivial inference is okay. And the last one, which ultimately we rejected was where we do a lot more inference. But we also ran into some problems we're going to see in a few slots. So let's see what our code would look like with each one of these versions.

So no inference. What this would mean is that you would have to specify types on absolutely everything. For example, you're assigning 20 to the variable margin. It's pretty obvious that type should be number here, but we don't allow any annotations, so you would have to specify it. You would have to specify it for this constant, basically duplicating the literal in both places, both in the type position and in the initializer. It gets even worse for things like object literals, where you have to duplicate the property names and the values, and it really becomes very painful to do so. For constants that are initialized with a function, well, you would have to copy the parameter list again to the type annotation, and also specify the return type. And again, for fields, even if you're initializing it with very basic values, that should be obvious what type they are, we would still need to specify the type explicitly.

So we decided to make our declaration a bit smarter. So if we would see a number literal, well, the type there is pretty obviously number. So you don't need to specify that. Same for literal types. For object types, it's a bit more complicated, but it's not impossible to deduce the type from the initializer. So we decided that we can support this as well. For functions that, for constants that are initialized with a function, well, you don't need to specify the whole function signature as a type annotation. We can just extract most of the information we need from the parameter list. You will, however, need to specify the return type because we don't want to have to go into the function body itself and look for any returns and try to synthesize a return type based on that.

6. Advanced Inference and Quick Fix

Short description:

You will need to specify the return type. For fields, you can omit them if the initializer is clear. TypeScript's declaration emitter is smart, but we want to keep it dumb and fast. We can do more inference, but it may not always be right. Declarations would have to be optimistically omitted, causing problems and complexity. Adding more inferences might degrade the developer experience. To ensure easier adoption, we added a quick fix.

You will, however, need to specify the return type because we don't want to have to go into the function body itself and look for any returns and try to synthesize a return type based on that. And for function fields, and for class fields, again, you can just omit them if the initializer is clear enough for us to infer a type from.

What about if we want more, right? We were very excited by our initial success with this trivial form of inference, and we decided, well, maybe we can do more. Maybe we can infer more things. And the problem is that TypeScript's declaration emitter is very smart, and we want to keep our emitters dumb, and hopefully fast. So let's take an example. Let's say we have a variable, and we're initializing it with the value of another variable. Could we trivially infer a type for orientation? What type could we write to declaration files without having to involve the type checker? So we could try using typeOfHorizontal. This seems like a very good fit. typeOf will give us the type of the variable, and orientation should have the same type as what we're assigning, right? Well, that sometimes works.

So the answer is, can we do more inference? The answer to that is yes, but we will probably not always be right, so declarations would have to be optimistically omitted. This basically causes some problems, though, because what we're saying here is that, okay, a declaration emitter could produce declarations without the type checker, but they could be wrong. And then TypeScript itself could come back and error out looking at all of the information in the type system and say, okay, well, in this particular case, if you're using isolated declarations and you're using a standalone declaration emitter, it cannot possibly have known what the correct type is there. So this is possible, but it kind of creates a very complicated mental model, makes it very difficult to understand. Also, the errors would become much harder to understand because we would need to tell people why exactly they're erring on a certain construct. And what we ended up was errors that sounded something like, this type that is inferred in isolated declarations is different from this other type that would be inferred by TypeScript normally. And it kind of sounds like the compiler is having an argument with itself. Isolated declarations is one thing, regular TSC says another thing. And the developers involved basically nowhere in this process. So, all of this might result in actually degrading the developer experience instead of improving it, which was the reason why we wanted more inference in the first place. So definitely adding more inferences that might not always be right is not the best way to go.

So, we still wanted to make sure that people would have an easier time adopting this feature. How could we ensure that? Well, we decided to add a quick fix. The information is already present in there.

7. Improving Developer Experience

Short description:

Adding more inferences that might not always be right is not the best way to go. To ensure easier adoption, we added a quick fix.

And the developers involved basically nowhere in this process. So, all of this might result in actually degrading the developer experience instead of improving it, which was the reason why we wanted more inference in the first place. So definitely adding more inferences that might not always be right is not the best way to go.

So, we still wanted to make sure that people would have an easier time adopting this feature. How could we ensure that? Well, we decided to add a quick fix. The information is already present in there. We just need to insert a type inside our code. Visual Studio Code already has all the information, the language service that is running in there already knows what the types of all the variables are even if they're not spelled out. So, what we could do if there's an isolation declaration we could just use a quick fix to actually insert the type without having to write it ourselves which I think is something that will definitely help all the developers adopt this feature.

8. Improving TypeScript with Isolated Declarations

Short description:

Isolated declarations improve TypeScript in several ways. They enhance speed and unlock more parallelism by emitting declarations up front. They also improve compatibility with other tools and promote transparency by requiring developers to write types in code.

I want to send a special thanks to Hannah Jewell from Google which worked extensively on this feature and most of it is her doing.

So, how would isolated declarations improve TypeScript? Well, we hope to improve it in several ways. With this feature, first of all, we hope to improve speed, by passing the type checker to synthesize new types will probably improve declaration limit speed all on itself. We hope to unlock a lot more parallelism. If we can emit declarations up front, projects don't need to wait for the type checking phase of all of their dependencies to finish. They already have the declarations and they can just do their own type checking with that. We also hope to improve compatibility with other tools. Like I said, there are already tools out there that generate JavaScript from TypeScript. We're hoping to create a similar ecosystem of tools that could emit declaration files without the use of the type checker, but that would be 100% compatible with current TypeScript declarations. And we also hope that we will improve transparency. Developers sometimes forget to write types for their exported values. And sometimes the type checker synthesizes some things that are not particular developer friendly. There are the very large types, or types without any semantic meaning, resulting in a bad developer experience for the clients of those libraries. So if people are forced to actually write these types in code, they'll probably think more about them and the declarations will probably have a better quality just because of this.

9. Improving Performance with Isolated Declarations

Short description:

Isolated declarations increase parallelism and improve performance dramatically. The future holds the hope of getting this feature in TypeScript soon, potentially within the next half a year. While it's uncertain if isolated declarations can help in a single project setup, there is potential for even faster performance with the possibility of an ecosystem of TypeScript declaration emitters. Special thanks to all the individuals involved in the development of this feature.

But coming back to our experiment of running the build for a monorepo in parallel, we saw that without isolated declarations, there was a limited amount of parallelism. With isolated declarations, we hope to increase the amount of available parallelism. We saw that in the other version, this was pretty limited. At most, two projects could type check in parallel in this example. What we want to do is front-load all of the declarations amid, so we can unblock all projects as quickly as possible. We want to go from this diagram to this diagram. Hopefully, the difference between these two is that time we won't need to wait for TypeScript anymore because type checking will be done much faster if we can do all this in parallel. This was the hope. Without actual numbers, theoretically this might work. But we didn't actually know what kind of performance gains we were looking at in real-life projects.

So what we did is we actually converted our sample monorepo to use isolated declarations. And we tried to see what kind of performance gain we would get from trying to build in parallel with this new approach with front-loading all of the declaration amid. So drumroll, let's look at the times. As you can see, even just by looking at the numbers, there's a great improvement there. We get even single-digit build times. Looking at a chart maybe makes it more clear the improvement in performance is quite dramatic. We're talking about three times improvement in performance. So when we saw these numbers, we were really happy that our theory sort of panned out and our expectations were actually met and even exceeded by some measures.

So what does the future hold? Well, hopefully we're going to get this feature in TypeScript soon. We don't know exactly when, but hopefully it'll happen within the next half a year. The other question I usually get when I tell people about this feature is can this help in a single project setup? We're not sure about this yet. We have done some experimentation but there are some limitations that still need to be solved in the TypeScript compiler first. We will see if we can help with that as well. Can this get even faster? We use the TypeScript compiler backbone to actually implement our own declaration emitter. There is space there for other tools to be a lot faster just as there's an ecosystem of JavaScript emitters. Hopefully, there will be an ecosystem of TypeScript declaration emitters and hopefully that will make things even faster.

I'd like to say a warm thank you to everyone who was involved from Google, Hannah Joo and Yann Kuehler, from Microsoft Daniel Rosenwasser, Jake Bailey and Ryan Kavanagh and from Bloomberg, Rob Palmer, Thomas Chatwin and Ashley Claymore. Also, a special shout out to the Flow team who implemented a very similar feature a while back and which was definitely a source of inspiration for us. Thank you very much for listening and I'm ready for any questions in the chat. Thank you.

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

React Day Berlin 2023React Day Berlin 2023
21 min
React's Most Useful Types
We don't think of React as shipping its own types. But React's types are a core part of the framework - overseen by the React team, and co-ordinated with React's major releases.In this live coding talk, we'll look at all the types you've been missing out on. How do you get the props type from a component? How do you know what ref a component takes? Should you use React.FC? And what's the deal with JSX.Element?You'll walk away with a bunch of exciting ideas to take to your React applications, and hopefully a new appreciation for the wonders of React and TypeScript working together.
Vue.js London 2023Vue.js London 2023
30 min
Stop Writing Your Routes
The more you keep working on an application, the more complicated its routing becomes, and the easier it is to make a mistake. ""Was the route named users or was it user?"", ""Did it have an id param or was it userId?"". If only TypeScript could tell you what are the possible names and params. If only you didn't have to write a single route anymore and let a plugin do it for you. In this talk we will go through what it took to bring automatically typed routes for Vue Router.
TypeScript Congress 2023TypeScript Congress 2023
31 min
Making Magic: Building a TypeScript-First Framework
I'll dive into the internals of Nuxt to describe how we've built a TypeScript-first framework that is deeply integrated with the user's IDE and type checking setup to offer end-to-end full-stack type safety, hints for layouts, middleware and more, typed runtime configuration options and even typed routing. Plus, I'll highlight what I'm most excited about doing in the days to come and how TypeScript makes that possible not just for us but for any library author.
React Advanced Conference 2021React Advanced Conference 2021
6 min
Full-stack & typesafe React (+Native) apps with
Top Content
Why are we devs so obsessed with decoupling things that are coupled nature? tRPC is a library that replaces the need for GraphQL or REST for internal APIs. When using it, you simply write backend functions whose input and output shapes are instantly inferred in your frontend without any code generation; making writing API schemas a thing of the past. It's lightweight, not tied to React, HTTP-cacheable, and can be incrementally adopted. In this talk, I'll give a glimpse of the DX you can get from tRPC and how (and why) to get started.
TypeScript Congress 2022TypeScript Congress 2022
10 min
How to properly handle URL slug changes in Next.js
Top Content
If you're using a headless CMS for storing content, you also work with URL slugs, the last parts of any URL. The problem is, content editors are able to freely change the slugs which can cause 404 errors, lost page ranks, broken links, and in the end confused visitors on your site. In this talk, I will present a solution for keeping a history of URL slugs in the CMS and explain how to implement a proper redirect mechanism (using TypeScript!) for dynamically generated pages on a Next.js website.

Add to the talk notes: 

Workshops on related topic

React Advanced Conference 2021React Advanced Conference 2021
174 min
React, TypeScript, and TDD
Top Content
Featured WorkshopFree
ReactJS is wildly popular and thus wildly supported. TypeScript is increasingly popular, and thus increasingly supported.

The two together? Not as much. Given that they both change quickly, it's hard to find accurate learning materials.

React+TypeScript, with JetBrains IDEs? That three-part combination is the topic of this series. We'll show a little about a lot. Meaning, the key steps to getting productive, in the IDE, for React projects using TypeScript. Along the way we'll show test-driven development and emphasize tips-and-tricks in the IDE.
React Advanced Conference 2022React Advanced Conference 2022
148 min
Best Practices and Advanced TypeScript Tips for React Developers
Top Content
Featured Workshop
Are you a React developer trying to get the most benefits from TypeScript? Then this is the workshop for you.In this interactive workshop, we will start at the basics and examine the pros and cons of different ways you can declare React components using TypeScript. After that we will move to more advanced concepts where we will go beyond the strict setting of TypeScript. You will learn when to use types like any, unknown and never. We will explore the use of type predicates, guards and exhaustive checking. You will learn about the built-in mapped types as well as how to create your own new type map utilities. And we will start programming in the TypeScript type system using conditional types and type inferring.
Node Congress 2024Node Congress 2024
83 min
Deep TypeScript Tips & Tricks
TypeScript has a powerful type system with all sorts of fancy features for representing wild and wacky JavaScript states. But the syntax to do so isn't always straightforward, and the error messages aren't always precise in telling you what's wrong. Let's dive into how many of TypeScript's more powerful features really work, what kinds of real-world problems they solve, and how to wrestle the type system into submission so you can write truly excellent TypeScript code.
TypeScript Congress 2023TypeScript Congress 2023
131 min
Practice TypeScript Techniques Building React Server Components App
In this hands-on workshop, Maurice will personally guide you through a series of exercises designed to empower you with a deep understanding of React Server Components and the power of TypeScript. Discover how to optimize your applications, improve performance, and unlock new possibilities.
During the workshop, you will:
- Maximize code maintainability and scalability with advanced TypeScript practices
- Unleash the performance benefits of React Server Components, surpassing traditional approaches
- Turbocharge your TypeScript with the power of Mapped Types
- Make your TypeScript types more secure with Opaque Types
- Explore the power of Template Literal Types when using Mapped Types
Maurice will virtually be by your side, offering comprehensive guidance and answering your questions as you navigate each exercise. By the end of the workshop, you'll have mastered React Server Components, armed with a newfound arsenal of TypeScript knowledge to supercharge your React applications.
Don't miss this opportunity to elevate your React expertise to new heights. Join our workshop and unlock the potential of React Server Components with TypeScript. Your apps will thank you.
TypeScript Congress 2022TypeScript Congress 2022
116 min
Advanced TypeScript types for fun and reliability
If you're looking to get the most out of TypeScript, this workshop is for you! In this interactive workshop, we will explore the use of advanced types to improve the safety and predictability of your TypeScript code. You will learn when to use types like unknown or never. We will explore the use of type predicates, guards and exhaustive checking to make your TypeScript code more reliable both at compile and run-time. You will learn about the built-in mapped types as well as how to create your own new type map utilities. And we will start programming in the TypeScript type system using conditional types and type inferring.
Are you familiar with the basics of TypeScript and want to dive deeper? Then please join me with your laptop in this advanced and interactive workshop to learn all these topics and more.
You can find the slides, with links, here:
And the repository we will be using is here:
TestJS Summit 2023TestJS Summit 2023
78 min
Mastering Node.js Test Runner
Node.js test runner is modern, fast, and doesn't require additional libraries, but understanding and using it well can be tricky. You will learn how to use Node.js test runner to its full potential. We'll show you how it compares to other tools, how to set it up, and how to run your tests effectively. During the workshop, we'll do exercises to help you get comfortable with filtering, using native assertions, running tests in parallel, using CLI, and more. We'll also talk about working with TypeScript, making custom reports, and code coverage.