Exploring TypeScript 5.8 Beta: Smarter Returns, Improved Module Support, and More

Tue Feb 04 2025

TypeScript 5.8 Beta is here, bringing exciting improvements for developers working with advanced type inference, Node.js module interoperability, and performance optimizations. This update introduces checked returns for conditional and indexed access types, enhanced support for require() in ECMAScript Modules (ESM), and new compiler flags that improve developer experience.

Let’s dive into the key features of TypeScript 5.8 and how they can enhance your development workflow.


Checked Returns for Conditional and Indexed Access Types

One of the standout features in TypeScript 5.8 is better checking for return types when using conditional types. Previously, when defining a function that returned a conditional type, developers had to use type assertions to satisfy TypeScript’s type system. Now, TypeScript can infer the correct return type automatically, improving both type safety and developer ergonomics.

The Problem: Unclear Return Types

Consider a function showQuickPick that allows a user to select one or multiple options from a list:

async function showQuickPick(
    prompt: string,
    selectionKind: SelectionKind,
    items: readonly string[],
): Promise<string | string[]> {
    // Implementation...
}

Here, the return type is Promise<string | string[]>, meaning callers must manually check the type before using it. This can lead to errors, as seen in the following example:

let shoppingList = await showQuickPick(
    "Which fruits do you want to purchase?",
    SelectionKind.Multiple,
    ["apples", "oranges", "bananas", "durian"]
);

console.log(`Going out to buy ${shoppingList.join(", ")}`);
// ⚠️ Error: Property 'join' does not exist on type 'string | string[]'.

Since shoppingList could be either a string or string[], TypeScript doesn’t know which one it is at compile time.

The Solution: Conditional Return Types

With TypeScript 5.8, you can define precise return types using a conditional type:

type QuickPickReturn<S extends SelectionKind> =
    S extends SelectionKind.Multiple ? string[] : string;

async function showQuickPick<S extends SelectionKind>(
    prompt: string,
    selectionKind: S,
    items: readonly string[],
): Promise<QuickPickReturn<S>> {
    // Implementation...
}

Now, TypeScript correctly infers the expected return type:

let shoppingList: string[] = await showQuickPick(
    "Which fruits do you want to purchase?",
    SelectionKind.Multiple,
    ["apples", "oranges", "bananas", "durian"]
); // ✅ Correctly inferred as `string[]`

let dinner: string = await showQuickPick(
    "What's for dinner?",
    SelectionKind.Single,
    ["sushi", "pasta", "tacos"]
); // ✅ Correctly inferred as `string`

Improved Type Checking in Implementations

Previously, implementing a function with higher-order conditional return types required explicit type assertions, leading to potential runtime bugs. TypeScript 5.8 removes this requirement by enabling control flow analysis for generic conditional types.

if (selectionKind === SelectionKind.Single) {
    return selectedItems[0]; // ✅ No type assertion needed!
} else {
    return selectedItems;
}

This change enhances type safety and prevents developers from mistakenly swapping return values in different branches.


Support for require() of ECMAScript Modules in --module nodenext

A long-standing pain point in Node.js development has been the difficulty of interoperability between CommonJS and ECMAScript Modules (ESM). In Node.js:

  • ESM files can import CommonJS files
  • CommonJS files cannot require() ESM files

This has forced developers to either dual-publish libraries (providing both ESM and CommonJS versions) or stay on CommonJS indefinitely.

With Node.js 22, this restriction is relaxed—CommonJS files can now require() ESM files as long as the ESM file does not use top-level await. TypeScript 5.8 now supports this behavior when using --module nodenext.

What This Means for Developers

If you're developing a Node.js package, you no longer need to dual-publish for CommonJS and ESM compatibility. Instead, you can simply enable --module nodenext, and TypeScript will stop issuing errors on require("esm") calls.

const esmModule = require("./some-esm-module.js"); // ✅ Now valid in TypeScript 5.8

For now, library authors targeting older Node.js versions should stick to --module node16 or --module node18, while users on Node.js 22+ should use --module nodenext.


New Compiler Flags for Better Developer Experience

--erasableSyntaxOnly: Stricter TypeScript Code for Node.js Compatibility

With Node.js 23.6 adding experimental support for running TypeScript files directly, it enforces a key rule: TypeScript syntax must be fully erasable—meaning no enums, namespaces, or import aliases.

TypeScript 5.8 introduces the --erasableSyntaxOnly flag to enforce this restriction at compile time.

class MyClass {
    constructor(public x: number) { }
    // ❌ Error: Parameter properties are not allowed under `--erasableSyntaxOnly`
}

This flag helps developers write TypeScript that is directly executable in Node.js without a separate compilation step.


Performance Optimizations in TypeScript 5.8

TypeScript 5.8 introduces several optimizations that improve build times and editor responsiveness:

  1. Faster Path Normalization

    • TypeScript avoids unnecessary array allocations when resolving paths, speeding up large project builds.
  2. Smarter Incremental Compilation

    • In --watch mode, TypeScript avoids re-validating unchanged compiler options, making rebuilds faster after edits.

For large codebases, these changes should result in noticeable speed improvements when working in editors or running TypeScript builds.


Other Notable Changes in TypeScript 5.8

  • --module node18 Flag:
    • Provides a stable alternative to nodenext for users on Node.js 18.
  • --libReplacement Flag:
    • Allows disabling automatic lib package lookups, improving performance in projects not using custom DOM typings.
  • Preserved Computed Property Names in Declaration Files:
    • TypeScript now retains computed property names in declaration files, improving type predictability for libraries.

Conclusion

TypeScript 5.8 introduces several developer-friendly improvements, from smarter return type inference to better ESM/CommonJS interoperability and faster builds. These updates help streamline type safety, performance, and modern Node.js support.

To try out TypeScript 5.8 Beta today, run:

npm install -D typescript@beta

For more details, check out the official Microsoft blog post.

TypeScript 5.8TypeScript new featuresTypeScript ESM supportTypeScript checked returns

Copyright © 2023 - All right reserved