TypeScript, a statically typed extension of JavaScript, is continually evolving, bringing new features and enhancements with each release. This guide will walk you through the significant additions and improvements in TypeScript 5.0 and 5.1, providing you with practical examples and unique insights.
Getting Started with TypeScript
To get started with TypeScript, you first need to install it. Here’s how you can do it for Visual Studio Code and Visual Studio 2022:
For Visual Studio Code:
First, you need to have Node.js installed on your machine. You can download it from the official Node.js website.
Once Node.js is installed, open your terminal (Command Prompt, PowerShell, or Terminal in VS Code) and run the following command to install TypeScript globally:
npm install -g typescript
This command installs the latest version of TypeScript, which is currently 5.1 as of this writing. If you specifically want to install TypeScript 5.0, you can specify the version like this:
npm install -g typescript@5.0
You can then run the TypeScript compiler using the following command:
npx tsc
For Visual Studio 2022:
In Visual Studio 2022, TypeScript support is provided by default for JavaScript and TypeScript files, enhancing IntelliSense without any specific project configuration. For TypeScript compilation, you have the flexibility to choose the TypeScript version on a per-project basis.
For MSBuild projects like ASP.NET Core, the TypeScript NuGet package is recommended for adding TypeScript compilation support. The first time you add a TypeScript file to your project, Visual Studio will suggest adding this package. You can also add it anytime through the NuGet package manager. When the NuGet package is used, the corresponding language service version will be used for language support in your project. The minimum supported version of this package is 3.6.
For npm-configured projects like Node.js, you can specify your TypeScript language service version by adding the TypeScript npm package. You can specify the version using the npm manager in supported projects. The minimum supported version of this package is 2.1.
The TypeScript SDK has been deprecated in Visual Studio 2022. Existing projects that rely on the SDK should be upgraded to use the NuGet package. For projects that cannot be upgraded immediately, the SDK is still available on the Visual Studio Marketplace and as an optional component in the Visual Studio installer.
For projects developed in Visual Studio 2022, it’s encouraged to use the TypeScript NuGet or the TypeScript npm package for greater portability across different platforms and environments. For more information, see Compile TypeScript code using NuGet and Compile TypeScript code using tsc.
Starting in Visual Studio 2022, a new JavaScript/TypeScript project type (.esproj) allows you to create standalone Angular, React, and Vue projects in Visual Studio. These front-end projects are created using the framework CLI tools you have installed on your local machine, so the version of the template is up to you. These projects allow you to run JavaScript and TypeScript unit tests, easily add and connect ASP.NET Core API projects, and download your npm modules using the npm manager.
A simplified, updated template is available starting in Visual Studio 2022 version 17.5. Compared to the ASP.NET SPA templates available in Visual Studio, the .esproj SPA templates for ASP.NET Core provide better npm dependency management, and better build and publish support.
Note: Remember, it’s recommended to have TypeScript set up on a per-project basis to keep each project working consistently with its own version of TypeScript.
Diving Deep into the New Features of TypeScript 5.0
Let’s explore the new features and enhancements introduced in TypeScript 5.0:
Decorators and Constant Type Parameters
Decorators offer a way to annotate and manipulate class declarations and members. They can modify or replace class declarations and members, which is beneficial when you want to alter multiple class members simultaneously, such as making them all readonly or initializing them with a specific value.
In the example below, the @sealed
decorator is a custom decorator that prevents new properties from being added to the Greeter
class and existing properties from being removed. Note that @sealed
is not a built-in decorator in TypeScript, but a custom one defined for this example.
function sealed(target) {
Object.seal(target);
Object.seal(target.prototype);
}
@sealed
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
TypeScript 5.0 introduces constant type parameters. These allow you to specify that a type parameter is constant, meaning it doesn’t change once assigned. This is beneficial when you want to ensure a function always returns a value of the same type as its argument.
Here’s an example of a function that uses a const assertion to ensure that an array is treated as a tuple:
function tuple<T extends readonly any[]>(...args: T): T {
return args;
}
let arr = tuple(1, 2, 3); // Type is [number, number, number]
Configuration Flexibility and Resolution Improvements
TypeScript 5.0 introduces support for extending multiple tsconfig.json files. This is beneficial when you have different configuration settings for various environments, such as development, testing, and production.
{
"extends": ["./tsconfig.base.json", "./tsconfig.prod.json"],
"compilerOptions": {
"sourceMap": true
}
}
In the example above, the tsconfig.json file extends two other configuration files: tsconfig.base.json and tsconfig.prod.json. The sourceMap option is set to true, which means that source maps will be generated to facilitate debugging.
The –moduleResolution flag now supports a bundler option. This option instructs TypeScript to mimic the Node.js runtime resolution strategy. This is beneficial when using a bundler like webpack or rollup, which have their own module resolution strategies that may differ from Node.js.
{
"compilerOptions": {
"moduleResolution": "bundler"
}
}
In the example above, the moduleResolution option is set to “bundler”, instructing TypeScript to use the module resolution strategy of the bundler.
Enhanced JSDoc Support and Import Sorting
TypeScript 5.0 introduces support for @satisfies and @overload in JSDoc. The @satisfies tag can be used to specify that a function satisfies a certain contract, while @overload can be used to specify multiple signatures for a function. This is beneficial when you want to document your functions’ behavior or provide multiple ways to call a function.
/**
* @satisfies {isPerson}
*/
function checkPerson(person: { name?: string, age?: number }): boolean {
return person.name !== undefined && person.age !== undefined;
}
/**
* @overload {function(number): number}
* @overload {function(string): string}
*/
function processInput(input) {
return typeof input === 'number' ? input * 2 : input.toUpperCase();
}
The checkPerson
function takes an object person
as an argument. This object has two optional properties: name
and age
. The function checks if both properties are not undefined, which would mean that the object satisfies the isPerson
contract. The @satisfies {isPerson}
JSDoc tag is a way to document that this function checks if an object meets the isPerson
contract.
The processInput
function can take either a number or a string as an argument. If the argument is a number, it doubles the number. If the argument is a string, it converts the string to uppercase. The @overload
JSDoc tags are used to document the different ways this function can be called and what it returns based on the argument type.
TypeScript 5.0 also introduces case-insensitive import sorting in editors. This means that when you auto-import a module, the import statement will be inserted in a location that maintains case-insensitive sorting of import statements. This is beneficial when you want to keep your import statements organized.
Performance Optimizations and Noteworthy Changes
TypeScript 5.0 introduces several optimizations to improve performance, including speed, memory, and package size optimizations. It also introduces several breaking changes and deprecations, such as the removal of certain deprecated APIs. This is important to know when you are upgrading to TypeScript 5.0, as you may need to update your code to accommodate these changes.
The TypeScript team worked on reducing the memory usage and startup time of the TypeScript server, which is crucial for large projects. They also made improvements to the compiler and language service, which resulted in a reduction in the size of the TypeScript npm package.
TypeScript 5.0 has been rebuilt to use ECMAScript modules, which reduces package size and boosts performance. Here’s a simple example of how you might use ECMAScript modules in a TypeScript project:
// math.ts
export function add(a: number, b: number): number {
return a + b;
}
// main.ts
import { add } from './math';
console.log(add(1, 2)); // 3
In this example, math.ts
exports a function add
, and main.ts
imports this function. This is a basic example, but in a larger project with many modules, using ECMAScript modules can significantly reduce the package size and improve performance.
Delving into TypeScript 5.1
Let’s explore the new features and enhancements introduced in TypeScript 5.1:
Simplified Function Returns and Accessor Types
In JavaScript, if a function finishes running without hitting a return, it returns the value undefined
. TypeScript 5.1 now allows undefined-returning functions to have no return statement. In TypeScript 5.1, functions that return undefined
can now omit the return statement. This makes your code more concise and easier to read.
function logMessage(message: string): undefined {
console.log(message);
// No return statement needed
}
In the example above, the logMessage
function logs a message to the console and returns undefined
. The return statement is omitted, as it is not necessary.
Unrelated Types for Getters and Setters
TypeScript 5.1 also allows unrelated types for getters and setters, provided they have explicit type annotations. This is beneficial when you want to enforce different types for getting and setting a property.
class User {
private _name: string | null = null;
set name(newName: string) {
this._name = newName;
}
get name(): string {
return this._name ?? 'Unknown';
}
}
In this example, the User
class has a private _name
property that can be either a string or null
. The name
setter takes a string newName
and assigns it to _name
. The name
getter returns the value of _name
if it’s not null
, or 'Unknown'
if it is. This allows you to enforce that the name
property can only be set with a string, but when getting the name
property, it could be either a string or 'Unknown'
if it hasn’t been set yet.
This feature enables more flexible and expressive type definitions as shown in the example below.
interface CSSStyleRule {
// Always reads as a `CSSStyleDeclaration`
get style(): CSSStyleDeclaration;
// Can only write a `string` here.
set style(newValue: string);
}
In the example above, the style
property has a getter that returns a CSSStyleDeclaration
and a setter that accepts a string
. These types are unrelated, but TypeScript 5.1 allows this kind of type definition.
JSX Enhancements
TypeScript 5.1 introduces several enhancements for JSX. It now allows decoupled type-checking between JSX elements and JSX tag types, which can be useful for libraries that allow components to return more than just JSX elements.
import * as React from "react";
async function AsyncComponent() {
return <div>Loaded</div>;
}
let element = <AsyncComponent />; // This is now allowed
In the example above, the AsyncComponent function is an asynchronous function that returns a JSX element. TypeScript 5.1 allows this kind of function to be used as a JSX component, which wasn’t possible in previous versions.
TypeScript 5.1 also introduces support for the new JSX Transform introduced in React 17. This allows you to use JSX without importing React.
// Before
import React from "react";
function Component() {
return <h1>Hello, world!</h1>;
}
// After
function Component() {
return <h1>Hello, world!</h1>;
}
In the example above, the Component function returns a JSX element. In TypeScript 5.1 and React 17, you no longer need to import React to use JSX.
Namespaced JSX Attributes
TypeScript 5.1 also supports namespaced JSX attributes, which can be useful when dealing with libraries that use namespaced attributes.
import * as React from "react";
const element = <Component ns:attribute="value" />;
In the example above, the Component
JSX tag has a namespaced attribute ns:attribute
. This is allowed in TypeScript 5.1, which supports namespaced JSX attributes.
Module Resolution and Editor Features
When TypeScript’s module lookup strategy is unable to resolve a path, it will now resolve packages relative to the specified typeRoots
. This is useful when you have custom type declarations that are not in the default node_modules/@types
directory.
{
"compilerOptions": {
"typeRoots": ["./node_modules/@types", "./src/types"]
}
}
In the example above, TypeScript will look for type declarations in the ./node_modules/@types
and ./src/types
directories.
TypeScript 5.1 introduces linked editing for JSX tags, which allows you to edit the opening and closing tags simultaneously. This is a great productivity feature that can save you time when editing JSX tags.
It also provides snippet completions for @param
JSDoc tags, which can help you document your code more efficiently.
/**
* @param {number} num - The number to be squared.
*/
function square(num) {
return num * num;
}
In the example above, the square
function has a @param
JSDoc tag that documents the num
parameter.
Performance Boosts and Significant Changes in TypeScript 5.1
TypeScript 5.1 introduces several optimizations to improve performance, including speed, memory, and package size optimizations, avoiding unnecessary type instantiation, negative case checks for union literals, and reduced calls into the scanner for JSDoc parsing.
Here’s an example of avoiding unnecessary type instantiation within object types that are known not to contain references to outer type parameters, and faster checks for union literals.
type Union = 'a' | 'b' | 'c'; function check(value: Union) { // ... }
In the example above, TypeScript 5.1 can quickly check if a value is part of the Union
type without having to check against every type in the union.
Here’s another example:
type Point = { x: number, y: number };
function translate(point: Point, dx: number, dy: number): Point {
return { x: point.x + dx, y: point.y + dy };
}
let p: Point = { x: 1, y: 2 };
p = translate(p, 1, 1);
In this example, the Point
type is an object type that does not contain any type parameters. When the translate
function is called, TypeScript 5.1 can avoid unnecessary type instantiation, which can significantly speed up type-checking. This optimization is particularly beneficial in larger codebases with many similar object types.
TypeScript 5.1 continues to focus on performance improvements. It introduced a new feature that avoids performing type instantiation within object types that are known not to contain references to outer type parameters. This has the potential to cut down on many unnecessary computations, and reduced the type-checking time of material-ui’s docs directory by over 50%.
It also introduces several breaking changes, such as the requirement for a modern runtime environment (Node.js 14.17 or later) due to the inclusion of ECMAScript 2020 features. Other optimizations include negative case checks for union literals, reduced calls into the scanner for JSDoc parsing, and more.
Conclusion
I hope this tutorial guide has been helpful in understanding the new features and enhancements in TypeScript 5.0 and 5.1. As TypeScript continues to evolve, it’s important to stay up-to-date with the latest changes to make the most of this powerful language.
Remember, understanding and leveraging these new features can greatly enhance your coding efficiency and project performance, so make sure to invest your time in learning something new!
This is just a brief overview of what’s new in TypeScript 5.0 and TypeScript 5.1. For more detailed information, you can check out the official TypeScript 5.1 announcement.
Happy coding!
This article has been editorially reviewed by Suprotim Agarwal.
C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.
We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).
Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.
Click here to Explore the Table of Contents or Download Sample Chapters!
Was this article worth reading? Share it with fellow developers too. Thanks!
Suprotim Agarwal, MCSD, MCAD, MCDBA, MCSE, is the founder of
DotNetCurry,
DNC Magazine for Developers,
SQLServerCurry and
DevCurry. He has also authored a couple of books
51 Recipes using jQuery with ASP.NET Controls and
The Absolutely Awesome jQuery CookBook.
Suprotim has received the prestigious Microsoft MVP award for Sixteen consecutive years. In a professional capacity, he is the CEO of A2Z Knowledge Visuals Pvt Ltd, a digital group that offers Digital Marketing and Branding services to businesses, both in a start-up and enterprise environment.
Get in touch with him on Twitter @suprotimagarwal or at LinkedIn