12 Mar 2024
5 min

Changes in Typescript 5.3

TypeScript 5.3 is not a revolution but an evolution. The new TypeScript version provides a few important changes in key areas for developers, such as type narrowing and performance fixes. Overall, the new TypeScript version has a significant, positive impact on the developer experience.

switch(true) narrowing

Switch statements allow us to use expressions in addition to values. This pattern can be used in many situations to improve code quality and readability. The most common use case for this is replacing complex if/else statements. TypeScript 5.3 provides better narrowing in this specific situation. Now your logic can depend on type of variable and you will not get an error during compilation. Look at the following example:

function f(x: unknown) {
 switch (true) {
   case typeof value === 'string':
     console.log("value is a 'string' here");
   case Array.isArray(x):
     console.log("value is a 'string | any[]' here");
   default:
     console.log("value is a 'unknown' here");
 }
}

True/False direct comparison narrowing

Sometimes, for code reability reasons, we want to use a direct comparison to true/false values in If statements. Typescript 5.3 provides better narrowing in those statements. In example code below before changes you will get an error that property propOnlyInFirstInterface not exist, now in Typescript 5.3 Typescript know that x is instance of interface FirstInterface and propOnlyInFirstInterface is defined, so you will not get an error during compilation.

interface FirstInterface {
 propOnlyInFirstInterface: string;
}
interface SecondInterface {
 propOnlyInSecondInterface: string;
}


type FirstOrSecondInterfaceType = FirstInterface | SecondInterface;


function isFirstInterfaceInstance(
 x: FirstOrSecondInterfaceType
): x is FirstInterface {
 return 'propOnlyInFirstInterface' in x;
}


function exampleFunction(x: FirstOrSecondInterfaceType) {
 if (isFirstInterfaceInstance(x) === true) {
   console.log(x.propOnlyInFirstInterface); // works, no errors during compilation
 }
}

Narrowed Symbol.hasInstance checks

Typescript 5.3 introduces improvements for narrowing instance of checks when they are used with user-defined classes. The compiler now narrows the type based on the value returned by the Symbol.hasInstance method.

Super property access checks

In Typescript 5.3, the compiler performs stricted accessibility checks on base class properties using the super keyword. First, before we will try to understand that improvement we must remember that the calling function with super keyword only works with members declared in the prototype. So if we declare something as a field it will not exist in prototype and during execution it will fail. Simpler, we can call only something which is method of class when we are using super keyword. In TypeScript 5.3 this is caught during compilation. For example:

class BaseClass {
 field = () => {
   console.log('field executed!');
 };
}
class Example extends BaseClass {
 method() {
   super.field(); //throw type-checking error
 }
}
new Example().method();

Skipping JSDoc parsing

In order to improve performance, when TypeScript is run through tsc, the compiler will not waste resources parsing JSDoc. Because of this, you get faster compile times in –watch mode.

tsserverlibrary.js and typescript.js consolidation

In Typescript 5.3, the contents of tsserverlibrary.js are now included within typescript.js. The old tsserverlibrary.js file now simply re-exports typescript.js. Previously, both of these modules had a lot of duplicated code, and it was very easy to load both of them. As you can see in the table below, this change reduces the overall package size by over 20%.

Before After Diff
Packed 6.90 MiB 5.48 MiB -1.42 MiB( -20.61% )
Unpacked 38.74 MiB 30.41 MiB -8.33 MiB( -21.50% )

Replacing import assertions with import attributes

TypeScript 5.3 allows you to provide information for both the browser and the runtime on how to treat imports. If you're importing a JSON file, you can add type information to the statement. This can be also used in dynamic imports. From now on, it's recommended that you use the new import keyword rather than the old asset. For instance, a static import will look like this:

import mockedData from "./mocked-data.json" with { type: "json" };

A dynamic import, on the other hand, will look like this:

const mockedData = await import("./mocked-data.json", {
 with: { type: "json" }
});

Support for resolution-mode in import types

The resolution-mode feature allows you to specify if importing should be handled using the older require method, or the new import one. This was first introduced in Typescript 4.7, but now it has been included within the stable release. TypeScript 5.3 additionally supports this feature as an attribute for import types, for instance:

// Resolve `pkg` as if we were importing with a `require()`
import type { TypeFromRequire } from "pkg" with {
 "resolution-mode": "require"
};
// Resolve `pkg` as if we were importing with an `import`
import type { TypeFromImport } from "pkg" with {
 "resolution-mode": "import"
};

In-editor value type interactions

In Typescript 5.3 you can easily go to type definitions within your editor. For example, if you're mapping elements from an array, you can hover over the predicted element type and then go to the definition. This feature is a godsend for those of us who expect to do a lot of refactoring, as well as those of us who work with complex projects with multiple data types. If you want to try that feature best IDE for that will be Visual studio code in except of other IDE( for example WebStorm) it doesnt have a lot of features provided by itself. Thats why every feature provided by Typescript in supporting IDE area have a lot of impact of increasing Dev experience in light IDE like VSC.

Summary

The changes introduced in TypeScript 5.3 go a long way to improve type narrowing and performance. It's clear that the team behind the language prioritizes developer experience — and that's commendable!

What's next for TypeScript? While we don't have any concrete announcements yet, rumour has it that version 5.4 will probably introduce changes like a reduction in the number of intersections between variable types and primitive types, as well as more accurate conditional type constraints. However, this is just speculation — we will just have to wait and see!

Frequently asked questions

How do I import a JSON file with import attributes in TypeScript 5.3?

Add a with clause such as with { type: 'json' } after the import, which TypeScript 5.3 recommends over the older assert syntax. For a static import, write import mockedData from './mocked-data.json' with { type: 'json' };. For a dynamic import, pass { with: { type: 'json' } } as the second argument to import(). This gives both the browser and the runtime information on how to treat the module.

Why does super.field() cause a type error in TypeScript 5.3 when the base class member is an arrow function property?

TypeScript 5.3 reports a type-checking error when super accesses a property declared as a field, such as an arrow function assigned in the class body. The super keyword only resolves members declared on the prototype, so a field does not exist there at runtime and the call fails during execution. Declare the base member as a method instead, and super.method() works. The check moves this failure from runtime to compile time.

How does TypeScript 5.3 improve switch(true) narrowing?

TypeScript 5.3 improves type narrowing in switch (true) statements, so each case expression that acts as a type guard narrows the variable correctly without a compilation error. This lets you replace long if/else chains with a switch that branches on the type of a variable, such as case typeof x === 'string': or case Array.isArray(x):. Use this pattern when expression-based branching needs to stay readable and type-safe.

Does TypeScript 5.3 reduce compile times and package size?

TypeScript 5.3 speeds up tsc in --watch mode by skipping JSDoc parsing, and it reduces package size by merging the contents of tsserverlibrary.js into typescript.js. The old tsserverlibrary.js file now re-exports typescript.js, which removes duplicated code. Measurements show the packed package dropping from 6.90 MiB to 5.48 MiB, a reduction of about 20.61%.

What does the resolution-mode attribute do in TypeScript import types?

The resolution-mode attribute tells TypeScript whether to resolve a package as if it were loaded with require() or with import. It was introduced in TypeScript 4.7 and became stable, and TypeScript 5.3 adds support for it on import types. For example, import type { TypeFromRequire } from 'pkg' with { 'resolution-mode': 'require' }; resolves types using the require path, while 'import' uses the ESM path.

Share this post

Sign up for our newsletter

Stay up-to-date with the trends and be a part of a thriving community.