TypeScript release

TypeScript 5.5 for JavaScript users

  • Released 20 June 2024
  • Examples run with TypeScript 5.9

TypeScript 5.5 was announced on 20 June 2024. It has the biggest JSDoc change of the 5.x line: the @import tag, which brings types from another module into a .js file with ES import syntax and imports nothing at run time.

It also infers type predicates from simple checks, so filter((x) => x !== undefined) narrows the array; narrows obj[key] when neither changes; checks the syntax of regular expression literals; and adds ${configDir} for shared config files.

At a glance

ReleaseTypeScript 5.5
Released20 June 2024, the day of its announcement on the TypeScript blog
SourceTypeScript 5.5 release notes
ExamplesEvery example runs through TypeScript 5.9.3 when this site is built.
Previous releaseTypeScript 5.4
Next releaseTypeScript 5.6

What changed for JavaScript

The @import tag

/** @import { Product } from "./models.js" */ makes Product usable in the JSDoc of the file, as import type does in TypeScript. It replaces the workarounds: an import type at every use, or a @typedef alias for each one. The @import page has the forms it takes.

cart.js cart.ts 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
/** @import { Product, LineItem } from "./models.js" */
/** @import * as models from "./models.js" */

/**
 * @param {Product} product
 * @param {number} quantity
 * @returns {LineItem}
 */
export function lineFor(product, quantity) {
  return { sku: product.sku, quantity, total: product.price * quantity };
}

/** @type {models.Product} */
const tee = { sku: "TEE-01", name: "Cotton tee", price: 19.5 };

lineFor(tee, 2);
import type { Product, LineItem } from "./models.js";
import type * as models from "./models.js";

export function lineFor(product: Product, quantity: number): LineItem {
  return { sku: product.sku, quantity, total: product.price * quantity };
}

const tee: models.Product = { sku: "TEE-01", name: "Cotton tee", price: 19.5 };

lineFor(tee, 2);
Importing types from another module with @import.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}

Inferred type predicates

A function that returns a check on its parameter, such as (sku) => sku !== undefined, is now inferred as a type predicate, so filter with it returns string[] instead of (string | undefined)[]. Truthiness checks on numbers do not qualify, because 0 is falsy.

catalog.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
/** @type {(string | undefined)[]} */
const skus = ["TEE-01", undefined, "MUG-07"];

// The arrow function is inferred as (sku) => sku is string, so the result is string[].
const present = skus.filter((sku) => sku !== undefined);
present.map((sku) => sku.toLowerCase());

export { present };
Since TypeScript 5.5, simple checks are predicates on their own.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}

obj[key] narrows when both are constant

When neither the object nor the key is reassigned, typeof fields[key] === "string" narrows fields[key] inside the if. Before 5.5 you had to copy the value into a variable first.

upper.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 {Record<string, unknown>} fields
 * @param {string} key
 */
export function upper(fields, key) {
  if (typeof fields[key] === "string") {
    return fields[key].toUpperCase();
  }
  return "";
}
obj[key] narrows when neither obj nor key changes.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": [
    "*.js"
  ]
}

Regular expression literals are checked

TypeScript now checks the syntax of regular expression literals: an unescaped parenthesis, a backreference to a group that does not exist (TS1532 for a named one), or a feature newer than the target. Strings passed to new RegExp() are not checked.

dates.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: dates.js(3,44): error TS1532: There is no capturing group named 'month' in this regular expression. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. dates.js(3,44): error TS1532: There is no capturing group named 'month' in this regular expression. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. dates.js(3,44): error TS1532: There is no capturing group named 'month' in this regular expression. checked 21 October 2026
export const isoDate = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
export const repeated = /(?<year>\d{4})-\k<month>/;error TS1532: There is no capturing group named 'month' in this regular expression.
A regular expression literal that names a group it does not have.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": [
    "*.js"
  ]
}

${configDir} in shared configs

A path in a config that others extend is resolved from the file it is written in. ${configDir} stands for the folder of the config being compiled instead, so a shared base can set outDir, typeRoots or declarationDir for every project that extends it. The release notes name jsconfig.json as well as tsconfig.json, and a one-off check with TypeScript 5.9 wrote declaration files to the extending project's own out folder.

config/base.json
{
  "compilerOptions": {
    "checkJs": true,
    "strict": true,
    // the folder of each jsconfig.json that extends this file
    "typeRoots": ["${configDir}/types", "${configDir}/node_modules/@types"]
  }
}

What did not change

isolatedDeclarations, the release's option for fast declaration output, cannot be combined with allowJs (TS5053, checked with TypeScript 5.9), so it is for .ts sources only.

  • Import types inside the braces (import("./x.js").Name) still work and need no tag.

Upgrade notes

From the behavioral changes:

  • The options deprecated in 5.0 (charset, target: ES3, importsNotUsedAsValues, out and others) stop working; remove them from your config.
  • Inferred predicates can narrow an array more than before, so pushing undefined into it later is an error; give the array an explicit @type.
  • Declaration output no longer adds /// <reference types> lines by itself, which matters to libraries that write .d.ts files from JavaScript.

Gotchas

  • Put each @import in its own comment near the top of the file; a misspelled name is reported on that line, and every use of it then has an error type.

Sources

Last reviewed 20 October 2026.