TypeScript 5.8 was announced on 28 February 2025. In checked JavaScript the change most people meet is a stricter return check: each branch of a conditional in a return statement is compared with the declared @returns type, so an any in one branch no longer hides a wrong value in the other.
For Node.js projects it follows Node.js 22: a CommonJS file may require() an ES module under module: nodenext, a new stable module: node18 keeps the older rules, and the old assert import syntax is an error under nodenext.
At a glance
| Release | TypeScript 5.8 |
|---|---|
| Released | 28 February 2025, the day of its announcement on the TypeScript blog |
| Source | TypeScript 5.8 release notes |
| Examples | Every example runs through TypeScript 5.9.3 when this site is built. |
| Previous release | TypeScript 5.7 |
| Next release | TypeScript 5.9 |
What changed for JavaScript
Each branch of a returned conditional is checked
In return cache.has(href) ? cache.get(href) : href, the first branch is any (cache is a Map<string, any>), and before 5.8 the whole conditional became any, so returning a string from a function declared @returns {Link} passed. Now each branch is checked against the return type on its own (TS2322 on href).
class Link {
/** @param {string} href */
constructor(href) {
this.href = href;
}
}
/** @type {Map<string, any>} */
const cache = new Map();
/**
* @param {string} href
* @returns {Link}
*/
export function toLink(href) {
return cache.has(href) ? cache.get(href) : href;error TS2322: Type 'string' is not assignable to type 'Link'.
}jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": [
"*.js"
]
} require() of an ES module under nodenext
Node.js 22 lets CommonJS code require() an ES module (one without top-level await). Under module: nodenext, TypeScript 5.8 accepts it too, so a .cjs file, or a .js file in a package without "type": "module", can require an ESM-only package. This site's tests confirm with TypeScript 5.9 that the same require is TS1479 under node16.
const { add } = require("./math.mjs");
console.log(add(1, 2));jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": [
"*.cjs",
"*.mjs"
]
} module node18
A stable setting for projects that stay on Node.js 18: it does not allow require() of ES modules, and it still accepts assert on imports.
assert on imports is an error under nodenext
Import assertions (assert { type: "json" }) were replaced by import attributes (with { type: "json" }), and Node.js 22 no longer accepts assert. Under nodenext, TypeScript 5.8 reports it (TS2880).
import settings from "./settings.json" assert { type: "json" };error TS2880: Import assertions have been replaced by import attributes. Use 'with' instead of 'assert'.
export const port = settings.port;jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": [
"*.js"
]
} libReplacement
TypeScript looks for packages like @typescript/lib-dom to replace its lib files (see 4.5). "libReplacement": false turns the lookup, and the watching of node_modules that comes with it, off. TypeScript 6.0 makes false the default.
What did not change
erasableSyntaxOnly, the other new option, reports TypeScript-only syntax such as enum and parameter properties. JavaScript cannot contain that syntax, so it changes nothing in a .js file.
Upgrade notes
From the release notes:
- Replace
assertwithwithon imports undernodenext. - Expect new TS2322 errors where one branch of a returned conditional was
any. - The DOM types changed again.
Gotchas
- Under
nodenext, a JSON import from an ES module also needswith { type: "json" }(TS1543, since 5.7).
Sources
- TypeScript 5.8 release notes · TypeScript handbook
- Announcing TypeScript 5.8 · TypeScript blog
Last reviewed 20 October 2026.