TypeScript release

TypeScript 5.3 for JavaScript users

  • Released 20 November 2023
  • Examples run with TypeScript 5.9

TypeScript 5.3 was announced on 20 November 2023. It supports import attributes, with { type: "json" }, which replaced the older assert syntax, and narrows in more places JavaScript code reaches for: switch (true), comparisons with true and false, and instanceof against a class with a Symbol.hasInstance method.

One optimization in the release reads as if tsc stopped parsing JSDoc, which would matter a great deal for checked JavaScript. It did not stop for .js files; see below.

At a glance

ReleaseTypeScript 5.3
Released20 November 2023, the day of its announcement on the TypeScript blog
SourceTypeScript 5.3 release notes
ExamplesEvery example runs through TypeScript 5.9.3 when this site is built.
Previous releaseTypeScript 5.2
Next releaseTypeScript 5.4

What changed for JavaScript

Import attributes

import data from "./data.json" with { type: "json" } tells the runtime what kind of module to expect. TypeScript does not check the attribute's value in 5.3 (5.4 began checking it against a global ImportAttributes type). Under nodenext, 5.7 made the attribute required for JSON.

main.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: main.js(4,36): error TS2339: Property 'hots' does not exist on type '{ port: number; host: string; }'. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. main.js(4,36): error TS2339: Property 'hots' does not exist on type '{ port: number; host: string; }'. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. main.js(4,36): error TS2339: Property 'hots' does not exist on type '{ port: number; host: string; }'. checked 21 October 2026
import config from "./config.json" with { type: "json" };

console.log(`Listening on ${config.hots}:${config.port}`);error TS2339: Property 'hots' does not exist on type '{ port: number; host: string; }'.
module nodenext: the JSON file is typed, so a typo is caught.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": [
    "*.js"
  ]
}

switch (true) narrows

Each case condition in a switch (true) now narrows inside that case, the way an if chain does.

describe.js TypeScript 5.9.3 TypeScript 5.9.3, run 21 October 2026: No errors. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. No errors. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. No errors. checked 21 October 2026
/** @param {unknown} value */
export function describe(value) {
  switch (true) {
    case typeof value === "string":
      return value.toUpperCase();
    case Array.isArray(value):
      return `${value.length} items`;
    default:
      return "something else";
  }
}
switch (true) narrows in each case.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": [
    "*.js"
  ]
}

instanceof and Symbol.hasInstance

A class can decide what instanceof means with a static [Symbol.hasInstance] method. When that method is a type predicate, value instanceof Point narrows value to the predicate's type, which can be a plain object type rather than the class.

point.js TypeScript 5.9.3 TypeScript 5.9.3, run 21 October 2026: No errors. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. No errors. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. No errors. checked 21 October 2026
/** @typedef {{ x: number, y: number }} PointLike */

export class Point {
  /**
   * @param {unknown} value
   * @returns {value is PointLike}
   */
  static [Symbol.hasInstance](value) {
    return typeof value === "object" && value !== null && "x" in value && "y" in value;
  }
}

/** @param {unknown} value */
export function distance(value) {
  if (value instanceof Point) {
    return Math.hypot(value.x, value.y);
  }
  return 0;
}
instanceof narrows through a static Symbol.hasInstance predicate.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": [
    "*.js"
  ]
}

super cannot reach class fields

super.greet() only finds methods on the base class's prototype. If greet is a class field (an arrow function assigned in the class body), it is not there and the call throws. TypeScript 5.3 reports it (TS2855).

greeter.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: greeter.js(8,18): error TS2855: Class field 'greet' defined by the parent class is not accessible in the child class via super. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. greeter.js(8,18): error TS2855: Class field 'greet' defined by the parent class is not accessible in the child class via super. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. greeter.js(8,18): error TS2855: Class field 'greet' defined by the parent class is not accessible in the child class via super. checked 21 October 2026
class Base {
  greet = () => "hello";
}

export class Loud extends Base {
  shout() {
    return super.greet().toUpperCase();error TS2855: Class field 'greet' defined by the parent class is not accessible in the child class via super.
  }
}
super.x cannot reach a class field.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": [
    "*.js"
  ]
}

tsc skips JSDoc only in .ts files

The release notes say tsc now avoids parsing JSDoc to save time. In TypeScript's source the default mode for tsc parses every JSDoc comment in JavaScript files, where JSDoc carries the types, and in .ts files only the comments that contain @see or @link. This site's tests check that behavior with TypeScript 5.9.

What did not change

resolution-mode became stable for import types, so import("pkg", { with: { "resolution-mode": "require" } }) can pick a package's CommonJS or ES module types. It is a TypeScript type syntax and the notes show it only for .ts files; this site has not tested it inside JSDoc.

Upgrade notes

The breaking changes are the DOM types and the new super check (TS2855).

Gotchas

  • switch (true) narrowing is per case; once a case falls through into the next, the variable has the union of both narrowings.

Sources

Last reviewed 20 October 2026.