Typescript 5.0 Released: New Features and Improvements!

Typescript 5.0 Released: New Features and Improvements!

Typescript, which developers mocked earlier, is now the most efficient programming language for building production-level applications. It was released in 2012, and it has been constantly evolving and releasing new features to improve the development experience since then. Microsoft released Typescript 5.0 a few months ago, with many new and exciting features as well as a few breaking changes.

Let’s take a closer look at what new Typescript 5.0 has to offer and how you can start using it in your existing or new projects 🚀

Decorators

Decorators are not unique to Typescript; they are a general programming concept used in many languages, such as Python. These are not yet natively supported by ECMAScript (JavaScript), but there is a proposal, currently in stage 3, to add Decorators to ECMAScript.

However, Decorators have been a part of Typescript for a long time, but now these are revamped and include the ECMAScript Stage 3 Decorator Proposal.

You can read more about the proposal here.

But what exactly are Decorators?

Decorators, as the name implies, are used to decorating something. This something can be a function, a class, or the methods of a class.

Let us consider a simple example: suppose you have the following class:

class Dog { name: string; constructor(name: string) { this.name = name; } bark() { return 'Woof!'; } }
Code language: TypeScript (typescript)

Now, if you create an object of this class and call the bark method on it, you’ll get the output shown below:

const dog = new Dog('Jack'); console.log(dog.bark()); // Woof!
Code language: TypeScript (typescript)

Assume you want to log something before and after calling the function; you’d write something like this:

console.log('Function started execution'); console.log(dog.bark()); console.log('Function finished execution');
Code language: TypeScript (typescript)

But what if you want to do the same thing for all methods or methods from a different class?

console.log('Function started execution'); console.log(dog.bark()); console.log('Function finished execution'); console.log('Function started execution'); console.log(dog.run()); console.log('Function finished execution'); console.log('Function started execution'); console.log(dog.sit()); console.log('Function finished execution');
Code language: TypeScript (typescript)

It’s redundant and ineffective. There is a better way to accomplish this, and it is called Decorators.

Decorators are functions that modify (or decorate) another function, class, or method and return the modified version.

Let’s talk code now 👨‍💻

We’ll write a log function that accepts an argument from another function. It returns another function, which is a modified version of the one received.

function log(originalFunction: any, context: any) { return function () { console.log('Function started execution'); const result = originalFunction.apply(this, arguments); console.log('Function finished execution'); return result; }; }
Code language: TypeScript (typescript)

We’re not doing anything fancy to modify the original function. Simply put, we’re logging something before and after the original function is called.

Before defining the class methods, we’ll use the @function syntax to modify the bark function.

@log bark() { return 'Woof!'; }
Code language: TypeScript (typescript)

Calling the bark method now will produce the following results:

const dog = new Dog('Jack'); console.log(dog.bark()); // Output: // Function started execution // Woof! // Function finished execution
Code language: TypeScript (typescript)

The log the function that we just created, is called a Decorator.

See, how simple that was!

These decorators can be as complex as you want, and you can use multiple decorators for a single method:

@log @decorator1 @decorator2 bark() { return 'Woof!'; }
Code language: TypeScript (typescript)

So, give decorators a shot; you’ll fall in love with how it simplifies complex problems.

The const Type Parameters

Typescript will infer the object’s type to be its general type by default. As an example:

type Cities = { names: string[] }; function getCities<T extends Cities>(arg: T): T['names'] { return arg.names; } // Inferred type: string[] const names = getCities({ names: ['Banglore', 'Jhansi', 'Jaipur'] });
Code language: TypeScript (typescript)

But suppose we have a more specific use case, such as a read-only string array:

type Cities = { readonly names: string[] };
Code language: TypeScript (typescript)

Assume we’re going to use this read-only array in the getCities function:

type Cities = { readonly names: string[] }; function getCities<T extends Cities>(arg: T): T['names'] { return arg.names; } // Inferred type: string[] const names = getCities({ names: ['Banglore', 'Jhansi', 'Jaipur'] });
Code language: TypeScript (typescript)

The return value of the getCities function shows that we got the incorrect inferred type string[], rather than the readonly string[].

To solve this problem, Typescript 5.0 gives us the option to use the const type parameter on the function declaration as follows:

type Cities = { readonly names: string[] }; function getCities<const T extends Cities>(arg: T): T['names'] { return arg.names; } // Inferred type: readonly string[] const names = getCities({ names: ['Banglore', 'Jhansi', 'Jaipur'] });
Code language: TypeScript (typescript)

However, keep in mind that the const type modifier is only applicable when the objects are written directly within the function call:

func([...]);
Code language: TypeScript (typescript)

There will be no change in behavior if arguments cannot be modified with as const modifier.

const cities = { names: ['Banglore', 'Jhansi', 'Jaipur'] }; // Inferred type: string[] const names = getCities(cities);
Code language: TypeScript (typescript)

Extending from multiple configuration files

Typescript 5.0 now allows you to extend configuration from multiple other configuration files, which was previously limited to just one. You can now easily manage and share configuration files across projects.

To extend from a different file, use the extends option in tsconfig.json to pass an array of file paths.

{ "extends": ["file1", "file2", "file3"], "compilerOptions": { // ... other options here ... } }
Code language: JSON / JSON with Comments (json)

It will combine the configuration settings from all files from which you extend. You can also override them by modifying the current configuration to include the new values.

All enums Are Union enums

Typescript 5.0 now treats all enums as union enums. That is, each member can have a distinct type, and members can be referred to by their respective types. It also implies that an enum member can now be initialized via a function call.

enum Demo { A = 1, B = 'Codedamn', C = 12 + 5, D = Math.random(), }
Code language: TypeScript (typescript)

bundler Module Resolution Strategy

Earlier versions of Typescript used node16 and nodenext options for module resolution strategies. These options were excellent, but there was one flaw: they imposed restrictions that some tools did not require.

Consider the ESM imports in Node.js. When attempting to import a file, you must specify the extension.

import { something } from './something.ts';
Code language: TypeScript (typescript)

However, when using module bundlers such as Vite, Webpack, and others, you do not need to add an extension. As a result, having to use options like node16 or nodenext to work with different module bundlers was very painful for developers.

Keeping this in mind, Typescript 5.0 now implements the new hybrid bundler module resolution strategy, which works well with various bundlers to provide hassle-free builds.

To configure it, use the moduleResolution option in the compilerOptions section of tsconfig.json file:

{ "compilerOptions": { // ... other options "moduleResolution": "bundler" } }
Code language: JSON / JSON with Comments (json)

Module Resolution Customization Flags

As previously stated, the new bundler module resolution strategy is best suited for hybrid module resolution in projects that use multiple tools. However, these tools may support different module resolutions, which might be problematic.

Keeping this in mind, Typescript 5.0 allows you to enable or disable features and have more control over your configuration.

Here is a list of the new module resolution flags added in Typescript 5.0.

  1. --allowImportingTsExtensions: You can use this flag to import Typescript extension files such as .ts, .tsx, and so on. Note that this flag is only available when the --noEmit or --emitDeclarationOnly flag is set to true.
  2. -resolvePackageJsonExports: This flag is useful when you want Typescript to do exports based on the to exports field in the package.json file.
  3. --resolvePackageJsonImports: This flag, like the --resolvePackageJsonExports flag, is used when we want Typescript to do lookups, starting with # having its parent directory containing a package.json file, according to the package.json file’s imports field.
  4. --customConditions: This option is useful when you want a custom condition to be followed when resolving files based on the exports and imports fields of the package.json file.

Persist the Module Syntax During Builds

Typescript will drop your type imports in the emitted JavaScript files. This is done by default and is called import elision. For example:

// This type import might be removed entirely in the emitted JavaScript import { Dog } from './dog';
Code language: TypeScript (typescript)

To prevent this, Typescript 5.0 adds the --verbatimModuleSyntax and deprecates older solutions, such as --importsNotUsedAsValues and --preserveValueImports.

When using the --verbatimModuleSyntax flag, the following rules apply: any imports or exports without a type modifier won’t be affected. However, anything that makes use of the type modifier is removed completely.

// Removed completely import type { Dog } from 'dog'; // Rewritten to 'import { cat, cow } from "animal";' // The type lion is removed import { cat, type lion, cow } from 'animal';
Code language: TypeScript (typescript)

Support for export type *

Exporting types from another module was a pain in an earlier version of Typescript. You were not permitted to export types using the export type * syntax.

// File: animals.ts export type Dog; export type Cat; // File: index.js // Won't work in older versions of TypeScript export type * from './animals';
Code language: TypeScript (typescript)

However, with Typescript 5.0, you can use the export type * syntax to export your types from a different module.

// File: animals.ts export type Dog; export type Cat; // File: index.js // Works with TypeScript 5.0 export type * from './animals'; // Even this works export type * as Animals from './animals';
Code language: TypeScript (typescript)

@satisfies Support in JSDoc

Typescript 5.0 now supports the @satisfies tag in JSDoc. You can use this feature to specify whether an expression is compatible with a type or interface. This is the same as the satisfies operator introduced in Typescript 4.9.

interface Speak { speak(dogName: string): string; } /** * @satisfies {Speak} */ function Bark(dogName: string) { return `Woof! My name is ${dogName}`; }
Code language: TypeScript (typescript)

@overload Support in JSDoc

Function overloading occurs when a single function has several variants based on the type and number of arguments it accepts. For example:

function adder(arg1, arg2) { return arg1 + arg2; }
Code language: TypeScript (typescript)

If two numbers are passed to the adder function, it will return their sum; however, if two strings are passed, it will return the concatenation of the strings.

With Typescript 5.0, we can now specify different function overloads in JSDoc using the @overload tag. It informs the compiler about the various versions of the function. Let us return to our previous example:

/** * @overload * @param {string} arg1 * @param {string} arg2 * @return {string} */ /** * @overload * @param {number} arg1 * @param {number} arg2 * @return {number} */ /** * @param {string | number} arg1 * @param {string | number} arg2 */ function adder(arg1, arg2) { return arg1 + arg2; }
Code language: TypeScript (typescript)

The compiler is aware that there are now two overloads of the function, one of which accepts numbers and other strings as arguments.

Passing Emit-Specific Flags Under --build

When building your application, you can now include emit flags under the --build mode. It gives you more granular control over the build configuration when working on different project environments that require different build configurations.

Here are the flags available to use in Typescript 5.0:

  1. --declaration: Generates declaration and JavaScript files for the project.
  2. --emitDeclarationOnly: Only the declaration files are generated; no JavaScript files are generated.
  3. --declarationMap: Generates a source map for the declarations.
  4. --sourceMap: Generates a source map for the JavaScript files.
  5. --inlineSourceMap: Put the generated source map inside the JavaScript files.

Case-Insensitive Import Sorting in Editors

Sorting imports in code editors like VSCode, etc., was very basic in earlier versions of Typescript and was case-sensitive. As a result, you may have noticed some incorrect sorting, as shown below.:

import { Dog, cat, lion } from './animals';
Code language: TypeScript (typescript)

Because uppercase letters are placed before lowercase letters in the ASCII representation, they appear first in the sorted import list.

This is no longer the case because Typescript 5.0 now sorts imports without considering the case (case-insensitive), so you won’t see any weird sorting in the editor.

Exhaustive switch/case Completions

Dealing with switch statements are now easier with improved code completion in Typescript 5.0. Typescript 5.0 can now detect the referencing type within the switch statements and display suggestions for all possible cases.

type Animal = | { type: 'dog' } | { type: 'cow' } | { type: 'cat' } | { type: 'lion' }; function speak(animal: Animal): string { switch (animal.type) { /** * Typescript will suggest to add cases for 'dog, 'cow', 'cat', and 'lion' * * case 'dog': * case 'cow': * case 'cat': * case 'lion': */ } }
Code language: TypeScript (typescript)

It comes in handy when dealing with a long list of cases because it ensures that none of the cases are missed.

Speed, Memory, and Package Size Optimizations

Typescript is now significantly faster, more memory efficient, and much smaller in size. It improves the overall development experience with Typescript.

Here are some charts comparing the new and old Typescript versions.

Build time comparison of Typescript 5.0 with respect to Typescript 4.9 (Source: Microsoft)
Build time comparison of Typescript 5.0 with respect to Typescript 4.9 (Source: Microsoft)
Typescript 5.0 bundle size with respect to Typescript 4.9 (Source: Microsoft)
Typescript 5.0 bundle size with respect to Typescript 4.9 (Source: Microsoft)

There is a significant difference in the size of Typescript’s npm package. The performance enhancements are also significant, resulting in a more smooth and more efficient experience.

Summary

Typescript is the most effective programming language for developing production-level applications. Since its initial release in 2012, it has been constantly evolving and releasing new features to improve the development experience.

Typescript 5.0 was released a few months ago, and it included a lot of new and exciting features, as well as a few breaking changes. Aside from that, several performance enhancements have resulted in a significant increase in performance, memory efficiency, and smaller package size.

It is, in my opinion, a worthwhile upgrade. You can read the entire announcement blog for more information on all of the listed Typescript additions here.

This article hopefully provided you with some new information. Share it with your friends if you enjoy it. Also, please provide your feedback in the comments section.

Thank you so much for reading 😄

Sharing is caring

Did you like what Varun Tiwari wrote? Thank them for their work by sharing it on social media.

0/10000

No comments so far