Whatsapp

What’s New In Typescript 5.0: Microsoft Advancement For ECMAScript Modules And More

author
Parth Kotecha
~ 9 min read
What’s New in Typescript 5.0

Summary: Microsoft aims to make Typescript, smaller, simpler, and faster with its new version ‘Typescript 5.0.’ The particular upgrade is for rebuilding ECMAScript modules. It also has come with many other enhancements without hampering the previous version. Dive into the blog to know about the accurate offerings of Typescript 5.0- a strong typed Javascript programming language.

TypeScript is a programming language that is a superset of JavaScript, which means it includes all the features of JavaScript. It also consists of additional features like static typing, interfaces, and classes. The sole purpose of the programming language is to design and make it easy to write and maintain large-scale JavaScript applications. And Microsoft just announced Typescript 5.0.

This version comes with new support for ECMAScript modules, new decorator standards, const type parameters, and many other features and deprecations. Below is a brief idea about the features Typescript 5.0 has introduced:

Decorators:

Decorators are the newest edition to the Typescript family. It was introduced as an ECMAScript feature that allows the customization of classes with members in a reusable way.
For instance,

class Person {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    greet() {
        console.log("LOG: Entering method.");
        console.log(`Hello, my name is ${this.name}.`);
        console.log("LOG: Exiting method.")
    }
}
const p = new Person("Ron");
p.greet();

Here in greet, the method logs the entering and exiting logs. This fairly simple pattern is used by developers to check their code. The decorators play a pivotal role in providing such functionality. A developer can seamlessly write a function called loggedMethod. Look into the below-written code to understand the feature properly.

function loggedMethod(originalMethod: any, _context: any) {

    function replacementMethod(this: any, ...args: any[]) {
        console.log("LOG: Entering method.")
        const result = originalMethod.call(this, ...args);
        console.log("LOG: Exiting method.")
        return result;
    }

    return replacementMethod;
}

We have put any here for simplicity. So, we can focus on what this function is doing. Notice that loggedMethod takes the original method and returns:
– logs an “Entering…” message
– sends this along to the original method, along with all of its arguments.
– logs an “Exiting…” message
– Returns the result of the original method.

Now, a developer can easily use loggedMethod to decorate the method greet:

class Person {
    name: string;
    constructor(name: string) {
        this.name = name;
    }

    @loggedMethod
    greet() {
        console.log(`Hello, my name is ${this.name}.`);
    }
}

const p = new Person("Ron");
p.greet();
Output:
LOG: Entering method.
Hello, my name is Ron.
LOG: Exiting method.

You’ll see that we typed @loggedMethod after using loggedMethod as a decorator above hello. When we did that, it was invoked together with a context object and the method target. The previous definition of greet was replaced with the new function that loggedMethod returned.

Decorators are not only useful for methods! They may be applied to getters, setters, auto-accessors, and properties and fields. For purposes like subclassing and registration, classes themselves can be ornamented.

Const Type Parameters:

TypeScript 5.0 introduces a new feature allowing a const modifier to be added to a type parameter declaration. This means that const-like inference can be used by default, making it easier to infer more specific types. Let’s consider an example:

type HasNames = { names: readonly string[] };

function getNamesExactly<const T extends HasNames>(arg: T): T["names"] {
  return arg.names;
}

// Inferred type: readonly ["Alice", "Bob", "Eve"]
const names = getNamesExactly({ names: ["Alice", "Bob", "Eve"] });

In this instance, the const modifier is added to the type parameter declaration, allowing the const-like inference. As a result, the inferred type of names is readonly [“Alice”, “Bob”, “Eve”].

It’s important to note that using mutable types in functions can give surprising results. For example:

declare function fnBad<const T extends string[]>(args: T): void;

// 'T' is still 'string[]' since 'readonly ["a", "b", "c"]' is not assignable to 'string[]'
fnBad(["a", "b" ,"c"]);

Here, the inferred type of T is still string[], since a readonly array cannot be used where a mutable one is needed. In this case, the call proceeds successfully, but using readonly string[] would be a better definition for this function.

It’s worth noting that the const modifier impacts the inference of object, array, and also primitive expressions written within the call. For example:

declare function fnGood<const T extends readonly string[]>(args: T): void;
const arr = ["a", "b", "c"];

// 'T' is still 'string[]'-- the 'const' modifier has no effect here
fnGood(arr);

In this example, T is still inferred as string[], since the const modifier has no effect on the already defined array arr.

Support for export type *:

When Typescript 3.8 introduced type-only imports, the new syntax wasn’t allowed on export * from “module” or export * as ns from “module” re-exports. Typescript 5.0 adds supports for both these forms:

// models/vehicles.ts
export class Spaceship {
  // ...
}

// models/index.ts
export type * as vehicles from "./vehicles";

// main.ts
import { vehicles } from "./models";

function takeASpaceship(s: vehicles.Spaceship) {
  //  ok - `vehicles` only used in a type position
}

function makeASpaceship() {
  return new vehicles.Spaceship();
  //         ^^^^^^^^
  // 'vehicles' cannot be used as a value because it was exported using 'export type'.
}

@satisfies Support in JSDoc:

TypeScript 4.9 introduced the satisfies operator, which also ensures that the type of an expression is compatible without affecting the type itself. Indeed, the feature has been extended to support JSDoc annotations in TypeScript 5.0, with the new @satisfies tag.

For example, using @satisfies, we can catch type mismatches in JSDoc annotated JavaScript code, as shown below:

// @ts-check

/**
 * @typedef CompilerOptions
 * @prop {boolean} [strict]
 * @prop {string} [outDir]
 */

/**
 * @satisfies {CompilerOptions}
 */
let myCompilerOptions = {
    outdir: "../lib",
//  ~~~~~~ oops! we meant outDir
};

In the above code, @satisfies catches the type mismatch and preserves the original type of expression. We can also use @satisfies inline on any parenthesized expression, like in the following example:

let myConfigSettings = /** @satisfies {ConfigSettings} */ ({
    compilerOptions: {
        strict: true,
        outDir: "../lib",
    },
    extends: [
        "@tsconfig/strictest/tsconfig.json",
        "../../../tsconfig.base.json"
    ],
});

This can be useful when you’re deeper in some other code, like a function call. Overall, @satisfies helps to catch type mismatches and preserve the original type of expressions in JSDoc annotated code.

@overload support in JSDoc

TypeScript 5.0 introduces a new @overload JSDoc tag that allows developers to specify overloads for a function in JSDoc comments. Each comment block identifies a different overload for the following function declaration by the @overload tag. Here is an example:

// @ts-check
/**
@overload
@param {string} value
@return {void}
*/
/**

@overload
@param {number} value
@param {number} [maximumFractionDigits]
@return {void}
*/
/**

@param {string | number} value

@param {number} [maximumFractionDigits]
*/
function printValue(value, maximumFractionDigits) {
      if (typeof value === "number") {
           const formatter = Intl.NumberFormat("en-US", {
                   maximumFractionDigits,
            });
            value = formatter.format(value);
      }

      console.log(value);
}

By using the @overload tag, TypeScript can now check if we’ve called our function incorrectly, regardless of whether we’re working in TypeScript or JavaScript files. For instance, TypeScript will allow us to call the printValue function with “hello!”, 123.45 and 123.45, 2, but it will give us an error if we try to call it with (“hello!”, 123).

Exhaustive switch/case Completions:

When writing a switch statement, TypeScript is now able to detect if the value being checked has a literal type. If so, it will provide a conclusion that builds upon each example that is discovered.

Speed, Memory, and Package Size Optimizations:

TypeScript 5.0 brings many improvements to the speed and size of the language. It is also migrated from namespaces to modules, which allowed for modern build tooling optimizations and reduced package size by 26.4 MB. TypeScript also added uniformity to internal object types, slimmed data stored on some of these types, and thus, implemented caching to reduce expensive operations.

Proportions of build times in Typescript 5.0 relative to Typescript 4.9

Additionally, leveraging “var” instead of “let” and “const” in closures improved parsing performance. And so, these changes should result in faster build times and smaller package sizes, with consistent improvements between 10-20%.

All enums Are Union enums:

A new feature in TypeScript 5.0 converts all enums into union enums by designating a distinct type for each calculated member. Moreover, it allows for the advantages of unions and literal types to apply to all enums.

For example:

enum E {
  A = 1,
  B,
  C = Math.random(), // Computed member
  D = 'Hello'.length, // Computed member
  E = B // Computed member that references another member
}

// E is now a union of all the possible values of its members, including the computed members
type E = E.A | E.B | E.C | E.D | E.E;

// Each member has its own type that can be referred to
function doSomething(value: E.B) {
  // ...
}

// The computed members can also be referenced as types
type ComputedType = E.C | E.D | E.E;

// This allows for more precise type checking and prevents bugs
function doSomethingElse(value: ComputedType) {
  // ...
}

In this example, the enum E has three computed members: C, D, and E. TypeScript 5.0 creates a unique type for each of these members, allowing E to be a union of all possible values of its members, including the computed members. This also makes it possible to refer to each member as a type and to use the computed members in type annotations.

Thus, the feature allows for more precise type checking and can help prevent bugs in code that uses enums with computed members.

Develop Custom Web Applications With Our Javascript Developers

Hire our skilled developers who have technically advanced knowledge of web development. Discuss your idea with us!

Get a Free Quote

Depreciation and Default Changes:

Lastly, developers of Typescript deprecated the following options and configurations…

  • Target: ES3
  • Out
  • NoImplicitUseStrict
  • KeyofStringsOnly
  • SuppressExcessPropertyErrors
  • SuppressImplicitAnyIndexErrors
  • NoStrictGenericChecks
  • Charset
  • ImportsNotUsedAsValues
  • PreserveValueImports
  • Prepend project references

And so, up to TypeScript 5.5, certain configurations will be permitted; however, if you use them, you will get a warning before they are completely deleted.

Typescript 5

Subscribe to Our Newsletter!

Stay Updated to the Technology Trends for Every Industry Niche.