# Table of Contents - [TypeScript: The starting point for learning TypeScript](#typescript-the-starting-point-for-learning-typescript) - [TypeScript: Documentation - TypeScript 5.7](#typescript-documentation-typescript-5-7) - [TypeScript: Documentation - TypeScript 5.6](#typescript-documentation-typescript-5-6) - [TypeScript: Documentation - TypeScript 5.5](#typescript-documentation-typescript-5-5) - [TypeScript: Documentation - TypeScript 5.4](#typescript-documentation-typescript-5-4) - [TypeScript: Documentation - TypeScript 5.3](#typescript-documentation-typescript-5-3) - [TypeScript: Documentation - TypeScript 5.2](#typescript-documentation-typescript-5-2) - [TypeScript: Documentation - TypeScript 5.0](#typescript-documentation-typescript-5-0) - [TypeScript: Documentation - TypeScript 4.8](#typescript-documentation-typescript-4-8) - [TypeScript: Documentation - TypeScript 5.1](#typescript-documentation-typescript-5-1) - [TypeScript: Documentation - TypeScript 4.9](#typescript-documentation-typescript-4-9) - [TypeScript: Documentation - TypeScript 4.5](#typescript-documentation-typescript-4-5) - [TypeScript: Documentation - TypeScript 4.2](#typescript-documentation-typescript-4-2) - [TypeScript: Documentation - TypeScript 4.7](#typescript-documentation-typescript-4-7) - [TypeScript: Documentation - TypeScript 3.9](#typescript-documentation-typescript-3-9) - [TypeScript: Documentation - TypeScript 4.1](#typescript-documentation-typescript-4-1) - [TypeScript: Documentation - TypeScript 4.6](#typescript-documentation-typescript-4-6) - [TypeScript: Documentation - TypeScript 4.4](#typescript-documentation-typescript-4-4) - [TypeScript: Documentation - TypeScript 4.3](#typescript-documentation-typescript-4-3) - [TypeScript: Documentation - TypeScript 3.8](#typescript-documentation-typescript-3-8) - [TypeScript: Documentation - TypeScript 4.0](#typescript-documentation-typescript-4-0) - [TypeScript: Documentation - TypeScript 3.7](#typescript-documentation-typescript-3-7) - [TypeScript: Documentation - TypeScript 3.1](#typescript-documentation-typescript-3-1) - [TypeScript: Handbook - Interfaces](#typescript-handbook-interfaces) - [TypeScript: Documentation - TypeScript 3.5](#typescript-documentation-typescript-3-5) - [TypeScript: Documentation - TypeScript 3.3](#typescript-documentation-typescript-3-3) - [TypeScript: Documentation - TypeScript 3.6](#typescript-documentation-typescript-3-6) - [TypeScript: Documentation - TypeScript 3.2](#typescript-documentation-typescript-3-2) - [TypeScript: Documentation - TypeScript 3.4](#typescript-documentation-typescript-3-4) - [TypeScript: Documentation - TypeScript 3.0](#typescript-documentation-typescript-3-0) - [TypeScript: Documentation - TypeScript 2.9](#typescript-documentation-typescript-2-9) - [TypeScript: Documentation - TypeScript 2.0](#typescript-documentation-typescript-2-0) - [TypeScript: Documentation - TypeScript 2.5](#typescript-documentation-typescript-2-5) - [TypeScript: Documentation - TypeScript 2.6](#typescript-documentation-typescript-2-6) - [TypeScript: Documentation - TypeScript 1.8](#typescript-documentation-typescript-1-8) - [TypeScript: Documentation - TypeScript 2.7](#typescript-documentation-typescript-2-7) - [TypeScript: Documentation - TypeScript 2.8](#typescript-documentation-typescript-2-8) - [TypeScript: Documentation - TypeScript 2.4](#typescript-documentation-typescript-2-4) - [TypeScript: Documentation - TypeScript 1.3](#typescript-documentation-typescript-1-3) - [TypeScript: Documentation - TypeScript 2.3](#typescript-documentation-typescript-2-3) - [TypeScript: Documentation - TypeScript 2.2](#typescript-documentation-typescript-2-2) - [TypeScript: Documentation - TypeScript 1.1](#typescript-documentation-typescript-1-1) - [TypeScript: Documentation - TypeScript 1.7](#typescript-documentation-typescript-1-7) - [TypeScript: Documentation - TypeScript 1.4](#typescript-documentation-typescript-1-4) - [TypeScript: Documentation - TypeScript 2.1](#typescript-documentation-typescript-2-1) - [TypeScript: Documentation - Templates](#typescript-documentation-templates) - [TypeScript: Documentation - TypeScript 1.5](#typescript-documentation-typescript-1-5) - [TypeScript: Documentation - TypeScript 1.6](#typescript-documentation-typescript-1-6) - [TypeScript: Documentation - Global: Plugin](#typescript-documentation-global-plugin) --- # TypeScript: The starting point for learning TypeScript TypeScript Documentation ======================== #### Get Started Quick introductions based on your background or preference. * [TS for the New Programmer](https://www.typescriptlang.org/docs/handbook/typescript-from-scratch.html) * [TypeScript for JS Programmers](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html) * [TS for Java/C# Programmers](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes-oop.html) * [TS for Functional Programmers](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes-func.html) * [TypeScript Tooling in 5 minutes](https://www.typescriptlang.org/docs/handbook/typescript-tooling-in-5-minutes.html) #### Handbook A great first read for your daily TS work. * [The TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/intro.html) * [The Basics](https://www.typescriptlang.org/docs/handbook/2/basic-types.html) * [Everyday Types](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html) * [Narrowing](https://www.typescriptlang.org/docs/handbook/2/narrowing.html) * [More on Functions](https://www.typescriptlang.org/docs/handbook/2/functions.html) * [Object Types](https://www.typescriptlang.org/docs/handbook/2/objects.html) * Type Manipulation * * [Creating Types from Types](https://www.typescriptlang.org/docs/handbook/2/types-from-types.html) * [Generics](https://www.typescriptlang.org/docs/handbook/2/generics.html) * [Keyof Type Operator](https://www.typescriptlang.org/docs/handbook/2/keyof-types.html) * [Typeof Type Operator](https://www.typescriptlang.org/docs/handbook/2/typeof-types.html) * [Indexed Access Types](https://www.typescriptlang.org/docs/handbook/2/indexed-access-types.html) * [Conditional Types](https://www.typescriptlang.org/docs/handbook/2/conditional-types.html) * [Mapped Types](https://www.typescriptlang.org/docs/handbook/2/mapped-types.html) * [Template Literal Types](https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html) * [Classes](https://www.typescriptlang.org/docs/handbook/2/classes.html) * [Modules](https://www.typescriptlang.org/docs/handbook/2/modules.html) #### Reference Deep dive reference materials. * [Utility Types](https://www.typescriptlang.org/docs/handbook/utility-types.html) * [Cheat Sheets](https://www.typescriptlang.org/cheatsheets/) * [Decorators](https://www.typescriptlang.org/docs/handbook/decorators.html) * [Declaration Merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html) * [Enums](https://www.typescriptlang.org/docs/handbook/enums.html) * [Iterators and Generators](https://www.typescriptlang.org/docs/handbook/iterators-and-generators.html) * [JSX](https://www.typescriptlang.org/docs/handbook/jsx.html) * [Mixins](https://www.typescriptlang.org/docs/handbook/mixins.html) * [Namespaces](https://www.typescriptlang.org/docs/handbook/namespaces.html) * [Namespaces and Modules](https://www.typescriptlang.org/docs/handbook/namespaces-and-modules.html) * [Symbols](https://www.typescriptlang.org/docs/handbook/symbols.html) * [Triple-Slash Directives](https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html) * [Type Compatibility](https://www.typescriptlang.org/docs/handbook/type-compatibility.html) * [Type Inference](https://www.typescriptlang.org/docs/handbook/type-inference.html) * [Variable Declaration](https://www.typescriptlang.org/docs/handbook/variable-declarations.html) #### Modules Reference How TypeScript models JavaScript modules. * [Introduction](https://www.typescriptlang.org/docs/handbook/modules/introduction.html) * [Theory](https://www.typescriptlang.org/docs/handbook/modules/theory.html) * Guides * * [Choosing Compiler Options](https://www.typescriptlang.org/docs/handbook/modules/guides/choosing-compiler-options.html) * [Reference](https://www.typescriptlang.org/docs/handbook/modules/reference.html) * Appendices * * [ESM/CJS Interoperability](https://www.typescriptlang.org/docs/handbook/modules/appendices/esm-cjs-interop.html) #### Tutorials Using TypeScript in several environments. * [ASP.NET Core](https://www.typescriptlang.org/docs/handbook/asp-net-core.html) * [Gulp](https://www.typescriptlang.org/docs/handbook/gulp.html) * [DOM Manipulation](https://www.typescriptlang.org/docs/handbook/dom-manipulation.html) * [Migrating from JavaScript](https://www.typescriptlang.org/docs/handbook/migrating-from-javascript.html) * [Using Babel with TypeScript](https://www.typescriptlang.org/docs/handbook/babel-with-typescript.html) #### Declaration Files Learn how to write declaration files to describe existing JavaScript. Important for DefinitelyTyped contributions. * [Introduction](https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html) * [Declaration Reference](https://www.typescriptlang.org/docs/handbook/declaration-files/by-example.html) * [Library Structures](https://www.typescriptlang.org/docs/handbook/declaration-files/library-structures.html) * .d.ts Templates * * [Modules .d.ts](https://www.typescriptlang.org/docs/handbook/declaration-files/templates/module-d-ts.html) * [Module: Plugin](https://www.typescriptlang.org/docs/handbook/declaration-files/templates/module-plugin-d-ts.html) * [Module: Class](https://www.typescriptlang.org/docs/handbook/declaration-files/templates/module-class-d-ts.html) * [Module: Function](https://www.typescriptlang.org/docs/handbook/declaration-files/templates/module-function-d-ts.html) * [Global .d.ts](https://www.typescriptlang.org/docs/handbook/declaration-files/templates/global-d-ts.html) * [Global: Modifying Module](https://www.typescriptlang.org/docs/handbook/declaration-files/templates/global-modifying-module-d-ts.html) * [Do's and Don'ts](https://www.typescriptlang.org/docs/handbook/declaration-files/do-s-and-don-ts.html) * [Deep Dive](https://www.typescriptlang.org/docs/handbook/declaration-files/deep-dive.html) * [Publishing](https://www.typescriptlang.org/docs/handbook/declaration-files/publishing.html) * [Consumption](https://www.typescriptlang.org/docs/handbook/declaration-files/consumption.html) #### JavaScript How to use TypeScript-powered JavaScript tooling. * [JS Projects Utilizing TypeScript](https://www.typescriptlang.org/docs/handbook/intro-to-js-ts.html) * [Type Checking JavaScript Files](https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html) * [JSDoc Reference](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html) * [Creating .d.ts Files from .js files](https://www.typescriptlang.org/docs/handbook/declaration-files/dts-from-js.html) #### Project Configuration Compiler configuration reference. * [What is a tsconfig.json](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html) * [Compiler Options in MSBuild](https://www.typescriptlang.org/docs/handbook/compiler-options-in-msbuild.html) * [TSConfig Reference](https://www.typescriptlang.org/tsconfig/) * [tsc CLI Options](https://www.typescriptlang.org/docs/handbook/compiler-options.html) * [Project References](https://www.typescriptlang.org/docs/handbook/project-references.html) * [Integrating with Build Tools](https://www.typescriptlang.org/docs/handbook/integrating-with-build-tools.html) * [Configuring Watch](https://www.typescriptlang.org/docs/handbook/configuring-watch.html) * [Nightly Builds](https://www.typescriptlang.org/docs/handbook/nightly-builds.html) #### Cheat Sheets Downloadable syntax reference pages for different parts of everyday TypeScript code. * [Control Flow Analysis](https://www.typescriptlang.org/static/TypeScript%20Control%20Flow%20Analysis-8a549253ad8470850b77c4c5c351d457.png) * [Classes](https://www.typescriptlang.org/static/TypeScript%20Classes-83cc6f8e42ba2002d5e2c04221fa78f9.png) * [Interfaces](https://www.typescriptlang.org/static/TypeScript%20Interfaces-34f1ad12132fb463bd1dfe5b85c5b2e6.png) * [Types](https://www.typescriptlang.org/static/TypeScript%20Types-ae199d69aeecf7d4a2704a528d0fd3f9.png) * [Download PDFs and PNGs](https://www.typescriptlang.org/assets/typescript-cheat-sheets.zip) Learning Resources ------------------ #### Get Started * [JS to TS](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html) * [New to Programming](https://www.typescriptlang.org/docs/handbook/typescript-from-scratch.html) * [OOP to JS](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes-oop.html) * [Functional to JS](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes-func.html) * [Installation](https://www.typescriptlang.org/download/) #### Handbook * [Everyday Types](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html) * [Creating Types from Types](https://www.typescriptlang.org/docs/handbook/2/types-from-types.html) * [Object Types](https://www.typescriptlang.org/docs/handbook/2/objects.html) * [Variable Declarations](https://www.typescriptlang.org/docs/handbook/variable-declarations.html) * [More on Functions](https://www.typescriptlang.org/docs/handbook/2/functions.html) #### Tools * [Playground](https://www.typescriptlang.org/play/) * [TSConfig Reference](https://www.typescriptlang.org/tsconfig/) #### Release Notes * [What's upcoming in 5.9?](https://devblogs.microsoft.com/typescript/announcing-typescript-5-9-beta/) * [What's new in 5.8](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-8.html) #### Tutorials * [ASP.NET](https://www.typescriptlang.org/docs/handbook/asp-net-core.html) * [Migrating from JS](https://www.typescriptlang.org/docs/handbook/migrating-from-javascript.html) * [Working with the DOM](https://www.typescriptlang.org/docs/handbook/dom-manipulation.html) * [React & Webpack](https://www.typescriptlang.org/docs/handbook/react-&-webpack.html) MSG --- # TypeScript: Documentation - TypeScript 5.7 Was this page helpful? TypeScript 5.7 ============== [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-7.html#checks-for-never-initialized-variables) Checks for Never-Initialized Variables ---------------------------------------------------------------------------------------------------------------------------------------------------------------- For a long time, TypeScript has been able to catch issues when a variable has not yet been initialized in all prior branches. ts ` let result: number if (someCondition()) { result = doSomeWork(); } else { let temporaryWork = doSomeWork(); temporaryWork *= 2; // forgot to assign to 'result' } console.log(result); // error: Variable 'result' is used before being assigned. ` Unfortunately, there are some places where this analysis doesn’t work. For example, if the variable is accessed in a separate function, the type system doesn’t know when the function will be called, and instead takes an “optimistic” view that the variable will be initialized. ts ` function foo() { let result: number if (someCondition()) { result = doSomeWork(); } else { let temporaryWork = doSomeWork(); temporaryWork *= 2; // forgot to assign to 'result' } printResult(); function printResult() { console.log(result); // no error here. } } ` While TypeScript 5.7 is still lenient on variables that have _possibly_ been initialized, the type system is able to report errors when variables have _never_ been initialized at all. ts ` function foo() { let result: number // do work, but forget to assign to 'result' function printResult() { console.log(result); // error: Variable 'result' is used before being assigned. } } ` [This change](https://github.com/microsoft/TypeScript/pull/55887) was contributed thanks to the work of GitHub user [Zzzen](https://github.com/Zzzen) ! [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-7.html#path-rewriting-for-relative-paths) Path Rewriting for Relative Paths ------------------------------------------------------------------------------------------------------------------------------------------------------ There are several tools and runtimes that allow you to run TypeScript code “in-place”, meaning they do not require a build step which generates output JavaScript files. For example, ts-node, tsx, Deno, and Bun all support running `.ts` files directly. More recently, Node.js has been investigating such support with `--experimental-strip-types` (soon to be unflagged!) and `--experimental-transform-types`. This is extremely convenient because it allows us to iterate faster without worrying about re-running a build task. There is some complexity to be aware of when using these modes though. To be maximally compatible with all these tools, a TypeScript file that’s imported “in-place” **must** be imported with the appropriate TypeScript extension at runtime. For example, to import a file called `foo.ts`, we have to write the following in Node’s new experimental support: ts ` // main.ts import * as foo from "./foo.ts"; // <- we need foo.ts here, not foo.js ` Typically, TypeScript would issue an error if we did this, because it expects us to import _the output file_. Because some tools do allow `.ts` imports, TypeScript has supported this import style with an option called `--allowImportingTsExtensions` for a while now. This works fine, but what happens if we need to actually generate `.js` files out of these `.ts` files? This is a requirement for library authors who will need to be able to distribute just `.js` files, but up until now TypeScript has avoided rewriting any paths. To support this scenario, we’ve added a new compiler option called `--rewriteRelativeImportExtensions`. When an import path is _relative_ (starts with `./` or `../`), ends in a TypeScript extension (`.ts`, `.tsx`, `.mts`, `.cts`), and is a non-declaration file, the compiler will rewrite the path to the corresponding JavaScript extension (`.js`, `.jsx`, `.mjs`, `.cjs`). ts ` // Under --rewriteRelativeImportExtensions... // these will be rewritten. import * as foo from "./foo.ts"; import * as bar from "../someFolder/bar.mts"; // these will NOT be rewritten in any way. import * as a from "./foo"; import * as b from "some-package/file.ts"; import * as c from "@some-scope/some-package/file.ts"; import * as d from "#/file.ts"; import * as e from "./file.js"; ` This allows us to write TypeScript code that can be run in-place and then compiled into JavaScript when we’re ready. Now, we noted that TypeScript generally avoided rewriting paths. There are several reasons for this, but the most obvious one is dynamic imports. If a developer writes the following, it’s not trivial to handle the path that `import` receives. In fact, it’s impossible to override the behavior of `import` within any dependencies. ts ` function getPath() { if (Math.random() < 0.5) { return "./foo.ts"; } else { return "./foo.js"; } } let myImport = await import(getPath()); ` Another issue is that (as we saw above) only _relative_ paths are rewritten, and they are written “naively”. This means that any path that relies on TypeScript’s `baseUrl` and `paths` will not get rewritten: json ` // tsconfig.json { "compilerOptions": { "module": "nodenext", // ... "paths": { "@/*": ["./src/*"] } } } ` ts ` // Won't be transformed, won't work. import * as utilities from "@/utilities.ts"; ` Nor will any path that might resolve through the [`exports`](https://nodejs.org/api/packages.html#exports) and [`imports`](https://nodejs.org/api/packages.html#imports) fields of a `package.json`. json ` // package.json { "name": "my-package", "imports": { "#root/*": "./dist/*" } } ` ts ` // Won't be transformed, won't work. import * as utilities from "#root/utilities.ts"; ` As a result, if you’ve been using a workspace-style layout with multiple packages referencing each other, you might need to use [conditional exports](https://nodejs.org/api/packages.html#conditional-exports) with [scoped custom conditions](https://nodejs.org/api/packages.html#resolving-user-conditions) to make this work: json ` // my-package/package.json { "name": "my-package", "exports": { ".": { "@my-package/development": "./src/index.ts", "import": "./lib/index.js" }, "./*": { "@my-package/development": "./src/*.ts", "import": "./lib/*.js" } } } ` Any time you want to import the `.ts` files, you can run it with `node --conditions=@my-package/development`. Note the “namespace” or “scope” we used for the condition `@my-package/development`. This is a bit of a makeshift solution to avoid conflicts from dependencies that might also use the `development` condition. If everyone ships a `development` in their package, then resolution may try to resolve to a `.ts` file which will not necessarily work. This idea is similar to what’s described in Colin McDonnell’s essay _[Live types in a TypeScript monorepo](https://colinhacks.com/essays/live-types-typescript-monorepo#:~:text=custom%20conditions) _, along with [tshy’s guidance for loading from source](https://github.com/isaacs/tshy#loading-from-source) . For more specifics on how this feature works, [read up on the change here](https://github.com/microsoft/TypeScript/pull/59767) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-7.html#support-for---target-es2024-and---lib-es2024) Support for `--target es2024` and `--lib es2024` -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- TypeScript 5.7 now supports `--target es2024`, which allows users to target ECMAScript 2024 runtimes. This target primarily enables specifying the new `--lib es2024` which contains many features for `SharedArrayBuffer` and `ArrayBuffer`, `Object.groupBy`, `Map.groupBy`, `Promise.withResolvers`, and more. It also moves `Atomics.waitAsync` from `--lib es2022` to `--lib es2024`. Note that as part of the changes to `SharedArrayBuffer` and `ArrayBuffer`, the two now diverge a bit. To bridge the gap and preserve the underlying buffer type, all `TypedArrays` (like `Uint8Array` and others) [are now also generic](https://github.com/microsoft/TypeScript/pull/59417) . ts ` interface Uint8Array { // ... } ` Each `TypedArray` now contains a type parameter named `TArrayBuffer`, though that type parameter has a default type argument so that we can continue to refer to `Int32Array` without explicitly writing out `Int32Array`. If you encounter any issues as part of this update, you may need to update `@types/node`. [This work](https://github.com/microsoft/TypeScript/pull/58573) was primarily provided thanks to [Kenta Moriuchi](https://github.com/petamoriken) ! [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-7.html#searching-ancestor-configuration-files-for-project-ownership) Searching Ancestor Configuration Files for Project Ownership ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ When a TypeScript file is loaded in an editor using TSServer (like Visual Studio or VS Code), the editor will try to find the relevant `tsconfig.json` file that “owns” the file. To do this, it walks up the directory tree from the file being edited, looking for any file named `tsconfig.json`. Previously, this search would stop at the first `tsconfig.json` file found; however, imagine a project structure like the following: ` project/ ├── src/ │ ├── foo.ts │ ├── foo-test.ts │ ├── tsconfig.json │ └── tsconfig.test.json └── tsconfig.json ` Here, the idea is that `src/tsconfig.json` is the “main” configuration file for the project, and `src/tsconfig.test.json` is a configuration file for running tests. json ` // src/tsconfig.json { "compilerOptions": { "outDir": "../dist" }, "exclude": ["**/*.test.ts"] } ` json ` // src/tsconfig.test.json { "compilerOptions": { "outDir": "../dist/test" }, "include": ["**/*.test.ts"], "references": [ { "path": "./tsconfig.json" } ] } ` json ` // tsconfig.json { // This is a "workspace-style" or "solution-style" tsconfig. // Instead of specifying any files, it just references all the actual projects. "files": [], "references": [ { "path": "./src/tsconfig.json" }, { "path": "./src/tsconfig.test.json" }, ] } ` The problem here is that when editing `foo-test.ts`, the editor would find `project/src/tsconfig.json` as the “owning” configuration file - but that’s not the one we want! If the walk stops at this point, that might not be desirable. The only way to avoid this previously was to rename `src/tsconfig.json` to something like `src/tsconfig.src.json`, and then all files would hit the top-level `tsconfig.json` which references every possible project. ` project/ ├── src/ │ ├── foo.ts │ ├── foo-test.ts │ ├── tsconfig.src.json │ └── tsconfig.test.json └── tsconfig.json ` Instead of forcing developers to do this, TypeScript 5.7 now continues walking up the directory tree to find other appropriate `tsconfig.json` files for editor scenarios. This can provide more flexibility in how projects are organized and how configuration files are structured. You can get more specifics on the implementation on GitHub [here](https://github.com/microsoft/TypeScript/pull/57196) and [here](https://github.com/microsoft/TypeScript/pull/59688) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-7.html#faster-project-ownership-checks-in-editors-for-composite-projects) Faster Project Ownership Checks in Editors for Composite Projects ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Imagine a large codebase with the following structure: ` packages ├── graphics/ │ ├── tsconfig.json │ └── src/ │ └── ... ├── sound/ │ ├── tsconfig.json │ └── src/ │ └── ... ├── networking/ │ ├── tsconfig.json │ └── src/ │ └── ... ├── input/ │ ├── tsconfig.json │ └── src/ │ └── ... └── app/ ├── tsconfig.json ├── some-script.js └── src/ └── ... ` Each directory in `packages` is a separate TypeScript project, and the `app` directory is the main project that depends on all the other projects. json ` // app/tsconfig.json { "compilerOptions": { // ... }, "include": ["src"], "references": [ { "path": "../graphics/tsconfig.json" }, { "path": "../sound/tsconfig.json" }, { "path": "../networking/tsconfig.json" }, { "path": "../input/tsconfig.json" } ] } ` Now notice we have the file `some-script.js` in the `app` directory. When we open `some-script.js` in the editor, the TypeScript language service (which also handles the editor experience for JavaScript files!) has to figure out which project the file belongs to so it can apply the right settings. In this case, the nearest `tsconfig.json` does _not_ include `some-script.js`, but TypeScript will proceed to ask “could one of the projects _referenced_ by `app/tsconfig.json` include `some-script.js`?“. To do so, TypeScript would previously load up each project, one-by-one, and stop as soon as it found a project which contained `some-script.js`. Even if `some-script.js` isn’t included in the root set of files, TypeScript would still parse all the files within a project because some of the root set of files can still _transitively_ reference `some-script.js`. What we found over time was that this behavior caused extreme and unpredictable behavior in larger codebases. Developers would open up stray script files and find themselves waiting for their entire codebase to be opened up. Thankfully, every project that can be referenced by another (non-workspace) project must enable a flag called `composite`, which enforces a rule that all input source files must be known up-front. So when probing a `composite` project, TypeScript 5.7 will only check if a file belongs to the _root set of files_ of that project. This should avoid this common worst-case behavior. For more information, [see the change here](https://github.com/microsoft/TypeScript/pull/59688) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-7.html#validated-json-imports-in---module-nodenext) Validated JSON Imports in `--module nodenext` When importing from a `.json` file under `--module nodenext`, TypeScript will now enforce certain rules to prevent runtime errors. For one, an import attribute containing `type: "json"` needs to be present for any JSON file import. ts `` import myConfig from "./myConfig.json"; // ~~~~~~~~~~~~~~~~~ // ❌ error: Importing a JSON file into an ECMAScript module requires a 'type: "json"' import attribute when 'module' is set to 'NodeNext'. import myConfig from "./myConfig.json" with { type: "json" }; // ^^^^^^^^^^^^^^^^ // ✅ This is fine because we provided `type: "json"` `` On top of this validation, TypeScript will not generate “named” exports, and the contents of a JSON import will only be accessible via a default. ts ` // ✅ This is okay: import myConfigA from "./myConfig.json" with { type: "json" }; let version = myConfigA.version; /////////// import * as myConfigB from "./myConfig.json" with { type: "json" }; // ❌ This is not: let version = myConfig.version; // ✅ This is okay: let version = myConfig.default.version; ` [See here](https://github.com/microsoft/TypeScript/pull/60019) for more information on this change. [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-7.html#support-for-v8-compile-caching-in-nodejs) Support for V8 Compile Caching in Node.js --------------------------------------------------------------------------------------------------------------------------------------------------------------------- Node.js 22 supports [a new API called `module.enableCompileCache()`](https://github.com/nodejs/node/pull/54501) . This API allows the runtime to reuse some of the parsing and compilation work done after the first run of a tool. TypeScript 5.7 now leverages the API so that it can start doing useful work sooner. In some of our own testing, we’ve witnessed about a 2.5x speed-up in running `tsc --version`. ` Benchmark 1: node ./built/local/_tsc.js --version (*without* caching) Time (mean ± σ): 122.2 ms ± 1.5 ms [User: 101.7 ms, System: 13.0 ms] Range (min … max): 119.3 ms … 132.3 ms 200 runs Benchmark 2: node ./built/local/tsc.js --version (*with* caching) Time (mean ± σ): 48.4 ms ± 1.0 ms [User: 34.0 ms, System: 11.1 ms] Range (min … max): 45.7 ms … 52.8 ms 200 runs Summary node ./built/local/tsc.js --version ran 2.52 ± 0.06 times faster than node ./built/local/_tsc.js --version ` For more information, [see the pull request here](https://github.com/microsoft/TypeScript/pull/59720) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-7.html#notable-behavioral-changes) Notable Behavioral Changes ---------------------------------------------------------------------------------------------------------------------------------------- This section highlights a set of noteworthy changes that should be acknowledged and understood as part of any upgrade. Sometimes it will highlight deprecations, removals, and new restrictions. It can also contain bug fixes that are functionally improvements, but which can also affect an existing build by introducing new errors. ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-7.html#libdts) `lib.d.ts` Types generated for the DOM may have an impact on type-checking your codebase. For more information, [see linked issues related to DOM and `lib.d.ts` updates for this version of TypeScript](https://github.com/microsoft/TypeScript/pull/60061) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-7.html#typedarrays-are-now-generic-over-arraybufferlike) `TypedArray`s Are Now Generic Over `ArrayBufferLike` In ECMAScript 2024, `SharedArrayBuffer` and `ArrayBuffer` have types that slightly diverge. To bridge the gap and preserve the underlying buffer type, all `TypedArrays` (like `Uint8Array` and others) [are now also generic](https://github.com/microsoft/TypeScript/pull/59417) . ts ` interface Uint8Array { // ... } ` Each `TypedArray` now contains a type parameter named `TArrayBuffer`, though that type parameter has a default type argument so that users can continue to refer to `Int32Array` without explicitly writing out `Int32Array`. If you encounter any issues as part of this update, such as ` error TS2322: Type 'Buffer' is not assignable to type 'Uint8Array'. error TS2345: Argument of type 'Buffer' is not assignable to parameter of type 'Uint8Array'. error TS2345: Argument of type 'ArrayBufferLike' is not assignable to parameter of type 'ArrayBuffer'. error TS2345: Argument of type 'Buffer' is not assignable to parameter of type 'string | ArrayBufferView | Stream | Iterable | AsyncIterable'. ` then you may need to update `@types/node`. You can read the [specifics about this change on GitHub](https://github.com/microsoft/TypeScript/pull/59417) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-7.html#creating-index-signatures-from-non-literal-method-names-in-classes) Creating Index Signatures from Non-Literal Method Names in Classes TypeScript now has a more consistent behavior for methods in classes when they are declared with non-literal computed property names. For example, in the following: ts ` declare const symbolMethodName: symbol; export class A { [symbolMethodName]() { return 1 }; } ` Previously TypeScript just viewed the class in a way like the following: ts ` export class A { } ` In other words, from the type system’s perspective, `[symbolMethodName]` contributed nothing to the type of `A` TypeScript 5.7 now views the method `[symbolMethodName]() {}` more meaningfully, and generates an index signature. As a result, the code above is interpreted as something like the following code: ts ` export class A { [x: symbol]: () => number; } ` This provides behavior that is consistent with properties and methods in object literals. [Read up more on this change here](https://github.com/microsoft/TypeScript/pull/59860) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-7.html#more-implicit-any-errors-on-functions-returning-null-and-undefined) More Implicit `any` Errors on Functions Returning `null` and `undefined` When a function expression is contextually typed by a signature returning a generic type, TypeScript now appropriately provides an implicit `any` error under `noImplicitAny`, but outside of `strictNullChecks`. ts ` declare var p: Promise; const p2 = p.catch(() => null); // ~~~~~~~~~~ // error TS7011: Function expression, which lacks return-type annotation, implicitly has an 'any' return type. ` [See this change for more details](https://github.com/microsoft/TypeScript/pull/59661) . The TypeScript docs are an open source project. Help us improve these pages [by sending a Pull Request](https://github.com/microsoft/TypeScript-Website/blob/v2/packages/documentation/copy/en/release-notes/TypeScript%205.7.md) ❤ Contributors to this page: N![navya9singh (6)](https://gravatar.com/avatar/e896fd3c90d7bd8d3c276d3ea2dd552ae62d81d4a497cdd589f551c5eb5ccb93?s=32&&d=blank) Last updated: Jul 14, 2025   MSG --- # TypeScript: Documentation - TypeScript 5.6 Was this page helpful? TypeScript 5.6 ============== [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-6.html#disallowed-nullish-and-truthy-checks) Disallowed Nullish and Truthy Checks ------------------------------------------------------------------------------------------------------------------------------------------------------------ Maybe you’ve written a regex and forgotten to call `.test(...)` on it: ts ` if (/0x[0-9a-f]/) { // Oops! This block always runs. // ... } ` or maybe you’ve accidentally written `=>` (which creates an arrow function) instead of `>=` (the greater-than-or-equal-to operator): ts ` if (x => 0) { // Oops! This block always runs. // ... } ` or maybe you’ve tried to use a default value with `??`, but mixed up the precedence of `??` and a comparison operator like `<`: ts ` function isValid(value: string | number, options: any, strictness: "strict" | "loose") { if (strictness === "loose") { value = +value } return value < options.max ?? 100; // Oops! This is parsed as (value < options.max) ?? 100 } ` or maybe you’ve misplaced a parenthesis in a complex expression: ts ` if ( isValid(primaryValue, "strict") || isValid(secondaryValue, "strict") || isValid(primaryValue, "loose" || isValid(secondaryValue, "loose")) ) { // ^^^^ 👀 Did we forget a closing ')'? } ` None of these examples do what the author intended, but they’re all valid JavaScript code. Previously TypeScript also quietly accepted these examples. But with a little bit of experimentation, we found that many _many_ bugs could be caught from flagging down suspicious examples like above. In TypeScript 5.6, the compiler now errors when it can syntactically determine a truthy or nullish check will always evaluate in a specific way. So in the above examples, you’ll start to see errors: ts ` if (/0x[0-9a-f]/) { // ~~~~~~~~~~~~ // error: This kind of expression is always truthy. } if (x => 0) { // ~~~~~~ // error: This kind of expression is always truthy. } function isValid(value: string | number, options: any, strictness: "strict" | "loose") { if (strictness === "loose") { value = +value } return value < options.max ?? 100; // ~~~~~~~~~~~~~~~~~~~ // error: Right operand of ?? is unreachable because the left operand is never nullish. } if ( isValid(primaryValue, "strict") || isValid(secondaryValue, "strict") || isValid(primaryValue, "loose" || isValid(secondaryValue, "loose")) ) { // ~~~~~~~ // error: This kind of expression is always truthy. } ` Similar results can be achieved by enabling the ESLint `no-constant-binary-expression` rule, and you can [see some of the results they achieved in their blog post](https://eslint.org/blog/2022/07/interesting-bugs-caught-by-no-constant-binary-expression/) ; but the new checks TypeScript performs does not have perfect overlap with the ESLint rule, and we also believe there is a lot of value in having these checks built into TypeScript itself. Note that certain expressions are still allowed, even if they are always truthy or nullish. Specifically, `true`, `false`, `0`, and `1` are all still allowed despite always being truthy or falsy, since code like the following: ts ` while (true) { doStuff(); if (something()) { break; } doOtherStuff(); } ` is still idiomatic and useful, and code like the following: ts ` if (true || inDebuggingOrDevelopmentEnvironment()) { // ... } ` is useful while iterating/debugging code. If you’re curious about the implementation or the sorts of bugs it catches, take a look at [the pull request that implemented this feature](https://github.com/microsoft/TypeScript/pull/59217) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-6.html#iterator-helper-methods) Iterator Helper Methods ---------------------------------------------------------------------------------------------------------------------------------- JavaScript has a notion of _iterables_ (things which we can iterate over by calling a `[Symbol.iterator]()` and getting an iterator) and _iterators_ (things which have a `next()` method which we can call to try to get the next value as we iterate). By and large, you don’t typically have to think about these things when you toss them into a `for`/`of` loop, or `[...spread]` them into a new array. But TypeScript does model these with the types `Iterable` and `Iterator` (and even `IterableIterator` which acts as both!), and these types describe the minimal set of members you need for constructs like `for`/`of` to work on them. `Iterable`s (and `IterableIterator`s) are nice because they can be used in all sorts of places in JavaScript - but a lot of people found themselves missing methods on `Array`s like `map`, `filter`, and for some reason `reduce`. That’s why [a recent proposal was brought forward in ECMAScript](https://github.com/tc39/proposal-iterator-helpers) to add many methods (and more) from `Array` onto most of the `IterableIterator`s that are produced in JavaScript. For example, every generator now produces an object that also has a `map` method and a `take` method. ts ` function* positiveIntegers() { let i = 1; while (true) { yield i; i++; } } const evenNumbers = positiveIntegers().map(x => x * 2); // Output: // 2 // 4 // 6 // 8 // 10 for (const value of evenNumbers.take(5)) { console.log(value); } ` The same is true for methods like `keys()`, `values()`, and `entries()` on `Map`s and `Set`s. ts ` function invertKeysAndValues(map: Map): Map { return new Map( map.entries().map(([k, v]) => [v, k]) ); } ` You can also extend the new `Iterator` object: ts `` /** * Provides an endless stream of `0`s. */ class Zeroes extends Iterator { next() { return { value: 0, done: false } as const; } } const zeroes = new Zeroes(); // Transform into an endless stream of `1`s. const ones = zeroes.map(x => x + 1); `` And you can adapt any existing `Iterable`s or `Iterator`s into this new type with `Iterator.from`: ts ` Iterator.from(...).filter(someFunction); ` Now, we have to talk about naming. Earlier we mentioned that TypeScript has types for `Iterable` and `Iterator`; however, like we mentioned, these act sort of like “protocols” to ensure certain operations work. _That means that not every value that is declared `Iterable` or `Iterator` in TypeScript will have those methods we mentioned above._ But there is still a new **runtime value** called `Iterator`. You can reference `Iterator`, as well as `Iterator.prototype`, as actual values in JavaScript. This is a bit awkward since TypeScript already defines its own thing called `Iterator` purely for type-checking. So due to this unfortunate name clash, TypeScript needs to introduce a separate type to describe these native/built-in iterable iterators. TypeScript 5.6 introduces a new type called `IteratorObject`. It is defined as follows: ts ` interface IteratorObject extends Iterator { [Symbol.iterator](): IteratorObject; } ` Lots of built-in collections and methods produce subtypes of `IteratorObject`s (like `ArrayIterator`, `SetIterator`, `MapIterator`, and more), and both the core JavaScript and DOM types in `lib.d.ts`, along with `@types/node`, have been updated to use this new type. Similarly, there is a `AsyncIteratorObject` type for parity. `AsyncIterator` does not yet exist as a runtime value in JavaScript that brings the same methods for `AsyncIterable`s, [but it is an active proposal](https://github.com/tc39/proposal-async-iterator-helpers) and this new type prepares for it. We’d like to thank [Kevin Gibbons](https://github.com/bakkot) who contributed [the changes for these types](https://github.com/microsoft/TypeScript/pull/58222) , and who is one of the co-authors of [the proposal](https://github.com/tc39/proposal-iterator-helpers) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-6.html#strict-builtin-iterator-checks-and---strictbuiltiniteratorreturn) Strict Builtin Iterator Checks (and `--strictBuiltinIteratorReturn`) ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ When you call the `next()` method on an `Iterator`, it returns an object with a `value` and a `done` property. This is modeled with the type `IteratorResult`. ts ` type IteratorResult = IteratorYieldResult | IteratorReturnResult; interface IteratorYieldResult { done?: false; value: TYield; } interface IteratorReturnResult { done: true; value: TReturn; } ` The naming here is inspired by the way a generator function works. Generator functions can `yield` values, and then `return` a final value - but the types between the two can be unrelated. ts ` function abc123() { yield "a"; yield "b"; yield "c"; return 123; } const iter = abc123(); iter.next(); // { value: "a", done: false } iter.next(); // { value: "b", done: false } iter.next(); // { value: "c", done: false } iter.next(); // { value: 123, done: true } ` With the new `IteratorObject` type, we discovered some difficulties in allowing safe implementations of `IteratorObject`s. At the same time, there’s been a long standing unsafety with `IteratorResult` in cases where `TReturn` was `any` (the default!). For example, let’s say we have an `IteratorResult`. If we end up reaching for the `value` of this type, we’ll end up with `string | any`, which is just `any`. ts `` function* uppercase(iter: Iterator) { while (true) { const { value, done } = iter.next(); yield value.toUppercase(); // oops! forgot to check for `done` first and misspelled `toUpperCase` if (done) { return; } } } `` It would be hard to fix this on every `Iterator` today without introducing a lot of breaks, but we can at least fix it with most `IteratorObject`s that get created. TypeScript 5.6 introduces a new intrinsic type called `BuiltinIteratorReturn` and a new `--strict`\-mode flag called `--strictBuiltinIteratorReturn`. Whenever `IteratorObject`s are used in places like `lib.d.ts`, they are always written with `BuiltinIteratorReturn` type for `TReturn` (though you’ll see the more-specific `MapIterator`, `ArrayIterator`, `SetIterator` more often). ts ` interface MapIterator extends IteratorObject { [Symbol.iterator](): MapIterator; } // ... interface Map { // ... /** * Returns an iterable of key, value pairs for every entry in the map. */ entries(): MapIterator<[K, V]>; /** * Returns an iterable of keys in the map */ keys(): MapIterator; /** * Returns an iterable of values in the map */ values(): MapIterator; } ` By default, `BuiltinIteratorReturn` is `any`, but when `--strictBuiltinIteratorReturn` is enabled (possibly via `--strict`), it is `undefined`. Under this new mode, if we use `BuiltinIteratorReturn`, our earlier example now correctly errors: ts ` function* uppercase(iter: Iterator) { while (true) { const { value, done } = iter.next(); yield value.toUppercase(); // ~~~~~ ~~~~~~~~~~~ // error! ┃ ┃ // ┃ ┗━ Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'? // ┃ // ┗━ 'value' is possibly 'undefined'. if (done) { return; } } } ` You’ll typically see `BuiltinIteratorReturn` paired up with `IteratorObject` throughout `lib.d.ts`. In general, we recommend being more explicit around the `TReturn` in your own code when possible. For more information, you can [read up on the feature here](https://github.com/microsoft/TypeScript/pull/58243) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-6.html#support-for-arbitrary-module-identifiers) Support for Arbitrary Module Identifiers -------------------------------------------------------------------------------------------------------------------------------------------------------------------- JavaScript allows modules to export bindings with invalid identifier names as string literals: ts ` const banana = "🍌"; export { banana as "🍌" }; ` Likewise, it allows modules to grab imports with these arbitrary names and bind them to valid identifiers: ts ` import { "🍌" as banana } from "./foo" /** * om nom nom */ function eat(food: string) { console.log("Eating", food); }; eat(banana); ` This seems like a cute party trick (if you’re as fun as we are at parties), but it has its uses for interoperability with other languages (typically via JavaScript/WebAssembly boundaries), since other languages may have different rules for what constitutes a valid identifier. It can also be useful for tools that generate code, like esbuild [with its `inject` feature](https://esbuild.github.io/api/#inject) . TypeScript 5.6 now allows you to use these arbitrary module identifiers in your code! We’d like to thank [Evan Wallace](https://github.com/evanw) who [contributed this change to TypeScript](https://github.com/microsoft/TypeScript/pull/58640) ! [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-6.html#the---nouncheckedsideeffectimports-option) The `--noUncheckedSideEffectImports` Option ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ In JavaScript it’s possible to `import` a module without actually importing any values from it. ts ` import "some-module"; ` These imports are often called _side effect imports_ because the only useful behavior they can provide is by executing some side effect (like registering a global variable, or adding a polyfill to a prototype). In TypeScript, this syntax has had a pretty strange quirk: if the `import` could be resolved to a valid source file, then TypeScript would load and check the file. On the other hand, if no source file could be found, TypeScript would silently ignore the `import`! This is surprising behavior, but it partially stems from modeling patterns in the JavaScript ecosystem. For example, this syntax has also been used with special loaders in bundlers to load CSS or other assets. Your bundler might be configured in such a way where you can include specific `.css` files by writing something like the following: tsx ` import "./button-component.css"; export function Button() { // ... } ` Still, this masks potential typos on side effect imports. That’s why TypeScript 5.6 introduces a new compiler option called `--noUncheckedSideEffectImports`, to catch these cases. When `--noUncheckedSideEffectImports` is enabled, TypeScript will now error if it can’t find a source file for a side effect import. ts ` import "oops-this-module-does-not-exist"; // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // error: Cannot find module 'oops-this-module-does-not-exist' or its corresponding type declarations. ` When enabling this option, some working code may now receive an error, like in the CSS example above. To work around this, users who want to just write side effect `import`s for assets might be better served by writing what’s called an _ambient module declaration_ with a wildcard specifier. It would go in a global file and look something like the following: ts ` // ./src/globals.d.ts // Recognize all CSS files as module imports. declare module "*.css" {} ` In fact, you might already have a file like this in your project! For example, running something like `vite init` might create a similar `vite-env.d.ts`. While this option is currently off by default, we encourage users to give it a try! For more information, [check out the implementation here](https://github.com/microsoft/TypeScript/pull/58941) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-6.html#the---nocheck-option) The `--noCheck` Option ------------------------------------------------------------------------------------------------------------------------------ TypeScript 5.6 introduces a new compiler option, `--noCheck`, which allows you to skip type checking for all input files. This avoids unnecessary type-checking when performing any semantic analysis necessary for emitting output files. One scenario for this is to separate JavaScript file generation from type-checking so that the two can be run as separate phases. For example, you could run `tsc --noCheck` while iterating, and then `tsc --noEmit` for a thorough type check. You could also run the two tasks in parallel, even in `--watch` mode, though note you’d probably want to specify a separate `--tsBuildInfoFile` path if you’re truly running them at the same time. `--noCheck` is also useful for emitting declaration files in a similar fashion. In a project where `--noCheck` is specified on a project that conforms to `--isolatedDeclarations`, TypeScript can quickly generate declaration files without a type-checking pass. The generated declaration files will rely purely on quick syntactic transformations. Note that in cases where `--noCheck` is specified, but a project does _not_ use `--isolatedDeclarations`, TypeScript may still perform as much type-checking as necessary to generate `.d.ts` files. In this sense, `--noCheck` is a bit of a misnomer; however, the process will be lazier than a full type-check, only calculating the types of unannotated declarations. This should be much faster than a full type-check. `noCheck` is also available via the TypeScript API as a standard option. Internally, `transpileModule` and `transpileDeclaration` already used `noCheck` to speed things up (at least as of TypeScript 5.5). Now any build tool should be able to leverage the flag, taking a variety of custom strategies to coordinate and speed up builds. For more information, see [the work done in TypeScript 5.5 to power up `noCheck` internally](https://github.com/microsoft/TypeScript/pull/58364) , along with the relevant work to make it publicly available [on the command line](https://github.com/microsoft/TypeScript/pull/58839) and [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-6.html#allow---build-with-intermediate-errors) Allow `--build` with Intermediate Errors ------------------------------------------------------------------------------------------------------------------------------------------------------------------ TypeScript’s concept of _project references_ allows you to organize your codebase into multiple projects and create dependencies between them. Running the TypeScript compiler in `--build` mode (or `tsc -b` for short) is the built-in way of actually conducting that build across projects and figuring out which projects and files need to be compiled. Previously, using `--build` mode would assume `--noEmitOnError` and immediately stop the build if any errors were encountered. This meant that “downstream” projects could never be checked and built if any of their “upstream” dependencies had build errors. In theory, this is a very cromulent approach - if a project has errors, it is not necessarily in a coherent state for its dependencies. In reality, this sort of rigidity made things like upgrades a pain. For example, if `projectB` depends on `projectA`, then people more familiar with `projectB` can’t proactively upgrade their code until their dependencies are upgraded. They are blocked by work on upgrading `projectA` first. As of TypeScript 5.6, `--build` mode will continue to build projects even if there are intermediate errors in dependencies. In the face of intermediate errors, they will be reported consistently and output files will be generated on a best-effort basis; however, the build will continue to completion on the specified project. If you want to stop the build on the first project with errors, you can use a new flag called `--stopOnBuildErrors`. This can be useful when running in a CI environment, or when iterating on a project that’s heavily depended upon by other projects. Note that to accomplish this, TypeScript now always emits a `.tsbuildinfo` file for any project in a `--build` invocation (even if `--incremental`/`--composite` is not specified). This is to keep track of the state of how `--build` was invoked and what work needs to be performed in the future. You can [read more about this change here on the implementation](https://github.com/microsoft/TypeScript/pull/58838) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-6.html#region-prioritized-diagnostics-in-editors) Region-Prioritized Diagnostics in Editors ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- When TypeScript’s language service is asked for the _diagnostics_ for a file (things like errors, suggestions, and deprecations), it would typically require checking the _entire file_. Most of the time this is fine, but in extremely large files it can incur a delay. That can be frustrating because fixing a typo should feel like a quick operation, but can take _seconds_ in a big-enough file. To address this, TypeScript 5.6 introduces a new feature called _region-prioritized diagnostics_ or _region-prioritized checking_. Instead of just requesting diagnostics for a set of files, editors can now also provide a relevant region of a given file - and the intent is that this will typically be the region of the file that is currently visible to a user. The TypeScript language server can then choose to provide two sets of diagnostics: one for the region, and one for the file in its entirety. This allows editing to feel _way_ more responsive in large files so you’re not waiting as long for thoes red squiggles to disappear. For some specific numbers, in our testing [on TypeScript’s own `checker.ts`](https://github.com/microsoft/TypeScript/blob/7319968e90600102892a79142fb804bcbe384160/src/compiler/checker.ts) , a full semantic diagnostics response took 3330ms. In contrast, the response for the first region-based diagnostics response took 143ms! While the remaining whole-file response took about 3200ms, this can make a huge difference for quick edits. This feature also includes quite a bit of work to also make diagnostics report more consistently throughout your experience. Due the way our type-checker leverages caching to avoid work, subsequent checks between the same types could often have a different (typically shorter) error message. Technically, lazy out-of-order checking could cause diagnostics to report differently between two locations in an editor - even before this feature - but we didn’t want to exacerbate the issue. With recent work, we’ve ironed out many of these error inconsistencies. Currently, this functionality is available in Visual Studio Code for TypeScript 5.6 and later. For more detailed information, [take a look at the implementation and write-up here](https://github.com/microsoft/TypeScript/pull/57842) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-6.html#granular-commit-characters) Granular Commit Characters ---------------------------------------------------------------------------------------------------------------------------------------- TypeScript’s language service now provides its own _commit characters_ for each completion item. Commit characters are specific characters that, when typed, will automatically commit the currently-suggested completion item. What this means is that over time your editor will now more frequently commit to the currently-suggested completion item when you type certain characters. For example, take the following code: ts ` declare let food: { eat(): any; } let f = (foo/**/ ` If our cursor is at `/**/`, it’s unclear if the code we’re writing is going to be something like `let f = (food.eat())` or `let f = (foo, bar) => foo + bar`. You could imagine that the editor might be able to auto-complete differently depending on which character we type out next. For instance, if we type in the period/dot character (`.`), we probably want the editor to complete with the variable `food`; but if we type the comma character (`,`), we might be writing out a parameter in an arrow function. Unfortunately, previously TypeScript just signaled to editors that the current text might define a new parameter name so that _no_ commit characters were safe. So hitting a `.` wouldn’t do anything even if it was “obvious” that the editor should auto-complete with the word `food`. TypeScript now explicitly lists which characters are safe to commit for each completion item. While this won’t _immediately_ change your day-to-day experience, editors that support these commit characters should see behavioral improvements over time. To see those improvements right now, you can now [use the TypeScript nightly extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.vscode-typescript-next) with [Visual Studio Code Insiders](https://code.visualstudio.com/insiders/) . Hitting `.` in the code above correctly auto-completes with `food`. For more information, see [the pull request that added commit characters](https://github.com/microsoft/TypeScript/pull/59339) along with our [adjustments to commit characters depending on context](https://github.com/microsoft/TypeScript/pull/59523) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-6.html#exclude-patterns-for-auto-imports) Exclude Patterns for Auto-Imports ------------------------------------------------------------------------------------------------------------------------------------------------------ TypeScript’s language service now allows you to specify a list of regular expression patterns which will filter away auto-import suggestions from certain specifiers. For example, if you want to exclude all “deep” imports from a package like `lodash`, you could configure the following preference in Visual Studio Code: json ` { "typescript.preferences.autoImportSpecifierExcludeRegexes": [ "^lodash/.*$" ] } ` Or going the other way, you might want to disallow importing from the entry-point of a package: json ` { "typescript.preferences.autoImportSpecifierExcludeRegexes": [ "^lodash$" ] } ` One could even avoid `node:` imports by using the following setting: json ` { "typescript.preferences.autoImportSpecifierExcludeRegexes": [ "^node:" ] } ` Note that if you want to specify certain flags like `i` or `u`, you will need to surround your regular expression with slashes. When providing surrounding slashes, you’ll need to escape other inner slashes. json ` { "typescript.preferences.autoImportSpecifierExcludeRegexes": [ "^./lib/internal", // no escaping needed "/^.\\/lib\\/internal/", // escaping needed - note the leading and trailing slashes "/^.\\/lib\\/internal/i" // escaping needed - we needed slashes to provide the 'i' regex flag ] } ` In Visual Studio Code, the same settings can be applied for JavaScript through `javascript.preferences.autoImportSpecifierExcludeRegexes`. For more information, [see the implementation here](https://github.com/microsoft/TypeScript/pull/59543) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-6.html#notable-behavioral-changes) Notable Behavioral Changes ---------------------------------------------------------------------------------------------------------------------------------------- This section highlights a set of noteworthy changes that should be acknowledged and understood as part of any upgrade. Sometimes it will highlight deprecations, removals, and new restrictions. It can also contain bug fixes that are functionally improvements, but which can also affect an existing build by introducing new errors. ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-6.html#libdts) `lib.d.ts` Types generated for the DOM may have an impact on type-checking your codebase. For more information, [see linked issues related to DOM and `lib.d.ts` updates for this version of TypeScript](https://github.com/microsoft/TypeScript/issues/58764) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-6.html#tsbuildinfo-is-always-written) `.tsbuildinfo` is Always Written To enable `--build` to continue building projects even if there are intermediate errors in dependencies, and to support `--noCheck` on the command line, TypeScript now always emits a `.tsbuildinfo` file for any project in a `--build` invocation. This happens regardless of whether `--incremental` is actually on. [See more information here](https://github.com/microsoft/TypeScript/pull/58626) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-6.html#respecting-file-extensions-and-packagejson-from-within-node_modules) Respecting File Extensions and `package.json` from within `node_modules` Before Node.js implemented support for ECMAScript modules in v12, there was never a good way for TypeScript to know whether `.d.ts` files it found in `node_modules` represented JavaScript files authored as CommonJS or ECMAScript modules. When the vast majority of npm was CommonJS-only, this didn’t cause many problems - if in doubt, TypeScript could just assume that everything behaved like CommonJS. Unfortunately, if that assumption was wrong it could allow unsafe imports: ts ` // node_modules/dep/index.d.ts export declare function doSomething(): void; // index.ts // Okay if "dep" is a CommonJS module, but fails if // it's an ECMAScript module - even in bundlers! import dep from "dep"; dep.doSomething(); ` In practice, this didn’t come up very often. But in the years since Node.js started supporting ECMAScript modules, the share of ESM on npm has grown. Fortunately, Node.js also introduced a mechanism that can help TypeScript determine if a file is an ECMAScript module or a CommonJS module: the `.mjs` and `.cjs` file extensions and the `package.json` `"type"` field. TypeScript 4.7 added support for understanding these indicators, as well as authoring `.mts` and `.cts` files; however, TypeScript would _only_ read those indicators under `--module node16` and `--module nodenext`, so the unsafe import above was still a problem for anyone using `--module esnext` and `--moduleResolution bundler`, for example. To solve this, TypeScript 5.6 collects module format information and uses it to resolve ambiguities like the one in the example above in _all_ `module` modes (except `amd`, `umd`, and `system`). Format-specific file extensions (`.mts` and `.cts`) are respected anywhere they’re found, and the `package.json` `"type"` field is consulted inside `node_modules` dependencies, regardless of the `module` setting. Previously, it was technically possible to produce CommonJS output into a `.mjs` file or vice versa: ts ` // main.mts export default "oops"; // $ tsc --module commonjs main.mts // main.mjs Object.defineProperty(exports, "__esModule", { value: true }); exports.default = "oops"; ` Now, `.mts` files never emit CommonJS output, and `.cts` files never emit ESM output. Note that much of this behavior was provided in pre-release versions of TypeScript 5.5 ([implementation details here](https://github.com/microsoft/TypeScript/pull/57896) ), but in 5.6 this behavior is only extended to files within `node_modules`. More details are available [on the change here](https://github.com/microsoft/TypeScript/pull/58825) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-6.html#correct-override-checks-on-computed-properties) Correct `override` Checks on Computed Properties Previously, computed properties marked with `override` did not correctly check for the existence of a base class member. Similarly, if you used `noImplicitOverride`, you would not get an error if you _forgot_ to add an `override` modifier to a computed property. TypeScript 5.6 now correctly checks computed properties in both cases. ts ` const foo = Symbol("foo"); const bar = Symbol("bar"); class Base { [bar]() {} } class Derived extends Base { override [foo]() {} // ~~~~~ // error: This member cannot have an 'override' modifier because it is not declared in the base class 'Base'. [bar]() {} // ~~~~~ // error under noImplicitOverride: This member must have an 'override' modifier because it overrides a member in the base class 'Base'. } ` This fix was contributed thanks to [Oleksandr Tarasiuk](https://github.com/a-tarasyuk) in [this pull request](https://github.com/microsoft/TypeScript/pull/57146) . The TypeScript docs are an open source project. Help us improve these pages [by sending a Pull Request](https://github.com/microsoft/TypeScript-Website/blob/v2/packages/documentation/copy/en/release-notes/TypeScript%205.6.md) ❤ Contributors to this page: N![navya9singh (6)](https://gravatar.com/avatar/e896fd3c90d7bd8d3c276d3ea2dd552ae62d81d4a497cdd589f551c5eb5ccb93?s=32&&d=blank) Last updated: Jul 14, 2025   MSG --- # TypeScript: Documentation - TypeScript 5.5 Was this page helpful? TypeScript 5.5 ============== [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#inferred-type-predicates) Inferred Type Predicates ------------------------------------------------------------------------------------------------------------------------------------ _This section was written by [Dan Vanderkam](https://github.com/danvk) , who [implemented this feature in TypeScript 5.5](https://github.com/microsoft/TypeScript/pull/57465) . Thanks Dan!_ TypeScript’s control flow analysis does a great job of tracking how the type of a variable changes as it moves through your code: tsx ` interface Bird { commonName: string; scientificName: string; sing(): void; } // Maps country names -> national bird. // Not all nations have official birds (looking at you, Canada!) declare const nationalBirds: Map; function makeNationalBirdCall(country: string) { const bird = nationalBirds.get(country); // bird has a declared type of Bird | undefined if (bird) { bird.sing(); // bird has type Bird inside the if statement } else { // bird has type undefined here. } } ` By making you handle the `undefined` case, TypeScript pushes you to write more robust code. In the past, this sort of type refinement was more difficult to apply to arrays. This would have been an error in all previous versions of TypeScript: tsx ` function makeBirdCalls(countries: string[]) { // birds: (Bird | undefined)[] const birds = countries .map(country => nationalBirds.get(country)) .filter(bird => bird !== undefined); for (const bird of birds) { bird.sing(); // error: 'bird' is possibly 'undefined'. } } ` This code is perfectly fine: we’ve filtered all the `undefined` values out of the list. But TypeScript hasn’t been able to follow along. With TypeScript 5.5, the type checker is fine with this code: tsx ` function makeBirdCalls(countries: string[]) { // birds: Bird[] const birds = countries .map(country => nationalBirds.get(country)) .filter(bird => bird !== undefined); for (const bird of birds) { bird.sing(); // ok! } } ` Note the more precise type for `birds`. This works because TypeScript now infers a [type predicate](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates) for the `filter` function. You can see what’s going on more clearly by pulling it out into a standalone function: tsx ` // function isBirdReal(bird: Bird | undefined): bird is Bird function isBirdReal(bird: Bird | undefined) { return bird !== undefined; } ` `bird is Bird` is the type predicate. It means that, if the function returns `true`, then it’s a `Bird` (if the function returns `false` then it’s `undefined`). The type declarations for `Array.prototype.filter` know about type predicates, so the net result is that you get a more precise type and the code passes the type checker. TypeScript will infer that a function returns a type predicate if these conditions hold: 1. The function does not have an explicit return type or type predicate annotation. 2. The function has a single `return` statement and no implicit returns. 3. The function does not mutate its parameter. 4. The function returns a `boolean` expression that’s tied to a refinement on the parameter. Generally this works how you’d expect. Here’s a few more examples of inferred type predicates: tsx ` // const isNumber: (x: unknown) => x is number const isNumber = (x: unknown) => typeof x === 'number'; // const isNonNullish: (x: T) => x is NonNullable const isNonNullish = (x: T) => x != null; ` Previously, TypeScript would have just inferred that these functions return `boolean`. It now infers signatures with type predicates like `x is number` or `x is NonNullable`. Type predicates have “if and only if” semantics. If a function returns `x is T`, then it means that: 1. If the function returns `true` then `x` has the type `T`. 2. If the function returns `false` then `x` does _not_ have type `T`. If you’re expecting a type predicate to be inferred but it’s not, then you may be running afoul of the second rule. This often comes up with “truthiness” checks: tsx ` function getClassroomAverage(students: string[], allScores: Map) { const studentScores = students .map(student => allScores.get(student)) .filter(score => !!score); return studentScores.reduce((a, b) => a + b) / studentScores.length; // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // error: Object is possibly 'undefined'. } ` TypeScript did not infer a type predicate for `score => !!score`, and rightly so: if this returns `true` then `score` is a `number`. But if it returns `false`, then `score` could be either `undefined` or a `number` (specifically, `0`). This is a real bug: if any student got a zero on the test, then filtering out their score will skew the average upwards. Fewer will be above average and more will be sad! As with the first example, it’s better to explicitly filter out `undefined` values: tsx ` function getClassroomAverage(students: string[], allScores: Map) { const studentScores = students .map(student => allScores.get(student)) .filter(score => score !== undefined); return studentScores.reduce((a, b) => a + b) / studentScores.length; // ok! } ` A truthiness check _will_ infer a type predicate for object types, where there’s no ambiguity. Remember that functions must return a `boolean` to be a candidate for an inferred type predicate: `x => !!x` might infer a type predicate, but `x => x` definitely won’t. Explicit type predicates continue to work exactly as before. TypeScript will not check whether it would infer the same type predicate. Explicit type predicates (“is”) are no safer than a type assertion (“as”). It’s possible that this feature will break existing code if TypeScript now infers a more precise type than you want. For example: tsx ` // Previously, nums: (number | null)[] // Now, nums: number[] const nums = [1, 2, 3, null, 5].filter(x => x !== null); nums.push(null); // ok in TS 5.4, error in TS 5.5 ` The fix is to tell TypeScript the type that you want using an explicit type annotation: tsx ` const nums: (number | null)[] = [1, 2, 3, null, 5].filter(x => x !== null); nums.push(null); // ok in all versions ` For more information, check out the [implementing pull request](https://github.com/microsoft/TypeScript/pull/57465) and [Dan’s blog post about implementing this feature](https://effectivetypescript.com/2024/04/16/inferring-a-type-predicate/) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#control-flow-narrowing-for-constant-indexed-accesses) Control Flow Narrowing for Constant Indexed Accesses -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- TypeScript is now able to narrow expressions of the form `obj[key]` when both `obj` and `key` are effectively constant. ts ` function f1(obj: Record, key: string) { if (typeof obj[key] === "string") { // Now okay, previously was error obj[key].toUpperCase(); } } ` In the above, neither `obj` nor `key` are ever mutated, so TypeScript can narrow the type of `obj[key]` to `string` after the `typeof` check. For more information, [see the implementing pull request here](https://github.com/microsoft/TypeScript/pull/57847) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#the-jsdoc-import-tag) The JSDoc `@import` Tag ------------------------------------------------------------------------------------------------------------------------------- Today, if you want to import something only for type-checking in a JavaScript file, it is cumbersome. JavaScript developers can’t simply import a type named `SomeType` if it’s not there at runtime. js ` // ./some-module.d.ts export interface SomeType { // ... } // ./index.js import { SomeType } from "./some-module"; // ❌ runtime error! /** * @param {SomeType} myValue */ function doSomething(myValue) { // ... } ` `SomeType` won’t exist at runtime, so the import will fail. Developers can instead use a namespace import instead. js ` import * as someModule from "./some-module"; /** * @param {someModule.SomeType} myValue */ function doSomething(myValue) { // ... } ` But `./some-module` is still imported at runtime - which might also not be desirable. To avoid this, developers typically had to use `import(...)` types in JSDoc comments. js ` /** * @param {import("./some-module").SomeType} myValue */ function doSomething(myValue) { // ... } ` If you wanted to reuse the same type in multiple places, you could use a `typedef` to avoid repeating the import. js ` /** * @typedef {import("./some-module").SomeType} SomeType */ /** * @param {SomeType} myValue */ function doSomething(myValue) { // ... } ` This helps with local uses of `SomeType`, but it gets repetitive for many imports and can be a bit verbose. That’s why TypeScript now supports a new `@import` comment tag that has the same syntax as ECMAScript imports. js ` /** @import { SomeType } from "some-module" */ /** * @param {SomeType} myValue */ function doSomething(myValue) { // ... } ` Here, we used named imports. We could also have written our import as a namespace import. js ` /** @import * as someModule from "some-module" */ /** * @param {someModule.SomeType} myValue */ function doSomething(myValue) { // ... } ` Because these are just JSDoc comments, they don’t affect runtime behavior at all. We would like to extend a big thanks to [Oleksandr Tarasiuk](https://github.com/a-tarasyuk) who contributed [this change](https://github.com/microsoft/TypeScript/pull/57207) ! [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#regular-expression-syntax-checking) Regular Expression Syntax Checking -------------------------------------------------------------------------------------------------------------------------------------------------------- Until now, TypeScript has typically skipped over most regular expressions in code. This is because regular expressions technically have an extensible grammar and TypeScript never made any effort to compile regular expressions to earlier versions of JavaScript. Still, this meant that lots of common problems would go undiscovered in regular expressions, and they would either turn into errors at runtime, or silently fail. But TypeScript now does basic syntax checking on regular expressions! ts ` let myRegex = /@robot(\s+(please|immediately)))? do some task/; // ~ // error! // Unexpected ')'. Did you mean to escape it with backslash? ` This is a simple example, but this checking can catch a lot of common mistakes. In fact, TypeScript’s checking goes slightly beyond syntactic checks. For instance, TypeScript can now catch issues around backreferences that don’t exist. ts ` let myRegex = /@typedef \{import\((.+)\)\.([a-zA-Z_]+)\} \3/u; // ~ // error! // This backreference refers to a group that does not exist. // There are only 2 capturing groups in this regular expression. ` The same applies to named capturing groups. ts ` let myRegex = /@typedef \{import\((?.+)\)\.(?[a-zA-Z_]+)\} \k/; // ~~~~~~~~~~~ // error! // There is no capturing group named 'namedImport' in this regular expression. ` TypeScript’s checking is now also aware of when certain RegExp features are used when newer than your target version of ECMAScript. For example, if we use named capturing groups like the above in an ES5 target, we’ll get an error. ts ` let myRegex = /@typedef \{import\((?.+)\)\.(?[a-zA-Z_]+)\} \k/; // ~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~ // error! // Named capturing groups are only available when targeting 'ES2018' or later. ` The same is true for certain regular expression flags as well. Note that TypeScript’s regular expression support is limited to regular expression _literals_. If you try calling `new RegExp` with a string literal, TypeScript will not check the provided string. We would like to thank [GitHub user graphemecluster](https://github.com/graphemecluster/) who iterated a ton with us [to get this feature into TypeScript](https://github.com/microsoft/TypeScript/pull/55600) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#support-for-new-ecmascript-set-methods) Support for New ECMAScript `Set` Methods ------------------------------------------------------------------------------------------------------------------------------------------------------------------ TypeScript 5.5 declares [new proposed methods for the ECMAScript `Set` type](https://github.com/tc39/proposal-set-methods) . Some of these methods, like `union`, `intersection`, `difference`, and `symmetricDifference`, take another `Set` and return a new `Set` as the result. The other methods, `isSubsetOf`, `isSupersetOf`, and `isDisjointFrom`, take another `Set` and return a `boolean`. None of these methods mutate the original `Set`s. Here’s a quick example of how you might use these methods and how they behave: ts ` let fruits = new Set(["apples", "bananas", "pears", "oranges"]); let applesAndBananas = new Set(["apples", "bananas"]); let applesAndOranges = new Set(["apples", "oranges"]); let oranges = new Set(["oranges"]); let emptySet = new Set(); //// // union //// // Set(4) {'apples', 'bananas', 'pears', 'oranges'} console.log(fruits.union(oranges)); // Set(3) {'apples', 'bananas', 'oranges'} console.log(applesAndBananas.union(oranges)); //// // intersection //// // Set(2) {'apples', 'bananas'} console.log(fruits.intersection(applesAndBananas)); // Set(0) {} console.log(applesAndBananas.intersection(oranges)); // Set(1) {'apples'} console.log(applesAndBananas.intersection(applesAndOranges)); //// // difference //// // Set(3) {'apples', 'bananas', 'pears'} console.log(fruits.difference(oranges)); // Set(2) {'pears', 'oranges'} console.log(fruits.difference(applesAndBananas)); // Set(1) {'bananas'} console.log(applesAndBananas.difference(applesAndOranges)); //// // symmetricDifference //// // Set(2) {'bananas', 'oranges'} console.log(applesAndBananas.symmetricDifference(applesAndOranges)); // no apples //// // isDisjointFrom //// // true console.log(applesAndBananas.isDisjointFrom(oranges)); // false console.log(applesAndBananas.isDisjointFrom(applesAndOranges)); // true console.log(fruits.isDisjointFrom(emptySet)); // true console.log(emptySet.isDisjointFrom(emptySet)); //// // isSubsetOf //// // true console.log(applesAndBananas.isSubsetOf(fruits)); // false console.log(fruits.isSubsetOf(applesAndBananas)); // false console.log(applesAndBananas.isSubsetOf(oranges)); // true console.log(fruits.isSubsetOf(fruits)); // true console.log(emptySet.isSubsetOf(fruits)); //// // isSupersetOf //// // true console.log(fruits.isSupersetOf(applesAndBananas)); // false console.log(applesAndBananas.isSupersetOf(fruits)); // false console.log(applesAndBananas.isSupersetOf(oranges)); // true console.log(fruits.isSupersetOf(fruits)); // false console.log(emptySet.isSupersetOf(fruits)); ` We’d like to thank [Kevin Gibbons](https://github.com/bakkot) who not only co-championed the feature in ECMAScript, but [also provided the declarations for `Set`, `ReadonlySet`, and `ReadonlySetLike` in TypeScript](https://github.com/microsoft/TypeScript/pull/57230) ! [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#isolated-declarations) Isolated Declarations ------------------------------------------------------------------------------------------------------------------------------ _This section was co-authored by [Rob Palmer](https://github.com/robpalme) who supported the design of isolated declarations._ Declaration files (a.k.a. `.d.ts` files) describe the shape of existing libraries and modules to TypeScript. This lightweight description includes the library’s type signatures and excludes implementation details such as the function bodies. They are published so that TypeScript can efficiently check your usage of a library without needing to analyse the library itself. Whilst it is possible to handwrite declaration files, if you are authoring typed code, it’s much safer and simpler to let TypeScript generate them automatically from source files using `--declaration`. The TypeScript compiler and its APIs have always had the job of generating declaration files; however, there are some use-cases where you might want to use other tools, or where the traditional build process doesn’t scale. ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#use-case-faster-declaration-emit-tools) Use-case: Faster Declaration Emit Tools Imagine if you wanted to create a faster tool to generate declaration files, perhaps as part of a publishing service or a new bundler. Whilst there is a thriving ecosystem of blazing fast tools that can turn TypeScript into JavaScript, the same is not true for turning TypeScript into declaration files. The reason is that TypeScript’s inference allows us to write code without explicitly declaring types, meaning declaration emit can be complex. Let’s consider a simple example of a function that adds two imported variables. ts ` // util.ts export let one = "1"; export let two = "2"; // add.ts import { one, two } from "./util"; export function add() { return one + two; } ` Even if the only thing we want to do is generate `add.d.ts`, TypeScript needs to crawl into another imported file (`util.ts`), infer that the type of `one` and `two` are strings, and then calculate that the `+` operator on two strings will lead to a `string` return type. ts ` // add.d.ts export declare function add(): string; ` While this inference is important for the developer experience, it means that tools that want to generate declaration files would need to replicate parts of the type-checker including inference and the ability to resolve module specifiers to follow the imports. ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#use-case-parallel-declaration-emit-and-parallel-checking) Use-case: Parallel Declaration Emit and Parallel Checking Imagine if you had a monorepo containing many projects and a multi-core CPU that just wished it could help you check your code faster. Wouldn’t it be great if we could check all those projects at the same time by running each project on a different core? Unfortunately we don’t have the freedom to do all the work in parallel. The reason is that we have to build those projects in dependency order, because each project is checking against the declaration files of their dependencies. So we must build the dependency first to generate the declaration files. TypeScript’s project references feature works the same way, building the set of projects in “topological” dependency order. As an example, if we have two projects called `backend` and `frontend`, and they both depend on a project called `core`, TypeScript can’t start type-checking either `frontend` or `backend` until `core` has been built and its declaration files have been generated. ![frontend and backend point to core, other stuff might point to each of those](https://devblogs.microsoft.com/typescript/wp-content/uploads/sites/11/2024/04/5-5-beta-isolated-declarations-deps.png) In the above graph, you can see that we have a bottleneck. Whilst we can build `frontend` and `backend` in parallel, we need to first wait for `core` to finish building before either can start. How could we improve upon this? Well, if a fast tool could generate all those declaration files for `core` _in parallel_, TypeScript then could immediately follow that by type-checking `core`, `frontend`, and `backend` also _in parallel_. ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#solution-explicit-types) Solution: Explicit Types! The common requirement in both use-cases is that we need a cross-file type-checker to generate declaration files. Which is a lot to ask from the tooling community. As a more complex example, if we want a declaration file for the following code… ts ` import { add } from "./add"; const x = add(); export function foo() { return x; } ` …we would need to generate a signature for `foo`. Well that requires looking at the implementation of `foo`. `foo` just returns `x`, so getting the type of `x` requires looking at the implementation of `add`. But that might require looking at the implementation of `add`’s dependencies, and so on. What we’re seeing here is that generating declaration files requires a whole lot of logic to figure out the types of different places that might not even be local to the current file. Still, for developers looking for fast iteration time and fully parallel builds, there is another way of thinking about this problem. A declaration file only requires the types of the public API of a module - in other words, the types of the things that are exported. If, controversially, developers are willing to explicitly write out the types of the things they export, tools could generate declaration files without needing to look at the implementation of the module - and without reimplementing a full type-checker. This is where the new `--isolatedDeclarations` option comes in. `--isolatedDeclarations` reports errors when a module can’t be reliably transformed without a type-checker. More plainly, it makes TypeScript report errors if you have a file that isn’t sufficiently annotated on its exports. That means in the above example, we would see an error like the following: ts ` export function foo() { // ~~~ // error! Function must have an explicit // return type annotation with --isolatedDeclarations. return x; } ` ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#why-are-errors-desirable) Why are errors desirable? Because it means that TypeScript can 1. Tell us up-front whether other tools will have issues with generating declaration files 2. Provide a quick fix to help add these missing annotations. This mode doesn’t require annotations _everywhere_ though. For locals, these can be ignored, since they don’t affect the public API. For example, the following code would **not** produce an error: ts ` import { add } from "./add"; const x = add("1", "2"); // no error on 'x', it's not exported. export function foo(): string { return x; } ` There are also certain expressions where the type is “trivial” to calculate. ts ` // No error on 'x'. // It's trivial to calculate the type is 'number' export let x = 10; // No error on 'y'. // We can get the type from the return expression. export function y() { return 20; } // No error on 'z'. // The type assertion makes it clear what the type is. export function z() { return Math.max(x, y()) as number; } ` ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#using-isolateddeclarations) Using `isolatedDeclarations` `isolatedDeclarations` requires that either the `declaration` or `composite` flags are also set. Note that `isolatedDeclarations` does not change how TypeScript performs emit - just how it reports errors. Importantly, and similar to `isolatedModules`, enabling the feature in TypeScript won’t immediately bring about the potential benefits discussed here. So please be patient and look forward to future developments in this space. Keeping tool authors in mind, we should also recognize that today, not all of TypeScript’s declaration emit can be easily replicated by other tools wanting to use it as a guide. That’s something we’re actively working on improving. On top of this, isolated declarations are still a new feature, and we’re actively working on improving the experience. Some scenarios, like using computed property declarations in classes and object literals, are not _yet_ supported under `isolatedDeclarations`. Keep an eye on this space, and feel free to provide us with feedback. We also feel it is worth calling out that `isolatedDeclarations` should be adopted on a case-by-case basis. There are some developer ergonomics that are lost when using `isolatedDeclarations`, and thus it may not be the right choice if your setup is not leveraging the two scenarios mentioned earlier. For others, the work on `isolatedDeclarations` has already uncovered many optimizations and opportunities to unlock different parallel build strategies. In the meantime, if you’re willing to make the trade-offs, we believe `isolatedDeclarations` can be a powerful tool to speed up your build process as external tooling becomes more widely available. For more information, read up on the [Isolated Declarations: State of the Feature](https://github.com/microsoft/TypeScript/issues/58944) discussion on the TypeScript issue tracker. ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#credit) Credit Work on `isolatedDeclarations` has been a long-time collaborative effort between the TypeScript team and the infrastructure and tooling teams within Bloomberg and Google. Individuals like Hana Joo from Google who implemented [the quick fix for isolated declaration errors](https://github.com/microsoft/TypeScript/pull/58260) (more on that soon), as well as Ashley Claymore, Jan Kühle, Lisa Velden, Rob Palmer, and Thomas Chetwin have been involved in discussion, specification, and implementation for many months. But we feel it is specifically worth calling out the tremendous amount of work provided by [Titian Cernicova-Dragomir](https://github.com/dragomirtitian) from Bloomberg. Titian has been instrumental in driving the implementation of `isolatedDeclarations` and has been a contributor to the TypeScript project for years prior. While the feature involved many changes, you can see [the core work for Isolated Declarations here](https://github.com/microsoft/TypeScript/pull/58201) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#the-configdir-template-variable-for-configuration-files) The `${configDir}` Template Variable for Configuration Files ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- It’s common in many codebases to reuse a shared `tsconfig.json` file that acts as a “base” for other configuration files. This is done by using the `extends` field in a `tsconfig.json` file. json ` { "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "./dist" } } ` One of the issues with this is that all paths in the `tsconfig.json` file are relative to the location of the file itself. This means that if you have a shared `tsconfig.base.json` file that is used by multiple projects, relative paths often won’t be useful in the derived projects. For example, imagine the following `tsconfig.base.json`: json ` { "compilerOptions": { "typeRoots": [ "./node_modules/@types" "./custom-types" ], "outDir": "dist" } } ` If author’s intent was that every `tsconfig.json` that extends this file should 1. output to a `dist` directory relative to the derived `tsconfig.json` , and 2. have a `custom-types` directory relative to the derived `tsconfig.json`, then this would not work. The `typeRoots` paths would be relative to the location of the shared `tsconfig.base.json` file, not the project that extends it. Each project that extends this shared file would need to declare its own `outDir` and `typeRoots` with identical contents. This could be frustrating and hard to keep in sync between projects, and while the example above is using `typeRoots`, this is a common problem for `paths` and other options. To solve this, TypeScript 5.5 introduces a new template variable `${configDir}`. When `${configDir}` is written in certain path fields of a `tsconfig.json` or `jsconfig.json` files, this variable is substituted with the containing directory of the configuration file in a given compilation. This means that the above `tsconfig.base.json` could be rewritten as: json ` { "compilerOptions": { "typeRoots": [ "${configDir}/node_modules/@types" "${configDir}/custom-types" ], "outDir": "${configDir}/dist" } } ` Now, when a project extends this file, the paths will be relative to the derived `tsconfig.json`, not the shared `tsconfig.base.json` file. This makes it easier to share configuration files across projects and ensures that the configuration files are more portable. If you intend to make a `tsconfig.json` file extendable, consider if a `./` should instead be written with `${configDir}`. For more information, see [the proposal issue](https://github.com/microsoft/TypeScript/issues/57485) and [the implementing pull request](https://github.com/microsoft/TypeScript/pull/58042) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#consulting-packagejson-dependencies-for-declaration-file-generation) Consulting `package.json` Dependencies for Declaration File Generation ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Previously, TypeScript would often issue an error message like ` The inferred type of "X" cannot be named without a reference to "Y". This is likely not portable. A type annotation is necessary. ` This was often due to TypeScript’s declaration file generation finding itself in the contents of files that were never explicitly imported in a program. Generating an import to such a file could be risky if the path ended up being relative. Still, for codebases with explicit dependencies in the `dependencies` (or `peerDependencies` and `optionalDependencies`) of a `package.json`, generating such an import should be safe under certain resolution modes. So in TypeScript 5.5, we’re more lenient when that’s the case, and many occurrences of this error should disappear. [See this pull request](https://github.com/microsoft/TypeScript/issues/42873) for more details on the change. [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#editor-and-watch-mode-reliability-improvements) Editor and Watch-Mode Reliability Improvements -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- TypeScript has either added some new functionality or fixed existing logic that makes `--watch` mode and TypeScript’s editor integration feel more reliable. That should hopefully translate to fewer TSServer/editor restarts. ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#correctly-refresh-editor-errors-in-configuration-files) Correctly Refresh Editor Errors in Configuration Files TypeScript can generate errors for `tsconfig.json` files; however, those errors are actually generated from loading a project, and editors typically don’t directly request those errors for `tsconfig.json` files. While this sounds like a technical detail, it means that when all errors issued in a `tsconfig.json` are fixed, TypeScript doesn’t issue a new fresh empty set of errors, and users are left with stale errors unless they reload their editor. TypeScript 5.5 now intentionally issues an event to clear these out. [See more here](https://github.com/microsoft/TypeScript/pull/58120) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#better-handling-for-deletes-followed-by-immediate-writes) Better Handling for Deletes Followed by Immediate Writes Instead of overwriting files, some tools will opt to delete them and then create new files from scratch. This is the case when running `npm ci`, for instance. While this can be efficient for those tools, it can be problematic for TypeScript’s editor scenarios where deleting a watched might dispose of it and all of its transitive dependencies. Deleting and creating a file in quick succession could lead to TypeScript tearing down an entire project and then rebuilding it from scratch. TypeScript 5.5 now has a more nuanced approach by keeping parts of a deleted project around until it picks up on a new creation event. This should make operations like `npm ci` work a lot better with TypeScript. See [more information on the approach here](https://github.com/microsoft/TypeScript/pull/57492) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#symlinks-are-tracked-in-failed-resolutions) Symlinks are Tracked in Failed Resolutions When TypeScript fails to resolve a module, it will still need to watch for any failed lookup paths in case the module is added later. Previously this was not done for symlinked directories, which could cause reliability issues in monorepo-like scenarios when a build occurred in one project but was not witnessed in the other. This should be fixed in TypeScript 5.5, and means you won’t need to restart your editor as often. [See more information here](https://github.com/microsoft/TypeScript/pull/58139) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#project-references-contribute-to-auto-imports) Project References Contribute to Auto-Imports Auto-imports no longer requires at least one explicit import to dependent projects in a project reference setup. Instead, auto-import completions should just work across anything you’ve listed in the `references` field of your `tsconfig.json`. [See more on the implementing pull request](https://github.com/microsoft/TypeScript/pull/55955) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#performance-and-size-optimizations) Performance and Size Optimizations -------------------------------------------------------------------------------------------------------------------------------------------------------- ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#monomorphized-objects-in-language-service-and-public-api) Monomorphized Objects in Language Service and Public API In TypeScript 5.0, we ensured that our [`Node`](https://github.com/microsoft/TypeScript/pull/51682) and [`Symbol`](https://github.com/microsoft/TypeScript/pull/51880) objects had a consistent set of properties with a consistent initialization order. Doing so helps reduce polymorphism in different operations, which allows runtimes to fetch properties more quickly. By making this change, we witnessed impressive speed wins in the compiler; however, most of these changes were performed on internal allocators for our data structures. The language service, along with TypeScript’s public API, uses a different set of allocators for certain objects. This allowed the TypeScript compiler to be a bit leaner, as data used only for the language service would never be used in the compiler. In TypeScript 5.5, the same monomorphization work has been done for the language service and public API. What this means is that your editor experience, and any build tools that use the TypeScript API, will get a decent amount faster. In fact, in our benchmarks, we’ve seen a **5-8% speedup in build times** when using the public TypeScript API’s allocators, and **language service operations getting 10-20% faster**. While this does imply an increase in memory, we believe that tradeoff is worth it and hope to find ways to reduce that memory overhead. Things should feel a lot snappier now. For more information, [see the change here](https://github.com/microsoft/TypeScript/pull/58045) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#monomorphized-control-flow-nodes) Monomorphized Control Flow Nodes In TypeScript 5.5, nodes of the control flow graph have been monomorphized so that they always hold a consistent shape. By doing so, check times will often be reduced by about 1%. [See this change here](https://github.com/microsoft/TypeScript/pull/57977) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#optimizations-on-our-control-flow-graph) Optimizations on our Control Flow Graph In many cases, control flow analysis will traverse nodes that don’t provide any new information. We observed that in the absence of any early termination or effects in the antecedents (or “dominators”) of certain nodes meant that those nodes could always be skipped over. As such, TypeScript now constructs its control flow graphs to take advantage of this by linking to an earlier node that _does_ provide interesting information for control flow analysis. This yields a flatter control flow graph, which can be more efficient to traverse. This optimization has yielded modest gains, but with up to 2% reductions in build time on certain codebases. You can [read more here](https://github.com/microsoft/TypeScript/pull/58013) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#skipped-checking-in-transpilemodule-and-transpiledeclaration) Skipped Checking in `transpileModule` and `transpileDeclaration` TypeScript’s `transpileModule` API can be used for compiling a single TypeScript file’s contents into JavaScript. Similarly, the `transpileDeclaration` API (see below) can be used to generate a declaration file for a single TypeScript file. One of the issues with these APIs is that TypeScript internally would perform a full type-checking pass over the entire contents of the file before emitting the output. This was necessary to collect certain information which would later be used for the emit phase. In TypeScript 5.5, we’ve found a way to avoid performing a full check, only lazily collecting this information as necessary, and `transpileModule` and `transpileDeclaration` both enable this functionality by default. As a result, tools that integrate with these APIs, like [ts-loader](https://www.npmjs.com/package/ts-loader) with `transpileOnly` and [ts-jest](https://www.npmjs.com/package/ts-jest) , should see a noticeable speedup. In our testing, [we generally witness around a 2x speed-up in build time using `transpileModule`](https://github.com/microsoft/TypeScript/pull/58364#issuecomment-2138580690) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#typescript-package-size-reduction) TypeScript Package Size Reduction Further leveraging [our transition to modules in 5.0](https://devblogs.microsoft.com/typescript/typescripts-migration-to-modules/) , we’ve significantly reduced TypeScript’s overall package size [by making `tsserver.js` and `typingsInstaller.js` import from a common API library instead of having each of them produce standalone bundles](https://github.com/microsoft/TypeScript/pull/55326) . This reduces TypeScript’s size on disk from 30.2 MB to 20.4 MB, and reduces its packed size from 5.5 MB to 3.7 MB! ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#node-reuse-in-declaration-emit) Node Reuse in Declaration Emit As part of the work to enable `isolatedDeclarations`, we’ve substantially improved how often TypeScript can directly copy your input source code when producing declaration files. For example, let’s say you wrote ts ` export const strBool: string | boolean = "hello"; export const boolStr: boolean | string = "world"; ` Note that the union types are equivalent, but the order of the union is different. When emitting the declaration file, TypeScript has two equivalent output possibilities. The first is to use a consistent canonical representation for each type: ts ` export const strBool: string | boolean; export const boolStr: string | boolean; ` The second is to re-use the type annotations exactly as written: ts ` export const strBool: string | boolean; export const boolStr: boolean | string; ` The second approach is generally preferable for a few reasons: * Many equivalent representations still encode some level of intent that is better to preserve in the declaration file * Producing a fresh representation of a type can be somewhat expensive, so avoiding is better * User-written types are usually shorter than generated type representations In 5.5, we’ve greatly improved the number of places where TypeScript can correctly identify places where it’s safe and correct to print back types exactly as they were written in the input file. Many of these cases are invisible performance improvements - TypeScript would generate fresh sets of syntax nodes and serialize them into a string. Instead, TypeScript can now operate over the original syntax nodes directly, which is much cheaper and faster. ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#caching-contextual-types-from-discriminated-unions) Caching Contextual Types from Discriminated Unions When TypeScript asks for the contextual type of an expression like an object literal, it will often encounter a union type. In those cases, TypeScript tries to filter out members of the union based on known properties with well known values (i.e. discriminant properties). This work can be fairly expensive, especially if you end up with an object consisting of many many properties. In TypeScript 5.5, [much of the computation is cached once so that TypeScript doesn’t need to recompute it for every property in the object literal](https://github.com/microsoft/TypeScript/pull/58372) . Performing this optimization shaved 250ms off of compiling the TypeScript compiler itself. [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#easier-api-consumption-from-ecmascript-modules) Easier API Consumption from ECMAScript Modules -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Previously, if you were writing an ECMAScript module in Node.js, named imports were not available from the `typescript` package. ts ` import { createSourceFile } from "typescript"; // ❌ error import * as ts from "typescript"; ts.createSourceFile // ❌ undefined??? ts.default.createSourceFile // ✅ works - but ugh! ` This is because [cjs-module-lexer](https://github.com/nodejs/cjs-module-lexer) did not recognize the pattern of TypeScript’s generated CommonJS code. This has been fixed, and users can now use named imports from the TypeScript npm package with ECMAScript modules in Node.js. ts ` import { createSourceFile } from "typescript"; // ✅ works now! import * as ts from "typescript"; ts.createSourceFile // ✅ works now! ` For more information, [see the change here](https://github.com/microsoft/TypeScript/pull/57133) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#the-transpiledeclaration-api) The `transpileDeclaration` API ---------------------------------------------------------------------------------------------------------------------------------------------- TypeScript’s API exposes a function called `transpileModule`. It’s intended to make it easy to compile a single file of TypeScript code. Because it doesn’t have access to an entire _program_, the caveat is that it may not produce the right output if the code violates any errors under the `isolatedModules` option. In TypeScript 5.5, we’ve added a new similar API called `transpileDeclaration`. This API is similar to `transpileModule`, but it’s specifically designed to generate a single _declaration file_ based on some input source text. Just like `transpileModule`, it doesn’t have access to a full program, and a similar caveat applies: it only generates an accurate declaration file if the input code is free of errors under the new `isolatedDeclarations` option. If desired, this function can be used to parallelize declaration emit across all files under `isolatedDeclarations` mode. For more information, [see the implementation here](https://github.com/microsoft/TypeScript/pull/58261) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#notable-behavioral-changes) Notable Behavioral Changes ---------------------------------------------------------------------------------------------------------------------------------------- This section highlights a set of noteworthy changes that should be acknowledged and understood as part of any upgrade. Sometimes it will highlight deprecations, removals, and new restrictions. It can also contain bug fixes that are functionally improvements, but which can also affect an existing build by introducing new errors. ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#disabling-features-deprecated-in-typescript-50) Disabling Features Deprecated in TypeScript 5.0 TypeScript 5.0 deprecated the following options and behaviors: * `charset` * `target: ES3` * `importsNotUsedAsValues` * `noImplicitUseStrict` * `noStrictGenericChecks` * `keyofStringsOnly` * `suppressExcessPropertyErrors` * `suppressImplicitAnyIndexErrors` * `out` * `preserveValueImports` * `prepend` in project references * implicitly OS-specific `newLine` To continue using the deprecated options above, developers using TypeScript 5.0 and other more recent versions have had to specify a new option called `ignoreDeprecations` with the value `"5.0"`. In TypeScript 5.5, these options no longer have any effect. To help with a smooth upgrade path, you may still specify them in your tsconfig, but these will be an error to specify in TypeScript 6.0. See also the [Flag Deprecation Plan](https://github.com/microsoft/TypeScript/issues/51000) which outlines our deprecation strategy. [More information around these deprecation plans is available on GitHub](https://github.com/microsoft/TypeScript/issues/51909) , which contains suggestions in how to best adapt your codebase. ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#libdts-changes) `lib.d.ts` Changes Types generated for the DOM may have an impact on type-checking your codebase. For more information, [see the DOM updates for TypeScript 5.5](https://github.com/microsoft/TypeScript/pull/58211) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#stricter-parsing-for-decorators) Stricter Parsing for Decorators Since TypeScript originally introduced support for decorators, the specified grammar for the proposal has been tightened up. TypeScript is now stricter about what forms it allows. While rare, existing decorators may need to be parenthesized to avoid errors. ts ` class DecoratorProvider { decorate(...args: any[]) { } } class D extends DecoratorProvider { m() { class C { @super.decorate // ❌ error method1() { } @(super.decorate) // ✅ okay method2() { } } } } ` See [more information on the change here](https://github.com/microsoft/TypeScript/pull/57749) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#undefined-is-no-longer-a-definable-type-name) `undefined` is No Longer a Definable Type Name TypeScript has always disallowed type alias names that conflict with built-in types: ts ` // Illegal type null = any; // Illegal type number = any; // Illegal type object = any; // Illegal type any = any; ` Due to a bug, this logic didn’t also apply to the built-in type `undefined`. In 5.5, this is now correctly identified as an error: ts ` // Now also illegal type undefined = any; ` Bare references to type aliases named `undefined` never actually worked in the first place. You could define them, but you couldn’t use them as an unqualified type name. ts ` export type undefined = string; export const m: undefined = ""; // ^ // Errors in 5.4 and earlier - the local definition of 'undefined' was not even consulted. ` For more information, [see the change here](https://github.com/microsoft/TypeScript/pull/57575) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#simplified-reference-directive-declaration-emit) Simplified Reference Directive Declaration Emit When producing a declaration file, TypeScript would synthesize a reference directive when it believed one was required. For example, all Node.js modules are declared ambiently, so cannot be loaded by module resolution alone. A file like: tsx ` import path from "path"; export const myPath = path.parse(__filename); ` Would emit a declaration file like: tsx ` /// import path from "path"; export declare const myPath: path.ParsedPath; ` Even though the reference directive never appeared in the original source. Similarly, TypeScript also _removed_ reference directives that it did not believe needed to be a part of the output. For example, let’s imagine we had a reference directive to `jest`; however, imagine the reference directive isn’t necessary to generate the declaration file. TypeScript would simply drop it. So in the following example: tsx ` /// import path from "path"; export const myPath = path.parse(__filename); ` TypeScript would still emit: tsx ` /// import path from "path"; export declare const myPath: path.ParsedPath; ` In the course of working on `isolatedDeclarations`, we realized that this logic was untenable for anyone attempting to implement a declaration emitter without type checking or using more than a single file’s context. This behavior is also hard to understand from a user’s perspective; whether or not a reference directive appeared in the emitted file seems inconsistent and difficult to predict unless you understand exactly what’s going on during typechecking. To prevent declaration emit from being different when `isolatedDeclarations` was enabled, we knew that our emit needed to change. Through [experimentation](https://github.com/microsoft/TypeScript/pull/57569) , we found that nearly all cases where TypeScript synthesized reference directives were just to pull in `node` or `react`. These are cases where the expectation is that a downstream user already references those types through tsconfig.json `"types"` or library imports, so no longer synthesizing these reference directives would be unlikely to break anyone. It’s worth noting that this is already how it works for `lib.d.ts`; TypeScript doesn’t synthesize a reference to `lib="es2015"` when a module exports a `WeakMap`, instead assuming that a downstream user will have included that as part of their environment. For reference directives that had been written by library authors (not synthesized), [further experimentation](https://github.com/microsoft/TypeScript/pull/57656) showed that nearly all were removed, never showing up in the output. Most reference directives that were preserved were broken and likely not intended to be preserved. Given those results, we decided to greatly simplfy reference directives in declaration emit in TypeScript 5.5. A more consistent strategy will help library authors and consumers have better control of their declaration files. Reference directives are no longer synthesized. User-written reference directives are no longer preserved, unless annotated with a new `preserve="true"` attribute. Concretely, an input file like: tsx ` /// /// import path from "path"; export const myPath = path.parse(__filename); ` will emit: tsx ` /// import path from "path"; export declare const myPath: path.ParsedPath; ` Adding `preserve="true"` is backwards compatible with older versions of TypeScript as unknown attributes are ignored. This change also improved performance; in our benchmarks, the emit stage saw a 1-4% improvement in projects with declaration emit enabled. The TypeScript docs are an open source project. Help us improve these pages [by sending a Pull Request](https://github.com/microsoft/TypeScript-Website/blob/v2/packages/documentation/copy/en/release-notes/TypeScript%205.5.md) ❤ Contributors to this page: N![navya9singh (6)](https://gravatar.com/avatar/e896fd3c90d7bd8d3c276d3ea2dd552ae62d81d4a497cdd589f551c5eb5ccb93?s=32&&d=blank) DR![Daniel Rosenwasser (1)](https://gravatar.com/avatar/3cb42391bbae78f84e416c9407fb9ef82c008ab291b8193611b0a77946c499d8?s=32&&d=blank) Last updated: Jul 14, 2025   MSG --- # TypeScript: Documentation - TypeScript 5.4 Was this page helpful? TypeScript 5.4 ============== [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-4.html#preserved-narrowing-in-closures-following-last-assignments) Preserved Narrowing in Closures Following Last Assignments -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- TypeScript can usually figure out a more specific type for a variable based on checks that you might perform. This process is called narrowing. ts ` function uppercaseStrings(x: string | number) { if (typeof x === "string") { // TypeScript knows 'x' is a 'string' here. return x.toUpperCase(); } } ` One common pain point was that these narrowed types weren’t always preserved within function closures. ts ` function getUrls(url: string | URL, names: string[]) { if (typeof url === "string") { url = new URL(url); } return names.map(name => { url.searchParams.set("name", name) // ~~~~~~~~~~~~ // error! // Property 'searchParams' does not exist on type 'string | URL'. return url.toString(); }); } ` Here, TypeScript decided that it wasn’t “safe” to assume that `url` was _actually_ a `URL` object in our callback function because it was mutated elsewhere; however, in this instance, that arrow function is _always_ created after that assignment to `url`, and it’s also the _last_ assignment to `url`. TypeScript 5.4 takes advantage of this to make narrowing a little smarter. When parameters and `let` variables are used in non-[hoisted](https://developer.mozilla.org/en-US/docs/Glossary/Hoisting) functions, the type-checker will look for a last assignment point. If one is found, TypeScript can safely narrow from outside the containing function. What that means is the above example just works now. Note that narrowing analysis doesn’t kick in if the variable is assigned anywhere in a nested function. This is because there’s no way to know for sure whether the function will be called later. ts ` function printValueLater(value: string | undefined) { if (value === undefined) { value = "missing!"; } setTimeout(() => { // Modifying 'value', even in a way that shouldn't affect // its type, will invalidate type refinements in closures. value = value; }, 500); setTimeout(() => { console.log(value.toUpperCase()); // ~~~~~ // error! 'value' is possibly 'undefined'. }, 1000); } ` This should make lots of typical JavaScript code easier to express. You can [read more about the change on GitHub](https://github.com/microsoft/TypeScript/pull/56908) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-4.html#the-noinfer-utility-type) The `NoInfer` Utility Type -------------------------------------------------------------------------------------------------------------------------------------- When calling generic functions, TypeScript is able to infer type arguments from whatever you pass in. ts ` function doSomething(arg: T) { // ... } // We can explicitly say that 'T' should be 'string'. doSomething("hello!"); // We can also just let the type of 'T' get inferred. doSomething("hello!"); ` One challenge, however, is that it is not always clear what the “best” type is to infer. This might lead to TypeScript rejecting valid calls, accepting questionable calls, or just reporting worse error messages when it catches a bug. For example, let’s imagine a `createStreetLight` function that takes a list of color names, along with an optional default color. ts ` function createStreetLight(colors: C[], defaultColor?: C) { // ... } createStreetLight(["red", "yellow", "green"], "red"); ` What happens when we pass in a `defaultColor` that wasn’t in the original `colors` array? In this function, `colors` is supposed to be the “source of truth” and describe what can be passed to `defaultColor`. ts ` // Oops! This is undesirable, but is allowed! createStreetLight(["red", "yellow", "green"], "blue"); ` In this call, type inference decided that `"blue"` was just as valid of a type as `"red"` or `"yellow"` or `"green"`. So instead of rejecting the call, TypeScript infers the type of `C` as `"red" | "yellow" | "green" | "blue"`. You might say that inference just blue up in our faces! One way people currently deal with this is to add a separate type parameter that’s bounded by the existing type parameter. ts ` function createStreetLight(colors: C[], defaultColor?: D) { } createStreetLight(["red", "yellow", "green"], "blue"); // ~~~~~~ // error! // Argument of type '"blue"' is not assignable to parameter of type '"red" | "yellow" | "green" | undefined'. ` This works, but is a little bit awkward because `D` probably won’t be used anywhere else in the signature for `createStreetLight`. While not bad _in this case_, using a type parameter only once in a signature is often a code smell. That’s why TypeScript 5.4 introduces a new `NoInfer` utility type. Surrounding a type in `NoInfer<...>` gives a signal to TypeScript not to dig in and match against the inner types to find candidates for type inference. Using `NoInfer`, we can rewrite `createStreetLight` as something like this: ts ` function createStreetLight(colors: C[], defaultColor?: NoInfer) { // ... } createStreetLight(["red", "yellow", "green"], "blue"); // ~~~~~~ // error! // Argument of type '"blue"' is not assignable to parameter of type '"red" | "yellow" | "green" | undefined'. ` Excluding the type of `defaultColor` from being explored for inference means that `"blue"` never ends up as an inference candidate, and the type-checker can reject it. You can see the specific changes in [the implementing pull request](https://github.com/microsoft/TypeScript/pull/56794) , along with [the initial implementation](https://github.com/microsoft/TypeScript/pull/52968) provided thanks to [Mateusz Burzyński](https://github.com/Andarist) ! [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-4.html#objectgroupby-and-mapgroupby) `Object.groupBy` and `Map.groupBy` -------------------------------------------------------------------------------------------------------------------------------------------------- TypeScript 5.4 adds declarations for JavaScript’s new `Object.groupBy` and `Map.groupBy` static methods. `Object.groupBy` takes an iterable, and a function that decides which “group” each element should be placed in. The function needs to make a “key” for each distinct group, and `Object.groupBy` uses that key to make an object where every key maps to an array with the original element in it. So the following JavaScript: js ` const array = [0, 1, 2, 3, 4, 5]; const myObj = Object.groupBy(array, (num, index) => { return num % 2 === 0 ? "even": "odd"; }); ` is basically equivalent to writing this: js ` const myObj = { even: [0, 2, 4], odd: [1, 3, 5], }; ` `Map.groupBy` is similar, but produces a `Map` instead of a plain object. This might be more desirable if you need the guarantees of `Map`s, you’re dealing with APIs that expect `Map`s, or you need to use any kind of key for grouping - not just keys that can be used as property names in JavaScript. js ` const myObj = Map.groupBy(array, (num, index) => { return num % 2 === 0 ? "even" : "odd"; }); ` and just as before, you could have created `myObj` in an equivalent way: js ` const myObj = new Map(); myObj.set("even", [0, 2, 4]); myObj.set("odd", [1, 3, 5]); ` Note that in the above example of `Object.groupBy`, the object produced uses all optional properties. ts ` interface EvenOdds { even?: number[]; odd?: number[]; } const myObj: EvenOdds = Object.groupBy(...); myObj.even; // ~~~~ // Error to access this under 'strictNullChecks'. ` This is because there’s no way to guarantee in a general way that _all_ the keys were produced by `groupBy`. Note also that these methods are only accessible by configuring your `target` to `esnext` or adjusting your `lib` settings. We expect they will eventually be available under a stable `es2024` target. We’d like to extend a thanks to [Kevin Gibbons](https://github.com/bakkot) for [adding the declarations to these `groupBy` methods](https://github.com/microsoft/TypeScript/pull/56805) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-4.html#support-for-require-calls-in---moduleresolution-bundler-and---module-preserve) Support for `require()` calls in `--moduleResolution bundler` and `--module preserve` ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ TypeScript has a `moduleResolution` option called `bundler` that is meant to model the way modern bundlers figure out which file an import path refers to. One of the limitations of the option is that it had to be paired with `--module esnext`, making it impossible to use the `import ... = require(...)` syntax. ts ` // previously errored import myModule = require("module/path"); ` That might not seem like a big deal if you’re planning on just writing standard ECMAScript `import`s, but there’s a difference when using a package with [conditional exports](https://nodejs.org/api/packages.html#conditional-exports) . In TypeScript 5.4, `require()` can now be used when setting the `module` setting to a new option called `preserve`. Between `--module preserve` and `--moduleResolution bundler`, the two more accurately model what bundlers and runtimes like Bun will allow, and how they’ll perform module lookups. In fact, when using `--module preserve`, the `bundler` option will be implicitly set for `--moduleResolution` (along with `--esModuleInterop` and `--resolveJsonModule`) json ` { "compilerOptions": { "module": "preserve", // ^ also implies: // "moduleResolution": "bundler", // "esModuleInterop": true, // "resolveJsonModule": true, // ... } } ` Under `--module preserve`, an ECMAScript `import` will always be emitted as-is, and `import ... = require(...)` will be emitted as a `require()` call (though in practice you may not even use TypeScript for emit, since it’s likely you’ll be using a bundler for your code). This holds true regardless of the file extension of the containing file. So the output of this code: ts ` import * as foo from "some-package/foo"; import bar = require("some-package/bar"); ` should look something like this: js ` import * as foo from "some-package/foo"; var bar = require("some-package/bar"); ` What this also means is that the syntax you choose directs how [conditional exports](https://nodejs.org/api/packages.html#conditional-exports) are matched. So in the above example, if the `package.json` of `some-package` looks like this: json ` { "name": "some-package", "version": "0.0.1", "exports": { "./foo": { "import": "./esm/foo-from-import.mjs", "require": "./cjs/foo-from-require.cjs" }, "./bar": { "import": "./esm/bar-from-import.mjs", "require": "./cjs/bar-from-require.cjs" } } } ` TypeScript will resolve these paths to `[...]/some-package/esm/foo-from-import.mjs` and `[...]/some-package/cjs/bar-from-require.cjs`. For more information, you can [read up on these new settings here](https://github.com/microsoft/TypeScript/pull/56785) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-4.html#checked-import-attributes-and-assertions) Checked Import Attributes and Assertions -------------------------------------------------------------------------------------------------------------------------------------------------------------------- Import attributes and assertions are now checked against the global `ImportAttributes` type. This means that runtimes can now more accurately describe the import attributes ts ` // In some global file. interface ImportAttributes { type: "json"; } // In some other module import * as ns from "foo" with { type: "not-json" }; // ~~~~~~~~~~ // error! // // Type '{ type: "not-json"; }' is not assignable to type 'ImportAttributes'. // Types of property 'type' are incompatible. // Type '"not-json"' is not assignable to type '"json"'. ` [This change](https://github.com/microsoft/TypeScript/pull/56034) was provided thanks to [Oleksandr Tarasiuk](https://github.com/a-tarasyuk) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-4.html#quick-fix-for-adding-missing-parameters) Quick Fix for Adding Missing Parameters ------------------------------------------------------------------------------------------------------------------------------------------------------------------ TypeScript now has a quick fix to add a new parameter to functions that are called with too many arguments. ![A quick fix being offered when someFunction calls someHelperFunction with 2 more arguments than are expected.](https://devblogs.microsoft.com/typescript/wp-content/uploads/sites/11/2024/01/add-missing-params-5-4-beta-before.png) ![The missing arguments have been added to someHelperFunction after the quick fix was applied.](https://devblogs.microsoft.com/typescript/wp-content/uploads/sites/11/2024/01/add-missing-params-5-4-beta-after.png) This can be useful when threading a new argument through several existing functions, which can be cumbersome today. [This quick fix](https://github.com/microsoft/TypeScript/pull/56411) was provided courtsey of [Oleksandr Tarasiuk](https://github.com/a-tarasyuk) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-4.html#upcoming-changes-from-typescript-50-deprecations) Upcoming Changes from TypeScript 5.0 Deprecations ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- TypeScript 5.0 deprecated the following options and behaviors: * `charset` * `target: ES3` * `importsNotUsedAsValues` * `noImplicitUseStrict` * `noStrictGenericChecks` * `keyofStringsOnly` * `suppressExcessPropertyErrors` * `suppressImplicitAnyIndexErrors` * `out` * `preserveValueImports` * `prepend` in project references * implicitly OS-specific `newLine` To continue using them, developers using TypeScript 5.0 and other more recent versions have had to specify a new option called `ignoreDeprecations` with the value `"5.0"`. However, TypScript 5.4 will be the last version in which these will continue to function as normal. By TypeScript 5.5 (likely June 2024), these will become hard errors, and code using them will need to be migrated away. For more information, you can [read up on this plan on GitHub](https://github.com/microsoft/TypeScript/issues/51909) , which contains suggestions in how to best adapt your codebase. [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-4.html#notable-behavioral-changes) Notable Behavioral Changes ---------------------------------------------------------------------------------------------------------------------------------------- This section highlights a set of noteworthy changes that should be acknowledged and understood as part of any upgrade. Sometimes it will highlight deprecations, removals, and new restrictions. It can also contain bug fixes that are functionally improvements, but which can also affect an existing build by introducing new errors. ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-4.html#libdts-changes) `lib.d.ts` Changes Types generated for the DOM may have an impact on type-checking your codebase. For more information, [see the DOM updates for TypeScript 5.4](https://github.com/microsoft/TypeScript/pull/57027) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-4.html#more-accurate-conditional-type-constraints) More Accurate Conditional Type Constraints The following code no longer allows the second variable declaration in the function `foo`. ts ` type IsArray = T extends any[] ? true : false; function foo(x: IsArray) { let first: true = x; // Error let second: false = x; // Error, but previously wasn't } ` Previously, when TypeScript checked the initializer for `second`, it needed to determine whether `IsArray` was assignable to the unit type `false`. While `IsArray` isn’t compatible any obvious way, TypeScript looks at the _constraint_ of that type as well. In a conditional type like `T extends Foo ? TrueBranch : FalseBranch`, where `T` is generic, the type system would look at the constraint of `T`, substitute it in for `T` itself, and decide on either the true or false branch. But this behavior was inaccurate because it was overly eager. Even if the constraint of `T` isn’t assignable to `Foo`, that doesn’t mean that it won’t be instantiated with something that is. And so the more correct behavior is to produce a union type for the constraint of the conditional type in cases where it can’t be proven that `T` _never_ or _always_ extends `Foo.` TypeScript 5.4 adopts this more accurate behavior. What this means in practice is that you may begin to find that some conditional type instances are no longer compatible with their branches. [You can read about the specific changes here](https://github.com/microsoft/TypeScript/pull/56004) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-4.html#more-aggressive-reduction-of-intersections-between-type-variables-and-primitive-types) More Aggressive Reduction of Intersections Between Type Variables and Primitive Types TypeScript now reduces intersections with type variables and primitives more aggressively, depending on how the type variable’s constraint overlaps with those primitives. ts ` declare function intersect(x: T, y: U): T & U; function foo(x: T, str: string, num: number) { // Was 'T & string', now is just 'T' let a = intersect(x, str); // Was 'T & number', now is just 'never' let b = intersect(x, num) // Was '(T & "abc") | (T & "def")', now is just 'T' let c = Math.random() < 0.5 ? intersect(x, "abc") : intersect(x, "def"); } ` For more information, [see the change here](https://github.com/microsoft/TypeScript/pull/56515) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-4.html#improved-checking-against-template-strings-with-interpolations) Improved Checking Against Template Strings with Interpolations TypeScript now more accurately checks whether or not strings are assignable to the placeholder slots of a template string type. ts `` function a() { let x: `-${keyof T & string}`; // Used to error, now doesn't. x = "-id"; } `` This behavior is more desirable, but may cause breaks in code using constructs like conditional types, where these rule changes are easy to witness. [See this change](https://github.com/microsoft/TypeScript/pull/56598) for more details. ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-4.html#errors-when-type-only-imports-conflict-with-local-values) Errors When Type-Only Imports Conflict with Local Values Previously, TypeScript would permit the following code under `isolatedModules` if the import to `Something` only referred to a type. ts ` import { Something } from "./some/path"; let Something = 123; ` However, it’s not safe for single-file compilers to assume whether it’s “safe” to drop the `import`, even if the code is guaranteed to fail at runtime. In TypeScript 5.4, this code will trigger an error like the following: ` Import 'Something' conflicts with local value, so must be declared with a type-only import when 'isolatedModules' is enabled. ` The fix should be to either make a local rename, or, as the error states, add the `type` modifier to the import: ts ` import type { Something } from "./some/path"; // or import { type Something } from "./some/path"; ` [See more information on the change itself](https://github.com/microsoft/TypeScript/pull/56354) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-4.html#new-enum-assignability-restrictions) New Enum Assignability Restrictions When two enums have the same declared names and enum member names, they were previously always considered compatible; however, when the values were known, TypeScript would silently allow them to have differing values. TypeScript 5.4 tightens this restriction by requiring the values to be identical when they are known. ts ` namespace First { export enum SomeEnum { A = 0, B = 1, } } namespace Second { export enum SomeEnum { A = 0, B = 2, } } function foo(x: First.SomeEnum, y: Second.SomeEnum) { // Both used to be compatible - no longer the case, // TypeScript errors with something like: // // Each declaration of 'SomeEnum.B' differs in its value, where '1' was expected but '2' was given. x = y; y = x; } ` Additionally, there are new restrictions for when one of the enum members does not have a statically known value. In these cases, the other enum must at least be implicitly numeric (e.g. it has no statically resolved initializer), or it is explicitly numeric (meaning TypeScript could resolve the value to something numeric). Practically speaking, what this means is that string enum members are only ever compatible with other string enums of the same value. ts ` namespace First { export declare enum SomeEnum { A, B, } } namespace Second { export declare enum SomeEnum { A, B = "some known string", } } function foo(x: First.SomeEnum, y: Second.SomeEnum) { // Both used to be compatible - no longer the case, // TypeScript errors with something like: // // One value of 'SomeEnum.B' is the string '"some known string"', and the other is assumed to be an unknown numeric value. x = y; y = x; } ` For more information, [see the pull request that introduced this change](https://github.com/microsoft/TypeScript/pull/55924) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-4.html#name-restrictions-on-enum-members) Name Restrictions on Enum Members TypeScript no longer allows enum members to use the names `Infinity`, `-Infinity`, or `NaN`. ts ` // Errors on all of these: // // An enum member cannot have a numeric name. enum E { Infinity = 0, "-Infinity" = 1, NaN = 2, } ` [See more details here](https://github.com/microsoft/TypeScript/pull/56161) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-4.html#better-mapped-type-preservation-over-tuples-with-any-rest-elements) Better Mapped Type Preservation Over Tuples with `any` Rest Elements Previously, applying a mapped type with `any` into a tuple would create an `any` element type. This is undesirable and is now fixed. ts ` Promise.all(["", ...([] as any)]) .then((result) => { const head = result[0]; // 5.3: any, 5.4: string const tail = result.slice(1); // 5.3 any, 5.4: any[] }); ` For more information, see [the fix](https://github.com/microsoft/TypeScript/pull/57031) along with [the follow-on discussion around behavioral changes](https://github.com/microsoft/TypeScript/issues/57389) and [further tweaks](https://github.com/microsoft/TypeScript/issues/57389) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-4.html#emit-changes) Emit Changes While not a breaking change per se, developers may have implicitly taken dependencies on TypeScript’s JavaScript or declaration emit outputs. The following are notable changes. * [Preserve type parameter names more often when shadowed](https://github.com/microsoft/TypeScript/pull/55820) * [Move complex parameter lists of async function into downlevel generator body](https://github.com/microsoft/TypeScript/pull/56296) * [Do not remove binding alias in function declarations](https://github.com/microsoft/TypeScript/pull/57020) * [ImportAttributes should go through the same emit phases when in an ImportTypeNode](https://github.com/microsoft/TypeScript/pull/56395) The TypeScript docs are an open source project. Help us improve these pages [by sending a Pull Request](https://github.com/microsoft/TypeScript-Website/blob/v2/packages/documentation/copy/en/release-notes/TypeScript%205.4.md) ❤ Contributors to this page: N![navya9singh (6)](https://gravatar.com/avatar/e896fd3c90d7bd8d3c276d3ea2dd552ae62d81d4a497cdd589f551c5eb5ccb93?s=32&&d=blank) I![IC-EnzoD-FRA (1)](https://gravatar.com/avatar/a86c74b5166ec42050a2c2227b2b84cac659f19d96d9515d2cc8ac58fc3a118c?s=32&&d=blank) Last updated: Jul 14, 2025   MSG --- # TypeScript: Documentation - TypeScript 5.3 Was this page helpful? TypeScript 5.3 ============== [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-3.html#import-attributes) Import Attributes ---------------------------------------------------------------------------------------------------------------------- TypeScript 5.3 supports the latest updates to the [import attributes](https://github.com/tc39/proposal-import-attributes) proposal. One use-case of import attributes is to provide information about the expected format of a module to the runtime. ts `` // We only want this to be interpreted as JSON, // not a runnable/malicious JavaScript file with a `.json` extension. import obj from "./something.json" with { type: "json" }; `` The contents of these attributes are not checked by TypeScript since they’re host-specific, and are simply left alone so that browsers and runtimes can handle them (and possibly error). ts ` // TypeScript is fine with this. // But your browser? Probably not. import * as foo from "./foo.js" with { type: "fluffy bunny" }; ` Dynamic `import()` calls can also use import attributes through a second argument. ts ` const obj = await import("./something.json", { with: { type: "json" } }); ` The expected type of that second argument is defined by a type called `ImportCallOptions`, which by default just expects a property called `with`. Note that import attributes are an evolution of an earlier proposal called [“import assertions”, which were implemented in TypeScript 4.5](https://devblogs.microsoft.com/typescript/announcing-typescript-4-5/#import-assertions) . The most obvious difference is the use of the `with` keyword over the `assert` keyword. But the less-visible difference is that runtimes are now free to use attributes to guide the resolution and interpretation of import paths, whereas import assertions could only assert some characteristics after loading a module. Over time, TypeScript will be deprecating the old syntax for import assertions in favor of the proposed syntax for import attributes. Existing code using `assert` should migrate towards the `with` keyword. New code that needs an import attribute should use `with` exclusively. We’d like to thank [Oleksandr Tarasiuk](https://github.com/a-tarasyuk) for [implementing this proposal](https://github.com/microsoft/TypeScript/pull/54242) ! And we’d also like to call out [Wenlu Wang](https://github.com/Kingwl) for their implementation of [import assertions](https://github.com/microsoft/TypeScript/pull/40698) ! [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-3.html#stable-support-resolution-mode-in-import-types) Stable Support `resolution-mode` in Import Types ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- In TypeScript 4.7, TypeScript added support for a `resolution-mode` attribute in `/// ` to control whether a specifier should be resolved via `import` or `require` semantics. ts ` /// // or /// ` A corresponding field was added to import assertions on type-only imports as well; however, it was only supported in nightly versions of TypeScript. The rationale was that in spirit, import _assertions_ were not intended to guide module resolution. So this feature was shipped experimentally in a nightly-only mode to get more feedback. But given that _[import attributes](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-3.html#import-attributes) _ can guide resolution, and that we’ve seen reasonable use-cases, TypeScript 5.3 now supports the `resolution-mode` attribute for `import type`. ts `` // Resolve `pkg` as if we were importing with a `require()` import type { TypeFromRequire } from "pkg" with { "resolution-mode": "require" }; // Resolve `pkg` as if we were importing with an `import` import type { TypeFromImport } from "pkg" with { "resolution-mode": "import" }; export interface MergedType extends TypeFromRequire, TypeFromImport {} `` These import attributes can also be used on `import()` types. ts ` export type TypeFromRequire = import("pkg", { with: { "resolution-mode": "require" } }).TypeFromRequire; export type TypeFromImport = import("pkg", { with: { "resolution-mode": "import" } }).TypeFromImport; export interface MergedType extends TypeFromRequire, TypeFromImport {} ` For more information, [check out the change here](https://github.com/microsoft/TypeScript/pull/55725) [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-3.html#resolution-mode-supported-in-all-module-modes) `resolution-mode` Supported in All Module Modes -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Previously, using `resolution-mode` was only allowed under the `moduleResolution` options `node16` and `nodenext`. To make it easier to look up modules specifically for type purposes, `resolution-mode` now works appropriately in all other `moduleResolution` options like `bundler`, `node10`, and simply doesn’t error under `classic`. For more information, [see the implementing pull request](https://github.com/microsoft/TypeScript/pull/55725) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-3.html#switch-true-narrowing) `switch (true)` Narrowing ---------------------------------------------------------------------------------------------------------------------------------- TypeScript 5.3 now can perform narrowing based on conditions in each `case` clause within a `switch (true)`. ts ` function f(x: unknown) { switch (true) { case typeof x === "string": // 'x' is a 'string' here console.log(x.toUpperCase()); // falls through... case Array.isArray(x): // 'x' is a 'string | any[]' here. console.log(x.length); // falls through... default: // 'x' is 'unknown' here. // ... } } ` [This feature](https://github.com/microsoft/TypeScript/pull/55991) was spearheaded [initial work](https://github.com/microsoft/TypeScript/pull/53681) by [Mateusz Burzyński](https://github.com/Andarist) We’d like to extend a “thank you!” for this contribution. [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-3.html#narrowing-on-comparisons-to-booleans) Narrowing On Comparisons to Booleans ------------------------------------------------------------------------------------------------------------------------------------------------------------ Occasionally you may find yourself performing a direct comparison with `true` or `false` in a condition. Usually these are unnecessary comparisons, but you might prefer it as a point of style, or to avoid certain issues around JavaScript truthiness. Regardless, previously TypeScript just didn’t recognize such forms when performing narrowing. TypeScript 5.3 now keeps up and understands these expressions when narrowing variables. ts ` interface A { a: string; } interface B { b: string; } type MyType = A | B; function isA(x: MyType): x is A { return "a" in x; } function someFn(x: MyType) { if (isA(x) === true) { console.log(x.a); // works! } } ` We’d like to thank [Mateusz Burzyński](https://github.com/Andarist) for [the pull request](https://github.com/microsoft/TypeScript/pull/53681) that implemented this. [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-3.html#instanceof-narrowing-through-symbolhasinstance) `instanceof` Narrowing Through `Symbol.hasInstance` ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- A slightly esoteric feature of JavaScript is that it is possible to override the behavior of the `instanceof` operator. To do so, the value on the right side of the `instanceof` operator needs to have a specific method named by `Symbol.hasInstance`. js ` class Weirdo { static [Symbol.hasInstance](testedValue) { // wait, what? return testedValue === undefined; } } // false console.log(new Thing() instanceof Weirdo); // true console.log(undefined instanceof Weirdo); ` To better model this behavior in `instanceof`, TypeScript now checks if such a `[Symbol.hasInstance]` method exists and is declared as a type predicate function. If it does, the tested value on the left side of the `instanceof` operator will be narrowed appropriately by that type predicate. ts ` interface PointLike { x: number; y: number; } class Point implements PointLike { x: number; y: number; constructor(x: number, y: number) { this.x = x; this.y = y; } distanceFromOrigin() { return Math.sqrt(this.x ** 2 + this.y ** 2); } static [Symbol.hasInstance](val: unknown): val is PointLike { return !!val && typeof val === "object" && "x" in val && "y" in val && typeof val.x === "number" && typeof val.y === "number"; } } function f(value: unknown) { if (value instanceof Point) { // Can access both of these - correct! value.x; value.y; // Can't access this - we have a 'PointLike', // but we don't *actually* have a 'Point'. value.distanceFromOrigin(); } } ` As you can see in this example, `Point` defines its own `[Symbol.hasInstance]` method. It actually acts as a custom type guard over a separate type called `PointLike`. In the function `f`, we were able to narrow `value` down to a `PointLike` with `instanceof`, but _not_ a `Point`. That means that we can access the properties `x` and `y`, but not the method `distanceFromOrigin`. For more information, you can [read up on this change here](https://github.com/microsoft/TypeScript/pull/55052) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-3.html#checks-for-super-property-accesses-on-instance-fields) Checks for `super` Property Accesses on Instance Fields ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ In JavaScript, it’s possible to access a declaration in a base class through the `super` keyword. js ` class Base { someMethod() { console.log("Base method called!"); } } class Derived extends Base { someMethod() { console.log("Derived method called!"); super.someMethod(); } } new Derived().someMethod(); // Prints: // Derived method called! // Base method called! ` This is different from writing something like `this.someMethod()`, since that could invoke an overridden method. This is a subtle distinction, made more subtle by the fact that often the two can be interchangeable if a declaration is never overridden at all. js ` class Base { someMethod() { console.log("someMethod called!"); } } class Derived extends Base { someOtherMethod() { // These act identically. this.someMethod(); super.someMethod(); } } new Derived().someOtherMethod(); // Prints: // someMethod called! // someMethod called! ` The problem is using them interchangeably is that `super` only works on members declared on the prototype — _not_ instance properties. That means that if you wrote `super.someMethod()`, but `someMethod` was defined as a field, you’d get a runtime error! ts ` class Base { someMethod = () => { console.log("someMethod called!"); } } class Derived extends Base { someOtherMethod() { super.someMethod(); } } new Derived().someOtherMethod(); // 💥 // Doesn't work because 'super.someMethod' is 'undefined'. ` TypeScript 5.3 now more-closely inspects `super` property accesses/method calls to see if they correspond to class fields. If they do, we’ll now get a type-checking error. [This check](https://github.com/microsoft/TypeScript/pull/54056) was contributed thanks to [Jack Works](https://github.com/Jack-Works) ! [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-3.html#interactive-inlay-hints-for-types) Interactive Inlay Hints for Types ------------------------------------------------------------------------------------------------------------------------------------------------------ TypeScript’s inlay hints now support jumping to the definition of types! This makes it easier to casually navigate your code. ![Ctrl-clicking an inlay hint to jump to the definition of a parameter type.](https://devblogs.microsoft.com/typescript/wp-content/uploads/sites/11/2023/10/clickable-inlay-hints-for-types-5-3-beta.gif) See more at [the implementation here](https://github.com/microsoft/TypeScript/pull/55141) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-3.html#settings-to-prefer-type-auto-imports) Settings to Prefer `type` Auto-Imports -------------------------------------------------------------------------------------------------------------------------------------------------------------- Previously when TypeScript generated auto-imports for something in a type position, it would add a `type` modifier based on your settings. For example, when getting an auto-import on `Person` in the following: ts ` export let p: Person ` TypeScript’s editing experience would usually add an import for `Person` as: ts ` import { Person } from "./types"; export let p: Person ` and under certain settings like `verbatimModuleSyntax`, it would add the `type` modifier: ts ` import { type Person } from "./types"; export let p: Person ` However, maybe your codebase isn’t able to use some of these options; or you just have a preference for explicit `type` imports when possible. [With a recent change](https://github.com/microsoft/TypeScript/pull/56090) , TypeScript now enables this to be an editor-specific option. In Visual Studio Code, you can enable it in the UI under “TypeScript › Preferences: Prefer Type Only Auto Imports”, or as the JSON configuration option `typescript.preferences.preferTypeOnlyAutoImports` [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-3.html#optimizations-by-skipping-jsdoc-parsing) Optimizations by Skipping JSDoc Parsing ------------------------------------------------------------------------------------------------------------------------------------------------------------------ When running TypeScript via `tsc`, the compiler will now avoid parsing JSDoc. This drops parsing time on its own, but also reduces memory usage to store comments along with time spent in garbage collection. All-in-all, you should see slightly faster compiles and quicker feedback in `--watch` mode. [The specific changes can be viewed here](https://github.com/microsoft/TypeScript/pull/52921) . Because not every tool using TypeScript will need to store JSDoc (e.g. typescript-eslint and Prettier), this parsing strategy has been surfaced as part of the API itself. This can enable these tools to gain the same memory and speed improvements we’ve brought to the TypeScript compiler. The new options for comment parsing strategy are described in `JSDocParsingMode`. More information is available [on this pull request](https://github.com/microsoft/TypeScript/pull/55739) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-3.html#optimizations-by-comparing-non-normalized-intersections) Optimizations by Comparing Non-Normalized Intersections -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- In TypeScript, unions and intersections always follow a specific form, where intersections can’t contain union types. That means that when we create an intersection over a union like `A & (B | C)`, that intersection will be normalized into `(A & B) | (A & C)`. Still, in some cases the type system will maintain the original form for display purposes. It turns out that the original form can be used for some clever fast-path comparisons between types. For example, let’s say we have `SomeType & (Type1 | Type2 | ... | Type99999NINE)` and we want to see if that’s assignable to `SomeType`. Recall that we don’t really have an intersection as our source type — we have a union that looks like `(SomeType & Type1) | (SomeType & Type2) | ... |(SomeType & Type99999NINE)`. When checking if a union is assignable to some target type, we have to check if _every_ member of the union is assignable to the target type, and that can be very slow. In TypeScript 5.3, we peek at the original intersection form that we were able to tuck away. When we compare the types, we do a quick check to see if the target exists in any constituent of the source intersection. For more information, [see this pull request](https://github.com/microsoft/TypeScript/pull/55851) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-3.html#consolidation-between-tsserverlibraryjs-and-typescriptjs) Consolidation Between `tsserverlibrary.js` and `typescript.js` ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- TypeScript itself ships two library files: `tsserverlibrary.js` and `typescript.js`. There are certain APIs available only in `tsserverlibrary.js` (like the `ProjectService` API), which may be useful to some importers. Still, the two are distinct bundles with a lot of overlap, duplicating code in the package. What’s more, it can be challenging to consistently use one over the other due to auto-imports or muscle memory. Accidentally loading both modules is far too easy, and code may not work properly on a different instance of the API. Even if it does work, loading a second bundle increases resource usage. Given this, we’ve decided to consolidate the two. `typescript.js` now contains what `tsserverlibrary.js` used to contain, and `tsserverlibrary.js` now simply re-exports `typescript.js`. Comparing the before/after of this consolidation, we saw the following reduction in package size: | | Before | After | Diff | Diff (percent) | | --- | --- | --- | --- | --- | | Packed | 6.90 MiB | 5.48 MiB | \-1.42 MiB | \-20.61% | | Unpacked | 38.74 MiB | 30.41 MiB | \-8.33 MiB | \-21.50% | | | Before | After | Diff | Diff (percent) | | --- | --- | --- | --- | --- | | `lib/tsserverlibrary.d.ts` | 570.95 KiB | 865.00 B | \-570.10 KiB | \-99.85% | | `lib/tsserverlibrary.js` | 8.57 MiB | 1012.00 B | \-8.57 MiB | \-99.99% | | `lib/typescript.d.ts` | 396.27 KiB | 570.95 KiB | +174.68 KiB | +44.08% | | `lib/typescript.js` | 7.95 MiB | 8.57 MiB | +637.53 KiB | +7.84% | In other words, this is over a 20.5% reduction in package size. For more information, you can [see the work involved here](https://github.com/microsoft/TypeScript/pull/55273) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-3.html#breaking-changes-and-correctness-improvements) Breaking Changes and Correctness Improvements ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-3.html#libdts-changes) `lib.d.ts` Changes Types generated for the DOM may have an impact on your codebase. For more information, [see the DOM updates for TypeScript 5.3](https://github.com/microsoft/TypeScript/pull/55798) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-3.html#checks-for-super-accesses-on-instance-properties) Checks for `super` Accesses on Instance Properties TypeScript 5.3 now detects when the declaration referenced by a `super.` property access is a class field and issues an error. This prevents errors that might occur at runtime. [See more on this change here](https://github.com/microsoft/TypeScript/pull/54056) . The TypeScript docs are an open source project. Help us improve these pages [by sending a Pull Request](https://github.com/microsoft/TypeScript-Website/blob/v2/packages/documentation/copy/en/release-notes/TypeScript%205.3.md) ❤ Contributors to this page: AB![Andrew Branch (6)](https://gravatar.com/avatar/aa8d4849bebf8520e56d85b1127185b1797030080f3b06c9e3975664c0d926b2?s=32&&d=blank) EL![Eliran Levi (1)](https://gravatar.com/avatar/f00fd5d066bb6174a9369ab456ce99bbc444afa281ff050a70a8a6cb0820894a?s=32&&d=blank) Last updated: Jul 14, 2025   MSG --- # TypeScript: Documentation - TypeScript 5.2 Was this page helpful? TypeScript 5.2 ============== [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#using-declarations-and-explicit-resource-management) `using` Declarations and Explicit Resource Management -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- TypeScript 5.2 adds support for the upcoming [Explicit Resource Management](https://github.com/tc39/proposal-explicit-resource-management) feature in ECMAScript. Let’s explore some of the motivations and understand what the feature brings us. It’s common to need to do some sort of “clean-up” after creating an object. For example, you might need to close network connections, delete temporary files, or just free up some memory. Let’s imagine a function that creates a temporary file, reads and writes to it for various operations, and then closes and deletes it. ts ` import * as fs from "fs"; export function doSomeWork() { const path = ".some_temp_file"; const file = fs.openSync(path, "w+"); // use file... // Close the file and delete it. fs.closeSync(file); fs.unlinkSync(path); } ` This is fine, but what happens if we need to perform an early exit? ts ` export function doSomeWork() { const path = ".some_temp_file"; const file = fs.openSync(path, "w+"); // use file... if (someCondition()) { // do some more work... // Close the file and delete it. fs.closeSync(file); fs.unlinkSync(path); return; } // Close the file and delete it. fs.closeSync(file); fs.unlinkSync(path); } ` We’re starting to see some duplication of clean-up which can be easy to forget. We’re also not guaranteed to close and delete the file if an error gets thrown. This could be solved by wrapping this all in a `try`/`finally` block. ts ` export function doSomeWork() { const path = ".some_temp_file"; const file = fs.openSync(path, "w+"); try { // use file... if (someCondition()) { // do some more work... return; } } finally { // Close the file and delete it. fs.closeSync(file); fs.unlinkSync(path); } } ` While this is more robust, it’s added quite a bit of “noise” to our code. There are also other foot-guns we can run into if we start adding more clean-up logic to our `finally` block — for example, exceptions preventing other resources from being disposed. This is what the [explicit resource management](https://github.com/tc39/proposal-explicit-resource-management) proposal aims to solve. The key idea of the proposal is to support resource disposal — this clean-up work we’re trying to deal with — as a first class idea in JavaScript. This starts by adding a new built-in `symbol` called `Symbol.dispose`, and we can create objects with methods named by `Symbol.dispose`. For convenience, TypeScript defines a new global type called `Disposable` which describes these. ts ` class TempFile implements Disposable { #path: string; #handle: number; constructor(path: string) { this.#path = path; this.#handle = fs.openSync(path, "w+"); } // other methods [Symbol.dispose]() { // Close the file and delete it. fs.closeSync(this.#handle); fs.unlinkSync(this.#path); } } ` Later on we can call those methods. ts ` export function doSomeWork() { const file = new TempFile(".some_temp_file"); try { // ... } finally { file[Symbol.dispose](); } } ` Moving the clean-up logic to `TempFile` itself doesn’t buy us much; we’ve basically just moved all the clean-up work from the `finally` block into a method, and that’s always been possible. But having a well-known “name” for this method means that JavaScript can build other features on top of it. That brings us to the first star of the feature: `using` declarations! `using` is a new keyword that lets us declare new fixed bindings, kind of like `const`. The key difference is that variables declared with `using` get their `Symbol.dispose` method called at the end of the scope! So we could simply have written our code like this: ts ` export function doSomeWork() { using file = new TempFile(".some_temp_file"); // use file... if (someCondition()) { // do some more work... return; } } ` Check it out — no `try`/`finally` blocks! At least, none that we see. Functionally, that’s exactly what `using` declarations will do for us, but we don’t have to deal with that. You might be familiar with [`using` declarations in C#](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/using) , [`with` statements in Python](https://docs.python.org/3/reference/compound_stmts.html#the-with-statement) , or [`try`\-with-resource declarations in Java](https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html) . These are all similar to JavaScript’s new `using` keyword, and provide a similar explicit way to perform a “tear-down” of an object at the end of a scope. `using` declarations do this clean-up at the very end of their containing scope or right before an “early return” like a `return` or a `throw`n error. They also dispose in a first-in-last-out order like a stack. ts `` function loggy(id: string): Disposable { console.log(`Creating ${id}`); return { [Symbol.dispose]() { console.log(`Disposing ${id}`); } } } function func() { using a = loggy("a"); using b = loggy("b"); { using c = loggy("c"); using d = loggy("d"); } using e = loggy("e"); return; // Unreachable. // Never created, never disposed. using f = loggy("f"); } func(); // Creating a // Creating b // Creating c // Creating d // Disposing d // Disposing c // Creating e // Disposing e // Disposing b // Disposing a `` `using` declarations are supposed to be resilient to exceptions; if an error is thrown, it’s rethrown after disposal. On the other hand, the body of your function might execute as expected, but the `Symbol.dispose` might throw. In that case, that exception is rethrown as well. But what happens if both the logic before and during disposal throws an error? For those cases, `SuppressedError` has been introduced as a new subtype of `Error`. It features a `suppressed` property that holds the last-thrown error, and an `error` property for the most-recently thrown error. ts `` class ErrorA extends Error { name = "ErrorA"; } class ErrorB extends Error { name = "ErrorB"; } function throwy(id: string) { return { [Symbol.dispose]() { throw new ErrorA(`Error from ${id}`); } }; } function func() { using a = throwy("a"); throw new ErrorB("oops!") } try { func(); } catch (e: any) { console.log(e.name); // SuppressedError console.log(e.message); // An error was suppressed during disposal. console.log(e.error.name); // ErrorA console.log(e.error.message); // Error from a console.log(e.suppressed.name); // ErrorB console.log(e.suppressed.message); // oops! } `` You might have noticed that we’re using synchronous methods in these examples. However, lots of resource disposal involves _asynchronous_ operations, and we need to wait for those to complete before we continue running any other code. That’s why there is also a new `Symbol.asyncDispose`, and it brings us to the next star of the show — `await using` declarations. These are similar to `using` declarations, but the key is that they look up whose disposal must be `await`ed. They use a different method named by `Symbol.asyncDispose`, though they can operate on anything with a `Symbol.dispose` as well. For convenience, TypeScript also introduces a global type called `AsyncDisposable` that describes any object with an asynchronous dispose method. ts `` async function doWork() { // Do fake work for half a second. await new Promise(resolve => setTimeout(resolve, 500)); } function loggy(id: string): AsyncDisposable { console.log(`Constructing ${id}`); return { async [Symbol.asyncDispose]() { console.log(`Disposing (async) ${id}`); await doWork(); }, } } async function func() { await using a = loggy("a"); await using b = loggy("b"); { await using c = loggy("c"); await using d = loggy("d"); } await using e = loggy("e"); return; // Unreachable. // Never created, never disposed. await using f = loggy("f"); } func(); // Constructing a // Constructing b // Constructing c // Constructing d // Disposing (async) d // Disposing (async) c // Constructing e // Disposing (async) e // Disposing (async) b // Disposing (async) a `` Defining types in terms of `Disposable` and `AsyncDisposable` can make your code much easier to work with if you expect others to do tear-down logic consistently. In fact, lots of existing types exist in the wild which have a `dispose()` or `close()` method. For example, the Visual Studio Code APIs even define [their own `Disposable` interface](https://code.visualstudio.com/api/references/vscode-api#Disposable) . APIs in the browser and in runtimes like Node.js, Deno, and Bun might also choose to use `Symbol.dispose` and `Symbol.asyncDispose` for objects which already have clean-up methods, like file handles, connections, and more. Now maybe this all sounds great for libraries, but a little bit heavy-weight for your scenarios. If you’re doing a lot of ad-hoc clean-up, creating a new type might introduce a lot of over-abstraction and questions about best-practices. For example, take our `TempFile` example again. ts ` class TempFile implements Disposable { #path: string; #handle: number; constructor(path: string) { this.#path = path; this.#handle = fs.openSync(path, "w+"); } // other methods [Symbol.dispose]() { // Close the file and delete it. fs.closeSync(this.#handle); fs.unlinkSync(this.#path); } } export function doSomeWork() { using file = new TempFile(".some_temp_file"); // use file... if (someCondition()) { // do some more work... return; } } ` All we wanted was to remember to call two functions — but was this the best way to write it? Should we be calling `openSync` in the constructor, create an `open()` method, or pass in the handle ourselves? Should we expose a method for every possible operation we need to perform, or should we just make the properties public? That brings us to the final stars of the feature: `DisposableStack` and `AsyncDisposableStack`. These objects are useful for doing both one-off clean-up, along with arbitrary amounts of cleanup. A `DisposableStack` is an object that has several methods for keeping track of `Disposable` objects, and can be given functions for doing arbitrary clean-up work. We can also assign them to `using` variables because — get this — _they’re also `Disposable`_! So here’s how we could’ve written the original example. ts ` function doSomeWork() { const path = ".some_temp_file"; const file = fs.openSync(path, "w+"); using cleanup = new DisposableStack(); cleanup.defer(() => { fs.closeSync(file); fs.unlinkSync(path); }); // use file... if (someCondition()) { // do some more work... return; } // ... } ` Here, the `defer()` method just takes a callback, and that callback will be run once `cleanup` is disposed of. Typically, `defer` (and other `DisposableStack` methods like `use` and `adopt`) should be called immediately after creating a resource. As the name suggests, `DisposableStack` disposes of everything it keeps track of like a stack, in a first-in-last-out order, so `defer`ing immediately after creating a value helps avoid odd dependency issues. `AsyncDisposableStack` works similarly, but can keep track of `async` functions and `AsyncDisposable`s, and is itself an `AsyncDisposable.` The `defer` method is similar in many ways to the `defer` keyword in [Go](https://go.dev/tour/flowcontrol/12) , [Swift](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/statements/#Defer-Statement) , [Zig](https://ziglang.org/documentation/master/#defer) , [Odin](https://odin-lang.org/docs/overview/#defer-statement) , and others, where the conventions should be similar. Because this feature is so recent, most runtimes will not support it natively. To use it, you will need runtime polyfills for the following: * `Symbol.dispose` * `Symbol.asyncDispose` * `DisposableStack` * `AsyncDisposableStack` * `SuppressedError` However, if all you’re interested in is `using` and `await using`, you should be able to get away with only polyfilling the built-in `symbol`s. Something as simple as the following should work for most cases: ts ` Symbol.dispose ??= Symbol("Symbol.dispose"); Symbol.asyncDispose ??= Symbol("Symbol.asyncDispose"); ` You will also need to set your compilation `target` to `es2022` or below, and configure your `lib` setting to either include `"esnext"` or `"esnext.disposable"`. json ` { "compilerOptions": { "target": "es2022", "lib": ["es2022", "esnext.disposable", "dom"] } } ` For more information on this feature, [take a look at the work on GitHub](https://github.com/microsoft/TypeScript/pull/54505) ! [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#decorator-metadata) Decorator Metadata ------------------------------------------------------------------------------------------------------------------------ TypeScript 5.2 implements [an upcoming ECMAScript feature called decorator metadata](https://github.com/tc39/proposal-decorator-metadata) . The key idea of this feature is to make it easy for decorators to create and consume metadata on any class they’re used on or within. Whenever decorator functions are used, they now have access to a new `metadata` property on their context object. The `metadata` property just holds a simple object. Since JavaScript lets us add properties arbitrarily, it can be used as a dictionary that is updated by each decorator. Alternatively, since every `metadata` object will be identical for each decorated portion of a class, it can be used as a key into a `Map`. After all decorators on or in a class get run, that object can be accessed on the class via `Symbol.metadata`. ts ` interface Context { name: string; metadata: Record; } function setMetadata(_target: any, context: Context) { context.metadata[context.name] = true; } class SomeClass { @setMetadata foo = 123; @setMetadata accessor bar = "hello!"; @setMetadata baz() { } } const ourMetadata = SomeClass[Symbol.metadata]; console.log(JSON.stringify(ourMetadata)); // { "bar": true, "baz": true, "foo": true } ` This can be useful in a number of different scenarios. Metadata could possibly be attached for lots of uses like debugging, serialization, or performing dependency injection with decorators. Since metadata objects are created per decorated class, frameworks can either privately use them as keys into a `Map` or `WeakMap`, or tack properties on as necessary. For example, let’s say we wanted to use decorators to keep track of which properties and accessors are serializable when using `JSON.stringify` like so: ts `` import { serialize, jsonify } from "./serializer"; class Person { firstName: string; lastName: string; @serialize age: number @serialize get fullName() { return `${this.firstName} ${this.lastName}`; } toJSON() { return jsonify(this) } constructor(firstName: string, lastName: string, age: number) { // ... } } `` Here, the intent is that only `age` and `fullName` should be serialized because they are marked with the `@serialize` decorator. We define a `toJSON` method for this purpose, but it just calls out to `jsonify` which uses the metadata that `@serialize` created. Here’s an example of how the module `./serialize.ts` might be defined: ts `` const serializables = Symbol(); type Context = | ClassAccessorDecoratorContext | ClassGetterDecoratorContext | ClassFieldDecoratorContext ; export function serialize(_target: any, context: Context): void { if (context.static || context.private) { throw new Error("Can only serialize public instance members.") } if (typeof context.name === "symbol") { throw new Error("Cannot serialize symbol-named properties."); } const propNames = (context.metadata[serializables] as string[] | undefined) ??= []; propNames.push(context.name); } export function jsonify(instance: object): string { const metadata = instance.constructor[Symbol.metadata]; const propNames = metadata?.[serializables] as string[] | undefined; if (!propNames) { throw new Error("No members marked with @serialize."); } const pairStrings = propNames.map(key => { const strKey = JSON.stringify(key); const strValue = JSON.stringify((instance as any)[key]); return `${strKey}: ${strValue}`; }); return `{ ${pairStrings.join(", ")} }`; } `` This module has a local `symbol` called `serializables` to store and retrieve the names of properties marked `@serializable`. It stores a list of these property names on the metadata on each invocation of `@serializable`. When `jsonify` is called, the list of properties is fetched off of the metadata and used to retrieve the actual values from the instance, eventually serializing those names and values. Using a `symbol` technically makes this data accessible to others. An alternative might be to use a `WeakMap` using the metadata object as a key. This keeps data private and happens to use fewer type assertions in this case, but is otherwise similar. ts `` const serializables = new WeakMap(); type Context = | ClassAccessorDecoratorContext | ClassGetterDecoratorContext | ClassFieldDecoratorContext ; export function serialize(_target: any, context: Context): void { if (context.static || context.private) { throw new Error("Can only serialize public instance members.") } if (typeof context.name !== "string") { throw new Error("Can only serialize string properties."); } let propNames = serializables.get(context.metadata); if (propNames === undefined) { serializables.set(context.metadata, propNames = []); } propNames.push(context.name); } export function jsonify(instance: object): string { const metadata = instance.constructor[Symbol.metadata]; const propNames = metadata && serializables.get(metadata); if (!propNames) { throw new Error("No members marked with @serialize."); } const pairStrings = propNames.map(key => { const strKey = JSON.stringify(key); const strValue = JSON.stringify((instance as any)[key]); return `${strKey}: ${strValue}`; }); return `{ ${pairStrings.join(", ")} }`; } `` As a note, these implementations don’t handle subclassing and inheritance. That’s left as an exercise to you (and you might find that it is easier in one version of the file than the other!). Because this feature is still fresh, most runtimes will not support it natively. To use it, you will need a polyfill for `Symbol.metadata`. Something as simple as the following should work for most cases: ts ` Symbol.metadata ??= Symbol("Symbol.metadata"); ` You will also need to set your compilation `target` to `es2022` or below, and configure your `lib` setting to either include `"esnext"` or `"esnext.decorators"`. json ` { "compilerOptions": { "target": "es2022", "lib": ["es2022", "esnext.decorators", "dom"] } } ` We’d like to thank [Oleksandr Tarasiuk](https://github.com/a-tarasyuk) for contributing [the implementation of decorator metadata](https://github.com/microsoft/TypeScript/pull/54657) for TypeScript 5.2! [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#named-and-anonymous-tuple-elements) Named and Anonymous Tuple Elements -------------------------------------------------------------------------------------------------------------------------------------------------------- Tuple types have supported optional labels or names for each element. ts ` type Pair = [first: T, second: T]; ` These labels don’t change what you’re allowed to do with them — they’re solely to help with readability and tooling. However, TypeScript previously had a rule that tuples could not mix and match between labeled and unlabeled elements. In other words, either no element could have a label in a tuple, or all elements needed one. ts ` // ✅ fine - no labels type Pair1 = [T, T]; // ✅ fine - all fully labeled type Pair2 = [first: T, second: T]; // ❌ previously an error type Pair3 = [first: T, T]; // ~ // Tuple members must all have names // or all not have names. ` This could be annoying for rest elements where we’d be forced to just add a label like `rest` or `tail`. ts ` // ❌ previously an error type TwoOrMore_A = [first: T, second: T, ...T[]]; // ~~~~~~ // Tuple members must all have names // or all not have names. // ✅ type TwoOrMore_B = [first: T, second: T, rest: ...T[]]; ` It also meant that this restriction had to be enforced internally in the type system, meaning TypeScript would lose labels. ts ` type HasLabels = [a: string, b: string]; type HasNoLabels = [number, number]; type Merged = [...HasNoLabels, ...HasLabels]; // ^ [number, number, string, string] // // 'a' and 'b' were lost in 'Merged' ` In TypeScript 5.2, the all-or-nothing restriction on tuple labels has been lifted. The language can now also preserve labels when spreading into an unlabeled tuple. We’d like to extend our thanks to [Josh Goldberg](https://github.com/JoshuaKGoldberg) and [Mateusz Burzyński](https://github.com/Andarist) who [collaborated to lift this restriction](https://github.com/microsoft/TypeScript/pull/53356) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#easier-method-usage-for-unions-of-arrays) Easier Method Usage for Unions of Arrays -------------------------------------------------------------------------------------------------------------------------------------------------------------------- In previous versions of TypeScript, calling a method on a union of arrays could end in pain. ts ` declare let array: string[] | number[]; array.filter(x => !!x); // ~~~~~~ error! // This expression is not callable. // Each member of the union type '...' has signatures, // but none of those signatures are compatible // with each other. ` In this example, TypeScript would try to see if each version of `filter` is compatible across `string[]` and `number[]`. Without a coherent strategy, TypeScript threw its hands in the air and said “I can’t make it work”. In TypeScript 5.2, before giving up in these cases, unions of arrays are treated as a special case. A new array type is constructed out of each member’s element type, and then the method is invoked on that. Taking the above example, `string[] | number[]` is transformed into `(string | number)[]` (or `Array`), and `filter` is invoked on that type. There is a slight caveat which is that `filter` will produce an `Array` instead of a `string[] | number[]`; but for a freshly produced value there is less risk of something “going wrong”. This means lots of methods like `filter`, `find`, `some`, `every`, and `reduce` should all be invokable on unions of arrays in cases where they were not previously. You can [read up more details on the implementing pull request](https://github.com/microsoft/TypeScript/pull/53489) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#type-only-import-paths-with-typescript-implementation-file-extensions) Type-Only Import Paths with TypeScript Implementation File Extensions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ TypeScript now allows both declaration _and_ implementation file extensions to be included in type-only import paths, regardless of whether `allowImportingTsExtensions` is enabled. This means that you can now write `import type` statements that use `.ts`, `.mts`, `.cts`, and `.tsx` file extensions. ts ` import type { JustAType } from "./justTypes.ts"; export function f(param: JustAType) { // ... } ` It also means that `import()` types, which can be used in both TypeScript and JavaScript with JSDoc, can use those file extensions. js ` /** * @param {import("./justTypes.ts").JustAType} param */ export function f(param) { // ... } ` For more information, [see the change here](https://github.com/microsoft/TypeScript/pull/54746) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#comma-completions-for-object-members) Comma Completions for Object Members ------------------------------------------------------------------------------------------------------------------------------------------------------------ It can be easy to forget to add a comma when adding a new property to an object. Previously, if you forgot a comma and requested auto-completion, TypeScript would confusingly give poor unrelated completion results. TypeScript 5.2 now gracefully provides object member completions when you’re missing a comma. But to just skip past hitting you with a syntax error, it will _also_ auto-insert the missing comma. ![Properties in an object literal are completed despite missing a comma after a prior property. When the property name is completed, the missing comma is automatically inserted.](https://devblogs.microsoft.com/typescript/wp-content/uploads/sites/11/2023/06/comma-completions-5-2-beta.gif) For more information, [see the implementation here](https://github.com/microsoft/TypeScript/pull/52899) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#inline-variable-refactoring) Inline Variable Refactoring ------------------------------------------------------------------------------------------------------------------------------------------ TypeScript 5.2 now has a refactoring to inline the contents of a variable to all usage sites. ![A variable called 'path' initialized to a string, having both of its usages replaced](https://devblogs.microsoft.com/typescript/wp-content/uploads/sites/11/2023/06/inline-variable-5-2-beta.gif). Using the “inline variable” refactoring will eliminate the variable and replace all the variable’s usages with its initializer. Note that this may cause that initializer’s side-effects to run at a different time, and as many times as the variable has been used. For more details, [see the implementing pull request](https://github.com/microsoft/TypeScript/pull/54281) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#optimized-checks-for-ongoing-type-compatibility) Optimized Checks for Ongoing Type Compatibility ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Because TypeScript is a structural type system, types occasionally need to be compared in a member-wise fashion; however, recursive types add some issues here. For example: ts ` interface A { value: A; other: string; } interface B { value: B; other: number; } ` When checking whether the type `A` is compatible with the type `B`, TypeScript will end up checking whether the types of `value` in `A` and `B` are respectively compatible. At this point, the type system needs to stop checking any further and proceed to check other members. To do this, the type system has to track when any two types are already being related. Previously TypeScript already kept a stack of type pairs, and iterated through that to determine whether those types are being related. When this stack is shallow that’s not a problem; but when the stack isn’t shallow, that, uh, [is a problem](https://accidentallyquadratic.tumblr.com/) . In TypeScript 5.3, a simple `Set` helps track this information. This reduced the time spent on a reported test case that used the [drizzle](https://github.com/drizzle-team/drizzle-orm) library by over 33%! ` Benchmark 1: old Time (mean ± σ): 3.115 s ± 0.067 s [User: 4.403 s, System: 0.124 s] Range (min … max): 3.018 s … 3.196 s 10 runs Benchmark 2: new Time (mean ± σ): 2.072 s ± 0.050 s [User: 3.355 s, System: 0.135 s] Range (min … max): 1.985 s … 2.150 s 10 runs Summary 'new' ran 1.50 ± 0.05 times faster than 'old' ` [Read more on the change here](https://github.com/microsoft/TypeScript/pull/55224) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#breaking-changes-and-correctness-fixes) Breaking Changes and Correctness Fixes ---------------------------------------------------------------------------------------------------------------------------------------------------------------- TypeScript strives not to unnecessarily introduce breaks; however, occasionally we must make corrections and improvements so that code can be better-analyzed. ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#libdts-changes) `lib.d.ts` Changes Types generated for the DOM may have an impact on your codebase. For more information, [see the DOM updates for TypeScript 5.2](https://github.com/microsoft/TypeScript/pull/54725) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#labeledelementdeclarations-may-hold-undefined-elements) `labeledElementDeclarations` May Hold `undefined` Elements In order [to support a mixture of labeled and unlabeled elements](https://github.com/microsoft/TypeScript/pull/53356) , TypeScript’s API has changed slightly. The `labeledElementDeclarations` property of `TupleType` may hold `undefined` for at each position where an element is unlabeled. diff `interface TupleType { - labeledElementDeclarations?: readonly (NamedTupleMember | ParameterDeclaration)[]; + labeledElementDeclarations?: readonly (NamedTupleMember | ParameterDeclaration | undefined)[]; }` ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#module-and-moduleresolution-must-match-under-recent-nodejs-settings) `module` and `moduleResolution` Must Match Under Recent Node.js settings The `--module` and `--moduleResolution` options each support a `node16` and `nodenext` setting. These are effectively “modern Node.js” settings that should be used on any recent Node.js project. What we’ve found is that when these two options don’t agree on whether they are using Node.js-related settings, projects are effectively misconfigured. In TypeScript 5.2, when using `node16` or `nodenext` for either of the `--module` and `--moduleResolution` options, TypeScript now requires the other to have a similar Node.js-related setting. In cases where the settings diverge, you’ll likely get an error message like either ` Option 'moduleResolution' must be set to 'NodeNext' (or left unspecified) when option 'module' is set to 'NodeNext'. ` or ` Option 'module' must be set to 'Node16' when option 'moduleResolution' is set to 'Node16'. ` So for example `--module esnext --moduleResolution node16` will be rejected — but you may be better off just using `--module nodenext` alone, or `--module esnext --moduleResolution bundler`. For more information, [see the change here](https://github.com/microsoft/TypeScript/pull/54567) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#consistent-export-checking-for-merged-symbols) Consistent Export Checking for Merged Symbols When two declarations merge, they must agree on whether they are both exported. Due to a bug, TypeScript missed specific cases in ambient contexts, like in declaration files or `declare module` blocks. For example, it would not issue an error on a case like the following, where `replaceInFile` is declared once as an exported function, and one as an un-exported namespace. ts ` declare module 'replace-in-file' { export function replaceInFile(config: unknown): Promise; export {}; namespace replaceInFile { export function sync(config: unknown): unknown[]; } } ` In an ambient module, adding an `export { ... }` or a similar construct like `export default ...` implicitly changes whether all declarations are automatically exported. TypeScript now recognizes these unfortunately confusing semantics more consistently, and issues an error on the fact that all declarations of `replaceInFile` need to agree in their modifiers, and will issue the following error: ` Individual declarations in merged declaration 'replaceInFile' must be all exported or all local. ` For more information, [see the change here](https://github.com/microsoft/TypeScript/pull/54659) . The TypeScript docs are an open source project. Help us improve these pages [by sending a Pull Request](https://github.com/microsoft/TypeScript-Website/blob/v2/packages/documentation/copy/en/release-notes/TypeScript%205.2.md) ❤ Contributors to this page: AB![Andrew Branch (6)](https://gravatar.com/avatar/aa8d4849bebf8520e56d85b1127185b1797030080f3b06c9e3975664c0d926b2?s=32&&d=blank) EI![Eugene Ilyin (1)](https://gravatar.com/avatar/a65caef54cbaba95975117e96ca2d411668cedaea6e5bbc59287ed673c84751e?s=32&&d=blank) Last updated: Jul 14, 2025   MSG --- # TypeScript: Documentation - TypeScript 5.0 Was this page helpful? TypeScript 5.0 ============== [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#decorators) Decorators -------------------------------------------------------------------------------------------------------- Decorators are an upcoming ECMAScript feature that allow us to customize classes and their members in a reusable way. Let’s consider the following code: ts `` class Person { name: string; constructor(name: string) { this.name = name; } greet() { console.log(`Hello, my name is ${this.name}.`); } } const p = new Person("Ray"); p.greet(); `` `greet` is pretty simple here, but let’s imagine it’s something way more complicated - maybe it does some async logic, it’s recursive, it has side effects, etc. Regardless of what kind of ball-of-mud you’re imagining, let’s say you throw in some `console.log` calls to help debug `greet`. ts `` class Person { name: string; constructor(name: string) { this.name = name; } greet() { console.log("LOG: Entering method."); console.log(`Hello, my name is ${this.name}.`); console.log("LOG: Exiting method.") } } `` This pattern is fairly common. It sure would be nice if there was a way we could do this for every method! This is where decorators come in. We can write a function called `loggedMethod` that looks like the following: ts ` function loggedMethod(originalMethod: any, _context: any) { function replacementMethod(this: any, ...args: any[]) { console.log("LOG: Entering method.") const result = originalMethod.call(this, ...args); console.log("LOG: Exiting method.") return result; } return replacementMethod; } ` “What’s the deal with all of these `any`s? What is this, `any`Script!?” Just be patient - we’re keeping things simple for now so that we can focus on what this function is doing. Notice that `loggedMethod` takes the original method (`originalMethod`) and returns a function that 1. logs an “Entering…” message 2. passes along `this` and all of its arguments to the original method 3. logs an “Exiting…” message, and 4. returns whatever the original method returned. Now we can use `loggedMethod` to _decorate_ the method `greet`: ts `` class Person { name: string; constructor(name: string) { this.name = name; } @loggedMethod greet() { console.log(`Hello, my name is ${this.name}.`); } } const p = new Person("Ray"); p.greet(); // Output: // // LOG: Entering method. // Hello, my name is Ray. // LOG: Exiting method. `` We just used `loggedMethod` as a decorator above `greet` - and notice that we wrote it as `@loggedMethod`. When we did that, it got called with the method _target_ and a _context object_. Because `loggedMethod` returned a new function, that function replaced the original definition of `greet`. We didn’t mention it yet, but `loggedMethod` was defined with a second parameter. It’s called a “context object”, and it has some useful information about how the decorated method was declared - like whether it was a `#private` member, or `static`, or what the name of the method was. Let’s rewrite `loggedMethod` to take advantage of that and print out the name of the method that was decorated. ts `` function loggedMethod(originalMethod: any, context: ClassMethodDecoratorContext) { const methodName = String(context.name); function replacementMethod(this: any, ...args: any[]) { console.log(`LOG: Entering method '${methodName}'.`) const result = originalMethod.call(this, ...args); console.log(`LOG: Exiting method '${methodName}'.`) return result; } return replacementMethod; } `` We’re now using the context parameter - and that it’s the first thing in `loggedMethod` that has a type stricter than `any` and `any[]`. TypeScript provides a type called `ClassMethodDecoratorContext` that models the context object that method decorators take. Apart from metadata, the context object for methods also has a useful function called `addInitializer`. It’s a way to hook into the beginning of the constructor (or the initialization of the class itself if we’re working with `static`s). As an example - in JavaScript, it’s common to write something like the following pattern: ts `` class Person { name: string; constructor(name: string) { this.name = name; this.greet = this.greet.bind(this); } greet() { console.log(`Hello, my name is ${this.name}.`); } } `` Alternatively, `greet` might be declared as a property initialized to an arrow function. ts `` class Person { name: string; constructor(name: string) { this.name = name; } greet = () => { console.log(`Hello, my name is ${this.name}.`); }; } `` This code is written to ensure that `this` isn’t re-bound if `greet` is called as a stand-alone function or passed as a callback. ts ` const greet = new Person("Ray").greet; // We don't want this to fail! greet(); ` We can write a decorator that uses `addInitializer` to call `bind` in the constructor for us. ts `` function bound(originalMethod: any, context: ClassMethodDecoratorContext) { const methodName = context.name; if (context.private) { throw new Error(`'bound' cannot decorate private properties like ${methodName as string}.`); } context.addInitializer(function () { this[methodName] = this[methodName].bind(this); }); } `` `bound` isn’t returning anything - so when it decorates a method, it leaves the original alone. Instead, it will add logic before any other fields are initialized. ts `` class Person { name: string; constructor(name: string) { this.name = name; } @bound @loggedMethod greet() { console.log(`Hello, my name is ${this.name}.`); } } const p = new Person("Ray"); const greet = p.greet; // Works! greet(); `` Notice that we stacked two decorators - `@bound` and `@loggedMethod`. These decorations run in “reverse order”. That is, `@loggedMethod` decorates the original method `greet`, and `@bound` decorates the result of `@loggedMethod`. In this example, it doesn’t matter - but it could if your decorators have side-effects or expect a certain order. Also worth noting - if you’d prefer stylistically, you can put these decorators on the same line. ts ``@bound @loggedMethod greet() { console.log(`Hello, my name is ${this.name}.`); }`` Something that might not be obvious is that we can even make functions that _return_ decorator functions. That makes it possible to customize the final decorator just a little. If we wanted, we could have made `loggedMethod` return a decorator and customize how it logs its messages. ts `` function loggedMethod(headMessage = "LOG:") { return function actualDecorator(originalMethod: any, context: ClassMethodDecoratorContext) { const methodName = String(context.name); function replacementMethod(this: any, ...args: any[]) { console.log(`${headMessage} Entering method '${methodName}'.`) const result = originalMethod.call(this, ...args); console.log(`${headMessage} Exiting method '${methodName}'.`) return result; } return replacementMethod; } } `` If we did that, we’d have to call `loggedMethod` before using it as a decorator. We could then pass in any string as the prefix for messages that get logged to the console. ts `` class Person { name: string; constructor(name: string) { this.name = name; } @loggedMethod("⚠️") greet() { console.log(`Hello, my name is ${this.name}.`); } } const p = new Person("Ray"); p.greet(); // Output: // // ⚠️ Entering method 'greet'. // Hello, my name is Ray. // ⚠️ Exiting method 'greet'. `` Decorators can be used on more than just methods! They can be used on properties/fields, getters, setters, and auto-accessors. Even classes themselves can be decorated for things like subclassing and registration. To learn more about decorators in-depth, you can read up on [Axel Rauschmayer’s extensive summary](https://2ality.com/2022/10/javascript-decorators.html) . For more information about the changes involved, you can [view the original pull request](https://github.com/microsoft/TypeScript/pull/50820) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#differences-with-experimental-legacy-decorators) Differences with Experimental Legacy Decorators If you’ve been using TypeScript for a while, you might be aware of the fact that it’s had support for “experimental” decorators for years. While these experimental decorators have been incredibly useful, they modeled a much older version of the decorators proposal, and always required an opt-in compiler flag called `--experimentalDecorators`. Any attempt to use decorators in TypeScript without this flag used to prompt an error message. `--experimentalDecorators` will continue to exist for the foreseeable future; however, without the flag, decorators will now be valid syntax for all new code. Outside of `--experimentalDecorators`, they will be type-checked and emitted differently. The type-checking rules and emit are sufficiently different that while decorators _can_ be written to support both the old and new decorators behavior, any existing decorator functions are not likely to do so. This new decorators proposal is not compatible with `--emitDecoratorMetadata`, and it does not allow decorating parameters. Future ECMAScript proposals may be able to help bridge that gap. On a final note: in addition to allowing decorators to be placed before the `export` keyword, the proposal for decorators now provides the option of placing decorators after `export` or `export default`. The only exception is that mixing the two styles is not allowed. js ` // ✅ allowed @register export default class Foo { // ... } // ✅ also allowed export default @register class Bar { // ... } // ❌ error - before *and* after is not allowed @before export @after class Bar { // ... } ` ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#writing-well-typed-decorators) Writing Well-Typed Decorators The `loggedMethod` and `bound` decorator examples above are intentionally simple and omit lots of details about types. Typing decorators can be fairly complex. For example, a well-typed version of `loggedMethod` from above might look something like this: ts `` function loggedMethod( target: (this: This, ...args: Args) => Return, context: ClassMethodDecoratorContext Return> ) { const methodName = String(context.name); function replacementMethod(this: This, ...args: Args): Return { console.log(`LOG: Entering method '${methodName}'.`) const result = target.call(this, ...args); console.log(`LOG: Exiting method '${methodName}'.`) return result; } return replacementMethod; } `` We had to separately model out the type of `this`, the parameters, and the return type of the original method, using the type parameters `This`, `Args`, and `Return`. Exactly how complex your decorators functions are defined depends on what you want to guarantee. Just keep in mind, your decorators will be used more than they’re written, so a well-typed version will usually be preferable - but there’s clearly a trade-off with readability, so try to keep things simple. More documentation on writing decorators will be available in the future - but [this post](https://2ality.com/2022/10/javascript-decorators.html) should have a good amount of detail for the mechanics of decorators. [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#const-type-parameters) `const` Type Parameters -------------------------------------------------------------------------------------------------------------------------------- When inferring the type of an object, TypeScript will usually choose a type that’s meant to be general. For example, in this case, the inferred type of `names` is `string[]`: ts ` type HasNames = { names: readonly string[] }; function getNamesExactly(arg: T): T["names"] { return arg.names; } // Inferred type: string[] const names = getNamesExactly({ names: ["Alice", "Bob", "Eve"]}); ` Usually the intent of this is to enable mutation down the line. However, depending on what exactly `getNamesExactly` does and how it’s intended to be used, it can often be the case that a more-specific type is desired. Up until now, API authors have typically had to recommend adding `as const` in certain places to achieve the desired inference: ts ` // The type we wanted: // readonly ["Alice", "Bob", "Eve"] // The type we got: // string[] const names1 = getNamesExactly({ names: ["Alice", "Bob", "Eve"]}); // Correctly gets what we wanted: // readonly ["Alice", "Bob", "Eve"] const names2 = getNamesExactly({ names: ["Alice", "Bob", "Eve"]} as const); ` This can be cumbersome and easy to forget. In TypeScript 5.0, you can now add a `const` modifier to a type parameter declaration to cause `const`\-like inference to be the default: ts ` type HasNames = { names: readonly string[] }; function getNamesExactly(arg: T): T["names"] { // ^^^^^ return arg.names; } // Inferred type: readonly ["Alice", "Bob", "Eve"] // Note: Didn't need to write 'as const' here const names = getNamesExactly({ names: ["Alice", "Bob", "Eve"] }); ` Note that the `const` modifier doesn’t _reject_ mutable values, and doesn’t require immutable constraints. Using a mutable type constraint might give surprising results. For example: ts ` declare function fnBad(args: T): void; // 'T' is still 'string[]' since 'readonly ["a", "b", "c"]' is not assignable to 'string[]' fnBad(["a", "b" ,"c"]); ` Here, the inferred candidate for `T` is `readonly ["a", "b", "c"]`, and a `readonly` array can’t be used where a mutable one is needed. In this case, inference falls back to the constraint, the array is treated as `string[]`, and the call still proceeds successfully. A better definition of this function should use `readonly string[]`: ts ` declare function fnGood(args: T): void; // T is readonly ["a", "b", "c"] fnGood(["a", "b" ,"c"]); ` Similarly, remember to keep in mind that the `const` modifier only affects inference of object, array and primitive expressions that were written within the call, so arguments which wouldn’t (or couldn’t) be modified with `as const` won’t see any change in behavior: ts ` declare function fnGood(args: T): void; const arr = ["a", "b" ,"c"]; // 'T' is still 'string[]'-- the 'const' modifier has no effect here fnGood(arr); ` [See the pull request](https://github.com/microsoft/TypeScript/pull/51865) and the ([first](https://github.com/microsoft/TypeScript/issues/30680) and [second](https://github.com/microsoft/TypeScript/issues/41114) ) motivating issues for more details. [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#supporting-multiple-configuration-files-in-extends) Supporting Multiple Configuration Files in `extends` ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ When managing multiple projects, it can be helpful to have a “base” configuration file that other `tsconfig.json` files can extend from. That’s why TypeScript supports an `extends` field for copying over fields from `compilerOptions`. jsonc ` // packages/front-end/src/tsconfig.json { "extends": "../../../tsconfig.base.json", "compilerOptions": { "outDir": "../lib", // ... } } ` However, there are scenarios where you might want to extend from multiple configuration files. For example, imagine using [a TypeScript base configuration file shipped to npm](https://github.com/tsconfig/bases) . If you want all your projects to also use the options from the `@tsconfig/strictest` package on npm, then there’s a simple solution: have `tsconfig.base.json` extend from `@tsconfig/strictest`: jsonc ` // tsconfig.base.json { "extends": "@tsconfig/strictest/tsconfig.json", "compilerOptions": { // ... } } ` This works to a point. If you have any projects that _don’t_ want to use `@tsconfig/strictest`, they have to either manually disable the options, or create a separate version of `tsconfig.base.json` that _doesn’t_ extend from `@tsconfig/strictest`. To give some more flexibility here, Typescript 5.0 now allows the `extends` field to take multiple entries. For example, in this configuration file: jsonc ` { "extends": ["a", "b", "c"], "compilerOptions": { // ... } } ` Writing this is kind of like extending `c` directly, where `c` extends `b`, and `b` extends `a`. If any fields “conflict”, the latter entry wins. So in the following example, both `strictNullChecks` and `noImplicitAny` are enabled in the final `tsconfig.json`. jsonc ` // tsconfig1.json { "compilerOptions": { "strictNullChecks": true } } // tsconfig2.json { "compilerOptions": { "noImplicitAny": true } } // tsconfig.json { "extends": ["./tsconfig1.json", "./tsconfig2.json"], "files": ["./index.ts"] } ` As another example, we can rewrite our original example in the following way. jsonc ` // packages/front-end/src/tsconfig.json { "extends": ["@tsconfig/strictest/tsconfig.json", "../../../tsconfig.base.json"], "compilerOptions": { "outDir": "../lib", // ... } } ` For more details, [read more on the original pull request](https://github.com/microsoft/TypeScript/pull/50403) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#all-enums-are-union-enums) All `enum`s Are Union `enum`s ------------------------------------------------------------------------------------------------------------------------------------------ When TypeScript originally introduced enums, they were nothing more than a set of numeric constants with the same type. ts ` enum E { Foo = 10, Bar = 20, } ` The only thing special about `E.Foo` and `E.Bar` was that they were assignable to anything expecting the type `E`. Other than that, they were pretty much just `number`s. ts ` function takeValue(e: E) {} takeValue(E.Foo); // works takeValue(123); // error! ` It wasn’t until TypeScript 2.0 introduced enum literal types that enums got a bit more special. Enum literal types gave each enum member its own type, and turned the enum itself into a _union_ of each member type. They also allowed us to refer to only a subset of the types of an enum, and to narrow away those types. ts ` // Color is like a union of Red | Orange | Yellow | Green | Blue | Violet enum Color { Red, Orange, Yellow, Green, Blue, /* Indigo, */ Violet } // Each enum member has its own type that we can refer to! type PrimaryColor = Color.Red | Color.Green | Color.Blue; function isPrimaryColor(c: Color): c is PrimaryColor { // Narrowing literal types can catch bugs. // TypeScript will error here because // we'll end up comparing 'Color.Red' to 'Color.Green'. // We meant to use ||, but accidentally wrote &&. return c === Color.Red && c === Color.Green && c === Color.Blue; } ` One issue with giving each enum member its own type was that those types were in some part associated with the actual value of the member. In some cases it’s not possible to compute that value - for instance, an enum member could be initialized by a function call. ts ` enum E { Blah = Math.random() } ` Whenever TypeScript ran into these issues, it would quietly back out and use the old enum strategy. That meant giving up all the advantages of unions and literal types. TypeScript 5.0 manages to make all enums into union enums by creating a unique type for each computed member. That means that all enums can now be narrowed and have their members referenced as types as well. For more details on this change, you can [read the specifics on GitHub](https://github.com/microsoft/TypeScript/pull/50528) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#--moduleresolution-bundler) `--moduleResolution bundler` ------------------------------------------------------------------------------------------------------------------------------------------ TypeScript 4.7 introduced the `node16` and `nodenext` options for its `--module` and `--moduleResolution` settings. The intent of these options was to better model the precise lookup rules for ECMAScript modules in Node.js; however, this mode has many restrictions that other tools don’t really enforce. For example, in an ECMAScript module in Node.js, any relative import needs to include a file extension. js ` // entry.mjs import * as utils from "./utils"; // ❌ wrong - we need to include the file extension. import * as utils from "./utils.mjs"; // ✅ works ` There are certain reasons for this in Node.js and the browser - it makes file lookups faster and works better for naive file servers. But for many developers using tools like bundlers, the `node16`/`nodenext` settings were cumbersome because bundlers don’t have most of these restrictions. In some ways, the `node` resolution mode was better for anyone using a bundler. But in some ways, the original `node` resolution mode was already out of date. Most modern bundlers use a fusion of the ECMAScript module and CommonJS lookup rules in Node.js. For example, extensionless imports work just fine just like in CommonJS, but when looking through the [`export` conditions](https://nodejs.org/api/packages.html#nested-conditions) of a package, they’ll prefer an `import` condition just like in an ECMAScript file. To model how bundlers work, TypeScript now introduces a new strategy: `--moduleResolution bundler`. jsonc ` { "compilerOptions": { "target": "esnext", "moduleResolution": "bundler" } } ` If you are using a modern bundler like Vite, esbuild, swc, Webpack, Parcel, and others that implement a hybrid lookup strategy, the new `bundler` option should be a good fit for you. On the other hand, if you’re writing a library that’s meant to be published on npm, using the `bundler` option can hide compatibility issues that may arise for your users who _aren’t_ using a bundler. So in these cases, using the `node16` or `nodenext` resolution options is likely to be a better path. To read more on `--moduleResolution bundler`, [take a look at the implementing pull request](https://github.com/microsoft/TypeScript/pull/51669) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#resolution-customization-flags) Resolution Customization Flags ------------------------------------------------------------------------------------------------------------------------------------------------ JavaScript tooling may now model “hybrid” resolution rules, like in the `bundler` mode we described above. Because tools may differ in their support slightly, TypeScript 5.0 provides ways to enable or disable a few features that may or may not work with your configuration. ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#allowimportingtsextensions) `allowImportingTsExtensions` `--allowImportingTsExtensions` allows TypeScript files to import each other with a TypeScript-specific extension like `.ts`, `.mts`, or `.tsx`. This flag is only allowed when `--noEmit` or `--emitDeclarationOnly` is enabled, since these import paths would not be resolvable at runtime in JavaScript output files. The expectation here is that your resolver (e.g. your bundler, a runtime, or some other tool) is going to make these imports between `.ts` files work. ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#resolvepackagejsonexports) `resolvePackageJsonExports` `--resolvePackageJsonExports` forces TypeScript to consult [the `exports` field of `package.json` files](https://nodejs.org/api/packages.html#exports) if it ever reads from a package in `node_modules`. This option defaults to `true` under the `node16`, `nodenext`, and `bundler` options for `--moduleResolution`. ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#resolvepackagejsonimports) `resolvePackageJsonImports` `--resolvePackageJsonImports` forces TypeScript to consult [the `imports` field of `package.json` files](https://nodejs.org/api/packages.html#imports) when performing a lookup that starts with `#` from a file whose ancestor directory contains a `package.json`. This option defaults to `true` under the `node16`, `nodenext`, and `bundler` options for `--moduleResolution`. ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#allowarbitraryextensions) `allowArbitraryExtensions` In TypeScript 5.0, when an import path ends in an extension that isn’t a known JavaScript or TypeScript file extension, the compiler will look for a declaration file for that path in the form of `{file basename}.d.{extension}.ts`. For example, if you are using a CSS loader in a bundler project, you might want to write (or generate) declaration files for those stylesheets: css ` /* app.css */ .cookie-banner { display: none; } ` ts ` // app.d.css.ts declare const css: { cookieBanner: string; }; export default css; ` ts ` // App.tsx import styles from "./app.css"; styles.cookieBanner; // string ` By default, this import will raise an error to let you know that TypeScript doesn’t understand this file type and your runtime might not support importing it. But if you’ve configured your runtime or bundler to handle it, you can suppress the error with the new `--allowArbitraryExtensions` compiler option. Note that historically, a similar effect has often been achievable by adding a declaration file named `app.css.d.ts` instead of `app.d.css.ts` - however, this just worked through Node’s `require` resolution rules for CommonJS. Strictly speaking, the former is interpreted as a declaration file for a JavaScript file named `app.css.js`. Because relative files imports need to include extensions in Node’s ESM support, TypeScript would error on our example in an ESM file under `--moduleResolution node16` or `nodenext`. For more information, read up [the proposal for this feature](https://github.com/microsoft/TypeScript/issues/50133) and [its corresponding pull request](https://github.com/microsoft/TypeScript/pull/51435) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#customconditions) `customConditions` `--customConditions` takes a list of additional [conditions](https://nodejs.org/api/packages.html#nested-conditions) that should succeed when TypeScript resolves from an [`exports`](https://nodejs.org/api/packages.html#exports) or [`imports`](https://nodejs.org/api/packages.html#imports) field of a `package.json`. These conditions are added to whatever existing conditions a resolver will use by default. For example, when this field is set in a `tsconfig.json` as so: jsonc ` { "compilerOptions": { "target": "es2022", "moduleResolution": "bundler", "customConditions": ["my-condition"] } } ` Any time an `exports` or `imports` field is referenced in `package.json`, TypeScript will consider conditions called `my-condition`. So when importing from a package with the following `package.json` jsonc ` { // ... "exports": { ".": { "my-condition": "./foo.mjs", "node": "./bar.mjs", "import": "./baz.mjs", "require": "./biz.mjs" } } } ` TypeScript will try to look for files corresponding to `foo.mjs`. This field is only valid under the `node16`, `nodenext`, and `bundler` options for `--moduleResolution` [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#--verbatimmodulesyntax) `--verbatimModuleSyntax` ---------------------------------------------------------------------------------------------------------------------------------- By default, TypeScript does something called _import elision_. Basically, if you write something like ts ` import { Car } from "./car"; export function drive(car: Car) { // ... } ` TypeScript detects that you’re only using an import for types and drops the import entirely. Your output JavaScript might look something like this: js ` export function drive(car) { // ... } ` Most of the time this is good, because if `Car` isn’t a value that’s exported from `./car`, we’ll get a runtime error. But it does add a layer of complexity for certain edge cases. For example, notice there’s no statement like `import "./car";` - the import was dropped entirely. That actually makes a difference for modules that have side-effects or not. TypeScript’s emit strategy for JavaScript also has another few layers of complexity - import elision isn’t always just driven by how an import is used - it often consults how a value is declared as well. So it’s not always clear whether code like the following ts ` export { Car } from "./car"; ` should be preserved or dropped. If `Car` is declared with something like a `class`, then it can be preserved in the resulting JavaScript file. But if `Car` is only declared as a `type` alias or `interface`, then the JavaScript file shouldn’t export `Car` at all. While TypeScript might be able to make these emit decisions based on information from across files, not every compiler can. The `type` modifier on imports and exports helps with these situations a bit. We can make it explicit whether an import or export is only being used for type analysis, and can be dropped entirely in JavaScript files by using the `type` modifier. ts ` // This statement can be dropped entirely in JS output import type * as car from "./car"; // The named import/export 'Car' can be dropped in JS output import { type Car } from "./car"; export { type Car } from "./car"; ` `type` modifiers are not quite useful on their own - by default, module elision will still drop imports, and nothing forces you to make the distinction between `type` and plain imports and exports. So TypeScript has the flag `--importsNotUsedAsValues` to make sure you use the `type` modifier, `--preserveValueImports` to prevent _some_ module elision behavior, and `--isolatedModules` to make sure that your TypeScript code works across different compilers. Unfortunately, understanding the fine details of those 3 flags is hard, and there are still some edge cases with unexpected behavior. TypeScript 5.0 introduces a new option called `--verbatimModuleSyntax` to simplify the situation. The rules are much simpler - any imports or exports without a `type` modifier are left around. Anything that uses the `type` modifier is dropped entirely. ts ` // Erased away entirely. import type { A } from "a"; // Rewritten to 'import { b } from "bcd";' import { b, type c, type d } from "bcd"; // Rewritten to 'import {} from "xyz";' import { type xyz } from "xyz"; ` With this new option, what you see is what you get. That does have some implications when it comes to module interop though. Under this flag, ECMAScript `import`s and `export`s won’t be rewritten to `require` calls when your settings or file extension implied a different module system. Instead, you’ll get an error. If you need to emit code that uses `require` and `module.exports`, you’ll have to use TypeScript’s module syntax that predates ES2015: | Input TypeScript | Output JavaScript | | --- | --- | | ts

` import foo = require("foo"); ` | js

` const foo = require("foo"); ` | | ts

` function foo() {} function bar() {} function baz() {} export = { foo, bar, baz }; ` | js

` function foo() {} function bar() {} function baz() {} module.exports = { foo, bar, baz }; ` | While this is a limitation, it does help make some issues more obvious. For example, it’s very common to forget to set the [`type` field in `package.json`](https://nodejs.org/api/packages.html#type) under `--module node16`. As a result, developers would start writing CommonJS modules instead of ES modules without realizing it, giving surprising lookup rules and JavaScript output. This new flag ensures that you’re intentional about the file type you’re using because the syntax is intentionally different. Because `--verbatimModuleSyntax` provides a more consistent story than `--importsNotUsedAsValues` and `--preserveValueImports`, those two existing flags are being deprecated in its favor. For more details, read up on \[the original pull request\][https://github.com/microsoft/TypeScript/pull/52203](https://github.com/microsoft/TypeScript/pull/52203) and [its proposal issue](https://github.com/microsoft/TypeScript/issues/51479) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#support-for-export-type-) Support for `export type *` --------------------------------------------------------------------------------------------------------------------------------------- When TypeScript 3.8 introduced type-only imports, the new syntax wasn’t allowed on `export * from "module"` or `export * as ns from "module"` re-exports. TypeScript 5.0 adds support for both of these forms: ts `` // models/vehicles.ts export class Spaceship { // ... } // models/index.ts export type * as vehicles from "./vehicles"; // main.ts import { vehicles } from "./models"; function takeASpaceship(s: vehicles.Spaceship) { // ✅ ok - `vehicles` only used in a type position } function makeASpaceship() { return new vehicles.Spaceship(); // ^^^^^^^^ // 'vehicles' cannot be used as a value because it was exported using 'export type'. } `` You can [read more about the implementation here](https://github.com/microsoft/TypeScript/pull/52217) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#satisfies-support-in-jsdoc) `@satisfies` Support in JSDoc ------------------------------------------------------------------------------------------------------------------------------------------- TypeScript 4.9 introduced the `satisfies` operator. It made sure that the type of an expression was compatible, without affecting the type itself. For example, let’s take the following code: ts ` interface CompilerOptions { strict?: boolean; outDir?: string; // ... } interface ConfigSettings { compilerOptions?: CompilerOptions; extends?: string | string[]; // ... } let myConfigSettings = { compilerOptions: { strict: true, outDir: "../lib", // ... }, extends: [ "@tsconfig/strictest/tsconfig.json", "../../../tsconfig.base.json" ], } satisfies ConfigSettings; ` Here, TypeScript knows that `myConfigSettings.extends` was declared with an array - because while `satisfies` validated the type of our object, it didn’t bluntly change it to `CompilerOptions` and lose information. So if we want to map over `extends`, that’s fine. ts ` declare function resolveConfig(configPath: string): CompilerOptions; let inheritedConfigs = myConfigSettings.extends.map(resolveConfig); ` This was helpful for TypeScript users, but plenty of people use TypeScript to type-check their JavaScript code using JSDoc annotations. That’s why TypeScript 5.0 is supporting a new JSDoc tag called `@satisfies` that does exactly the same thing. `/** @satisfies */` can catch type mismatches: js ` // @ts-check /** * @typedef CompilerOptions * @prop {boolean} [strict] * @prop {string} [outDir] */ /** * @satisfies {CompilerOptions} */ let myCompilerOptions = { outdir: "../lib", // ~~~~~~ oops! we meant outDir }; ` But it will preserve the original type of our expressions, allowing us to use our values more precisely later on in our code. js ` // @ts-check /** * @typedef CompilerOptions * @prop {boolean} [strict] * @prop {string} [outDir] */ /** * @typedef ConfigSettings * @prop {CompilerOptions} [compilerOptions] * @prop {string | string[]} [extends] */ /** * @satisfies {ConfigSettings} */ let myConfigSettings = { compilerOptions: { strict: true, outDir: "../lib", }, extends: [ "@tsconfig/strictest/tsconfig.json", "../../../tsconfig.base.json" ], }; let inheritedConfigs = myConfigSettings.extends.map(resolveConfig); ` `/** @satisfies */` can also be used inline on any parenthesized expression. We could have written `myCompilerOptions` like this: ts ` let myConfigSettings = /** @satisfies {ConfigSettings} */ ({ compilerOptions: { strict: true, outDir: "../lib", }, extends: [ "@tsconfig/strictest/tsconfig.json", "../../../tsconfig.base.json" ], }); ` Why? Well, it usually makes more sense when you’re deeper in some other code, like a function call. js ` compileCode(/** @satisfies {CompilerOptions} */ ({ // ... })); ` [This feature](https://github.com/microsoft/TypeScript/pull/51753) was provided thanks to [Oleksandr Tarasiuk](https://github.com/a-tarasyuk) ! [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#overload-support-in-jsdoc) `@overload` Support in JSDoc ----------------------------------------------------------------------------------------------------------------------------------------- In TypeScript, you can specify overloads for a function. Overloads give us a way to say that a function can be called with different arguments, and possibly return different results. They can restrict how callers can actually use our functions, and refine what results they’ll get back. ts ` // Our overloads: function printValue(str: string): void; function printValue(num: number, maxFractionDigits?: number): void; // Our implementation: function printValue(value: string | number, maximumFractionDigits?: number) { if (typeof value === "number") { const formatter = Intl.NumberFormat("en-US", { maximumFractionDigits, }); value = formatter.format(value); } console.log(value); } ` Here, we’ve said that `printValue` takes either a `string` or a `number` as its first argument. If it takes a `number`, it can take a second argument to determine how many fractional digits we can print. TypeScript 5.0 now allows JSDoc to declare overloads with a new `@overload` tag. Each JSDoc comment with an `@overload` tag is treated as a distinct overload for the following function declaration. js ` // @ts-check /** * @overload * @param {string} value * @return {void} */ /** * @overload * @param {number} value * @param {number} [maximumFractionDigits] * @return {void} */ /** * @param {string | number} value * @param {number} [maximumFractionDigits] */ function printValue(value, maximumFractionDigits) { if (typeof value === "number") { const formatter = Intl.NumberFormat("en-US", { maximumFractionDigits, }); value = formatter.format(value); } console.log(value); } ` Now regardless of whether we’re writing in a TypeScript or JavaScript file, TypeScript can let us know if we’ve called our functions incorrectly. ts ` // all allowed printValue("hello!"); printValue(123.45); printValue(123.45, 2); printValue("hello!", 123); // error! ` This new tag [was implemented](https://github.com/microsoft/TypeScript/pull/51234) thanks to [Tomasz Lenarcik](https://github.com/apendua) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#passing-emit-specific-flags-under---build) Passing Emit-Specific Flags Under `--build` ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ TypeScript now allows the following flags to be passed under `--build` mode * `--declaration` * `--emitDeclarationOnly` * `--declarationMap` * `--sourceMap` * `--inlineSourceMap` This makes it way easier to customize certain parts of a build where you might have different development and production builds. For example, a development build of a library might not need to produce declaration files, but a production build would. A project can configure declaration emit to be off by default and simply be built with sh ` tsc --build -p ./my-project-dir ` Once you’re done iterating in the inner loop, a “production” build can just pass the `--declaration` flag. sh ` tsc --build -p ./my-project-dir --declaration ` [More information on this change is available here](https://github.com/microsoft/TypeScript/pull/51241) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#case-insensitive-import-sorting-in-editors) Case-Insensitive Import Sorting in Editors ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ In editors like Visual Studio and VS Code, TypeScript powers the experience for organizing and sorting imports and exports. Often though, there can be different interpretations of when a list is “sorted”. For example, is the following import list sorted? ts ` import { Toggle, freeze, toBoolean, } from "./utils"; ` The answer might surprisingly be “it depends”. If we _don’t_ care about case-sensitivity, then this list is clearly not sorted. The letter `f` comes before both `t` and `T`. But in most programming languages, sorting defaults to comparing the byte values of strings. The way JavaScript compares strings means that `"Toggle"` always comes before `"freeze"` because according to the [ASCII character encoding](https://en.wikipedia.org/wiki/ASCII) , uppercase letters come before lowercase. So from that perspective, the import list is sorted. TypeScript previously considered the import list to be sorted because it was doing a basic case-sensitive sort. This could be a point of frustration for developers who preferred a case-_insensitive_ ordering, or who used tools like ESLint which require case-insensitive ordering by default. TypeScript now detects case sensitivity by default. This means that TypeScript and tools like ESLint typically won’t “fight” each other over how to best sort imports. Our team has also been experimenting [with further sorting strategies which you can read about here](https://github.com/microsoft/TypeScript/pull/52115) . These options may eventually be configurable by editors. For now, they are still unstable and experimental, and you can opt into them in VS Code today by using the `typescript.unstable` entry in your JSON options. Below are all of the options you can try out (set to their defaults): jsonc `` { "typescript.unstable": { // Should sorting be case-sensitive? Can be: // - true // - false // - "auto" (auto-detect) "organizeImportsIgnoreCase": "auto", // Should sorting be "ordinal" and use code points or consider Unicode rules? Can be: // - "ordinal" // - "unicode" "organizeImportsCollation": "ordinal", // Under `"organizeImportsCollation": "unicode"`, // what is the current locale? Can be: // - [any other locale code] // - "auto" (use the editor's locale) "organizeImportsLocale": "en", // Under `"organizeImportsCollation": "unicode"`, // should upper-case letters or lower-case letters come first? Can be: // - false (locale-specific) // - "upper" // - "lower" "organizeImportsCaseFirst": false, // Under `"organizeImportsCollation": "unicode"`, // do runs of numbers get compared numerically (i.e. "a1" < "a2" < "a100")? Can be: // - true // - false "organizeImportsNumericCollation": true, // Under `"organizeImportsCollation": "unicode"`, // do letters with accent marks/diacritics get sorted distinctly // from their "base" letter (i.e. is é different from e)? Can be // - true // - false "organizeImportsAccentCollation": true }, "javascript.unstable": { // same options valid here... }, } `` You can read more details on [the original work for auto-detecting and specifying case-insensitivity](https://github.com/microsoft/TypeScript/pull/51733) , followed by the [the broader set of options](https://github.com/microsoft/TypeScript/pull/52115) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#exhaustive-switchcase-completions) Exhaustive `switch`/`case` Completions ----------------------------------------------------------------------------------------------------------------------------------------------------------- When writing a `switch` statement, TypeScript now detects when the value being checked has a literal type. If so, it will offer a completion that scaffolds out each uncovered `case`. ![A set of case statements generated through auto-completion based on literal types.](https://devblogs.microsoft.com/typescript/wp-content/uploads/sites/11/2023/01/switchCaseSnippets-5-0_1.gif) You can [see specifics of the implementation on GitHub](https://github.com/microsoft/TypeScript/pull/50996) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#speed-memory-and-package-size-optimizations) Speed, Memory, and Package Size Optimizations ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- TypeScript 5.0 contains lots of powerful changes across our code structure, our data structures, and algorithmic implementations. What these all mean is that your entire experience should be faster - not just running TypeScript, but even installing it. Here are a few interesting wins in speed and size that we’ve been able to capture relative to TypeScript 4.9. | Scenario | Time or Size Relative to TS 4.9 | | --- | --- | | material-ui build time | 89% | | TypeScript Compiler startup time | 89% | | Playwright build time | 88% | | TypeScript Compiler self-build time | 87% | | Outlook Web build time | 82% | | VS Code build time | 80% | | typescript npm Package Size | 59% | ![Chart of build/run times and package size of TypeScript 5.0 relative to TypeScript 4.9: material-ui docs build time: 89%; Playwright build time: 88%; tsc startup time: 87%; tsc build time: 87%; Outlook Web build time: 82%; VS Code build time: 80%; typescript Package Size: 59%](https://devblogs.microsoft.com/typescript/wp-content/uploads/sites/11/2023/03/speed-and-size-5-0-rc.png?1) How? There are a few notable improvements we’d like give more details on in the future. But we won’t make you wait for that blog post. First off, we recently migrated TypeScript from namespaces to modules, allowing us to leverage modern build tooling that can perform optimizations like scope hoisting. Using this tooling, revisiting our packaging strategy, and removing some deprecated code has shaved off about 26.4 MB from TypeScript 4.9’s 63.8 MB package size. It also brought us a notable speed-up through direct function calls. TypeScript also added more uniformity to internal object types within the compiler, and also slimmed the data stored on some of these object types as well. This reduced polymorphic and megamorphic use sites, while offsetting most of the necessary memory consumption that was necessary for uniform shapes. We’ve also performed some caching when serializing information to strings. Type display, which can happen as part of error reporting, declaration emit, code completions, and more, can end up being fairly expensive. TypeScript now caches some commonly used machinery to reuse across these operations. Another notable change we made that improved our parser was leveraging `var` to occasionally side-step the cost of using `let` and `const` across closures. This improved some of our parsing performance. Overall, we expect most codebases should see speed improvements from TypeScript 5.0, and have consistently been able to reproduce wins between 10% to 20%. Of course this will depend on hardware and codebase characteristics, but we encourage you to try it out on your codebase today! For more information, see some of our notable optimizations: * [Migrate to Modules](https://github.com/microsoft/TypeScript/pull/51387) * [`Node` Monomorphization](https://github.com/microsoft/TypeScript/pull/51682) * [`Symbol` Monomorphization](https://github.com/microsoft/TypeScript/pull/51880) * [`Identifier` Size Reduction](https://github.com/microsoft/TypeScript/pull/52170) * [`Printer` Caching](https://github.com/microsoft/TypeScript/pull/52382) * [Limited Usage of `var`](https://github.com/microsoft/TypeScript/issues/52924) [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#breaking-changes-and-deprecations) Breaking Changes and Deprecations ------------------------------------------------------------------------------------------------------------------------------------------------------ ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#runtime-requirements) Runtime Requirements TypeScript now targets ECMAScript 2018. For Node users, that means a minimum version requirement of at least Node.js 10 and later. ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#libdts-changes) `lib.d.ts` Changes Changes to how types for the DOM are generated might have an impact on existing code. Notably, certain properties have been converted from `number` to numeric literal types, and properties and methods for cut, copy, and paste event handling have been moved across interfaces. ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#api-breaking-changes) API Breaking Changes In TypeScript 5.0, we moved to modules, removed some unnecessary interfaces, and made some correctness improvements. For more details on what’s changed, see our [API Breaking Changes](https://github.com/microsoft/TypeScript/wiki/API-Breaking-Changes) page. ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#forbidden-implicit-coercions-in-relational-operators) Forbidden Implicit Coercions in Relational Operators Certain operations in TypeScript will already warn you if you write code which may cause an implicit string-to-number coercion: ts ` function func(ns: number | string) { return ns * 4; // Error, possible implicit coercion } ` In 5.0, this will also be applied to the relational operators `>`, `<`, `<=`, and `>=`: ts ` function func(ns: number | string) { return ns > 4; // Now also an error } ` To allow this if desired, you can explicitly coerce the operand to a `number` using `+`: ts ` function func(ns: number | string) { return +ns > 4; // OK } ` This [correctness improvement](https://github.com/microsoft/TypeScript/pull/52048) was contributed courtesy of [Mateusz Burzyński](https://github.com/Andarist) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#enum-overhaul) Enum Overhaul TypeScript has had some long-standing oddities around `enum`s ever since its first release. In 5.0, we’re cleaning up some of these problems, as well as reducing the concept count needed to understand the various kinds of `enum`s you can declare. There are two main new errors you might see as part of this. The first is that assigning an out-of-domain literal to an `enum` type will now error as one might expect: ts ` enum SomeEvenDigit { Zero = 0, Two = 2, Four = 4 } // Now correctly an error let m: SomeEvenDigit = 1; ` The other is that declaration of certain kinds of indirected mixed string/number `enum` forms would, incorrectly, create an all-number `enum`: ts ` enum Letters { A = "a" } enum Numbers { one = 1, two = Letters.A } // Now correctly an error const t: number = Numbers.two; ` You can [see more details in relevant change](https://github.com/microsoft/TypeScript/pull/50528) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#more-accurate-type-checking-for-parameter-decorators-in-constructors-under---experimentaldecorators) More Accurate Type-Checking for Parameter Decorators in Constructors Under `--experimentalDecorators` TypeScript 5.0 makes type-checking more accurate for decorators under `--experimentalDecorators`. One place where this becomes apparent is when using a decorator on a constructor parameter. ts ` export declare const inject: (entity: any) => (target: object, key: string | symbol, index?: number) => void; export class Foo {} export class C { constructor(@inject(Foo) private x: any) { } } ` This call will fail because `key` expects a `string | symbol`, but constructor parameters receive a key of `undefined`. The correct fix is to change the type of `key` within `inject`. A reasonable workaround if you’re using a library that can’t be upgraded is is to wrap `inject` in a more type-safe decorator function, and use a type-assertion on `key`. For more details, [see this issue](https://github.com/microsoft/TypeScript/issues/52435) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#deprecations-and-default-changes) Deprecations and Default Changes In TypeScript 5.0, we’ve deprecated the following settings and setting values: * `--target: ES3` * `--out` * `--noImplicitUseStrict` * `--keyofStringsOnly` * `--suppressExcessPropertyErrors` * `--suppressImplicitAnyIndexErrors` * `--noStrictGenericChecks` * `--charset` * `--importsNotUsedAsValues` * `--preserveValueImports` * `prepend` in project references These configurations will continue to be allowed until TypeScript 5.5, at which point they will be removed entirely, however, you will receive a warning if you are using these settings. In TypeScript 5.0, as well as future releases 5.1, 5.2, 5.3, and 5.4, you can specify `"ignoreDeprecations": "5.0"` to silence those warnings. We’ll also shortly be releasing a 4.9 patch to allow specifying `ignoreDeprecations` to allow for smoother upgrades. Aside from deprecations, we’ve changed some settings to better improve cross-platform behavior in TypeScript. `--newLine`, which controls the line endings emitted in JavaScript files, used to be inferred based on the current operating system if not specified. We think builds should be as deterministic as possible, and Windows Notepad supports line-feed line endings now, so the new default setting is `LF`. The old OS-specific inference behavior is no longer available. `--forceConsistentCasingInFileNames`, which ensured that all references to the same file name in a project agreed in casing, now defaults to `true`. This can help catch differences issues with code written on case-insensitive file systems. You can leave feedback and view more information on the [tracking issue for 5.0 deprecations](https://github.com/microsoft/TypeScript/issues/51909) The TypeScript docs are an open source project. Help us improve these pages [by sending a Pull Request](https://github.com/microsoft/TypeScript-Website/blob/v2/packages/documentation/copy/en/release-notes/TypeScript%205.0.md) ❤ Contributors to this page: AB![Andrew Branch (7)](https://gravatar.com/avatar/86ed396e4ffb41b052654b9f8ad4d1caaf732bc42985860b0cac8191a66e33df?s=32&&d=blank) B![blisse (1)](https://gravatar.com/avatar/24796bee60c3a5acfd633308f200702e43c3cc4121e6d2e3be05858a028177e5?s=32&&d=blank) EI![Eugene Ilyin (1)](https://gravatar.com/avatar/a65caef54cbaba95975117e96ca2d411668cedaea6e5bbc59287ed673c84751e?s=32&&d=blank) M![marcustyphoon (1)](https://gravatar.com/avatar/9ebf66173d7989e161e8bf923221ad764844be328f1ddeaf40136b28ecfcc786?s=32&&d=blank) M![Mohi (1)](https://gravatar.com/avatar/f465867e32b6ef5414f2746e668a75bb83f241b26d385c466005f5510b8383cf?s=32&&d=blank) 2+ Last updated: Jul 14, 2025   MSG --- # TypeScript: Documentation - TypeScript 4.8 Was this page helpful? TypeScript 4.8 ============== [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-8.html#improved-intersection-reduction-union-compatibility-and-narrowing) Improved Intersection Reduction, Union Compatibility, and Narrowing ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ TypeScript 4.8 brings a series of correctness and consistency improvements under `--strictNullChecks`. These changes affect how intersection and union types work, and are leveraged in how TypeScript narrows types. For example, `unknown` is close in spirit to the union type `{} | null | undefined` because it accepts `null`, `undefined`, and any other type. TypeScript now recognizes this, and allows assignments from `unknown` to `{} | null | undefined`. ts ` function f(x: unknown, y: {} | null | undefined) { x = y; // always worked y = x; // used to error, now works } ` Another change is that `{}` intersected with any other object type simplifies right down to that object type. That meant that we were able to rewrite `NonNullable` to just use an intersection with `{}`, because `{} & null` and `{} & undefined` just get tossed away. diff ` - type NonNullable = T extends null | undefined ? never : T; + type NonNullable = T & {}; ` This is an improvement because intersection types like this can be reduced and assigned to, while conditional types currently cannot. So `NonNullable>` now simplifies at least to `NonNullable`, whereas it didn’t before. ts ` function foo(x: NonNullable, y: NonNullable>) { x = y; // always worked y = x; // used to error, now works } ` These changes also allowed us to bring in sensible improvements in control flow analysis and type narrowing. For example, `unknown` is now narrowed just like `{} | null | undefined` in truthy branches. ts ` function narrowUnknownishUnion(x: {} | null | undefined) { if (x) { x; // {} } else { x; // {} | null | undefined } } function narrowUnknown(x: unknown) { if (x) { x; // used to be 'unknown', now '{}' } else { x; // unknown } } ` Generic values also get narrowed similarly. When checking that a value isn’t `null` or `undefined`, TypeScript now just intersects it with `{}` - which again, is the same as saying it’s `NonNullable`. Putting many of the changes here together, we can now define the following function without any type assertions. ts ` function throwIfNullable(value: T): NonNullable { if (value === undefined || value === null) { throw Error("Nullable value!"); } // Used to fail because 'T' was not assignable to 'NonNullable'. // Now narrows to 'T & {}' and succeeds because that's just 'NonNullable'. return value; } ` `value` now gets narrowed to `T & {}`, and is now identical with `NonNullable` - so the body of the function just works with no TypeScript-specific syntax. On their own, these changes may appear small - but they represent fixes for many many paper cuts that have been reported over several years. For more specifics on these improvements, you can [read more here](https://github.com/microsoft/TypeScript/pull/49119) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-8.html#improved-inference-for-infer-types-in-template-string-types) Improved Inference for `infer` Types in Template String Types ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ TypeScript recently introduced a way to add `extends` constraints to `infer` type variables in conditional types. ts ` // Grabs the first element of a tuple if it's assignable to 'number', // and returns 'never' if it can't find one. type TryGetNumberIfFirst = T extends [infer U extends number, ...unknown[]] ? U : never; ` If these `infer` types appear in a template string type and are constrained to a primitive type, TypeScript will now try to parse out a literal type. ts `` // SomeNum used to be 'number'; now it's '100'. type SomeNum = "100" extends `${infer U extends number}` ? U : never; // SomeBigInt used to be 'bigint'; now it's '100n'. type SomeBigInt = "100" extends `${infer U extends bigint}` ? U : never; // SomeBool used to be 'boolean'; now it's 'true'. type SomeBool = "true" extends `${infer U extends boolean}` ? U : never; `` This can now better convey what a library will do at runtime, and give more precise types. One note on this is that when TypeScript parses these literal types out it will greedily try to parse out as much of what looks like of the appropriate primitive type; however it then checks to see if the print-back of that primitive matches up with the string contents. In other words, TypeScript checks whether the going from the string, to the primitive, and back matches. If it doesn’t see that the string can be “round-tripped”, then it will fall back to the base primitive type. ts ``// JustNumber is `number` here because TypeScript parses out `"1.0"`, but `String(Number("1.0"))` is `"1"` and doesn't match. type JustNumber = "1.0" extends `${infer T extends number}` ? T : never;`` You can [see more about this feature here](https://github.com/microsoft/TypeScript/pull/48094) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-8.html#--build---watch-and---incremental-performance-improvements) `--build`, `--watch`, and `--incremental` Performance Improvements ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- TypeScript 4.8 introduces several optimizations that should speed up scenarios around `--watch` and `--incremental`, along with project references builds using `--build`. For example, TypeScript is now able to avoid spending time updating timestamps during no-op changes in `--watch` mode, which makes rebuilds faster and avoids messing with other build tools that might be watching for TypeScript’s output. Many other optimizations where we’re able to reuse information across `--build`, `--watch`, and `--incremental` have been introduced as well. How big are these improvements? Well, on a fairly large internal codebase, we’ve seen time reductions on the order of 10%-25% on many simple common operations, with around 40% time reductions in no-change scenarios. We’ve seen similar results on the TypeScript codebase as well. You can see [the changes, along with the performance results on GitHub](https://github.com/microsoft/TypeScript/pull/48784) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-8.html#errors-when-comparing-object-and-array-literals) Errors When Comparing Object and Array Literals ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- In many languages, operators like `==` perform what’s called “value” equality on objects. For example, in Python it’s valid to check whether a list is empty by checking whether a value is equal to the empty list using `==`. py ` if people_at_home == []: print("here's where I lie, broken inside. (x: T, y: T): T; let [a, b, c] = chooseRandomly([42, true, "hi!"], [0, false, "bye!"]); // ^ ^ ^ // | | | // | | string // | | // | boolean // | // number ` When `chooseRandomly` needs to figure out a type for `T`, it will primarily look at `[42, true, "hi!"]` and `[0, false, "bye!"]`; but TypeScript needs to figure out whether those two types should be `Array` or the tuple type `[number, boolean, string]`. To do that, it will look for existing candidates as a hint to see whether there are any tuple types. When TypeScript sees the binding pattern `[a, b, c]`, it creates the type `[any, any, any]`, and that type gets picked up as a low-priority candidate for `T` which also gets used as a hint for the types of `[42, true, "hi!"]` and `[0, false, "bye!"]`. You can see how this was good for `chooseRandomly`, but it fell short in other cases. For example, take the following code ts ` declare function f(x?: T): T; let [x, y, z] = f(); ` The binding pattern `[x, y, z]` hinted that `f` should produce an `[any, any, any]` tuple; but `f` really shouldn’t change its type argument based on a binding pattern. It can’t suddenly conjure up a new array-like value based on what it’s being assigned to, so the binding pattern type has way too much influence on the produced type. On top of that, because the binding pattern type is full of `any`s, we’re left with `x`, `y`, and `z` being typed as `any`. In TypeScript 4.8, these binding patterns are never used as candidates for type arguments. Instead, they’re just consulted in case a parameter needs a more specific type like in our `chooseRandomly` example. If you need to revert to the old behavior, you can always provide explicit type arguments. You can [look at the change on GitHub](https://github.com/microsoft/TypeScript/pull/49086) if you’re curious to learn more. [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-8.html#file-watching-fixes-especially-across-git-checkouts) File-Watching Fixes (Especially Across `git checkout`s) ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- We’ve had a long-standing bug where TypeScript has a very hard time with certain file changes in `--watch` mode and editor scenarios. Sometimes the symptoms are stale or inaccurate errors that might show up that require restarting `tsc` or VS Code. Frequently these occur on Unix systems, and you might have seen these after saving a file with vim or swapping branches in git. This was caused by assumptions of how Node.js handles rename events across file systems. File systems used by Linux and macOS utilize [inodes](https://en.wikipedia.org/wiki/Inode) , and [Node.js will attach file watchers to inodes rather than file paths](https://nodejs.org/api/fs.html#inodes) . So when Node.js returns [a watcher object](https://nodejs.org/api/fs.html#class-fsfswatcher) , it might be watching a path or an inode depending on the platform and file system. To be a bit more efficient, TypeScript tries to reuse the same watcher objects if it detects a path still exists on disk. This is where things went wrong, because even if a file still exists at that path, a distinct file might have been created, and that file will have a different inode. So TypeScript would end up reusing the watcher object instead of installing a new watcher at the original location, and watch for changes at what might be a totally irrelevant file. So TypeScript 4.8 now handles these cases on inode systems and properly installs a new watcher and fixes this. We’d like to extend our thanks to [Marc Celani](https://github.com/MarcCelani-at) and his team at Airtable who invested lots of time in investigating the issues they were experiencing and pointing out the root cause. You can view [the specific fixes around file-watching here](https://github.com/microsoft/TypeScript/pull/48997) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-8.html#find-all-references-performance-improvements) Find-All-References Performance Improvements ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- When running find-all-references in your editor, TypeScript is now able to act a little smarter as it aggregates references. This reduced the amount of time TypeScript took to search a widely-used identifier in its own codebase by about 20%. [You can read up more on the improvement here](https://github.com/microsoft/TypeScript/pull/49581) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-8.html#exclude-specific-files-from-auto-imports) Exclude Specific Files from Auto-Imports -------------------------------------------------------------------------------------------------------------------------------------------------------------------- TypeScript 4.8 introduces an editor preference for excluding files from auto-imports. In Visual Studio Code, file names or globs can be added under “Auto Import File Exclude Patterns” in the Settings UI, or in a `.vscode/settings.json` file: jsonc `` { // Note that `javascript.preferences.autoImportFileExcludePatterns` can be specified for JavaScript too. "typescript.preferences.autoImportFileExcludePatterns": [ "**/node_modules/@types/node" ] } `` This can be useful in cases where you can’t avoid having certain modules or libraries in your compilation but you rarely want to import from them. These modules might have lots of exports that can pollute the auto-imports list and make it harder to navigate, and this option can help in those situations. You can [see more specifics about the implementation here](https://github.com/microsoft/TypeScript/pull/49578) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-8.html#correctness-fixes-and-breaking-changes) Correctness Fixes and Breaking Changes ---------------------------------------------------------------------------------------------------------------------------------------------------------------- Due to the nature of type system changes, there are very few changes that can be made that don’t affect _some_ code; however, there are a few changes that are more likely to require adapting existing code. ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-8.html#libdts-updates) `lib.d.ts` Updates While TypeScript strives to avoid major breaks, even small changes in the built-in libraries can cause issues. We don’t expect major breaks as a result of DOM and `lib.d.ts` updates, but one notable change is that the `cause` property on `Error`s now has the type `unknown` instead of `Error`. ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-8.html#unconstrained-generics-no-longer-assignable-to-) Unconstrained Generics No Longer Assignable to `{}` In TypeScript 4.8, for projects with `strictNullChecks` enabled, TypeScript will now correctly issue an error when an unconstrained type parameter is used in a position where `null` or `undefined` are not legal values. That will include any type that expects `{}`, `object`, or an object type with all-optional properties. A simple example can be seen in the following. ts ` // Accepts any non-null non-undefined value function bar(value: {}) { Object.keys(value); // This call throws on null/undefined at runtime. } // Unconstrained type parameter T... function foo(x: T) { bar(x); // Used to be allowed, now is an error in 4.8. // ~ // error: Argument of type 'T' is not assignable to parameter of type '{}'. } foo(undefined); ` As demonstrated above, code like this has a potential bug - the values `null` and `undefined` can be indirectly passed through these unconstrained type parameters to code that is not supposed to observe those values. This behavior will also be visible in type positions. One example would be: ts ` interface Foo { x: Bar; } interface Bar { } ` Existing code that didn’t want to handle `null` and `undefined` can be fixed by propagating the appropriate constraints through. diff ` - function foo(x: T) { + function foo(x: T) { ` Another work-around would be to check for `null` and `undefined` at runtime. diff `function foo(x: T) { + if (x !== null && x !== undefined) { bar(x); + } }` And if you know that for some reason, your generic value can’t be `null` or `undefined`, you can just use a non-null assertion. diff `function foo(x: T) { - bar(x); + bar(x!); }` When it comes to types, you’ll often either need to propagate constraints, or intersect your types with `{}`. For more information, you can [see the change that introduced this](https://github.com/microsoft/TypeScript/pull/49119) along with [the specific discussion issue regarding how unconstrained generics now work](https://github.com/microsoft/TypeScript/issues/49489) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-8.html#decorators-are-placed-on-modifiers-on-typescripts-syntax-trees) Decorators are placed on `modifiers` on TypeScript’s Syntax Trees The current direction of decorators in TC39 means that TypeScript will have to handle a break in terms of placement of decorators. Previously, TypeScript assumed decorators would always be placed prior to all keywords/modifiers. For example ts ` @decorator export class Foo { // ... } ` Decorators as currently proposed do not support this syntax. Instead, the `export` keyword must precede the decorator. ts ` export @decorator class Foo { // ... } ` Unfortunately, TypeScript’s trees are _concrete_ rather than _abstract_, and our architecture expects syntax tree node fields to be entirely ordered before or after each other. To support both legacy decorators and decorators as proposed, TypeScript will have to gracefully parse, and intersperse, modifiers and decorators. To do this, it exposes a new type alias called `ModifierLike` which is a `Modifier` or a `Decorator`. ts ` export type ModifierLike = Modifier | Decorator; ` Decorators are now placed in the same field as `modifiers` which is now a `NodeArray` when set, and the entire field is deprecated. diff `` - readonly modifiers?: NodeArray | undefined; + /** + * @deprecated ... + * Use `ts.canHaveModifiers()` to test whether a `Node` can have modifiers. + * Use `ts.getModifiers()` to get the modifiers of a `Node`. + * ... + */ + readonly modifiers?: NodeArray | undefined; `` All existing `decorators` properties have been marked as deprecated and will always be `undefined` if read. The type has also been changed to `undefined` so that existing tools know to handle them correctly. diff `` - readonly decorators?: NodeArray | undefined; + /** + * @deprecated ... + * Use `ts.canHaveDecorators()` to test whether a `Node` can have decorators. + * Use `ts.getDecorators()` to get the decorators of a `Node`. + * ... + */ + readonly decorators?: undefined; `` To avoid new deprecation warnings and other issues, TypeScript now exposes four new functions to use in place of the `decorators` and `modifiers` properties. There are individual predicates for testing whether a node has support modifiers and decorators, along with respective accessor functions for grabbing them. ts ` function canHaveModifiers(node: Node): node is HasModifiers; function getModifiers(node: HasModifiers): readonly Modifier[] | undefined; function canHaveDecorators(node: Node): node is HasDecorators; function getDecorators(node: HasDecorators): readonly Decorator[] | undefined; ` As an example of how to access modifiers off of a node, you can write ts ` const modifiers = canHaveModifiers(myNode) ? getModifiers(myNode) : undefined; ` With the note that each call to `getModifiers` and `getDecorators` may allocate a new array. For more information, see changes around * [the restructuring of our tree nodes](https://github.com/microsoft/TypeScript/pull/49089) * [the deprecations](https://github.com/microsoft/TypeScript/pull/50343) * [exposing the predicate functions](https://github.com/microsoft/TypeScript/pull/50399) ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-8.html#types-cannot-be-importedexported-in-javascript-files) Types Cannot Be Imported/Exported in JavaScript Files TypeScript previously allowed JavaScript files to import and export entities declared with a type, but no value, in `import` and `export` statements. This behavior was incorrect, because named imports and exports for values that don’t exist will cause a runtime error under ECMAScript modules. When a JavaScript file is type-checked under `--checkJs` or through a `// @ts-check` comment, TypeScript will now issue an error. ts ` // @ts-check // Will fail at runtime because 'SomeType' is not a value. import { someValue, SomeType } from "some-module"; /** * @type {SomeType} */ export const myValue = someValue; /** * @typedef {string | number} MyType */ // Will fail at runtime because 'MyType' is not a value. export { MyType as MyExportedType }; ` To reference a type from another module, you can instead directly qualify the import. diff ` - import { someValue, SomeType } from "some-module"; + import { someValue } from "some-module"; /** - * @type {SomeType} + * @type {import("some-module").SomeType} */ export const myValue = someValue; ` To export a type, you can just use a `/** @typedef */` comment in JSDoc. `@typedef` comments already automatically export types from their containing modules. diff `/** * @typedef {string | number} MyType */ + /** + * @typedef {MyType} MyExportedType + */ - export { MyType as MyExportedType };` You can [read more about the change here](https://github.com/microsoft/TypeScript/pull/49580) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-8.html#binding-patterns-do-not-directly-contribute-to-inference-candidates) Binding Patterns Do Not Directly Contribute to Inference Candidates As mentioned above, binding patterns no longer change the type of inference results in function calls. You can [read more about the original change here](https://github.com/microsoft/TypeScript/pull/49086) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-8.html#unused-renames-in-binding-patterns-are-now-errors-in-type-signatures) Unused Renames in Binding Patterns are Now Errors in Type Signatures TypeScript’s type annotation syntax often looks like it can be used when destructuring values. For example, take the following function. ts ` declare function makePerson({ name: string, age: number }): Person; ` You might read this signature and think that `makePerson` obviously takes an object with a `name` property with the type `string` and an `age` property with the type `number`; however, JavaScript’s destructuring syntax is actually taking precedence here. `makePerson` does say that it’s going to take an object with a `name` and an `age` property, but instead of specifying a type for them, it’s just saying that it renames `name` and `age` to `string` and `number` respectively. In a pure type construct, writing code like this is useless, and typically a mistake since developers usually assume they’re writing a type annotation. TypeScript 4.8 makes these an error unless they’re referenced later in the signature. The correct way to write the above signature would be as follows: ts ` declare function makePerson(options: { name: string, age: number }): Person; // or declare function makePerson({ name, age }: { name: string, age: number }): Person; ` This change can catch bugs in declarations, and has been helpful for improving existing code. We’d like to extend our thanks to [GitHub user uhyo](https://github.com/uhyo) for providing this check. [You can read up on the change here](https://github.com/microsoft/TypeScript/pull/41044) . The TypeScript docs are an open source project. Help us improve these pages [by sending a Pull Request](https://github.com/microsoft/TypeScript-Website/blob/v2/packages/documentation/copy/en/release-notes/TypeScript%204.8.md) ❤ Contributors to this page: AB![Andrew Branch (6)](https://gravatar.com/avatar/86ed396e4ffb41b052654b9f8ad4d1caaf732bc42985860b0cac8191a66e33df?s=32&&d=blank) Last updated: Jul 14, 2025   MSG --- # TypeScript: Documentation - TypeScript 5.1 Was this page helpful? TypeScript 5.1 ============== [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-1.html#easier-implicit-returns-for-undefined-returning-functions) Easier Implicit Returns for `undefined`\-Returning Functions --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- In JavaScript, if a function finishes running without hitting a `return`, it returns the value `undefined`. ts ` function foo() { // no return } // x = undefined let x = foo(); ` However, in previous versions of TypeScript, the _only_ functions that could have absolutely no return statements were `void`\- and `any`\-returning functions. That meant that even if you explicitly said “this function returns `undefined`” you were forced to have at least one return statement. ts ` // ✅ fine - we inferred that 'f1' returns 'void' function f1() { // no returns } // ✅ fine - 'void' doesn't need a return statement function f2(): void { // no returns } // ✅ fine - 'any' doesn't need a return statement function f3(): any { // no returns } // ❌ error! // A function whose declared type is neither 'void' nor 'any' must return a value. function f4(): undefined { // no returns } ` This could be a pain if some API expected a function returning `undefined` - you would need to have either at least one explicit return of `undefined` or a `return` statement _and_ an explicit annotation. ts ` declare function takesFunction(f: () => undefined): undefined; // ❌ error! // Argument of type '() => void' is not assignable to parameter of type '() => undefined'. takesFunction(() => { // no returns }); // ❌ error! // A function whose declared type is neither 'void' nor 'any' must return a value. takesFunction((): undefined => { // no returns }); // ❌ error! // Argument of type '() => void' is not assignable to parameter of type '() => undefined'. takesFunction(() => { return; }); // ✅ works takesFunction(() => { return undefined; }); // ✅ works takesFunction((): undefined => { return; }); ` This behavior was frustrating and confusing, especially when calling functions outside of one’s control. Understanding the interplay between inferring `void` over `undefined`, whether an `undefined`\-returning function needs a `return` statement, etc. seems like a distraction. First, TypeScript 5.1 now allows `undefined`\-returning functions to have no return statement. ts ` // ✅ Works in TypeScript 5.1! function f4(): undefined { // no returns } // ✅ Works in TypeScript 5.1! takesFunction((): undefined => { // no returns }); ` Second, if a function has no return expressions and is being passed to something expecting a function that returns `undefined`, TypeScript infers `undefined` for that function’s return type. ts ` // ✅ Works in TypeScript 5.1! takesFunction(function f() { // ^ return type is undefined // no returns }); // ✅ Works in TypeScript 5.1! takesFunction(function f() { // ^ return type is undefined return; }); ` To address another similar pain-point, under TypeScript’s `--noImplicitReturns` option, functions returning _only_ `undefined` now have a similar exception to `void`, in that not every single code path must end in an explicit `return`. ts ` // ✅ Works in TypeScript 5.1 under '--noImplicitReturns'! function f(): undefined { if (Math.random()) { // do some stuff... return; } } ` For more information, you can read up on [the original issue](https://github.com/microsoft/TypeScript/issues/36288) and [the implementing pull request](https://github.com/microsoft/TypeScript/pull/53607) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-1.html#unrelated-types-for-getters-and-setters) Unrelated Types for Getters and Setters ------------------------------------------------------------------------------------------------------------------------------------------------------------------ TypeScript 4.3 made it possible to say that a `get` and `set` accessor pair might specify two different types. ts ` interface Serializer { set value(v: string | number | boolean); get value(): string; } declare let box: Serializer; // Allows writing a 'boolean' box.value = true; // Comes out as a 'string' console.log(box.value.toUpperCase()); ` Initially we required that the `get` type had to be a subtype of the `set` type. This meant that writing ts ` box.value = box.value; ` would always be valid. However, there are plenty of existing and proposed APIs that have completely unrelated types between their getters and setters. For example, consider one of the most common examples - the `style` property in the DOM and [`CSSStyleRule`](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleRule) API. Every style rule has [a `style` property](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleRule/style) that is a [`CSSStyleDeclaration`](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration) ; however, if you try to write to that property, it will only work correctly with a string! TypeScript 5.1 now allows completely unrelated types for `get` and `set` accessor properties, provided that they have explicit type annotations. And while this version of TypeScript does not yet change the types for these built-in interfaces, `CSSStyleRule` can now be defined in the following way: ts `` interface CSSStyleRule { // ... /** Always reads as a `CSSStyleDeclaration` */ get style(): CSSStyleDeclaration; /** Can only write a `string` here. */ set style(newValue: string); // ... } `` This also allows other patterns like requiring `set` accessors to accept only “valid” data, but specifying that `get` accessors may return `undefined` if some underlying state hasn’t been initialized yet. ts ` class SafeBox { #value: string | undefined; // Only accepts strings! set value(newValue: string) { } // Must check for 'undefined'! get value(): string | undefined { return this.#value; } } ` In fact, this is similar to how optional properties are checked under `--exactOptionalProperties`. You can read up more on [the implementing pull request](https://github.com/microsoft/TypeScript/pull/53417) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-1.html#decoupled-type-checking-between-jsx-elements-and-jsx-tag-types) Decoupled Type-Checking Between JSX Elements and JSX Tag Types ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- One pain point TypeScript had with JSX was its requirements on the type of every JSX element’s tag. For context, a JSX element is either of the following: tsx ` // A self-closing JSX tag // A regular element with an opening/closing tag ` When type-checking `` or ``, TypeScript always looks up a namespace called `JSX` and fetches a type out of it called `Element` - or more directly, it looks up `JSX.Element`. But to check whether `Foo` or `Bar` themselves were valid to use as tag names, TypeScript would roughly just grab the types returned or constructed by `Foo` or `Bar` and check for compatibility with `JSX.Element` (or another type called `JSX.ElementClass` if the type is constructable). The limitations here meant that components could not be used if they returned or “rendered” a more broad type than just `JSX.Element`. For example, a JSX library might be fine with a component returning `string`s or `Promise`s. As a more concrete example, [React is considering adding limited support for components that return `Promise`s](https://github.com/acdlite/rfcs/blob/first-class-promises/text/0000-first-class-support-for-promises.md) , but existing versions of TypeScript cannot express that without someone drastically loosening the type of `JSX.Element`. tsx ` import * as React from "react"; async function Foo() { return
; } let element = ; // ~~~ // 'Foo' cannot be used as a JSX component. // Its return type 'Promise' is not a valid JSX element. ` To provide libraries with a way to express this, TypeScript 5.1 now looks up a type called `JSX.ElementType`. `ElementType` specifies precisely what is valid to use as a tag in a JSX element. So it might be typed today as something like tsx ` namespace JSX { export type ElementType = // All the valid lowercase tags keyof IntrinsicAttributes // Function components (props: any) => Element // Class components new (props: any) => ElementClass; export interface IntrinsicAttributes extends /*...*/ {} export type Element = /*...*/; export type ElementClass = /*...*/; } ` We’d like to extend our thanks to [Sebastian Silbermann](https://github.com/eps1lon) who contributed [this change](https://github.com/microsoft/TypeScript/pull/51328) ! [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-1.html#namespaced-jsx-attributes) Namespaced JSX Attributes -------------------------------------------------------------------------------------------------------------------------------------- TypeScript now supports namespaced attribute names when using JSX. tsx ` import * as React from "react"; // Both of these are equivalent: const x = ; const y = ; interface FooProps { "a:b": string; } function Foo(props: FooProps) { return
{props["a:b"]}
; } ` Namespaced tag names are looked up in a similar way on `JSX.IntrinsicAttributes` when the first segment of the name is a lowercase name. tsx ` // In some library's code or in an augmentation of that library: namespace JSX { interface IntrinsicElements { ["a:b"]: { prop: string }; } } // In our code: let x = ; ` [This contribution](https://github.com/microsoft/TypeScript/pull/53799) was provided thanks to [Oleksandr Tarasiuk](https://github.com/a-tarasyuk) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-1.html#typeroots-are-consulted-in-module-resolution) `typeRoots` Are Consulted In Module Resolution ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ When TypeScript’s specified module lookup strategy is unable to resolve a path, it will now resolve packages relative to the specified `typeRoots`. See [this pull request](https://github.com/microsoft/TypeScript/pull/51715) for more details. [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-1.html#move-declarations-to-existing-files) Move Declarations to Existing Files ---------------------------------------------------------------------------------------------------------------------------------------------------------- In addition to moving declarations to new files, TypeScript now ships a preview feature for moving declarations to existing files as well. You can try this functionality out in a recent version of Visual Studio Code. ![Moving a function 'getThanks' to an existing file in the workspace.](https://devblogs.microsoft.com/typescript/wp-content/uploads/sites/11/2023/05/moveToFile-5.1-preview.gif) Keep in mind that this feature is currently in preview, and we are seeking further feedback on it. [https://github.com/microsoft/TypeScript/pull/53542](https://github.com/microsoft/TypeScript/pull/53542) [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-1.html#linked-cursors-for-jsx-tags) Linked Cursors for JSX Tags ------------------------------------------------------------------------------------------------------------------------------------------ TypeScript now supports _linked editing_ for JSX tag names. Linked editing (occasionally called “mirrored cursors”) allows an editor to edit multiple locations at the same time automatically. ![An example of JSX tags with linked editing modifying a JSX fragment and a div element.](https://devblogs.microsoft.com/typescript/wp-content/uploads/sites/11/2023/04/linkedEditingJsx-5.1-1.gif) This new feature should work in both TypeScript and JavaScript files, and can be enabled in Visual Studio Code Insiders. In Visual Studio Code, you can either edit the `Editor: Linked Editing` option in the Settings UI: ![Visual Studio Code's Editor: Linked Editing` option](https://devblogs.microsoft.com/typescript/wp-content/uploads/sites/11/2023/04/linkedEditing-5.1-vscode-ui-1.png) or configure `editor.linkedEditing` in your JSON settings file: jsonc ` { // ... "editor.linkedEditing": true, } ` This feature will also be supported by Visual Studio 17.7 Preview 1. You can take a look at [our implementation of linked editing](https://github.com/microsoft/TypeScript/pull/53284) here! [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-1.html#snippet-completions-for-param-jsdoc-tags) Snippet Completions for `@param` JSDoc Tags ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- TypeScript now provides snippet completions when typing out a `@param` tag in both TypeScript and JavaScript files. This can help cut down on some typing and jumping around text as you document your code or add JSDoc types in JavaScript. ![An example of completing JSDoc param comments on an 'add' function.](https://devblogs.microsoft.com/typescript/wp-content/uploads/sites/11/2023/04/paramTagSnippets-5-1-1.gif) You can [check out how this new feature was implemented on GitHub](https://github.com/microsoft/TypeScript/pull/53260) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-1.html#optimizations) Optimizations -------------------------------------------------------------------------------------------------------------- ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-1.html#avoiding-unnecessary-type-instantiation) Avoiding Unnecessary Type Instantiation TypeScript 5.1 now avoids performing type instantiation within object types that are known not to contain references to outer type parameters. This has the potential to cut down on many unnecessary computations, and reduced the type-checking time of [material-ui’s docs directory](https://github.com/mui/material-ui/tree/b0351248fb396001a30330daac86d0e0794a0c1d/docs) by over 50%. You can [see the changes involved for this change on GitHub](https://github.com/microsoft/TypeScript/pull/53246) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-1.html#negative-case-checks-for-union-literals) Negative Case Checks for Union Literals When checking if a source type is part of a union type, TypeScript will first do a fast look-up using an internal type identifier for that source. If that look-up fails, then TypeScript checks for compatibility against every type within the union. When relating a literal type to a union of purely literal types, TypeScript can now avoid that full walk against every other type in the union. This assumption is safe because TypeScript always interns/caches literal types - though there are some edge cases to handle relating to “fresh” literal types. [This optimization](https://github.com/microsoft/TypeScript/pull/53192) was able to reduce the type-checking time of [the code in this issue](https://github.com/microsoft/TypeScript/issues/53191) from about 45 seconds to about 0.4 seconds. ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-1.html#reduced-calls-into-scanner-for-jsdoc-parsing) Reduced Calls into Scanner for JSDoc Parsing When older versions of TypeScript parsed out a JSDoc comment, they would use the scanner/tokenizer to break the comment into fine-grained tokens and piece the contents back together. This could be helpful for normalizing comment text, so that multiple spaces would just collapse into one; but it was extremely “chatty” and meant the parser and scanner would jump back and forth very often, adding overhead to JSDoc parsing. TypeScript 5.1 has moved more logic around breaking down JSDoc comments into the scanner/tokenizer. The scanner now returns larger chunks of content directly to the parser to do as it needs. [These changes](https://github.com/microsoft/TypeScript/pull/53081) have brought down the parse time of several 10Mb mostly-prose-comment JavaScript files by about half. For a more realistic example, our performance suite’s snapshot of [xstate](https://github.com/statelyai/xstate) dropped about 300ms of parse time, making it faster to load and analyze. [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-1.html#breaking-changes) Breaking Changes -------------------------------------------------------------------------------------------------------------------- ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-1.html#es2020-and-nodejs-1417-as-minimum-runtime-requirements) ES2020 and Node.js 14.17 as Minimum Runtime Requirements TypeScript 5.1 now ships JavaScript functionality that was introduced in ECMAScript 2020. As a result, at minimum TypeScript must be run in a reasonably modern runtime. For most users, this means TypeScript now only runs on Node.js 14.17 and later. If you try running TypeScript 5.1 under an older version of Node.js such as Node 10 or 12, you may see an error like the following from running either `tsc.js` or `tsserver.js`: ` node_modules/typescript/lib/tsserver.js:2406 for (let i = startIndex ?? 0; i < array.length; i++) { ^ SyntaxError: Unexpected token '?' at wrapSafe (internal/modules/cjs/loader.js:915:16) at Module._compile (internal/modules/cjs/loader.js:963:27) at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10) at Module.load (internal/modules/cjs/loader.js:863:32) at Function.Module._load (internal/modules/cjs/loader.js:708:14) at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:60:12) at internal/main/run_main_module.js:17:47 ` Additionally, if you try installing TypeScript you’ll get something like the following error messages from npm: ` npm WARN EBADENGINE Unsupported engine { npm WARN EBADENGINE package: 'typescript@5.1.1-rc', npm WARN EBADENGINE required: { node: '>=14.17' }, npm WARN EBADENGINE current: { node: 'v12.22.12', npm: '8.19.2' } npm WARN EBADENGINE } ` from Yarn: ` error typescript@5.1.1-rc: The engine "node" is incompatible with this module. Expected version ">=14.17". Got "12.22.12" error Found incompatible module. ` [See more information around this change here](https://github.com/microsoft/TypeScript/pull/53291) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-1.html#explicit-typeroots-disables-upward-walks-for-node_modulestypes) Explicit `typeRoots` Disables Upward Walks for `node_modules/@types` Previously, when the `typeRoots` option was specified in a `tsconfig.json` but resolution to any `typeRoots` directories had failed, TypeScript would still continue walking up parent directories, trying to resolve packages within each parent’s `node_modules/@types` folder. This behavior could prompt excessive look-ups and has been disabled in TypeScript 5.1. As a result, you may begin to see errors like the following based on entries in your `tsconfig.json`’s `types` option or `/// ` directives ` error TS2688: Cannot find type definition file for 'node'. error TS2688: Cannot find type definition file for 'mocha'. error TS2688: Cannot find type definition file for 'jasmine'. error TS2688: Cannot find type definition file for 'chai-http'. error TS2688: Cannot find type definition file for 'webpack-env"'. ` The solution is typically to add specific entries for `node_modules/@types` to your `typeRoots`: jsonc ` { "compilerOptions": { "types": [ "node", "mocha" ], "typeRoots": [ // Keep whatever you had around before. "./some-custom-types/", // You might need your local 'node_modules/@types'. "./node_modules/@types", // You might also need to specify a shared 'node_modules/@types' // if you're using a "monorepo" layout. "../../node_modules/@types", ] } } ` More information is available [on the original change on our issue tracker](https://github.com/microsoft/TypeScript/pull/51715) . The TypeScript docs are an open source project. Help us improve these pages [by sending a Pull Request](https://github.com/microsoft/TypeScript-Website/blob/v2/packages/documentation/copy/en/release-notes/TypeScript%205.1.md) ❤ Contributors to this page: N![navya9singh (6)](https://gravatar.com/avatar/e896fd3c90d7bd8d3c276d3ea2dd552ae62d81d4a497cdd589f551c5eb5ccb93?s=32&&d=blank) BT![Beeno Tung (1)](https://gravatar.com/avatar/abf3eb978b75f7c318037af0de9f1d7efd5562fa9f63ee1b3838cfc3c49ed36f?s=32&&d=blank) LL![Lazar Ljubenović (1)](https://gravatar.com/avatar/699ce6f1dced86e6721758c8eba6afaabf0632aa15f15a379e8a5c7e184cbc51?s=32&&d=blank) Last updated: Jul 14, 2025   MSG --- # TypeScript: Documentation - TypeScript 4.9 Was this page helpful? TypeScript 4.9 ============== [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#the-satisfies-operator) The `satisfies` Operator ---------------------------------------------------------------------------------------------------------------------------------- TypeScript developers are often faced with a dilemma: we want to ensure that some expression _matches_ some type, but also want to keep the _most specific_ type of that expression for inference purposes. For example: ts ` // Each property can be a string or an RGB tuple. const palette = { red: [255, 0, 0], green: "#00ff00", bleu: [0, 0, 255] // ^^^^ sacrebleu - we've made a typo! }; // We want to be able to use string methods on 'green'... const greenNormalized = palette.green.toUpperCase(); ` Notice that we’ve written `bleu`, whereas we probably should have written `blue`. We could try to catch that `bleu` typo by using a type annotation on `palette`, but we’d lose the information about each property. ts ` type Colors = "red" | "green" | "blue"; type RGB = [red: number, green: number, blue: number]; const palette: Record = { red: [255, 0, 0], green: "#00ff00", bleu: [0, 0, 255] // ~~~~ The typo is now correctly detected }; // But we now have an undesirable error here - 'palette.green' "could" be of type RGB and // property 'toUpperCase' does not exist on type 'string | RGB'. const greenNormalized = palette.green.toUpperCase(); ` The new `satisfies` operator lets us validate that the type of an expression matches some type, without changing the resulting type of that expression. As an example, we could use `satisfies` to validate that all the properties of `palette` are compatible with `string | number[]`: ts ` type Colors = "red" | "green" | "blue"; type RGB = [red: number, green: number, blue: number]; const palette = { red: [255, 0, 0], green: "#00ff00", bleu: [0, 0, 255] // ~~~~ The typo is now caught! } satisfies Record; // toUpperCase() method is still accessible! const greenNormalized = palette.green.toUpperCase(); ` `satisfies` can be used to catch lots of possible errors. For example, we could ensure that an object has _all_ the keys of some type, but no more: ts ` type Colors = "red" | "green" | "blue"; // Ensure that we have exactly the keys from 'Colors'. const favoriteColors = { "red": "yes", "green": false, "blue": "kinda", "platypus": false // ~~~~~~~~~~ error - "platypus" was never listed in 'Colors'. } satisfies Record; // All the information about the 'red', 'green', and 'blue' properties are retained. const g: boolean = favoriteColors.green; ` Maybe we don’t care about if the property names match up somehow, but we do care about the types of each property. In that case, we can also ensure that all of an object’s property values conform to some type. ts ` type RGB = [red: number, green: number, blue: number]; const palette = { red: [255, 0, 0], green: "#00ff00", blue: [0, 0] // ~~~~~~ error! } satisfies Record; // Information about each property is still maintained. const redComponent = palette.red.at(0); const greenNormalized = palette.green.toUpperCase(); ` For more examples, you can see the [issue proposing this](https://github.com/microsoft/TypeScript/issues/47920) and [the implementing pull request](https://github.com/microsoft/TypeScript/pull/46827) . We’d like to thank [Oleksandr Tarasiuk](https://github.com/a-tarasyuk) who implemented and iterated on this feature with us. [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#unlisted-property-narrowing-with-the-in-operator) Unlisted Property Narrowing with the `in` Operator -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- As developers, we often need to deal with values that aren’t fully known at runtime. In fact, we often don’t know if properties exist, whether we’re getting a response from a server or reading a configuration file. JavaScript’s `in` operator can check whether a property exists on an object. Previously, TypeScript allowed us to narrow away any types that don’t explicitly list a property. ts ` interface RGB { red: number; green: number; blue: number; } interface HSV { hue: number; saturation: number; value: number; } function setColor(color: RGB | HSV) { if ("hue" in color) { // 'color' now has the type HSV } // ... } ` Here, the type `RGB` didn’t list the `hue` and got narrowed away, and leaving us with the type `HSV`. But what about examples where no type listed a given property? In those cases, the language didn’t help us much. Let’s take the following example in JavaScript: js ` function tryGetPackageName(context) { const packageJSON = context.packageJSON; // Check to see if we have an object. if (packageJSON && typeof packageJSON === "object") { // Check to see if it has a string name property. if ("name" in packageJSON && typeof packageJSON.name === "string") { return packageJSON.name; } } return undefined; } ` Rewriting this to canonical TypeScript would just be a matter of defining and using a type for `context`; however, picking a safe type like `unknown` for the `packageJSON` property would cause issues in older versions of TypeScript. ts ` interface Context { packageJSON: unknown; } function tryGetPackageName(context: Context) { const packageJSON = context.packageJSON; // Check to see if we have an object. if (packageJSON && typeof packageJSON === "object") { // Check to see if it has a string name property. if ("name" in packageJSON && typeof packageJSON.name === "string") { // ~~~~ // error! Property 'name' does not exist on type 'object. return packageJSON.name; // ~~~~ // error! Property 'name' does not exist on type 'object. } } return undefined; } ` This is because while the type of `packageJSON` was narrowed from `unknown` to `object`, the `in` operator strictly narrowed to types that actually defined the property being checked. As a result, the type of `packageJSON` remained `object`. TypeScript 4.9 makes the `in` operator a little bit more powerful when narrowing types that _don’t_ list the property at all. Instead of leaving them as-is, the language will intersect their types with `Record<"property-key-being-checked", unknown>`. So in our example, `packageJSON` will have its type narrowed from `unknown` to `object` to `object & Record<"name", unknown>` That allows us to access `packageJSON.name` directly and narrow that independently. ts ` interface Context { packageJSON: unknown; } function tryGetPackageName(context: Context): string | undefined { const packageJSON = context.packageJSON; // Check to see if we have an object. if (packageJSON && typeof packageJSON === "object") { // Check to see if it has a string name property. if ("name" in packageJSON && typeof packageJSON.name === "string") { // Just works! return packageJSON.name; } } return undefined; } ` TypeScript 4.9 also tightens up a few checks around how `in` is used, ensuring that the left side is assignable to the type `string | number | symbol`, and the right side is assignable to `object`. This helps check that we’re using valid property keys, and not accidentally checking primitives. For more information, [read the implementing pull request](https://github.com/microsoft/TypeScript/pull/50666) [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#auto-accessors-in-classes) Auto-Accessors in Classes -------------------------------------------------------------------------------------------------------------------------------------- TypeScript 4.9 supports an upcoming feature in ECMAScript called auto-accessors. Auto-accessors are declared just like properties on classes, except that they’re declared with the `accessor` keyword. ts ` class Person { accessor name: string; constructor(name: string) { this.name = name; } } ` Under the covers, these auto-accessors “de-sugar” to a `get` and `set` accessor with an unreachable private property. ts ` class Person { #__name: string; get name() { return this.#__name; } set name(value: string) { this.#__name = value; } constructor(name: string) { this.name = name; } } ` You can [read up more about the auto-accessors pull request on the original PR](https://github.com/microsoft/TypeScript/pull/49705) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#checks-for-equality-on-nan) Checks For Equality on `NaN` ------------------------------------------------------------------------------------------------------------------------------------------ A major gotcha for JavaScript developers is checking against the value `NaN` using the built-in equality operators. For some background, `NaN` is a special numeric value that stands for “Not a Number”. Nothing is ever equal to `NaN` - even `NaN`! js ` console.log(NaN == 0) // false console.log(NaN === 0) // false console.log(NaN == NaN) // false console.log(NaN === NaN) // false ` But at least symmetrically _everything_ is always not-equal to `NaN`. js ` console.log(NaN != 0) // true console.log(NaN !== 0) // true console.log(NaN != NaN) // true console.log(NaN !== NaN) // true ` This technically isn’t a JavaScript-specific problem, since any language that contains IEEE-754 floats has the same behavior; but JavaScript’s primary numeric type is a floating point number, and number parsing in JavaScript can often result in `NaN`. In turn, checking against `NaN` ends up being fairly common, and the correct way to do so is to use [`Number.isNaN`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN) - _but_ as we mentioned, lots of people accidentally end up checking with `someValue === NaN` instead. TypeScript now errors on direct comparisons against `NaN`, and will suggest using some variation of `Number.isNaN` instead. ts ` function validate(someValue: number) { return someValue !== NaN; // ~~~~~~~~~~~~~~~~~ // error: This condition will always return 'true'. // Did you mean '!Number.isNaN(someValue)'? } ` We believe that this change should strictly help catch beginner errors, similar to how TypeScript currently issues errors on comparisons against object and array literals. We’d like to extend our thanks to [Oleksandr Tarasiuk](https://github.com/a-tarasyuk) who [contributed this check](https://github.com/microsoft/TypeScript/pull/50626) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#file-watching-now-uses-file-system-events) File-Watching Now Uses File System Events ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- In earlier versions, TypeScript leaned heavily on _polling_ for watching individual files. Using a polling strategy meant checking the state of a file periodically for updates. On Node.js, [`fs.watchFile`](https://nodejs.org/docs/latest-v18.x/api/fs.html#fswatchfilefilename-options-listener) is the built-in way to get a polling file-watcher. While polling tends to be more predictable across platforms and file systems, it means that your CPU has to periodically get interrupted and check for updates to the file, even when nothing’s changed. For a few dozen files, this might not be noticeable; but on a bigger project with lots of files - or lots of files in `node_modules` - this can become a resource hog. Generally speaking, a better approach is to use file system events. Instead of polling, we can announce that we’re interested in updates of specific files and provide a callback for when those files _actually do_ change. Most modern platforms in use provide facilities and APIs like `CreateIoCompletionPort`, `kqueue`, `epoll`, and `inotify`. Node.js mostly abstracts these away by providing [`fs.watch`](https://nodejs.org/docs/latest-v18.x/api/fs.html#fswatchfilename-options-listener) . File system events usually work great, but there are [lots of caveats](https://nodejs.org/docs/latest-v18.x/api/fs.html#caveats) to using them, and in turn, to using the `fs.watch` API. A watcher needs to be careful to consider [inode watching](https://nodejs.org/docs/latest-v18.x/api/fs.html#inodes) , [unavailability on certain file systems](https://nodejs.org/docs/latest-v18.x/api/fs.html#availability) (e.g.networked file systems), whether recursive file watching is available, whether directory renames trigger events, and even file watcher exhaustion! In other words, it’s not quite a free lunch, especially if you’re looking for something cross-platform. As a result, our default was to pick the lowest common denominator: polling. Not always, but most of the time. Over time, we’ve provided the means to [choose other file-watching strategies](https://www.typescriptlang.org/docs/handbook/configuring-watch.html) . This allowed us to get feedback and harden our file-watching implementation against most of these platform-specific gotchas. As TypeScript has needed to scale to larger codebases, and has improved in this area, we felt swapping to file system events as the default would be a worthwhile investment. In TypeScript 4.9, file watching is powered by file system events by default, only falling back to polling if we fail to set up event-based watchers. For most developers, this should provide a much less resource-intensive experience when running in `--watch` mode, or running with a TypeScript-powered editor like Visual Studio or VS Code. [The way file-watching works can still be configured](https://www.typescriptlang.org/docs/handbook/configuring-watch.html) through environment variables and `watchOptions` - and [some editors like VS Code can support `watchOptions` independently](https://code.visualstudio.com/docs/getstarted/settings#:~:text=typescript%2etsserver%2ewatchOptions) . Developers using more exotic set-ups where source code resides on a networked file systems (like NFS and SMB) may need to opt back into the older behavior; though if a server has reasonable processing power, it might just be better to enable SSH and run TypeScript remotely so that it has direct local file access. VS Code has plenty of [remote extensions](https://marketplace.visualstudio.com/search?term=remote&target=VSCode&category=All%20categories&sortBy=Relevance) to make this easier. You can [read up more on this change on GitHub](https://github.com/microsoft/TypeScript/pull/50366) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#remove-unused-imports-and-sort-imports-commands-for-editors) “Remove Unused Imports” and “Sort Imports” Commands for Editors -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Previously, TypeScript only supported two editor commands to manage imports. For our examples, take the following code: ts ` import { Zebra, Moose, HoneyBadger } from "./zoo"; import { foo, bar } from "./helper"; let x: Moose | HoneyBadger = foo(); ` The first was called “Organize Imports” which would remove unused imports, and then sort the remaining ones. It would rewrite that file to look like this one: ts ` import { foo } from "./helper"; import { HoneyBadger, Moose } from "./zoo"; let x: Moose | HoneyBadger = foo(); ` In TypeScript 4.3, we introduced a command called “Sort Imports” which would _only_ sort imports in the file, but not remove them - and would rewrite the file like this. ts ` import { bar, foo } from "./helper"; import { HoneyBadger, Moose, Zebra } from "./zoo"; let x: Moose | HoneyBadger = foo(); ` The caveat with “Sort Imports” was that in Visual Studio Code, this feature was only available as an on-save command - not as a manually triggerable command. TypeScript 4.9 adds the other half, and now provides “Remove Unused Imports”. TypeScript will now remove unused import names and statements, but will otherwise leave the relative ordering alone. ts ` import { Moose, HoneyBadger } from "./zoo"; import { foo } from "./helper"; let x: Moose | HoneyBadger = foo(); ` This feature is available to all editors that wish to use either command; but notably, Visual Studio Code (1.73 and later) will have support built in _and_ will surface these commands via its Command Palette. Users who prefer to use the more granular “Remove Unused Imports” or “Sort Imports” commands should be able to reassign the “Organize Imports” key combination to them if desired. You can [view specifics of the feature here](https://github.com/microsoft/TypeScript/pull/50931) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#go-to-definition-on-return-keywords) Go-to-Definition on `return` Keywords ------------------------------------------------------------------------------------------------------------------------------------------------------------ In the editor, when running a go-to-definition on the `return` keyword, TypeScript will now jump you to the top of the corresponding function. This can be helpful to get a quick sense of which function a `return` belongs to. We expect TypeScript will expand this functionality to more keywords [such as `await` and `yield`](https://github.com/microsoft/TypeScript/issues/51223) or [`switch`, `case`, and `default`](https://github.com/microsoft/TypeScript/issues/51225) . [This feature was implemented](https://github.com/microsoft/TypeScript/pull/51227) thanks to [Oleksandr Tarasiuk](https://github.com/a-tarasyuk) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#performance-improvements) Performance Improvements ------------------------------------------------------------------------------------------------------------------------------------ TypeScript has a few small, but notable, performance improvements. First, TypeScript’s `forEachChild` function has been rewritten to use a function table lookup instead of a `switch` statement across all syntax nodes. `forEachChild` is a workhorse for traversing syntax nodes in the compiler, and is used heavily in the binding stage of our compiler, along with parts of the language service. The refactoring of `forEachChild` yielded up to a 20% reduction of time spent in our binding phase and across language service operations. Once we discovered this performance win for `forEachChild`, we tried it out on `visitEachChild`, a function we use for transforming nodes in the compiler and language service. The same refactoring yielded up to a 3% reduction in time spent in generating project output. The initial exploration in `forEachChild` was [inspired by a blog post](https://artemis.sh/2022/08/07/emulating-calculators-fast-in-js.html) by [Artemis Everfree](https://artemis.sh/) . While we have some reason to believe the root cause of our speed-up might have more to do with function size/complexity than the issues described in the blog post, we’re grateful that we were able to learn from the experience and try out a relatively quick refactoring that made TypeScript faster. Finally, the way TypeScript preserves the information about a type in the true branch of a conditional type has been optimized. In a type like ts ` interface Zoo { // ... } type MakeZoo = A extends Animal ? Zoo : never; ` TypeScript has to “remember” that `A` must also be an `Animal` when checking if `Zoo` is valid. This is basically done by creating a special type that used to hold the intersection of `A` with `Animal`; however, TypeScript previously did this eagerly which isn’t always necessary. Furthermore, some faulty code in our type-checker prevented these special types from being simplified. TypeScript now defers intersecting these types until it’s necessary. For codebases with heavy use of conditional types, you might witness significant speed-ups with TypeScript, but in our performance testing suite, we saw a more modest 3% reduction in type-checking time. You can read up more on these optimizations on their respective pull requests: * [`forEachChild` as a jump-table](https://github.com/microsoft/TypeScript/pull/50225) * [`visitEachChild` as a jump-table](https://github.com/microsoft/TypeScript/pull/50266) * [Optimize substitition types](https://github.com/microsoft/TypeScript/pull/50397) [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#correctness-fixes-and-breaking-changes) Correctness Fixes and Breaking Changes ---------------------------------------------------------------------------------------------------------------------------------------------------------------- ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#libdts-updates) `lib.d.ts` Updates While TypeScript strives to avoid major breaks, even small changes in the built-in libraries can cause issues. We don’t expect major breaks as a result of DOM and `lib.d.ts` updates, but there may be some small ones. ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#better-types-for-promiseresolve) Better Types for `Promise.resolve` `Promise.resolve` now uses the `Awaited` type to unwrap Promise-like types passed to it. This means that it more often returns the right `Promise` type, but that improved type can break existing code if it was expecting `any` or `unknown` instead of a `Promise`. For more information, [see the original change](https://github.com/microsoft/TypeScript/pull/33074) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#javascript-emit-no-longer-elides-imports) JavaScript Emit No Longer Elides Imports When TypeScript first supported type-checking and compilation for JavaScript, it accidentally supported a feature called import elision. In short, if an import is not used as a value, or the compiler can detect that the import doesn’t refer to a value at runtime, the compiler will drop the import during emit. This behavior was questionable, especially the detection of whether the import doesn’t refer to a value, since it means that TypeScript has to trust sometimes-inaccurate declaration files. In turn, TypeScript now preserves imports in JavaScript files. js ` // Input: import { someValue, SomeClass } from "some-module"; /** @type {SomeClass} */ let val = someValue; // Previous Output: import { someValue } from "some-module"; /** @type {SomeClass} */ let val = someValue; // Current Output: import { someValue, SomeClass } from "some-module"; /** @type {SomeClass} */ let val = someValue; ` More information is available at [the implementing change](https://github.com/microsoft/TypeScript/pull/50404) . ### [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#exports-is-prioritized-over-typesversions) `exports` is Prioritized Over `typesVersions` Previously, TypeScript incorrectly prioritized the `typesVersions` field over the `exports` field when resolving through a `package.json` under `--moduleResolution node16`. If this change impacts your library, you may need to add `types@` version selectors in your `package.json`’s `exports` field. diff `{ "type": "module", "main": "./dist/main.js" "typesVersions": { "<4.8": { ".": ["4.8-types/main.d.ts"] }, "*": { ".": ["modern-types/main.d.ts"] } }, "exports": { ".": { + "types@<4.8": "./4.8-types/main.d.ts", + "types": "./modern-types/main.d.ts", "import": "./dist/main.js" } } }` For more information, [see this pull request](https://github.com/microsoft/TypeScript/pull/50890) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#substitute-replaced-with-constraint-on-substitutiontypes) `substitute` Replaced With `constraint` on `SubstitutionType`s ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- As part of an optimization on substitution types, `SubstitutionType` objects no longer contain the `substitute` property representing the effective substitution (usually an intersection of the base type and the implicit constraint) - instead, they just contain the `constraint` property. For more details, [read more on the original pull request](https://github.com/microsoft/TypeScript/pull/50397) . The TypeScript docs are an open source project. Help us improve these pages [by sending a Pull Request](https://github.com/microsoft/TypeScript-Website/blob/v2/packages/documentation/copy/en/release-notes/TypeScript%204.9.md) ❤ Contributors to this page: N![navya9singh (6)](https://gravatar.com/avatar/e896fd3c90d7bd8d3c276d3ea2dd552ae62d81d4a497cdd589f551c5eb5ccb93?s=32&&d=blank) DS![David Sherret (1)](https://gravatar.com/avatar/4fb9dbb35453509bfa126e2de592496ffc877368a87dc36a53fc3293770668ec?s=32&&d=blank) B![Brennan (1)](https://gravatar.com/avatar/8b8d800a527c9b6c78d9ade8d0f357b4a822008356accf3b99a12a5b51d7876a?s=32&&d=blank) M![Marius (1)](https://gravatar.com/avatar/dd6a3226bbd78a435ace4193917985c124456a45a463d2831fcaef5f527d7398?s=32&&d=blank) 제![제스 (1)](https://gravatar.com/avatar/32d718fa25a6c599a81f4ba0e3531b8a2641376c419f2a9f4057cb1c515cad25?s=32&&d=blank) 1+ Last updated: Jul 14, 2025   MSG --- # TypeScript: Documentation - TypeScript 4.5 Was this page helpful? TypeScript 4.5 ============== [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-5.html#supporting-lib-from-node_modules) Supporting `lib` from `node_modules` -------------------------------------------------------------------------------------------------------------------------------------------------------- To ensure that TypeScript and JavaScript support works well out of the box, TypeScript bundles a series of declaration files (`.d.ts` files). These declaration files represent the available APIs in the JavaScript language, and the standard browser DOM APIs. While there are some reasonable defaults based on your [`target`](https://www.typescriptlang.org/tsconfig#target) , you can pick and choose which declaration files your program uses by configuring the [`lib`](https://www.typescriptlang.org/tsconfig#lib) setting in the `tsconfig.json`. There are two occasional downsides to including these declaration files with TypeScript though: * When you upgrade TypeScript, you’re also forced to handle changes to TypeScript’s built-in declaration files, and this can be a challenge when the DOM APIs change as frequently as they do. * It is hard to customize these files to match your needs with the needs of your project’s dependencies (e.g. if your dependencies declare that they use the DOM APIs, you might also be forced into using the DOM APIs). TypeScript 4.5 introduces a way to override a specific built-in `lib` in a manner similar to how `@types/` support works. When deciding which `lib` files TypeScript should include, it will first look for a scoped `@typescript/lib-*` package in `node_modules`. For example, when including `dom` as an option in `lib`, TypeScript will use the types in `node_modules/@typescript/lib-dom` if available. You can then use your package manager to install a specific package to take over for a given `lib` For example, today TypeScript publishes versions of the DOM APIs on `@types/web`. If you wanted to lock your project to a specific version of the DOM APIs, you could add this to your `package.json`: json ` { "dependencies": { "@typescript/lib-dom": "npm:@types/web" } } ` Then from 4.5 onwards, you can update TypeScript and your dependency manager’s lockfile will ensure that it uses the exact same version of the DOM types. That means you get to update your types on your own terms. We’d like to give a shout-out to [saschanaz](https://github.com/saschanaz) who has been extremely helpful and patient as we’ve been building out and experimenting with this feature. For more information, you can [see the implementation of this change](https://github.com/microsoft/TypeScript/pull/45771) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-5.html#the-awaited-type-and-promise-improvements) The `Awaited` Type and `Promise` Improvements -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- TypeScript 4.5 introduces a new utility type called the `Awaited` type. This type is meant to model operations like `await` in `async` functions, or the `.then()` method on `Promise`s - specifically, the way that they recursively unwrap `Promise`s. ts ` // A = string type A = Awaited>; // B = number type B = Awaited>>; // C = boolean | number type C = Awaited>; ` The `Awaited` type can be helpful for modeling existing APIs, including JavaScript built-ins like `Promise.all`, `Promise.race`, etc. In fact, some of the problems around inference with `Promise.all` served as motivations for `Awaited`. Here’s an example that fails in TypeScript 4.4 and earlier. ts ` declare function MaybePromise(value: T): T | Promise | PromiseLike; async function doSomething(): Promise<[number, number]> { const result = await Promise.all([MaybePromise(100), MaybePromise(200)]); // Error! // // [number | Promise<100>, number | Promise<200>] // // is not assignable to type // // [number, number] return result; } ` Now `Promise.all` leverages the combination of certain features with `Awaited` to give much better inference results, and the above example works. For more information, you [can read about this change on GitHub](https://github.com/microsoft/TypeScript/pull/45350) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-5.html#template-string-types-as-discriminants) Template String Types as Discriminants ---------------------------------------------------------------------------------------------------------------------------------------------------------------- TypeScript 4.5 now can narrow values that have template string types, and also recognizes template string types as discriminants. As an example, the following used to fail, but now successfully type-checks in TypeScript 4.5. ts `` export interface Success { type: `${string}Success`; body: string; } export interface Error { type: `${string}Error`; message: string } export function handler(r: Success | Error) { if (r.type === "HttpSuccess") { const token = r.body; (parameter) r: Success } } ``[Try](https://www.typescriptlang.org/play/#code/KYDwDg9gTgLgBASwHY2FAZgQwMbDgZQFdtcBnUuAbwCg464YBPMYALjgAMASS0mKZAHMAvkRLByHANy16AIwgATRuz4CkgmcOrVQkWIhRosuOAFEoUaFVl0mLdt178hwi1ajTbcALYTSmIJscGpC1Nq64NDw6IRI2DAIEEhwABaYSIoANmgAFFDsYmQUAD7mltAAlDb0iOhw+QB09ngAvO1wAEQAEjAwYEX+ndU0tbXYyXwMEADWwCmtcFCNCsoyY-QA9JsbGwB6APze2sJAA) For more information, [see the change that enables this feature](https://github.com/microsoft/TypeScript/pull/46137) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-5.html#module-es2022) `module es2022` ---------------------------------------------------------------------------------------------------------------- Thanks to [Kagami S. Rosylight](https://github.com/saschanaz) , TypeScript now supports a new `module` setting: `es2022`. The main feature in [`module es2022`](https://www.typescriptlang.org/tsconfig#module) is top-level `await`, meaning you can use `await` outside of `async` functions. This was already supported in `--module esnext` (and now [`--module nodenext`](https://www.typescriptlang.org/tsconfig#target) ), but `es2022` is the first stable target for this feature. You can [read up more on this change here](https://github.com/microsoft/TypeScript/pull/44656) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-5.html#tail-recursion-elimination-on-conditional-types) Tail-Recursion Elimination on Conditional Types ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- TypeScript often needs to gracefully fail when it detects possibly infinite recursion, or any type expansions that can take a long time and affect your editor experience. As a result, TypeScript has heuristics to make sure it doesn’t go off the rails when trying to pick apart an infinitely-deep type, or working with types that generate a lot of intermediate results. ts ` type InfiniteBox = { item: InfiniteBox }; type Unpack = T extends { item: infer U } ? Unpack : T; // error: Type instantiation is excessively deep and possibly infinite. type Test = Unpack>; ` The above example is intentionally simple and useless, but there are plenty of types that are actually useful, and unfortunately trigger our heuristics. As an example, the following `TrimLeft` type removes spaces from the beginning of a string-like type. If given a string type that has a space at the beginning, it immediately feeds the remainder of the string back into `TrimLeft`. ts `` type TrimLeft = T extends ` ${infer Rest}` ? TrimLeft : T; // Test = "hello" | "world" type Test = TrimLeft<" hello" | " world">; `` This type can be useful, but if a string has 50 leading spaces, you’ll get an error. ts `` type TrimLeft = T extends ` ${infer Rest}` ? TrimLeft : T; // error: Type instantiation is excessively deep and possibly infinite. type Test = TrimLeft<" oops">; `` That’s unfortunate, because these kinds of types tend to be extremely useful in modeling operations on strings - for example, parsers for URL routers. To make matters worse, a more useful type typically creates more type instantiations, and in turn has even more limitations on input length. But there’s a saving grace: `TrimLeft` is written in a way that is _tail-recursive_ in one branch. When it calls itself again, it immediately returns the result and doesn’t do anything with it. Because these types don’t need to create any intermediate results, they can be implemented more quickly and in a way that avoids triggering many of type recursion heuristics that are built into TypeScript. That’s why TypeScript 4.5 performs some tail-recursion elimination on conditional types. As long as one branch of a conditional type is simply another conditional type, TypeScript can avoid intermediate instantiations. There are still heuristics to ensure that these types don’t go off the rails, but they are much more generous. Keep in mind, the following type _won’t_ be optimized, since it uses the result of a conditional type by adding it to a union. ts `` type GetChars = S extends `${infer Char}${infer Rest}` ? Char | GetChars : never; `` If you would like to make it tail-recursive, you can introduce a helper that takes an “accumulator” type parameter, just like with tail-recursive functions. ts `` type GetChars = GetCharsHelper; type GetCharsHelper = S extends `${infer Char}${infer Rest}` ? GetCharsHelper : Acc; `` You can read up more on the implementation [here](https://github.com/microsoft/TypeScript/pull/45711) . [](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-5.html#disabling-import-elision) Disabling Import Elision ------------------------------------------------------------------------------------------------------------------------------------ There are some cases where TypeScript can’t detect that you’re using an import. For example, take the following code: ts ` import { Animal } from "./animal.js"; eval("console.log(new Animal().isDangerous())"); ` By default, TypeScript always removes this import because it appears to be unused. In TypeScript 4.5, you can enable a new flag called [`preserveValueImports`](https://www.typescriptlang.org/tsconfig#preserveValueImports) to prevent TypeScript from stripping out any imported values from your JavaScript outputs. Good reasons to use `eval` are few and far between, but something very similar to this happens in Svelte: html ` ` along with in Vue.js, using its ` ` These frameworks generate some code based on markup outside of their `