TypeScript 5.0 Beta Deep Dive

TypeScript 5.0 beta brings a bunch of exciting features, including const annotations, decorators, speed improvements and a new option for resolving modules in TS.

Transcript

0:00 What's up wizards? TypeScript 5. Beta just dropped. It brings in a whole load of new functionality and new toys that we can play with. I'm going to be talking about the headline items and showing you exactly what you need to know for the upcoming 5.. Let's go.

0:13 To get started, you can add TypeScript at the beta version to your package.json. Then when you're inside a TypeScript file, you can run Command + Shift + P and select your TypeScript version. You should use the Workspace Version 5. Beta. I'm going to go quick fire through some of the small changes and we're going to see the big changes at the end.

0:29 They've added a new option here to moduleResolution called bundler. We had some options called node16 and nodenext before, but this meant that you had to add the file extension to things that you imported.

0:38 Using "moduleResolution": bundler now means that TypeScript is going to map up to your modern bundler like Vite, esbuild, swc, Webpack. This is an extremely welcome change, but probably, your framework is just going to handle it and it's all going to be fine.

0:50 Another small change they've made is they've slightly improved enums. I'm on record with my thoughts about enums, you can take a look here. Let's say we have an enum called LogLevel with DEBUG, WARN, and ERROR. We have a function called log which takes in a level and a message. In previous versions of TypeScript, you could pass any number into here and it would work.

1:09 TypeScript wouldn't give you an error message here, but now you can pass in zero for DEBUG, one for WARN, and two for ERROR, if you really want to. If you pass something that isn't represented by the enum, then it's going to error at you.

1:20 If you use literals instead, then this will have the same behavior as before. It will still force you to pass in LogLevel.DEBUG here, instead of the literal value. This is another extremely welcome change, but I don't think it changes my overall idea about enums, but you know, safety is good.

1:36 The other welcome change here is some speed optimizations in TypeScript itself. This means that the actual package size of TypeScript is now only 58 percent of what it was. If you're using TypeScript to build your application or your library, then you're going to see some speed improvements.

1:49 I imagine you're also going to see some speed improvements if you run TypeScript on CI to lint your project, which is what most people do. Without any changes, this is a massive win and I'm keen to hear in the comments if you found any improvements.

2:01 Let's talk about the two big headline items from this release. Number one is const annotations. Const annotations give you a new tool with generics in order to improve the inference that you get when you call functions.

2:12 Let's say we have a function called routes. The routes parameter takes in an array of T, which is going to be a set of routes. We create a function called addRedirect, which is going to redirect from one route to another route. Let's call this routes function with /users, /posts, and /admin/users, and we get back a router at the end of it.

2:31 Now, let's use the addRedirect function on that router to redirect from /admin/users to /users. This code doesn't do anything, of course. We could add some implementation inside here, if we wanted to, but I'm going to use it to explain how TypeScript infers this T.

2:44 You notice that even though we've added these three routes here, I can add anything to addRedirect and it won't yell at me. That's because if we hover over routes, you can see that it's inferred as an array of strings instead of the literal values I've passed in. Which means that addRedirect down here is from: string and to: string, meaning that it accepts any string.

3:03 This is quite a difficult problem to solve. There are some tricks to do it, but in TypeScript 5., you can just add a const T to the start here. Now, the things that you pass into routes are going to be inferred as their literal. We get /users or /posts or /admin/users. This means that addRedirect down here has from the route and to the route as well.

3:22 That means that when we change this, we're going to get autocomplete for the options that we have here. I can either add /admin/users or /posts or /users. Quietly, I think this is one of the most important features TypeScript has shipped for a while, because I'm starting to see how much easier it makes handling these generics when you care about the literal values.

3:41 Finally, let's look at decorators. Decorators have been around in TypeScript for a while under an experimental flag, but 5. brings them up to speed with the ECMAScript Proposal. Which is now in stage three, meaning it's in the stage where it gets added to TypeScript.

3:55 Let's imagine I create a class called SDK. I'm going to add a bunch of methods to this SDK, including getUser and getPost. I've stubbed them out with different methods, but you could imagine these would go and fetch the post and the user from a database.

4:06 Let's imagine that I want to log the getUser and getPost functions. I want to log when the method is called. I also want to log when the method resolves. For this, I'd need to make getUser async, I'd need to capture the result of Promise.resolve, add in a log, and then return the result.

4:20 I'm then going to have to copy this logic for every single method I have here. If only there were an abstraction where I could just wrap these methods and add some logging to them. This is where decorators come in. They allow you to wrap methods and entire classes in order to add functionality.

4:35 Let's create a function called log. It's going to take in the originalMethod, which is typed as a function that can take in anything and return anything. As a second parameter here, we're going to add underscore context, which is a ClassMethodDecoratorContext.

4:47 Next, we're going to return another function, which is going to be the method. Just like in getUser, I'm going to save the results here by calling the originalMethod using originalMethod.call. This lets us pass this into the originalMethod, meaning it doesn't lose the context of where it's called, in this case, in the SDK. Next, we can return the results.

5:05 Now, let's do the logging that we want to do from this decorator. Instead of hard coding getUser, we can replace it with _context.name. This is going to be the name of the method that we've called. We can replace the ID by JSON.stringify in the args that we get. Next, let's log when the function completes too. All the code that we need for our decorator is complete.

5:23 Instead of having all of this complicated code down here in repeated code, we can just decorate the functions with log. When getUser is called, log will be called and it will call the underlying method itself. This means you get a really beautiful descriptive syntax, which can abstract away some horrible complicated code.

5:41 There's a lot more to talk about with decorators and I think I'll do a separate video on typing them properly, because as you saw, there was quite a lot of any's in my code. I'm really excited to see where they go and I feel like this might rejuvenate classes in the JavaScript community a little bit.

5:53 Since function components took over React, classes have been on the out. Being able to capture abstractions and decorators seems like a really, really nice feature for TypeScript and JavaScript as a whole. I'm excited, bring the classes back. Let's have them. It's been lovely seeing you as always and I'll see you very soon.

TypeScript 5.0 beta is out! Here's a breakdown of all the most important features.

Discuss on Twitter

More Tips

Play Type Predicates

Type Predicates

1 min

Play TypeScript 5.1 Beta is OUT!

TypeScript 5.1 Beta is OUT!

2 mins

Play How to Name your Types

How to Name your Types

4 mins

Play Don't use return types, unless...

Don't use return types, unless...

4 mins

Play Conform a Derived Type Without Losing Its Literal Values

Conform a Derived Type Without Losing Its Literal Values

1 min

Play Avoid unexpected behavior of React’s useState

Avoid unexpected behavior of React’s useState

1 min

Play Understand assignability in TypeScript

Understand assignability in TypeScript

2 mins

Play Compare function overloads and generics

Compare function overloads and generics

1 min

Play Use infer in combination with string literals to manipulate keys of objects

Use infer in combination with string literals to manipulate keys of objects

1 min

Play Access deeper parts of objects and arrays

Access deeper parts of objects and arrays

1 min

Play Ensure that all call sites must be given value

Ensure that all call sites must be given value

1 min

Play Understand how TypeScript infers literal types

Understand how TypeScript infers literal types

1 min

Play Get a TypeScript package ready for release to NPM in under 2 minutes

Get a TypeScript package ready for release to NPM in under 2 minutes

1 min

Play Use assertion functions inside classes

Use assertion functions inside classes

1 min

Play Assign local variables to default generic slots to dry up your code and improve performance

Assign local variables to default generic slots to dry up your code and improve performance

2 mins

Play Know when to use generics

Know when to use generics

2 mins

Play Map over a union type

Map over a union type

1 min

Play Make accessing objects safer by enabling 'noUncheckedIndexedAccess' in tsconfig

Make accessing objects safer by enabling 'noUncheckedIndexedAccess' in tsconfig

1 min

Play Use generics to dynamically specify the number, and type, of arguments to functions

Use generics to dynamically specify the number, and type, of arguments to functions

1 min

Play Use 'declare global' to allow types to cross module boundaries

Use 'declare global' to allow types to cross module boundaries

2 mins

Play Turn a module into a type

Turn a module into a type

2 mins

Play Create autocomplete helper which allows for arbitrary values

Create autocomplete helper which allows for arbitrary values

2 mins

Play Use deep partials to help with mocking an entity

Use deep partials to help with mocking an entity

1 min

Play Throw detailed error messages for type checks

Throw detailed error messages for type checks

1 min

Play Create a 'key remover' function which can process any generic object

Create a 'key remover' function which can process any generic object

1 min

Play Use generics in React to make dynamic and flexible components

Use generics in React to make dynamic and flexible components

1 min

Play Create your own 'objectKeys' function using generics and the 'keyof' operator

Create your own 'objectKeys' function using generics and the 'keyof' operator

1 min

Play Write your own 'PropsFrom' helper to extract props from any React component

Write your own 'PropsFrom' helper to extract props from any React component

1 min

Play Use 'extends' keyword to narrow the value of a generic

Use 'extends' keyword to narrow the value of a generic

1 min

Play Use function overloads and generics to type a compose function

Use function overloads and generics to type a compose function

2 mins

Play Decode URL search params at the type level with ts-toolbelt

Decode URL search params at the type level with ts-toolbelt

2 mins

Play Use 'in' operator to transform a union to another union

Use 'in' operator to transform a union to another union

2 mins

Play Derive a union type from an object

Derive a union type from an object

2 mins