How to make our CLIs safer with types?

Rate this content
Bookmark

In this talk, we will explore the various issues encountered when building JavaScript CLIs, and demonstrate how TypeScript can address them and help us make our applications safer.

By the end of this talk, you'll understand how to abstract runtime validations type behind easy-to-use interfaces, and have all the tools you need to bring those benefits into your own CLIs at little cost - whether they are open-source projects, or internal tools.

29 min
21 Sep, 2023

Video Summary and Transcription

This talk explores the challenges of building CLIs and introduces a fully typed CLI framework built with TypeScript. It discusses the complexities and pitfalls of CLI implementation and the benefits of using decorators for metadata and logic. The talk also covers type inference challenges and presents a third way of assigning annotations as decorators. It highlights the integration of Tpanion, Zod, and ClipAnion for type checking and format validation. Finally, it mentions other CLI frameworks like ClipAllian, Common.js, and Oclif that offer similar functionality.

Available in Español

1. Introduction to the Talk

Short description:

Welcome to this talk about making our CLI safer with TypeScript. We will discuss the problems faced when building CLIs, techniques in TypeScript for better inference, and existing frameworks. Let's get started!

Hi everyone, and welcome to this talk about making our CLI safer with TypeScript. My name is Mayr Neonaysan. I work at Datadog as part of the front-end dev team, where we are building all sorts of tools in order to make sure our front-end engineers can be as efficient as they want to be. You can find me on Twitter and GitHub, as Arkalis, and yeah, hope to see you there.

So first, about this talk, we are going to split it into four different parts. First, we are going to talk about what are CLIs, what are the problems that we face when building them. Then we are going to see some techniques that we can use in TypeScript in order to make some better inference on the type of the options. And finally, we are going to go over some of the frameworks that already do that for us before jumping into a conclusion and recapitulating everything that we learned. Does it sound good?

2. Complexities of CLIs and Yarn CLI

Short description:

CLIs are simple and useful for parameterizing actions. However, they can be tricky, like when using npm run eslint ____ version or cp command with arguments at the beginning. Yarn CLI is one of the biggest in the JavaScript ecosystem and has specific behaviors. Commander JS was used initially, but we made run keyword and dash dash token optional, requiring additional implementation.

They are very simple to read on about and very simple to implement usually. We use them in order to parameterize actions, which can be as complicated as a full CLI application like YARN, or something much simpler like a script that you are using inside your build pipeline. The one thing they have in common is that they are typically very simple. Compared to full UI applications, they require very few boiler plates, and that makes them a very good thing to use if you are just trying to make a couple of behaviors configurable.

However, they can also be tricky. For instance, let's take npm run eslint ____ version. If you don't use the ____ token to separate the eslint and the ____ version token, you are going to end up running the version option on npm itself and not eslint. However, if you are running YARN, you don't have to do this ____ token. But in order to do that, as we are going to see later, things start to get more complicated.

The cp command looks simple. However, unlike many commands where the amount of parameters of variadic arguments is at the end of the command, in the case of cp, it's at the beginning of the command. You have slc1, slc2, any number of other sources, but you must have one destination at the end. So the required position of arguments is at the end rather than the beginning. You have the arm command that has the preserve-root option, but it also has the "-no-preserve-root option". And if you're implementing something like this in your scripts, you usually want to declare both options into one, so that you don't have to duplicate them. And finally, let's take the case of the vlc-v. If you look at this command and you don't know anything else about it, you would think that "-v", is actually a boolean flag. But it's not. If you run vlc-vvv, you will not get the same behavior as if you were running vlc-v. Because vvv is actually a counter. It counts the number of times that you're passing it to the command.

Now, let's talk about the Yarn CLI itself. It's very interesting, because the Yarn CLI is one of the biggest CLI interfaces that we all use in the JavaScript ecosystem. Each has more than twenty commands, and each of them accepts options, and we have some very specific behaviors like we previously saw. At first, it started to use Commander JS, which is a CLI framework for JavaScript that supports subcommands, and that's something that is very important for Yarn, because Yarn add, Yarn remove, Yarn upgrade to interact with you, all those kind of things are commands from the main application. However, we decided to eventually make the run keyword optional, so that you could run Yarn eslint instead of Yarn run-eslint, and that wasn't something that Commander JS actually supported out of the box. So we had to implement our own code in order to support that. Then, later on, we decided to also make the dash dash token optional, so that if you run Yarn eslint dash dash version, then the dash dash version is applied on eslint and not on Yarn itself. That's super handy. However, it required to implement something else on top of Commander JS.

3. Challenges with CLI Implementation

Short description:

Implementing something on top of Commander JS made the code complex and hard to maintain. CLIs can be error-prone, with the potential to forget declarations, default values, validation, or removing unused options.

However, it required to implement something else on top of Commander JS. In the end, the code ended up fairly complicated, and it was difficult to reason about it and to maintain it. This is a picture of me every time I had to do that. CLI is on top of that, or can also be error-prone. You can forget many things while writing a comment. You can forget to declare an option. You can forget to declare its default value. You can forget to validate it if it has a specific shape that you're expecting. You can even forget to use the option, and you can forget to remove it once you no longer need it.

4. Building a Fully Typed CLI Framework

Short description:

In 2018, we decided to rewrite Jan and make it fully compatible with TypeScript. We needed a CLI framework that was readable, fully type-checked, and protected against mistakes. So we built our own framework and learned a lot in the process. Let's build a fully typed CLI framework from scratch using metachaining and decorators.

For instance, in a couple of cases when working on Jan it happened that at some point of the development of a feature I added an option, worked with it for a while, then decided to remove it before managing the pull request. However, I may have forgot to remove the code that was relying on that. Or I could remove the code that was relying on this option, but forgot to remove the option from the actual declaration of the command, meaning that someone running the help command would see this option exists but where it wouldn't actually be there.

So in 2018 when we started with a large scale project in order to rewrite Jan and make it fully compatible with TypeScript, we decided to ditch several of our legacy systems, and one of them was the CLI implementation. So we started to look at what existed, and unfortunately we didn't really find anything that was solving all of our requirements. We needed something with good readability because we had a lot of contributors that would jump into the codebase without prior knowledge of anything that we were using. We needed something that was fully type-checked because we wanted to prevent as many errors as possible without having to rely on manual code reviews because we noticed that at every code review there's always something that lives by us undetected. And finally we wanted to really protect against mistakes. We wanted the pit of success to be there. So if you're not familiar with the concept, the pit of success is that whatever you're doing, you will fall into a command behavior. And then if you can improve it later, but by default everything is sane and works as you expect. And you really have to get out of your way, in order to make something that is broken.

So we talked about CLIs in abstract terms. And in the case of YARN what we needed to build. So we decided to build our own framework and by doing that we learned a lot of things. So that's what we are going to make together. Let's build a fully typed CLI framework from scratch. Of course we are going only to focus on the type-check aspect and not the implementation of how to parse the arguments because that would be much more than a 25 minutes talk. But as for the type-check, we have three ways of achieving these effects. First we can use metachaining, we can use decorators, and we could use what I call the third way. We are going to keep this one for the end because it's quite interesting but first we need to go over the two first in order to better understand why it's so good. So first let's talk about metachaining. It's the simplest one, in fact that's the exact same syntax as Commander.js uses and that's a good thing because it means that it's familiar to anyone that used to make CLI interfaces a couple of years ago. You have a command and then you declare the options by compounding the results of the command function and once you have declared all the options you can declare what the command does. And then the CLI framework when the command is called will parse the options and call the action by passing it an OptionBug that contains all the options that got parsed. For this talk, we are going to pretend that the options are always set because otherwise all the things like obd.check would be boolean or undefined rather than just boolean and it would make the code harder to read. But here what we want to reach is a state where obd.check exists and is a boolean, obd.require exists and is a string and obd.foo is properly detected by TypeScript as not existing and that's the important part. We can't have obd be any. That wouldn't work for us. In terms of TypeScript, what would be the type of the resultant value of the command function? First, we would want something that returns a command with no option, because it doesn't declare any option.

5. Command Interface and Method Chaining

Short description:

When using the boolean and string functions, the command is returned as a generic with the declared options. This approach has pros and cons. It's easy to write and familiar to those experienced with CLI frameworks. However, it can be verbose and less idiomatic, making it difficult for newcomers to understand. Additionally, it has poor tooling integration. For example, TypeScript may not catch unused options in the action callback.

Then, as soon as we call boolean, this time the command is a generic with a check property that is a boolean. And then, once we call the string function, the command is returned as a generic with both check and require. And finally, when we have action, it just takes a callback that accepts the option bag as parameter and returns void.

By doing that, we allow TypeScript to properly infer when we are passing the action callback that the OPITIZ type is composed of the option that we declared earlier. To do that, it's very simple, really. This is all that we need.

So we have our command interface with its generic type, which defaults on void, and we declare both Boolean and string as a function that takes a literal string as parameter. We even use some kind of string concatenation as types, because a recent version of TypeScript supports it. And each time we call either Boolean or string, we return the same command type, except that we are extending it to now include the newly declared option. And finally, we have the action function that, as we mentioned, just lets TypeScript infer the type of OPDs based on the generic itself.

This approach has pros and cons. It's easy enough to write. Clearly, there's not a lot of code involved, as you can see. It does feel a bit like 2017, because most of the CLI framework of the time were written like this by chaining functions, one after the other. It means that if you're already familiar with this kind of framework, then it's exactly what you would expect. However, it's a bit variable. You have to use a lot of functions in order to declare your options. It's not very idiomatic. You're using method chaining, which is something that you don't find in a lot of JavaScript interfaces. So people coming into your code may find this a little difficult to read. And it has poor tooling integration. And this is the main one I want to talk to you about. Let's take method chaining here. As you can see, we are declaring a command with a string option. And we have an action here, which prints a string with the name of the person running the command. But does it really? Actually, no, it doesn't. We forgot to use opent.name. But TypeScript is not able to see that, because from its perspective, perhaps the option bag that is passed from parameter to the action is also used somewhere else. So it's not a local. So the no unused locals check will not be able to see that we forgot to actually use that.

6. Using Decorators for Special Metadata and Logic

Short description:

Decorators provide a second option for checking code correctness. They allow you to attach annotations above class properties, providing special metadata and logic. This approach is better than meta-chaining as it uses regular private class properties, which TypeScript can analyze for unused variables. However, decorators have a complicated history and compatibility issues with old and new decorators. They also cannot affect types, leading to potential inference problems.

So that's what I mean by tuning integration. Since we are bypassing a little declaration of local variable and this kind of thing, we don't get to as easily check that our code is correct. So we have a second option to do that.

Decorators are interesting because they are using a new syntax that has appeared very recently. It's now stage three. Until then, it was kind of difficult to know whether we could use them or not. With decorators, you're attaching annotations above each property of your class. And by doing that, you can attribute special metadata, special logic to those properties.

So in theory, we could imagine something like this, where we're declaring a class for our command. Then we are declaring the options. And then we are attaching an annotation, which declares whether the check, for instance, is a boolean option or a string option, like for require. And that's much better than the meta-chaining in terms of threading integration. Because this time, we have just regular class properties, which are declared private. So TypeScript knows that they are not going to be used anywhere in this file. So if they are not used in this file, it means that there is a problem and it will warn you about it.

Additionally, yeah, see, that's the example that I was mentioning about. If you're not using name, then TypeScript emits an error. However, decorators have a couple of issues that we need to talk about. First, they have a complicated history. You have two different kinds of decorators. You have the old decorators and the new decorators, because decorators went through various stages and have various designs. So if you have a library that relies on old decorators in your code base, then you cannot use the new decorators. And if you have a library that use new decorators, then you cannot use libraries with all the decorators support. It also means that you need to have a compatible transpilation steps. And as we saw, there are both old transpilation steps and new transpilation steps. So it's a bit difficult to reconcile. And perhaps the biggest issue is that they cannot affect types. So if we take, again, the example that we wrote, here, we have the check option that is a Boolean and the string option and the require option that is a string. However, TypeScript is not actually able to infer that the type of check is Boolean and the type of require is string, despite the annotation.

7. Type Inference Challenges

Short description:

TypeScript cannot infer the types of check and require, so we have to explicitly set them. This leads to duplication and the potential for mistakes. It also requires a lot of code.

However, TypeScript is not actually able to infer that the type of check is Boolean and the type of require is string, despite the annotation. What we need to do is to explicitly set that check is a Boolean and require is a string. Which means that we have some kind of duplication, because we have to write Boolean twice, string twice. It's not great. Additionally, what if we make a mistake and set Boolean instead of string when we are declaring the require option? Turns out we have some way to prevent that, but there's still the duplication issue. And it requires a lot of code just to make sure that TypeScript is happy.

8. The Third Way: Assigning Annotations as Decorators

Short description:

With the third way, we assign the annotation as decorators to the property, eliminating the need for duplicate declarations and types. TypeScript can infer the types of check and require, making it a convenient solution.

Ideally, we would want something where we don't have to duplicate both the declaration and the types. So how can we do this? That's what I call the third way. With the third way, instead of assigning the annotation as decorators, we are just assigning them to the property. So here, we have our command class. We have our check and require options. And for each of them, we are just assigning the result of a Boolean and string value. And once the action is called, it's magic. Everything just works. TypeScript is able to infer the type of check and require, and you never had to declare them yourself. It seems the best of both worlds, right?

9. CLI Implementation Details

Short description:

The Boolean and String functions return metadata that masquerades as the desired type. We use 'as any' to lighten it for the type checker. The inject options into command function retrieves metadata associated with the option and assigns the real value based on the promised type. This allows for format validation and compatibility with TypeScript libraries like Tpanion and Zod.

So you would have your argv array, you would instantiate your command, you would inject options into the command, and then it just works. You can call the action method as you want. But how does that actually work, right? Because it does seem too great to actually work. You don't have complex transpilation. It's directly in the AST, so things like TypeScript or ESLint can operate very easily on it, and check that everything matches your requirements. And you have no type duplication as we see in the decorators. That's weird.

Now let's dig a bit to understand how that's possible. First, we are going to focus on the implementation of the Boolean and String functions. What do they look like? Well, they're very simple, they're just functions that return a Boolean and a String. But are they? No, they are actually functions that return a set of metadata that masquerades as the type that we want in the end. So for instance, here we have Boolean that returns the type Boolean object. But that says to TypeScript, no, it's actually a Boolean, I promise you. We are using as any to lighten it to the time checker. That might seem weird.

No, in the setup command, in the setup code, sorry, we saw that we have an inject options into command. How does it work? This is the general code of this function. For each argument, we are going to strip the double slash, and we are retrieving the metadata that are associated to the option. As we saw earlier when we are doing check equal Boolean or require equal string, we are assigning an object that contains a type property. Here, we are just retrieving this object. Since we typed command as any inside the parameter of inject options into command, TypeScript will let us do this. And now that we have the type, all we have to do is to actually assign the real value to the option, which matches the type that the metadata promised that it would be. And by doing that, it works. So in other words, we lied to the type checker, but it's for a good cause, so that's fine. It even allows us to make more complex things, like format validation. Let's take an option function that returns a string, just like the string annotation that we declared earlier. We can also add it overload so that it accepts validators. In TypeScript, you have different validators that allow you to check at runtime that a specific value has a specific type, and then for the rest of your code, treat it as the proper type that you validate. You have two libraries that are doing that. So you have a Tpanion and Zod that are both TypeScript compatible.

10. Integrating Tpanion, Zod, and ClipAnion

Short description:

You can integrate Tpanion and Zod with the described syntax. This approach offers a type that has been type-checked, allowing TypeScript to infer the resulting type. It requires minimal syntax and supports generics. It works well with ESLint and Prettier, and the syntax is pleasant to read. The complexity is hidden within the core, making it easy to declare CLI options without worrying about special requirements. However, it may surprise power users of TypeScript, and composition can be more challenging. Another option is ClipAnion, which follows a similar approach. It uses command classes, an option namespace, and the runExit() function. TypeScript can infer the types based on the provided annotations. Options can be assigned as booleans or strings, with support for undefined values.

So you have a Tpanion and Zod that are both TypeScript compatible. And we can integrate them with the syntax that we described. Since both of those libraries offer a type that has a generic on the type that has been type-checked, all you have to do is to tell TypeScript that your option function accepts a validator that returns something. And in returns, once the option is parsed, it will be this same type, and it will just work. For instance, here with T-Banyon, we are declaring an option that has a validator that checks that the argument is a number. And then inside the action, TypeScript properly infers that count is a number.

This third way has pros and cons like the others. First, it doesn't require any special syntax. It's just classes and properties, so it requires the bare minimum of ES features. It also supports generics as we saw. It's fairly easy to write some code that infers the resulting type based on the parameter provided to the annotation. It works very well with ESLint and Prettier. The syntax is pleasant to read. And finally, all of the complexity is completely hidden away within the core. You don't have to deal with anything special in order to understand how you're declaring your CLI options. You don't have to worry about anything, even if something is required. For instance, if you want to declare a required positional argument, that's something that is going to be abstracted inside the declaration of the positional arguments, but users won't have to worry about it in order to have the type work out of the box.

The cons is that we are aligning to TypeScript a bit, so it may come as a surprise to people who are more power users of TypeScript that may expect something and not understand why it doesn't work as they would expect. Additionally, composition is a little more difficult because then you have to wrap the annotation inside one another. But usually, from my experience, composition is fairly rare inside CLIs, so this has never been a problem on the CLI I worked on.

So, now let's talk about the options that you have when writing CLI tools. When I talk about YARN and say that we improved it, we ended up writing our own CLI framework. So first, I'm going to talk to you about this one. It's ClipAnion, and it allows you to do something very much like the third way I described earlier. You have a command class, you have an option namespace that contains a bunch of annotations, and you have the runExit() function that lets you run your actual program. Here you're declaring a command that contains a couple of options, so check and require. And you're assigning them to an option that is either a boolean or a string, with all the option names that you want. And finally, TypeScript is able to infer their types thanks to all the overloads that ClipAllian declares and make sure that it follows during the CLI-paired parsing. In fact, here you can see that this time we added the or-undefined() because an option may be set or may not be set. If it's not set, then it needs to be either boolean, in case it's set, or undefined.

11. ClipAllian and Other CLI Frameworks

Short description:

ClipAllian is a powerful CLI framework that supports validators, optional and required positional arguments, overloads, native error handling, and is fully type safe. It uses a state machine for token parsing, reducing bugs. Other tools like Common.js and Oclif offer similar functionality with slightly different syntaxes.

It also supports validators through ClipAllian, through, sorry, through TipAllian, which is the type valid, runtime type validator that I mentioned before. So if you have a string option, you can also validate that its format is actually a number, in which case TypeScript will properly refine the number to be number, will refine the type to be number instead of being string. ClipAllian supports many things. It's the framework that we are using in the Yarn CLI, which does a lot of different things. It supports optional positional arguments, required positional arguments, will card positional arguments. For instance, when you are running Yarn ESLint and accepting options, that's a will card because we are accepting anything. It supports boolean, strings, arrays, tables, counters. It implements the parsing of the CLI tokens through a state machine, which is something very unique. It means that we are just generating a full state machine with little room for bugs. A lot of CLI frameworks nowadays try to parse the token one-by-one through a for loop, basically. But you have the risk of having a lot of bugs. It also enables us to support overloads. For instance, you can have different commands with the same path but different options or different requirements. KeepOnion will be able to figure out what is the one that it should use, depending on what the user provided. Native error handling, low runtime override and, of course, it's fully type safe. So, for instance, given the following command declaration, it will be able to properly refine the types depending on what you declare. There is, of course, a regular option that has Boolean, that is Boolean, or undefined. But you can also assign a default value, in which case KeepOnion will see that since there is a default value, it cannot be undefined anymore. You can also declare optional option that accepts a string as argument, in which case a string or undefined, but you can also mark them as required, in which case they will become a string, because they are required and if they are not there, KeepOnion will throw an error. You can also declare tuples using the ret option. You have a lot of different choices and each time KeepOnion does the right thing and properly type-checks it. But you have also other tools that are doing the same thing with slightly different syntaxes. First, you have Common.js, of course, which is the oldest one and actually supports types really well using the extra typings package. So it works just like metachaining that we saw earlier. And you also have Oclif that is perhaps a little more verbose but works sensibly the same way as KeepOnion.

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
Top Content
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 tRPC.io
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 2023TypeScript Congress 2023
24 min
Faster TypeScript builds with --isolatedDeclarations
Top 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.

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
Top Content
Workshop
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
Workshop
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
Workshop
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: http://theproblemsolver.nl/docs/ts-advanced-workshop.pdf
And the repository we will be using is here: https://github.com/mauricedb/ts-advanced
TestJS Summit 2023TestJS Summit 2023
78 min
Mastering Node.js Test Runner
Workshop
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.