Daniel Rosenwasser, principal product manager at Microsoft, announced the release of TypeScript 5.8. The new release features, as usual, better type inference. It also improves performance and the interoperability with the JavaScript ecosystem.
TypeScript 5.8 has smarter type inference for conditional return types. Rosenwasser illustrates it in the release note with the following code sample:
declare const untypedCache: Map<any, any>;
function getUrlObject(urlString: string): URL {
return untypedCache.has(urlString) ?
untypedCache.get(urlString) :
urlString;
}
The previous code presents a subtle type error that is undetected in previous releases of TypeScript. If the cache is not hit, the function returns a string when it should return a URL.
The new release catches the type error:
declare const untypedCache: Map<any, any>;
function getUrlObject(urlString: string): URL {
return untypedCache.has(urlString) ?
untypedCache.get(urlString) :
urlString;
// ~~~~~~~~~
// error! Type 'string' is not assignable to type 'URL'.
}
The new release deals specifically with conditional expressions directly inside return
statements. Each branch of the conditional is checked against the declared return type of the containing functions (if one exists), so the type system can catch the bug in the example above.
Many developers welcomed the improved type inference though one Reddit user said:
The change regarding conditional types is big. But it annoys me to no end that the fact that up until now you had to typecast is not documented anywhere. I’ve run into this problem many times and always thought it was an issue on my end. Very frustrating.
Developers can now run TypeScript without a compilation step. Since Node.js 23.6 and with the new --erasableSyntaxOnly
flag, TypeScript can now run directly in Node.js without transpilation, as long as the syntax is erasable. Erasable syntax is TypeScript-specific syntax that does not have runtime semantics. This means that the type annotations can simply be removed and result in legal JavaScript without alteration in runtime semantics. In particular, the following code would provide several type errors:
// error: A namespace with runtime code.
namespace container {
foo.method();
export type Bar = string;
}
// error: An `import =` alias
import Bar = container.Bar;
class Point {
// error: Parameter properties
constructor(public x: number, public y: number) { }
}
// error: An enum declaration.
enum Direction {
Up,
Down,
Left,
Right,
}
Conversely, an example of erasable syntax is:
const foo: string = 'foo';
which when erased becomes:
const foo = 'foo';
TypeScript expert Matt Pocock speculates that “the TypeScript team is looking toward a future where these syntaxes will no longer be used” when one of the several proposals adding types to JavaScript reaches stage 4. This JavaScript proposal for erasable type syntax, currently in Stage 1, indeed also lists Rosenwasser as author and champion.
The proposal’s Trends in JavaScript Compilation chapter details:
Because type syntax is not natively supported in JavaScript, some tool had to exist to remove those types before running any code. […]
Build steps add another layer of concerns to writing code. For example, ensuring freshness of the build output, optimizing the speed of the build, and managing sourcemaps for debugging, are all concerns that JavaScript initially side-stepped. This simplicity made JavaScript much more approachable.
This proposal will reduce the need to have a build step which can make some development set-ups much simpler. Users can simply run the code they wrote.
With the --module nodenext
flag, TypeScript now fully supports require()
for JavaScript modules.
// Previously, this would fail in TypeScript
const { readFile } = require("fs");
const data = readFile("file.txt", "utf-8");
// Now it works seamlessly under --module nodenext!
Developers can review the full release note on Microsoft’s developer blog. The release note includes additional technical details, examples, and the full list of features part of the release.