Improved Type Safety
TypeScript improves type safety in several areas. Variables that are never initialized are now detected more reliably. If a variable is declared but never assigned a value, the compiler reports an error. In certain situations, however, this cannot be determined unambiguously for TypeScript. Listing 1 shows such a situation: Within the function definition of “printResult()”, TypeScript cannot clearly determine which path is taken in the outer (separate) function. Therefore, TypeScript makes the “optimistic” assumption that the variable will be initialized.
Listing 1: Optimistic type check in different functional contexts
function foo() {
let result: number
if (myCondition()) {
result = myCalculation();
} else {
const temporaryWork = myOtherCalculation();
// Vergessen, 'result' zuzuweisen
}
printResult();
function printResult() {
console.log(result); // kein Compiler-Error
}
}
With version 5.7, this situation has been improved, at least in cases where no conditions are used. In Listing 2, the variable “result” is not assigned, but this is also recognized within the function “printResult()” and now results in a compiler error.
iJS Newsletter
Keep up with JavaScript’s latest news!
Listing 2: Optimistic type check in different functional contexts
function foo() {
let result: number
// Weitere Logik, in der keine Zuweisung an 'result' erfolgt
printResult();
function printResult() {
console.log(result);
// Variable 'result' is used before being assigned.(2454)
}
}
Another type check ensures that methods with non-literal (or composite, ‘computed’) property names are consistently treated as index signatures in classes. This is shown in Listing 3 using a method that was created using an index signature.
Listing 3: Index signatures for classes
declare const sym: symbol;
export class MyClass {
[sym]() { return 1; }
}
// Wird interpretiert als
export class MyClass { [x: symbol]: () => number; }
Previously, this method was ignored by the type system. With 5.7, it now appears as an index signature ([x: symbol] signature). This harmonizes the behavior with object literals and can be particularly useful for generic APIs.
Last but not least, version 5.7 introduces a stricter error message under the “noImplicitAny” compiler option. When this option is enabled, function definitions that do not declare an explicit return type are now checked more thoroughly. Functions without a return type are often arrow functions that are used as callback handlers, for example, in promise chains: “catch(() => null)”. If such handlers implicitly return “null” or “undefined,” the error “TS7011: Function expression, which lacks return-type annotation, implicitly has an ‘any’ return type” is now displayed. The typing is therefore stricter here, so that runtime errors can be better avoided in the future.
Latest ECMAScript and Node.js Support
With TypeScript 5.7, ECMAScript version 2024 can now be used as the compile target (e.g., via compiler flag: –target es2024). This is particularly useful for staying up to date and gaining access to the latest language features and new APIs. New APIs include “Object.groupBy()” and “Map.groupBy()”, which can be used to group an iterable or a map. Listing 4 shows this using an array called “inventory” containing various supermarket products. The array is to be divided into two groups: products that are still available (“sufficient”) and products that need to be restocked (‘restock’). The function “Object.groupBy()” is now passed the array to be grouped and a function that returns which group each item in the array belongs to. The return value of the GroupBy function is an object (here the variable “result”) that contains the different groups as parameters. Each group is again an array (see the console.log outputs in Listing 4). If a group does not contain any entries, the entire group is “undefined.”
Listing 4: Arrays gruppieren mittels Object.groupBy()
const inventory = [
{ name: "asparagus", type: "vegetables", quantity: 9 },
{ name: "bananas", type: "fruit", quantity: 5 },
{ name: "cherries", type: "fruit", quantity: 12 }
];
const result = Object.groupBy(inventory, ({ quantity }) =>
quantity < 10 ? "restock" : "sufficient",
);
console.log(result.restock);
// [{ name: "asparagus", type: "vegetables", quantity: 9 },
// { name: "bananas", type: "fruit", quantity: 5 }]
console.log(result.sufficient);
// [{ name: "cherries", type: "fruit", quantity: 12 }]
If more complex calculations are performed, or if WASM, multiple workers, and correspondingly complex setups are used, TypedArray classes (e.g., “Uint8Array”), “ArrayBuffer,” and/or “SharedArrayBuffer” are also frequently used. The length of ArrayBuffers can be changed in ES2024 (‘resize()’), while SharedArrayBuffers can ‘only’ grow (‘grow()’). Therefore, both buffer variants obviously have different APIs. However, the TypedArray classes always use a buffer under the hood. To harmonize the newly created API differences, the common supertype ‘ArrayBufferLike’ is used. If a specific implementation is to be used, the buffer type used can now be specified explicitly, as all TypedArray classes are now generically typed with respect to the underlying buffer types. Listing 5 illustrates this, showing that in the case of “Uint8Array,” “view” can always access the correct buffer variant “SharedArrayBuffer.”
Listing 5: TypedArrays mit generischem Buffer-Typ
// Neu: TypedArray mit generischem ArrayBuffer-Typ
interface Uint8Array<T extends ArrayBufferLike = ArrayBufferLike> { /* ... */ }
// Verwendung mit einem konkreten Typen:
// Hier SharedArrayBuffer
const buffer = new SharedArrayBuffer(16, { maxByteLength: 1024 });
const view = new Uint8Array(buffer);
view.buffer.grow(512); // `grow` exisitiert nur auf SharedArrayBuffer
Directly Executable TypeScript
In addition to the new features, TypeScript also supports libraries that enable TypeScript files to be executed directly without a compile step (e.g., “ts-node,” “tsx,” or Node 23.x with “–experimental-strip-types”). Direct execution of TypeScript can speed up development processes, for example, by skipping the build/compile task between development and execution and “catching up” later. This becomes possible when relative imports are adjusted: Normally, imports do not have a file extension (see Listing 6), so that the imports do not have to differ between the source code and the compiled result. However, executing the file directly without translation requires the “.ts” extension (Listing 6). Such an import usually results in a compiler error. With the new compiler option “–rewriteRelativeImportExtensions,” all TypeScript extensions are automatically rewritten (from .ts, .tsx, .mts, .cts to .js, .jsx, .mjs, .cjs). On the one hand, this provides better support for direct execution. On the other hand, it is also possible to use and compile the TypeScript files in the normal TypeScript build process, which is important, for example, for authors of libraries who want to test their files quickly without a compile step, but also need the real TypeScript build before publishing the library.
Listing 6: Import with .ts extension
import {Demo} from './bar'; //<-Standard-Import
import {Demo} from './bar.ts'; //<-Zum direkten Ausführen nötig
If the Node.js option “–experimental-strip-types” is used to execute TypeScript directly, care must be taken to ensure that only TypeScript constructs that are easy to remove (strip) for Node.js are used. To better support this use case, the new compiler option “–erasableSyntaxOnly” has been added in 5.8. This option prohibits TypeScript-only features such as enums, namespaces, parameter properties (see also Listing 7), and special import forms and marks them as compiler errors.
Listing 7: Constructs prohibited under “–erasableSyntaxOnly”
// error: Namespace mit Runtime-Code
namespace container {
}
class Point {
// error: Implizite Properties/Parameter-Properties
constructor(public x: number, public y: number) { }
}
// error: Enum-Deklaration
enum Direction {
Up,
Down
}
Further Improvements
The TypeScript team naturally wants to make the development process as pleasant as possible for all developers. To this end, it naturally also uses all the new options available under the hood. In Node.js 22, for example, a caching API (“module.enableCompileCache()”) was introduced, which TypeScript now uses to save recurring parsing and compilation costs. In benchmarks, compiling tsc was about two to three times faster than before.
By default, the compiler checks whether special “@typescript/lib-**” packages are installed. These packages can be used to replace the standard TypeScript libraries in order to customize the behavior of what are actually native TypeScript APIs. The check for such library packages was always performed previously, even if no library packages were used. This can mean unnecessary overhead for many files or in large projects. With the new compiler option “–libReplacement=false*,” this behavior can be disabled, which can improve initialization time, especially in very large projects and monorepos.
Support for developer tools is also an important task for TypeScript. Therefore, there have also been updates to project and editor support. When an editor that uses the TS language server loads a file, it searches for the corresponding “tsconfig.json.” Previously, it stopped at the first match, which often led to the editor assigning the wrong configuration to a file in monorepo-like structures and thus not offering correct developer support. With the new TypeScript versions, the project is now searched further up if necessary to find a suitable configuration. For example, in Listing 8, the test file “foo-test.ts” is now correctly used with the configuration “projekt/src/tsconfig.test.json” and not accidentally with the main configuration “projekt/tsconfig.json”. This makes it easier to work in “workspaces” or composite setups with multiple subprojects.
iJS Newsletter
Keep up with JavaScript’s latest news!
Listing 8: Repo structure with multiple TSConfigs
projekt/
├── src/
│ ├── tsconfig.json
│ ├── tsconfig.test.json
│ ├── foo.ts
│ └── foo-test.ts
└── tsconfig.json
Conclusion
TypeScript 5.7 and 5.8 offer a variety of direct and indirect improvements for developers. In particular, they increase type safety (better errors for uninitialized variables, stricter return checks) and bring the language up to date with ECMAScript. At the same time, they improve the developer experience through faster build processes (compile caching, optimized checks), extended Node.js support, and more flexible configuration for monorepos.
The TypeScript team is already working on many large and small improvements for the future. TypeScript 5.9 is in the starting blocks and is scheduled for release at the end of July. In addition, a major change is planned: the TypeScript runtime is to be completely rewritten in Go for version 7. Initial tests have shown that with the help of the new compiler written in Go, it is possible to achieve up to 10 times faster builds for your own projects.
🔍 Frequently Asked Questions (FAQ)
1. What are the key improvements in TypeScript 5.7?
TypeScript 5.7 brings a host of enhancements, including better type safety, improved management of uninitialized variables, stricter enforcement of return types, and a more consistent approach to recognizing computed property names as index signatures.
2. How does TypeScript 5.8 support direct execution?
With TypeScript 5.8, you can now run .ts files directly using tools like ts-node or Node.js with the –experimental-strip-types flag. New compiler options like –rewriteRelativeImportExtensions and –erasableSyntaxOnly make this process even smoother.
3. What new JavaScript (ECMAScript 2024) features are supported?
TypeScript has added support for ECMAScript 2024 features, including Object.groupBy() and Map.groupBy(), which allow for powerful grouping operations on arrays and maps. It also introduces support for resizable and growable ArrayBuffer and SharedArrayBuffer types.
4. What does the –erasableSyntaxOnly compiler option do?
The –erasableSyntaxOnly option, introduced in TypeScript 5.8, prevents the use of TypeScript-specific constructs like enums, namespaces, and parameter properties in code meant for direct execution, ensuring it works seamlessly with Node.js’s stripping behavior.
5. How has type checking changed for computed method names?
In TypeScript 5.7, methods that use computed (non-literal) property names in classes are now treated as index signatures. This change aligns class behavior more closely with object literals, enhancing consistency for generic and dynamic APIs.
6. What are the benefits of compile caching in newer versions?
TypeScript now takes advantage of Node.js’s compile cache API, which cuts down on unnecessary parsing and compilation. This results in build times that can be 2 to 3 times faster, particularly in larger projects.
7. How does TypeScript handle multiple tsconfig files in monorepos?
In TypeScript 5.8, the compiler and language server have improved support for monorepos by continuing to search parent directories for the most suitable tsconfig.json. This enhancement boosts file association and IntelliSense accuracy in complex workspaces.