|
| 1 | +--- |
| 2 | +title: TypeScript 5.9 |
| 3 | +layout: docs |
| 4 | +permalink: /docs/handbook/release-notes/typescript-5-9.html |
| 5 | +oneline: TypeScript 5.9 Release Notes |
| 6 | +--- |
| 7 | + |
| 8 | + |
| 9 | +## Minimal and Updated `tsc --init` |
| 10 | + |
| 11 | +For a while, the TypeScript compiler has supported an `--init` flag that can create a `tsconfig.json` within the current directory. |
| 12 | +In the last few years, running `tsc --init` created a very "full" `tsconfig.json`, filled with commented-out settings and their descriptions. |
| 13 | +We designed this with the intent of making options discoverable and easy to toggle. |
| 14 | + |
| 15 | +However, given external feedback (and our own experience), we found it's common to immediately delete most of the contents of these new `tsconfig.json` files. |
| 16 | +When users want to discover new options, we find they rely on auto-complete from their editor, or navigate to [the tsconfig reference on our website](https://www.typescriptlang.org/tsconfig/) (which the generated `tsconfig.json` links to!). |
| 17 | +What each setting does is also documented on that same page, and can be seen via editor hovers/tooltips/quick info. |
| 18 | +While surfacing some commented-out settings might be helpful, the generated `tsconfig.json` was often considered overkill. |
| 19 | + |
| 20 | +We also felt that it was time that `tsc --init` initialized with a few more prescriptive settings than we already enable. |
| 21 | +We looked at some common pain points and papercuts users have when they create a new TypeScript project. |
| 22 | +For example, most users write in modules (not global scripts), and `--moduleDetection` can force TypeScript to treat every implementation file as a module. |
| 23 | +Developers also often want to use the latest ECMAScript features directly in their runtime, so `--target` can typically be set to `esnext`. |
| 24 | +JSX users often find that going back to set `--jsx` is needless friction, and its options are slightly confusing. |
| 25 | +And often, projects end up loading more declaration files from `node_modules/@types` than TypeScript actually needs; but specifying an empty `types` array can help limit this. |
| 26 | + |
| 27 | +In TypeScript 5.9, a plain `tsc --init` with no other flags will generate the following `tsconfig.json`: |
| 28 | + |
| 29 | +```json5 |
| 30 | +{ |
| 31 | + // Visit https://aka.ms/tsconfig to read more about this file |
| 32 | + "compilerOptions": { |
| 33 | + // File Layout |
| 34 | + // "rootDir": "./src", |
| 35 | + // "outDir": "./dist", |
| 36 | + |
| 37 | + // Environment Settings |
| 38 | + // See also https://aka.ms/tsconfig_modules |
| 39 | + "module": "nodenext", |
| 40 | + "target": "esnext", |
| 41 | + "types": [], |
| 42 | + // For nodejs: |
| 43 | + // "lib": ["esnext"], |
| 44 | + // "types": ["node"], |
| 45 | + // and npm install -D @types/node |
| 46 | + |
| 47 | + // Other Outputs |
| 48 | + "sourceMap": true, |
| 49 | + "declaration": true, |
| 50 | + "declarationMap": true, |
| 51 | + |
| 52 | + // Stricter Typechecking Options |
| 53 | + "noUncheckedIndexedAccess": true, |
| 54 | + "exactOptionalPropertyTypes": true, |
| 55 | + |
| 56 | + // Style Options |
| 57 | + // "noImplicitReturns": true, |
| 58 | + // "noImplicitOverride": true, |
| 59 | + // "noUnusedLocals": true, |
| 60 | + // "noUnusedParameters": true, |
| 61 | + // "noFallthroughCasesInSwitch": true, |
| 62 | + // "noPropertyAccessFromIndexSignature": true, |
| 63 | + |
| 64 | + // Recommended Options |
| 65 | + "strict": true, |
| 66 | + "jsx": "react-jsx", |
| 67 | + "verbatimModuleSyntax": true, |
| 68 | + "isolatedModules": true, |
| 69 | + "noUncheckedSideEffectImports": true, |
| 70 | + "moduleDetection": "force", |
| 71 | + "skipLibCheck": true, |
| 72 | + } |
| 73 | +} |
| 74 | +``` |
| 75 | + |
| 76 | +For more details, see the [implementing pull request](https://github.com/microsoft/TypeScript/pull/61813) and [discussion issue](https://github.com/microsoft/TypeScript/issues/58420). |
| 77 | + |
| 78 | +## Support for `import defer` |
| 79 | + |
| 80 | +TypeScript 5.9 introduces support for [ECMAScript's deferred module evaluation proposal](https://github.com/tc39/proposal-defer-import-eval/) using the new `import defer` syntax. |
| 81 | +This feature allows you to import a module without immediately executing the module and its dependencies, providing better control over when work and side-effects occur. |
| 82 | + |
| 83 | +The syntax only permits namespace imports: |
| 84 | + |
| 85 | +```ts |
| 86 | +import defer * as feature from "./some-feature.js"; |
| 87 | +``` |
| 88 | + |
| 89 | +The key benefit of `import defer` is that the module is only evaluated when one of its exports is first accessed. |
| 90 | +Consider this example: |
| 91 | + |
| 92 | +```ts |
| 93 | +// ./some-feature.ts |
| 94 | +initializationWithSideEffects(); |
| 95 | + |
| 96 | +function initializationWithSideEffects() { |
| 97 | + // ... |
| 98 | + specialConstant = 42; |
| 99 | + |
| 100 | + console.log("Side effects have occurred!"); |
| 101 | +} |
| 102 | + |
| 103 | +export let specialConstant: number; |
| 104 | +``` |
| 105 | + |
| 106 | +When using `import defer`, the `initializationWithSideEffects()` function will not be called until you actually access a property of the imported namespace: |
| 107 | + |
| 108 | +```ts |
| 109 | +import defer * as feature from "./some-feature.js"; |
| 110 | + |
| 111 | +// No side effects have occurred yet |
| 112 | + |
| 113 | +// ... |
| 114 | + |
| 115 | +// As soon as `specialConstant` is accessed, the contents of the `feature` |
| 116 | +// module are run and side effects have taken place. |
| 117 | +console.log(feature.specialConstant); // 42 |
| 118 | +``` |
| 119 | + |
| 120 | +Because evaluation of the module is deferred until you access a member off of the module, you cannot use named imports or default imports with `import defer`: |
| 121 | + |
| 122 | +```ts |
| 123 | +// ❌ Not allowed |
| 124 | +import defer { doSomething } from "some-module"; |
| 125 | + |
| 126 | +// ❌ Not allowed |
| 127 | +import defer defaultExport from "some-module"; |
| 128 | + |
| 129 | +// ✅ Only this syntax is supported |
| 130 | +import defer * as feature from "some-module"; |
| 131 | +``` |
| 132 | + |
| 133 | +Note that when you write `import defer`, the module and its dependencies are fully loaded and ready for execution. |
| 134 | +That means that the module will need to exist, and will be loaded from the file system or a network resource. |
| 135 | +The key difference between a regular `import` and `import defer` is that *the execution of statements and declarations* is deferred until you access a property of the imported namespace. |
| 136 | + |
| 137 | +This feature is particularly useful for conditionally loading modules with expensive or platform-specific initialization. It can also improve startup performance by deferring module evaluation for app features until they are actually needed. |
| 138 | + |
| 139 | +Note that `import defer` is not transformed or "downleveled" at all by TypeScript. |
| 140 | +It is intended to be used in runtimes that support the feature natively, or by tools such as bundlers that can apply the appropriate transformation. |
| 141 | +That means that `import defer` will only work under the `--module` modes `preserve` and `esnext`. |
| 142 | + |
| 143 | +We'd like to extend our thanks to [Nicolò Ribaudo](https://github.com/nicolo-ribaudo) who championed the proposal in TC39 and also provided [the implementation for this feature](https://github.com/microsoft/TypeScript/pull/60757). |
| 144 | + |
| 145 | +## Support for `--module node20` |
| 146 | + |
| 147 | +TypeScript provides several `node*` options for the `--module` and `--moduleResolution` settings. |
| 148 | +Most recently, `--module nodenext` has supported the ability to `require()` ECMAScript modules from CommonJS modules, and correctly rejects import assertions (in favor of the standards-bound [import attributes](https://github.com/tc39/proposal-import-attributes)). |
| 149 | + |
| 150 | +TypeScript 5.9 brings a stable option for these settings called `node20`, intended to model the behavior of Node.js v20. |
| 151 | +This option is unlikely to have new behaviors in the future, unlike `--module nodenext` or `--moduleResolution nodenext`. |
| 152 | +Also unlike `nodenext`, specifying `--module node20` will imply `--target es2023` unless otherwise configured. |
| 153 | +`--module nodenext`, on the other hand, implies the floating `--target esnext`. |
| 154 | + |
| 155 | +For more information, [take a look at the implementation here](https://github.com/microsoft/TypeScript/pull/61805). |
| 156 | + |
| 157 | +## Summary Descriptions in DOM APIs |
| 158 | + |
| 159 | +Previously, many of the DOM APIs in TypeScript only linked to the MDN documentation for the API. |
| 160 | +These links were useful, but they didn't provide a quick summary of what the API does. |
| 161 | +Thanks to a few changes from [Adam Naji](https://github.com/Bashamega), TypeScript now includes summary descriptions for many DOM APIs based on the MDN documentation. |
| 162 | +You can see more of these changes [here](https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1993) and [here](https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1940). |
| 163 | + |
| 164 | +## Expandable Hovers (Preview) |
| 165 | + |
| 166 | +*Quick Info* (also called "editor tooltips" and "hovers") can be very useful for peeking at variables to see their types, or at type aliases to see what they actually refer to. |
| 167 | +Still, it's common for people to want to *go deeper* and get details from whatever's displayed within the quick info tooltip. |
| 168 | +For example, if we hover our mouse over the parameter `options` in the following example: |
| 169 | + |
| 170 | +```ts |
| 171 | +export function drawButton(options: Options): void |
| 172 | +``` |
| 173 | + |
| 174 | +We're left with `(parameter) options: Options`. |
| 175 | + |
| 176 | + |
| 177 | + |
| 178 | +Do we really need to jump to the definition of the type `Options` just to see what members this value has? |
| 179 | + |
| 180 | +Previously, that was actually the case. |
| 181 | +To help here, TypeScript 5.9 is now previewing a feature called *expandable hovers*, or "quick info verbosity". |
| 182 | +If you use an editor like VS Code, you'll now see a `+` and `-` button on the left of these hover tooltips. |
| 183 | +Clicking on the `+` button will expand out types more deeply, while clicking on the `-` button will collapse to the last view. |
| 184 | + |
| 185 | +<video autoplay loop style="width: 100%;" src="https://devblogs.microsoft.com/typescript/wp-content/uploads/sites/11/2025/06/expandable-quick-info-1.mp4" aria-label="Expanding quick info to see more about the type of `Options`."></video> |
| 186 | + |
| 187 | +This feature is currently in preview, and we are seeking feedback for both TypeScript and our partners on Visual Studio Code. |
| 188 | +For more details, see [the PR for this feature here](https://github.com/microsoft/TypeScript/pull/59940). |
| 189 | + |
| 190 | +## Configurable Maximum Hover Length |
| 191 | + |
| 192 | +Occasionally, quick info tooltips can become so long that TypeScript will truncate them to make them more readable. |
| 193 | +The downside here is that often the most important information will be omitted from the hover tooltip, which can be frustrating. |
| 194 | +To help with this, TypeScript 5.9's language server supports a configurable hover length, which can be configured in VS Code via the `js/ts.hover.maximumLength` setting. |
| 195 | + |
| 196 | +Additionally, the new default hover length is substantially larger than the previous default. |
| 197 | +This means that in TypeScript 5.9, you should see more information in your hover tooltips by default. |
| 198 | +For more details, see [the PR for this feature here](https://github.com/microsoft/TypeScript/pull/61662) and [the corresponding change to Visual Studio Code here](https://github.com/microsoft/vscode/pull/248181). |
| 199 | + |
| 200 | +## Optimizations |
| 201 | + |
| 202 | +### Cache Instantiations on Mappers |
| 203 | + |
| 204 | +When TypeScript replaces type parameters with specific type arguments, it can end up instantiating many of the same intermediate types over and over again. |
| 205 | +In complex libraries like Zod and tRPC, this could lead to both performance issues and errors reported around excessive type instantiation depth. |
| 206 | +Thanks to [a change](https://github.com/microsoft/TypeScript/pull/61505) from [Mateusz Burzyński](https://github.com/Andarist), TypeScript 5.9 is able to cache many intermediate instantiations when work has already begun on a specific type instantiation. |
| 207 | +This in turn avoids lots of unnecessary work and allocations. |
| 208 | + |
| 209 | +### Avoiding Closure Creation in `fileOrDirectoryExistsUsingSource` |
| 210 | + |
| 211 | +In JavaScript, a function expression will typically allocate a new function object, even if the wrapper function is just passing through arguments to another function with no captured variables. |
| 212 | +In code paths around file existence checks, [Vincent Bailly](https://github.com/VincentBailly) found examples of these pass-through function calls, even though the underlying functions only took single arguments. |
| 213 | +Given the number of existence checks that could take place in larger projects, he cited a speed-up of around 11%. |
| 214 | +[See more on this change here](https://github.com/microsoft/TypeScript/pull/61822/). |
| 215 | + |
| 216 | +## Notable Behavioral Changes |
| 217 | + |
| 218 | +### `lib.d.ts` Changes |
| 219 | + |
| 220 | +Types generated for the DOM may have an impact on type-checking your codebase. |
| 221 | + |
| 222 | +Additionally, one notable change is that `ArrayBuffer` has been changed in such a way that it is no longer a supertype of several different `TypedArray` types. |
| 223 | +This also includes subtypes of `UInt8Array`, such as `Buffer` from Node.js. |
| 224 | +As a result, you'll see new error messages such as: |
| 225 | + |
| 226 | +``` |
| 227 | +error TS2345: Argument of type 'ArrayBufferLike' is not assignable to parameter of type 'BufferSource'. |
| 228 | +error TS2322: Type 'ArrayBufferLike' is not assignable to type 'ArrayBuffer'. |
| 229 | +error TS2322: Type 'Buffer' is not assignable to type 'Uint8Array<ArrayBufferLike>'. |
| 230 | +error TS2322: Type 'Buffer' is not assignable to type 'ArrayBuffer'. |
| 231 | +error TS2345: Argument of type 'Buffer' is not assignable to parameter of type 'string | Uint8Array<ArrayBufferLike>'. |
| 232 | +``` |
| 233 | + |
| 234 | +If you encounter issues with `Buffer`, you may first want to check that you are using the latest version of the `@types/node` package. |
| 235 | +This might include running |
| 236 | + |
| 237 | +``` |
| 238 | +npm update @types/node --save-dev |
| 239 | +``` |
| 240 | + |
| 241 | +Much of the time, the solution is to specify a more specific underlying buffer type instead of using the default `ArrayBufferLike` (i.e. explicitly writing out `Uint8Array<ArrayBuffer>` rather than a plain `Uint8Array`). |
| 242 | +In instances where some `TypedArray` (like `Uint8Array`) is passed to a function expecting an `ArrayBuffer` or `SharedArrayBuffer`, you can also try accessing the `buffer` property of that `TypedArray` like in the following example: |
| 243 | + |
| 244 | +```diff |
| 245 | + let data = new Uint8Array([0, 1, 2, 3, 4]); |
| 246 | +- someFunc(data) |
| 247 | ++ someFunc(data.buffer) |
| 248 | +``` |
| 249 | +
|
| 250 | +## Type Argument Inference Changes |
| 251 | +
|
| 252 | +In an effort to fix "leaks" of type variables during inference, TypeScript 5.9 may introduce changes in types and possibly new errors in some codebases. |
| 253 | +These are hard to predict, but can often be fixed by adding type arguments to generic functions calls. |
| 254 | +[See more details here](https://github.com/microsoft/TypeScript/pull/61668). |
0 commit comments