{"id":4165,"date":"2024-03-06T10:27:12","date_gmt":"2024-03-06T18:27:12","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/typescript\/?p=4165"},"modified":"2024-03-06T10:27:12","modified_gmt":"2024-03-06T18:27:12","slug":"announcing-typescript-5-4","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/typescript\/announcing-typescript-5-4\/","title":{"rendered":"Announcing TypeScript 5.4"},"content":{"rendered":"<p>Today we&#8217;re excited to announce the release of TypeScript 5.4!<\/p>\n<p>If you&#8217;re not familiar with TypeScript, it&#8217;s a language that builds on top of JavaScript by making it possible to declare and describe types.\nWriting types in our code allows us to explain intent and have other tools check our code to catch mistakes like typos, issues with <code>null<\/code> and <code>undefined<\/code>, and more.\nTypes also power TypeScript&#8217;s editor tooling like the auto-completion, code navigation, and refactorings that you might see in Visual Studio and VS Code.\nIn fact, if you&#8217;ve been writing JavaScript in either of those editors, you&#8217;ve been using TypeScript all this time!<\/p>\n<p>To get started using TypeScript  <a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.TypeScript.MSBuild\">through NuGet<\/a> or through npm with the following command:<\/p>\n<pre class=\"lang:default decode:true\" style=\"padding: 10px;border-radius: 10px;\"><code>npm install -D typescript\r\n<\/code><\/pre>\n<p>Here&#8217;s a quick list of what&#8217;s new in TypeScript 5.4!<\/p>\n<ul>\n<li><a href=\"#preserved-narrowing-in-closures-following-last-assignments\">Preserved Narrowing in Closures Following Last Assignments<\/a><\/li>\n<li><a href=\"#the-noinfer-utility-type\">The <code>NoInfer<\/code> Utility Type<\/a><\/li>\n<li><a href=\"#objectgroupby-and-mapgroupby\"><code>Object.groupBy<\/code> and <code>Map.groupBy<\/code><\/a><\/li>\n<li><a href=\"#support-for-require-calls-in---moduleresolution-bundler-and---module-preserve\">Support for <code>require()<\/code> calls in <code>--moduleResolution bundler<\/code> and <code>--module preserve<\/code><\/a><\/li>\n<li><a href=\"#checked-import-attributes-and-assertions\">Checked Import Attributes and Assertions<\/a><\/li>\n<li><a href=\"#quick-fix-for-adding-missing-parameters\">Quick Fix for Adding Missing Parameters<\/a><\/li>\n<li><a href=\"#auto-import-support-for-subpath-imports\">Auto-Import Support for Subpath Imports<\/a><\/li>\n<li><a href=\"#upcoming-changes-from-typescript-50-deprecations\">Upcoming 5.5 Deprecations<\/a><\/li>\n<li><a href=\"#notable-behavioral-changes\">Notable Behavioral Changes<\/a><\/li>\n<\/ul>\n<h1 id=\"whats-new-since-the-beta-and-rc\">What&#8217;s New Since the Beta and RC?<\/h2>\n<p>Since the beta, we&#8217;ve updated the release notes to document new <a href=\"#notable-behavioral-changes\">notable behavioral changes<\/a>, including restrictions around enum compatibility, restrictions on enum member naming, and improvements in mapped type behavior.<\/p>\n<p>Since the release candidate, we&#8217;ve documented our new <a href=\"#auto-import-support-for-subpath-imports\">auto-import support for subpath imports<\/a>.<\/p>\n<h2 id=\"preserved-narrowing-in-closures-following-last-assignments\">Preserved Narrowing in Closures Following Last Assignments<\/h2>\n<p>TypeScript can usually figure out a more specific type for a variable based on checks that you might perform.\nThis process is called narrowing.<\/p>\n<pre class=\"lang:default decode:true\" style=\"padding: 10px;border-radius: 10px;\"><code>function uppercaseStrings(x: string | number) {\r\n    if (typeof x === &quot;string&quot;) {\r\n        \/\/ TypeScript knows 'x' is a 'string' here.\r\n        return x.toUpperCase();\r\n    }\r\n}\r\n<\/code><\/pre>\n<p>One common pain-point was that these narrowed types weren&#8217;t always preserved within function closures.<\/p>\n<pre class=\"lang:default decode:true\" style=\"padding: 10px;border-radius: 10px;\"><code>function getUrls(url: string | URL, names: string[]) {\r\n    if (typeof url === &quot;string&quot;) {\r\n        url = new URL(url);\r\n    }\r\n\r\n    return names.map(name =&gt; {\r\n        url.searchParams.set(&quot;name&quot;, name)\r\n        \/\/  ~~~~~~~~~~~~\r\n        \/\/ error!\r\n        \/\/ Property 'searchParams' does not exist on type 'string | URL'.\r\n\r\n        return url.toString();\r\n    });\r\n}\r\n<\/code><\/pre>\n<p>Here, TypeScript decided that it wasn&#8217;t &quot;safe&quot; to assume that <code>url<\/code> was <em>actually<\/em> a <code>URL<\/code> object in our callback function because it was mutated elsewhere;\nhowever, in this instance, that arrow function is <em>always<\/em> created after that assignment to <code>url<\/code>, and it&#8217;s also the <em>last<\/em> assignment to <code>url<\/code>.<\/p>\n<p>TypeScript 5.4 takes advantage of this to make narrowing a little smarter.\nWhen parameters and <code>let<\/code> variables are used in non-<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Glossary\/Hoisting\">hoisted<\/a> functions, the type-checker will look for a last assignment point.\nIf one is found, TypeScript can safely narrow from outside the containing function.\nWhat that means is the above example just works now.<\/p>\n<p>Note that narrowing analysis doesn&#8217;t kick in if the variable is assigned anywhere in a nested function.\nThis is because there&#8217;s no way to know for sure whether the function will be called later.<\/p>\n<pre class=\"lang:default decode:true\" style=\"padding: 10px;border-radius: 10px;\"><code>function printValueLater(value: string | undefined) {\r\n    if (value === undefined) {\r\n        value = &quot;missing!&quot;;\r\n    }\r\n\r\n    setTimeout(() =&gt; {\r\n        \/\/ Modifying 'value', even in a way that shouldn't affect\r\n        \/\/ its type, will invalidate type refinements in closures.\r\n        value = value;\r\n    }, 500);\r\n\r\n    setTimeout(() =&gt; {\r\n        console.log(value.toUpperCase());\r\n        \/\/          ~~~~~\r\n        \/\/ error! 'value' is possibly 'undefined'.\r\n    }, 1000);\r\n}\r\n<\/code><\/pre>\n<p>This should make lots of typical JavaScript code easier to express.\nYou can <a href=\"https:\/\/github.com\/microsoft\/TypeScript\/pull\/56908\">read more about the change on GitHub<\/a>.<\/p>\n<h2 id=\"the-noinfer-utility-type\">The <code>NoInfer<\/code> Utility Type<\/h2>\n<p>When calling generic functions, TypeScript is able to infer type arguments from whatever you pass in.<\/p>\n<pre class=\"lang:default decode:true\" style=\"padding: 10px;border-radius: 10px;\"><code>function doSomething&lt;T&gt;(arg: T) {\r\n    \/\/ ...\r\n}\r\n\r\n\r\n\/\/ We can explicitly say that 'T' should be 'string'.\r\ndoSomething&lt;string&gt;(&quot;hello!&quot;);\r\n\r\n\/\/ We can also just let the type of 'T' get inferred.\r\ndoSomething(&quot;hello!&quot;);\r\n<\/code><\/pre>\n<p>One challenge, however, is that it is not always clear what the &quot;best&quot; type is to infer.\nThis might lead to TypeScript rejecting valid calls, accepting questionable calls, or just reporting worse error messages when it catches a bug.<\/p>\n<p>For example, let&#8217;s imagine a <code>createStreetLight<\/code> function that takes a list of color names, along with an optional default color.<\/p>\n<pre class=\"lang:default decode:true\" style=\"padding: 10px;border-radius: 10px;\"><code>function createStreetLight&lt;C extends string&gt;(colors: C[], defaultColor?: C) {\r\n    \/\/ ...\r\n}\r\n\r\ncreateStreetLight([&quot;red&quot;, &quot;yellow&quot;, &quot;green&quot;], &quot;red&quot;);\r\n<\/code><\/pre>\n<p>What happens when we pass in a <code>defaultColor<\/code> that wasn&#8217;t in the original <code>colors<\/code> array?\nIn this function, <code>colors<\/code> is supposed to be the &quot;source of truth&quot; and describe what can be passed to <code>defaultColor<\/code>.<\/p>\n<pre class=\"lang:default decode:true\" style=\"padding: 10px;border-radius: 10px;\"><code>\/\/ Oops! This undesirable, but is allowed!\r\ncreateStreetLight([&quot;red&quot;, &quot;yellow&quot;, &quot;green&quot;], &quot;blue&quot;);\r\n<\/code><\/pre>\n<p>In this call, type inference decided that <code>&quot;blue&quot;<\/code> was just as valid of a type as <code>&quot;red&quot;<\/code> or <code>&quot;yellow&quot;<\/code> or <code>&quot;green&quot;<\/code>.\nSo instead of rejecting the call, TypeScript infers the type of <code>C<\/code> as <code>&quot;red&quot; | &quot;yellow&quot; | &quot;green&quot; | &quot;blue&quot;<\/code>.\nYou might say that inference just blue up in our faces!<\/p>\n<p>One way people currently deal with this is to add a separate type parameter that&#8217;s bounded by the existing type parameter.<\/p>\n<pre class=\"lang:default decode:true\" style=\"padding: 10px;border-radius: 10px;\"><code>function createStreetLight&lt;C extends string, D extends C&gt;(colors: C[], defaultColor?: D) {\r\n}\r\n\r\ncreateStreetLight([&quot;red&quot;, &quot;yellow&quot;, &quot;green&quot;], &quot;blue&quot;);\r\n\/\/                                            ~~~~~~\r\n\/\/ error!\r\n\/\/ Argument of type '&quot;blue&quot;' is not assignable to parameter of type '&quot;red&quot; | &quot;yellow&quot; | &quot;green&quot; | undefined'.\r\n<\/code><\/pre>\n<p>This works, but is a little bit awkward because <code>D<\/code> probably won&#8217;t be used anywhere else in the signature for <code>createStreetLight<\/code>.\nWhile not bad <em>in this case<\/em>, using a type parameter only once in a signature is often a code smell.<\/p>\n<p>That&#8217;s why TypeScript 5.4 introduces a new <code>NoInfer&lt;T&gt;<\/code> utility type.\nSurrounding a type in <code>NoInfer&lt;...&gt;<\/code> gives a signal to TypeScript not to dig in and match against the inner types to find candidates for type inference.<\/p>\n<p>Using <code>NoInfer<\/code>, we can rewrite <code>createStreetLight<\/code> as something like this:<\/p>\n<pre class=\"lang:default decode:true\" style=\"padding: 10px;border-radius: 10px;\"><code>function createStreetLight&lt;C extends string&gt;(colors: C[], defaultColor?: NoInfer&lt;C&gt;) {\r\n    \/\/ ...\r\n}\r\n\r\ncreateStreetLight([&quot;red&quot;, &quot;yellow&quot;, &quot;green&quot;], &quot;blue&quot;);\r\n\/\/                                            ~~~~~~\r\n\/\/ error!\r\n\/\/ Argument of type '&quot;blue&quot;' is not assignable to parameter of type '&quot;red&quot; | &quot;yellow&quot; | &quot;green&quot; | undefined'.\r\n<\/code><\/pre>\n<p>Excluding the type of <code>defaultColor<\/code> from being explored for inference means that <code>&quot;blue&quot;<\/code> never ends up as an inference candidate, and the type-checker can reject it.<\/p>\n<p>You can see the specific changes in <a href=\"https:\/\/github.com\/microsoft\/TypeScript\/pull\/56794\">the implementing pull request<\/a>, along with <a href=\"https:\/\/github.com\/microsoft\/TypeScript\/pull\/52968\">the initial implementation<\/a> provided thanks to <a href=\"https:\/\/github.com\/Andarist\">Mateusz Burzy\u0144ski<\/a>!<\/p>\n<h2 id=\"objectgroupby-and-mapgroupby\"><code>Object.groupBy<\/code> and <code>Map.groupBy<\/code><\/h2>\n<p>TypeScript 5.4 adds declarations for JavaScript&#8217;s new <code>Object.groupBy<\/code> and <code>Map.groupBy<\/code> static methods.<\/p>\n<p><code>Object.groupBy<\/code> takes an iterable, and a function that decides which &quot;group&quot; each element should be placed in.\nThe function needs to make a &quot;key&quot; for each distinct group, and <code>Object.groupBy<\/code> uses that key to make an object where every key maps to an array with the original element in it.<\/p>\n<p>So the following JavaScript:<\/p>\n<pre class=\"lang:default decode:true\" style=\"padding: 10px;border-radius: 10px;\"><code>const array = [0, 1, 2, 3, 4, 5];\r\n\r\nconst myObj = Object.groupBy(array, (num, index) =&gt; {\r\n    return num % 2 === 0 ? &quot;even&quot;: &quot;odd&quot;;\r\n});\r\n<\/code><\/pre>\n<p>is basically equivalent to writing this:<\/p>\n<pre class=\"lang:default decode:true\" style=\"padding: 10px;border-radius: 10px;\"><code>const myObj = {\r\n    even: [0, 2, 4],\r\n    odd: [1, 3, 5],\r\n};\r\n<\/code><\/pre>\n<p><code>Map.groupBy<\/code> is similar, but produces a <code>Map<\/code> instead of a plain object.\nThis might be more desirable if you need the guarantees of <code>Map<\/code>s, you&#8217;re dealing with APIs that expect <code>Map<\/code>s, or you need to use any kind of key for grouping &#8211; not just keys that can be used as property names in JavaScript.<\/p>\n<pre class=\"lang:default decode:true\" style=\"padding: 10px;border-radius: 10px;\"><code>const myObj = Map.groupBy(array, (num, index) =&gt; {\r\n    return num % 2 === 0 ? &quot;even&quot; : &quot;odd&quot;;\r\n});\r\n<\/code><\/pre>\n<p>and just as before, you could have created <code>myObj<\/code> in an equivalent way:<\/p>\n<pre class=\"lang:default decode:true\" style=\"padding: 10px;border-radius: 10px;\"><code>const myObj = new Map();\r\n\r\nmyObj.set(&quot;even&quot;, [0, 2, 4]);\r\nmyObj.set(&quot;odd&quot;, [1, 3, 5]);\r\n<\/code><\/pre>\n<p>Note that in the above example of <code>Object.groupBy<\/code>, the object produced uses all optional properties.<\/p>\n<pre class=\"lang:default decode:true\" style=\"padding: 10px;border-radius: 10px;\"><code>interface EvenOdds {\r\n    even?: number[];\r\n    odd?: number[];\r\n}\r\n\r\nconst myObj: EvenOdds = Object.groupBy(...);\r\n\r\nmyObj.even;\r\n\/\/    ~~~~\r\n\/\/ Error to access this under 'strictNullChecks'.\r\n<\/code><\/pre>\n<p>This is because there&#8217;s no way to guarantee in a general way that <em>all<\/em> the keys were produced by <code>groupBy<\/code>.<\/p>\n<p>Note also that these methods are only accessible by configuring your <code>target<\/code> to <code>esnext<\/code> or adjusting your <code>lib<\/code> settings.\nWe expect they will eventually be available under a stable <code>es2024<\/code> target.<\/p>\n<p>We&#8217;d like to extend a thanks to <a href=\"https:\/\/github.com\/bakkot\">Kevin Gibbons<\/a> for <a href=\"https:\/\/github.com\/microsoft\/TypeScript\/pull\/56805\">adding the declarations to these <code>groupBy<\/code> methods<\/a>.<\/p>\n<h2 id=\"support-for-require-calls-in---moduleresolution-bundler-and---module-preserve\">Support for <code>require()<\/code> calls in <code>--moduleResolution bundler<\/code> and <code>--module preserve<\/code><\/h2>\n<p>TypeScript has a <code>moduleResolution<\/code> option called <code>bundler<\/code> that is meant to model the way modern bundlers figure out which file an import path refers to.\nOne of the limitations of the option is that it had to be paired with <code>--module esnext<\/code>, making it impossible to use the <code>import ... = require(...)<\/code> syntax.<\/p>\n<pre class=\"lang:default decode:true\" style=\"padding: 10px;border-radius: 10px;\"><code>\/\/ previously errored\r\nimport myModule = require(&quot;module\/path&quot;);\r\n<\/code><\/pre>\n<p>That might not seem like a big deal if you&#8217;re planning on just writing standard ECMAScript <code>import<\/code>s, but there&#8217;s a difference when using a package with <a href=\"https:\/\/nodejs.org\/api\/packages.html#conditional-exports\">conditional exports<\/a>.<\/p>\n<p>In TypeScript 5.4, <code>require()<\/code> can now be used when setting the <code>module<\/code> setting to a new option called <code>preserve<\/code>.<\/p>\n<p>Between <code>--module preserve<\/code> and <code>--moduleResolution bundler<\/code>, the two more accurately model what bundlers and runtimes like Bun will allow, and how they&#8217;ll perform module lookups.\nIn fact, when using <code>--module preserve<\/code>, the <code>bundler<\/code> option will be implicitly set for <code>--moduleResolution<\/code> (along with <code>--esModuleInterop<\/code> and <code>--resolveJsonModule<\/code>)<\/p>\n<pre class=\"lang:default decode:true\" style=\"padding: 10px;border-radius: 10px;\"><code>{\r\n    &quot;compilerOptions&quot;: {\r\n        &quot;module&quot;: &quot;preserve&quot;,\r\n        \/\/ ^ also implies:\r\n        \/\/ &quot;moduleResolution&quot;: &quot;bundler&quot;,\r\n        \/\/ &quot;esModuleInterop&quot;: true,\r\n        \/\/ &quot;resolveJsonModule&quot;: true,\r\n\r\n        \/\/ ...\r\n    }\r\n}\r\n<\/code><\/pre>\n<p>Under <code>--module preserve<\/code>, an ECMAScript <code>import<\/code> will always be emitted as-is, and <code>import ... = require(...)<\/code> will be emitted as a <code>require()<\/code> call (though in practice you may not even use TypeScript for emit, since it&#8217;s likely you&#8217;ll be using a bundler for your code).\nThis holds true regardless of the file extension of the containing file.\nSo the output of this code:<\/p>\n<pre class=\"lang:default decode:true\" style=\"padding: 10px;border-radius: 10px;\"><code>import * as foo from &quot;some-package\/foo&quot;;\r\nimport bar = require(&quot;some-package\/bar&quot;);\r\n<\/code><\/pre>\n<p>should look something like this:<\/p>\n<pre class=\"lang:default decode:true\" style=\"padding: 10px;border-radius: 10px;\"><code>import * as foo from &quot;some-package\/foo&quot;;\r\nvar bar = require(&quot;some-package\/bar&quot;);\r\n<\/code><\/pre>\n<p>What this also means is that the syntax you choose directs how <a href=\"https:\/\/nodejs.org\/api\/packages.html#conditional-exports\">conditional exports<\/a> are matched.\nSo in the above example, if the <code>package.json<\/code> of <code>some-package<\/code> looks like this:<\/p>\n<pre class=\"lang:default decode:true\" style=\"padding: 10px;border-radius: 10px;\"><code>{\r\n  &quot;name&quot;: &quot;some-package&quot;,\r\n  &quot;version&quot;: &quot;0.0.1&quot;,\r\n  &quot;exports&quot;: {\r\n    &quot;.\/foo&quot;: {\r\n        &quot;import&quot;: &quot;.\/esm\/foo-from-import.mjs&quot;,\r\n        &quot;require&quot;: &quot;.\/cjs\/foo-from-require.cjs&quot;\r\n    },\r\n    &quot;.\/bar&quot;: {\r\n        &quot;import&quot;: &quot;.\/esm\/bar-from-import.mjs&quot;,\r\n        &quot;require&quot;: &quot;.\/cjs\/bar-from-require.cjs&quot;\r\n    }\r\n  }\r\n}\r\n<\/code><\/pre>\n<p>TypeScript will resolve these paths to <code>[...]\/some-package\/esm\/foo-from-import.mjs<\/code> and <code>[...]\/some-package\/cjs\/bar-from-require.cjs<\/code>.<\/p>\n<p>For more information, you can <a href=\"https:\/\/github.com\/microsoft\/TypeScript\/pull\/56785\">read up on these new settings here<\/a>.<\/p>\n<h2 id=\"checked-import-attributes-and-assertions\">Checked Import Attributes and Assertions<\/h2>\n<p>Import attributes and assertions are now checked against the global <code>ImportAttributes<\/code> type.\nThis means that runtimes can now more accurately describe the import attributes<\/p>\n<pre class=\"lang:default decode:true\" style=\"padding: 10px;border-radius: 10px;\"><code>\/\/ In some global file.\r\ninterface ImportAttributes {\r\n    type: &quot;json&quot;;\r\n}\r\n\r\n\/\/ In some other module\r\nimport * as ns from &quot;foo&quot; with { type: &quot;not-json&quot; };\r\n\/\/                                     ~~~~~~~~~~\r\n\/\/ error!\r\n\/\/\r\n\/\/ Type '{ type: &quot;not-json&quot;; }' is not assignable to type 'ImportAttributes'.\r\n\/\/  Types of property 'type' are incompatible.\r\n\/\/    Type '&quot;not-json&quot;' is not assignable to type '&quot;json&quot;'.\r\n<\/code><\/pre>\n<p><a href=\"https:\/\/github.com\/microsoft\/TypeScript\/pull\/56034\">This change<\/a> was provided thanks to <a href=\"https:\/\/github.com\/a-tarasyuk\">Oleksandr Tarasiuk<\/a>.<\/p>\n<h2 id=\"quick-fix-for-adding-missing-parameters\">Quick Fix for Adding Missing Parameters<\/h2>\n<p>TypeScript now has a quick fix to add a new parameter to functions that are called with too many arguments.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/typescript\/wp-content\/uploads\/sites\/11\/2024\/01\/add-missing-params-5-4-beta-before.png\" alt=\"A quick fix being offered when someFunction calls someHelperFunction with 2 more arguments than are expected.\"><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/typescript\/wp-content\/uploads\/sites\/11\/2024\/01\/add-missing-params-5-4-beta-after.png\" alt=\"The missing arguments have been added to someHelperFunction after the quick fix was applied.\"><\/p>\n<p>This can be useful when threading a new argument through several existing functions, which can be cumbersome today.<\/p>\n<p><a href=\"https:\/\/github.com\/microsoft\/TypeScript\/pull\/56411\">This quick fix<\/a> was provided courtsey of <a href=\"https:\/\/github.com\/a-tarasyuk\">Oleksandr Tarasiuk<\/a>.<\/p>\n<h2 id=\"auto-import-support-for-subpath-imports\">Auto-Import Support for Subpath Imports<\/h2>\n<p>In Node.js, <code>package.json<\/code> supports <a href=\"https:\/\/nodejs.org\/api\/packages.html#subpath-imports\">a feature called &quot;subpath imports&quot; via a field called <code>imports<\/code>s<\/a>.\nIt&#8217;s a way to re-mapping paths inside of a package to other module paths.\nConceptually, this is pretty similar to path-mapping, a featue that certain module bundlers and loaders support (and which TypeScript supports via a feature called <code>paths<\/code>).\nThe only difference is that subpath imports always have to start with a <code>#<\/code>.<\/p>\n<p>TypeScript&#8217;s auto-imports feature previously did not consider paths in <code>imports<\/code> which could be frustrating.\nInstead, users might have to manually define <code>paths<\/code> in their <code>tsconfig.json<\/code>.\nHowever, thanks to a contribution from <a href=\"https:\/\/github.com\/emmatown\">Emma Hamilton<\/a>, <a href=\"https:\/\/github.com\/microsoft\/TypeScript\/pull\/55015\">TypeScript&#8217;s auto-imports now support subpath imports<\/a>!<\/p>\n<h2 id=\"upcoming-changes-from-typescript-50-deprecations\">Upcoming Changes from TypeScript 5.0 Deprecations<\/h2>\n<p>TypeScript 5.0 deprecated the following options and behaviors:<\/p>\n<ul>\n<li><code>charset<\/code><\/li>\n<li><code>target: ES3<\/code><\/li>\n<li><code>importsNotUsedAsValues<\/code><\/li>\n<li><code>noImplicitUseStrict<\/code><\/li>\n<li><code>noStrictGenericChecks<\/code><\/li>\n<li><code>keyofStringsOnly<\/code><\/li>\n<li><code>suppressExcessPropertyErrors<\/code><\/li>\n<li><code>suppressImplicitAnyIndexErrors<\/code><\/li>\n<li><code>out<\/code><\/li>\n<li><code>preserveValueImports<\/code><\/li>\n<li><code>prepend<\/code> in project references<\/li>\n<li>implicitly OS-specific <code>newLine<\/code><\/li>\n<\/ul>\n<p>To continue using them, developers using TypeScript 5.0 and other more recent versions have had to specify a new option called <code>ignoreDeprecations<\/code> with the value <code>&quot;5.0&quot;<\/code>.<\/p>\n<p>However, TypScript 5.4 will be the last version in which these will continue to function as normal.\nBy TypeScript 5.5 (likely June 2024), these will become hard errors, and code using them will need to be migrated away.<\/p>\n<p>For more information, you can <a href=\"https:\/\/github.com\/microsoft\/TypeScript\/issues\/51909\">read up on this plan on GitHub<\/a>, which contains suggestions in how to best adapt your codebase.<\/p>\n<h2 id=\"notable-behavioral-changes\">Notable Behavioral Changes<\/h2>\n<p>This section highlights a set of noteworthy changes that should be acknowledged and understood as part of any upgrade.\nSometimes it will highlight deprecations, removals, and new restrictions.\nIt can also contain bug fixes that are functionally improvements, but which can also affect an existing build by introducing new errors.<\/p>\n<h3 id=\"libdts-changes\"><code>lib.d.ts<\/code> Changes<\/h3>\n<p>Types generated for the DOM may have an impact on type-checking your codebase.\nFor more information, <a href=\"https:\/\/github.com\/microsoft\/TypeScript\/pull\/57027\">see the DOM updates for TypeScript 5.4<\/a>.<\/p>\n<h3 id=\"more-accurate-conditional-type-constraints\">More Accurate Conditional Type Constraints<\/h3>\n<p>The following code no longer allows the second variable declaration in the function <code>foo<\/code>.<\/p>\n<pre class=\"lang:default decode:true\" style=\"padding: 10px;border-radius: 10px;\"><code>type IsArray&lt;T&gt; = T extends any[] ? true : false;\r\n\r\nfunction foo&lt;U extends object&gt;(x: IsArray&lt;U&gt;) {\r\n    let first: true = x;    \/\/ Error\r\n    let second: false = x;  \/\/ Error, but previously wasn't\r\n}\r\n<\/code><\/pre>\n<p>Previously, when TypeScript checked the initializer for <code>second<\/code>, it needed to determine whether <code>IsArray&lt;U&gt;<\/code> was assignable to the unit type <code>false<\/code>.\nWhile <code>IsArray&lt;U&gt;<\/code> isn&#8217;t compatible any obvious way, TypeScript looks at the <em>constraint<\/em> of that type as well.\nIn a conditional type like <code>T extends Foo ? TrueBranch : FalseBranch<\/code>, where <code>T<\/code> is generic, the type system would look at the constraint of <code>T<\/code>, substitute it in for <code>T<\/code> itself, and decide on either the true or false branch.<\/p>\n<p>But this behavior was inaccurate because it was overly-eager.\nEven if the constraint of <code>T<\/code> isn&#8217;t assignable to <code>Foo<\/code>, that doesn&#8217;t mean that it won&#8217;t be instantiated with something that is.\nAnd so the more correct behavior is to produce a union type for the constraint of the conditional type in cases where it can&#8217;t be proven that <code>T<\/code> <em>never<\/em> or <em>always<\/em> extends <code>Foo.<\/code><\/p>\n<p>TypeScript 5.4 adopts this more accuratre behavior.\nWhat this means in practice is that you may begin to find that some conditional type instances are no longer compatible with their branches.<\/p>\n<p><a href=\"https:\/\/github.com\/microsoft\/TypeScript\/pull\/56004\">You can read about the specific changes here<\/a>.<\/p>\n<h3 id=\"more-aggressive-reduction-of-intersections-between-type-variables-and-primitive-types\">More Aggressive Reduction of Intersections Between Type Variables and Primitive Types<\/h3>\n<p>TypeScript now reduces intersections with type variables and primitives more aggressively, depending on how the type variable&#8217;s constraint overlaps with those primitives.<\/p>\n<pre class=\"lang:default decode:true\" style=\"padding: 10px;border-radius: 10px;\"><code>declare function intersect&lt;T, U&gt;(x: T, y: U): T &amp; U;\r\n\r\nfunction foo&lt;T extends &quot;abc&quot; | &quot;def&quot;&gt;(x: T, str: string, num: number) {\r\n\r\n    \/\/ Was 'T &amp; string', now is just 'T'\r\n    let a = intersect(x, str);\r\n\r\n    \/\/ Was 'T &amp; number', now is just 'never'\r\n    let b = intersect(x, num)\r\n\r\n    \/\/ Was '(T &amp; &quot;abc&quot;) | (T &amp; &quot;def&quot;)', now is just 'T'\r\n    let c = Math.random() &lt; 0.5 ?\r\n        intersect(x, &quot;abc&quot;) :\r\n        intersect(x, &quot;def&quot;);\r\n}\r\n<\/code><\/pre>\n<p>For more information, <a href=\"https:\/\/github.com\/microsoft\/TypeScript\/pull\/56515\">see the change here<\/a>.<\/p>\n<h3 id=\"improved-checking-against-template-strings-with-interpolations\">Improved Checking Against Template Strings with Interpolations<\/h3>\n<p>TypeScript now more accurately checks whether or not strings are assignable to the placeholder slots of a template string type.<\/p>\n<pre class=\"lang:default decode:true\" style=\"padding: 10px;border-radius: 10px;\"><code>function a&lt;T extends {id: string}&gt;() {\r\n    let x: `-${keyof T &amp; string}`;\r\n    \r\n    \/\/ Used to error, now doesn't.\r\n    x = &quot;-id&quot;;\r\n}\r\n<\/code><\/pre>\n<p>This behavior is more desirable, but may cause breaks in code using constructs like conditional types, where these rule changes are easy to witness.<\/p>\n<p><a href=\"https:\/\/github.com\/microsoft\/TypeScript\/pull\/56598\">See this change<\/a> for more details.<\/p>\n<h3 id=\"errors-when-type-only-imports-conflict-with-local-values\">Errors When Type-Only Imports Conflict with Local Values<\/h3>\n<p>Previously, TypeScript would permit the following code under <code>isolatedModules<\/code> if the import to <code>Something<\/code> only referred to a type.<\/p>\n<pre class=\"lang:default decode:true\" style=\"padding: 10px;border-radius: 10px;\"><code>import { Something } from &quot;.\/some\/path&quot;;\r\n\r\nlet Something = 123;\r\n<\/code><\/pre>\n<p>However, it&#8217;s not safe for a single-file compilers to assume whether it&#8217;s &quot;safe&quot; to drop the <code>import<\/code>, even if the code is guaranteed to fail at runtime.\nIn TypeScript 5.4, this code will trigger an error like the following:<\/p>\n<pre class=\"lang:default decode:true\" style=\"padding: 10px;border-radius: 10px;\"><code>Import 'Something' conflicts with local value, so must be declared with a type-only import when 'isolatedModules' is enabled.\r\n<\/code><\/pre>\n<p>The fix should be to either make a local rename, or, as the error states, add the <code>type<\/code> modifier to the import:<\/p>\n<pre class=\"lang:default decode:true\" style=\"padding: 10px;border-radius: 10px;\"><code>import type { Something } from &quot;.\/some\/path&quot;;\r\n\r\n\/\/ or\r\n\r\nimport { type Something } from &quot;.\/some\/path&quot;;\r\n<\/code><\/pre>\n<p><a href=\"https:\/\/github.com\/microsoft\/TypeScript\/pull\/56354\">See more information on the change itself<\/a>.<\/p>\n<h3 id=\"new-enum-assignability-restrictions\">New Enum Assignability Restrictions<\/h3>\n<p>When two enums have the same declared names and enum member names, they were previously always considered compatible;\nhowever, when the values were known, TypeScript would silently allow them to have differing values.<\/p>\n<p>TypeScript 5.4 tightens this restriction by requiring the values to be identical when they are known.<\/p>\n<pre class=\"lang:default decode:true\" style=\"padding: 10px;border-radius: 10px;\"><code>namespace First {\r\n    export enum SomeEnum {\r\n        A = 0,\r\n        B = 1,\r\n    }\r\n}\r\n\r\nnamespace Second {\r\n    export enum SomeEnum {\r\n        A = 0,\r\n        B = 2,\r\n    }\r\n}\r\n\r\nfunction foo(x: First.SomeEnum, y: Second.SomeEnum) {\r\n    \/\/ Both used to be compatible - no longer the case,\r\n    \/\/ TypeScript errors with something like:\r\n    \/\/\r\n    \/\/  Each declaration of 'SomeEnum.B' differs in its value, where '1' was expected but '2' was given.\r\n    x = y;\r\n    y = x;\r\n}\r\n<\/code><\/pre>\n<p>Additionally, there are new restrictions for when one of the enum members does not have a statically-known value.\nIn 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).\nPractically speaking, what this means is that string enum members are only ever compatible with other string enums of the same value.<\/p>\n<pre class=\"lang:default decode:true\" style=\"padding: 10px;border-radius: 10px;\"><code>namespace First {\r\n    export declare enum SomeEnum {\r\n        A,\r\n        B,\r\n    }\r\n}\r\n\r\nnamespace Second {\r\n    export declare enum SomeEnum {\r\n        A,\r\n        B = &quot;some known string&quot;,\r\n    }\r\n}\r\n\r\nfunction foo(x: First.SomeEnum, y: Second.SomeEnum) {\r\n    \/\/ Both used to be compatible - no longer the case,\r\n    \/\/ TypeScript errors with something like:\r\n    \/\/\r\n    \/\/  One value of 'SomeEnum.B' is the string '&quot;some known string&quot;', and the other is assumed to be an unknown numeric value.\r\n    x = y;\r\n    y = x;\r\n}\r\n<\/code><\/pre>\n<p>For more information, <a href=\"https:\/\/github.com\/microsoft\/TypeScript\/pull\/55924\">see the pull request that introduced this change<\/a>.<\/p>\n<h3 id=\"name-restrictions-on-enum-members\">Name Restrictions on Enum Members<\/h3>\n<p>TypeScript no longer allows enum members to use the names <code>Infinity<\/code>, <code>-Infinity<\/code>, or <code>NaN<\/code>.<\/p>\n<pre class=\"lang:default decode:true\" style=\"padding: 10px;border-radius: 10px;\"><code>\/\/ Errors on all of these:\r\n\/\/\r\n\/\/  An enum member cannot have a numeric name.\r\nenum E {\r\n    Infinity = 0,\r\n    &quot;-Infinity&quot; = 1,\r\n    NaN = 2,\r\n}\r\n<\/code><\/pre>\n<p><a href=\"https:\/\/github.com\/microsoft\/TypeScript\/pull\/56161\">See more details here<\/a>.<\/p>\n<h3 id=\"better-mapped-type-preservation-over-tuples-with-any-rest-elements\">Better Mapped Type Preservation Over Tuples with <code>any<\/code> Rest Elements<\/h3>\n<p>Previously, applying a mapped type with <code>any<\/code> into a tuple would create an <code>any<\/code> element type.\nThis is undesirable and is now fixed.<\/p>\n<pre class=\"lang:default decode:true\" style=\"padding: 10px;border-radius: 10px;\"><code>Promise.all([&quot;&quot;, ...([] as any)])\r\n    .then((result) =&gt; {\r\n        const head = result[0];       \/\/ 5.3: any, 5.4: string\r\n        const tail = result.slice(1); \/\/ 5.3 any, 5.4: any[]\r\n    });\r\n<\/code><\/pre>\n<p>For more information, see <a href=\"https:\/\/github.com\/microsoft\/TypeScript\/pull\/57031\">the fix<\/a> along with <a href=\"https:\/\/github.com\/microsoft\/TypeScript\/issues\/57389\">the follow-on discussion around behavioral changes<\/a> and <a href=\"https:\/\/github.com\/microsoft\/TypeScript\/issues\/57389\">further tweaks<\/a>.<\/p>\n<h3 id=\"emit-changes\">Emit Changes<\/h3>\n<p>While not a breaking change per-se, developers may have implicitly taken dependencies on TypeScript&#8217;s JavaScript or declaration emit outputs.\nThe following are notable changes.<\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/microsoft\/TypeScript\/pull\/55820\">Preserve type parameter names more often when shadowed<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/microsoft\/TypeScript\/pull\/56296\">Move complex parameter lists of async function into downlevel generator body<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/microsoft\/TypeScript\/pull\/57020\">Do not remove binding alias in function declarations<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/microsoft\/TypeScript\/pull\/56395\">ImportAttributes should go through the same emit phases when in an ImportTypeNode<\/a><\/li>\n<\/ul>\n<h2 id=\"whats-next\">What&#8217;s Next?<\/h2>\n<p>In the coming months, we&#8217;ll be working on TypeScript 5.5, and you can <a href=\"https:\/\/github.com\/microsoft\/TypeScript\/issues\/57475\">see our iteration plan available on GitHub<\/a>.\nOur target release dates are public so you, your team, and the broader TypeScript community can schedule accordingly.\nYou can also try out nightly releases on npm or <a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=ms-vscode.vscode-typescript-next\">use the latest version of TypeScript and JavaScript in Visual Studio Code<\/a>.<\/p>\n<p>But until then, TypeScript 5.4 is still the latest and greatest stable version, and we hope that it makes coding a joy for you!<\/p>\n<p>Happy Hacking!<\/p>\n<p>&#8211; Daniel Rosenwasser and the TypeScript Team<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Today we&#8217;re excited to announce the release of TypeScript 5.4! If you&#8217;re not familiar with TypeScript, it&#8217;s a language that builds on top of JavaScript by making it possible to declare and describe types. Writing types in our code allows us to explain intent and have other tools check our code to catch mistakes like [&hellip;]<\/p>\n","protected":false},"author":381,"featured_media":1797,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-4165","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-typescript"],"acf":[],"blog_post_summary":"<p>Today we&#8217;re excited to announce the release of TypeScript 5.4! If you&#8217;re not familiar with TypeScript, it&#8217;s a language that builds on top of JavaScript by making it possible to declare and describe types. Writing types in our code allows us to explain intent and have other tools check our code to catch mistakes like [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/typescript\/wp-json\/wp\/v2\/posts\/4165","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/typescript\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/typescript\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/typescript\/wp-json\/wp\/v2\/users\/381"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/typescript\/wp-json\/wp\/v2\/comments?post=4165"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/typescript\/wp-json\/wp\/v2\/posts\/4165\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/typescript\/wp-json\/wp\/v2\/media\/1797"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/typescript\/wp-json\/wp\/v2\/media?parent=4165"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/typescript\/wp-json\/wp\/v2\/categories?post=4165"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/typescript\/wp-json\/wp\/v2\/tags?post=4165"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}