{"id":4644,"date":"2025-02-28T11:35:56","date_gmt":"2025-02-28T19:35:56","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/typescript\/?p=4644"},"modified":"2025-02-28T11:36:42","modified_gmt":"2025-02-28T19:36:42","slug":"announcing-typescript-5-8","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/typescript\/announcing-typescript-5-8\/","title":{"rendered":"Announcing TypeScript 5.8"},"content":{"rendered":"<p>Today we&#8217;re excited to announce the release of TypeScript 5.8!<\/p>\n<p>If you&#8217;re not familiar with TypeScript, it&#8217;s a language that builds on top of JavaScript by adding syntax for 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 editors like Visual Studio and VS Code.\nIn fact, TypeScript and its ecosystem powers the JavaScript experience in both of those editors as well!<\/p>\n<p>To get started using TypeScript, you can install it through npm with the following command:<\/p>\n<pre class=\"prettyprint language-sh\" style=\"padding: 10px; border-radius: 10px;\"><code>npm install -D typescript\r\n<\/code><\/pre>\n<p>Let&#8217;s take a look at what&#8217;s new in TypeScript 5.8!<\/p>\n<h2 id=\"whats-new-since-the-beta-and-rc\">What&#8217;s New Since the Beta and RC?<\/h2>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/typescript\/announcing-typescript-5-8-beta\/\">Since our beta release<\/a>, we have had to pull back some work on how functions with conditional return types are checked.\nBased on some of the limitations and changes we wanted to make, we decided to iterate on the feature with the goal of shipping it in TypeScript 5.9.\nHowever, as part of this work, we added more granular checks for branches within return expressions.\nThis enhancement was not documented in the beta post, but will remain in TypeScript 5.8.<\/p>\n<p>No new major changes have been added since the release candidate.<\/p>\n<h2 id=\"granular-checks-for-branches-in-return-expressions\">Granular Checks for Branches in Return Expressions<\/h2>\n<p>Consider some code like the following:<\/p>\n<pre class=\"prettyprint language-typescript\" style=\"padding: 10px; border-radius: 10px;\"><code>declare const untypedCache: Map&lt;any, any&gt;;\r\n\r\nfunction getUrlObject(urlString: string): URL {\r\n    return untypedCache.has(urlString) ?\r\n        untypedCache.get(urlString) :\r\n        urlString;\r\n}\r\n<\/code><\/pre>\n<p>The intent of this code is to retrieve a URL object from a cache if it exists, or to create a new URL object if it doesn&#8217;t.\nHowever, there&#8217;s a bug: we forgot to actually construct a new URL object with the input.\nUnfortunately, TypeScript generally didn&#8217;t catch this sort of bug.<\/p>\n<p>When TypeScript checks conditional expressions like <code>cond ? trueBranch : falseBranch<\/code>, its type is treated as a union of the types of the two branches.\nIn other words, it gets the type of <code>trueBranch<\/code> and <code>falseBranch<\/code>, and combines them into a union type.\nIn this case, the type of <code>untypedCache.get(urlString)<\/code> is <code>any<\/code>, and the type of <code>urlString<\/code> is <code>string<\/code>.\nThis is where things go wrong because <code>any<\/code> is so infectious in how it interacts with other types.\nThe union <code>any | string<\/code> is simplified to <code>any<\/code>, so by the time TypeScript starts checking whether the expression in our <code>return<\/code> statement is compatible with the expected return type of <code>URL<\/code>, the type system has lost any information that would have caught the bug in this code.<\/p>\n<p>In TypeScript 5.8, the type system special-cases conditional expressions directly inside <code>return<\/code> statements.\nEach branch of the conditional is checked against the declared return type of the containing functions (if one exists), so the type system can catch the bug in the example above.<\/p>\n<pre class=\"prettyprint language-typescript\" style=\"padding: 10px; border-radius: 10px;\"><code>declare const untypedCache: Map&lt;any, any&gt;;\r\n\r\nfunction getUrlObject(urlString: string): URL {\r\n    return untypedCache.has(urlString) ?\r\n        untypedCache.get(urlString) :\r\n        urlString;\r\n    \/\/  ~~~~~~~~~\r\n    \/\/ error! Type 'string' is not assignable to type 'URL'.\r\n}\r\n<\/code><\/pre>\n<p>This change was made <a href=\"https:\/\/github.com\/microsoft\/TypeScript\/pull\/56941\">within this pull request<\/a>, as part of a broader set of future improvements for TypeScript.<\/p>\n<h2 id=\"support-for-require-of-ecmascript-modules-in---module-nodenext\">Support for <code>require()<\/code> of ECMAScript Modules in <code>--module nodenext<\/code><\/h2>\n<p>For years, Node.js supported ECMAScript modules (ESM) alongside CommonJS modules.\nUnfortunately, the interoperability between the two had some challenges.<\/p>\n<ul>\n<li>ESM files could <code>import<\/code> CommonJS files<\/li>\n<li>CommonJS files <em><strong>could not<\/strong><\/em> <code>require()<\/code> ESM files<\/li>\n<\/ul>\n<p>In other words, consuming CommonJS files from ESM files was possible, but not the other way around.\nThis introduced many challenges for library authors who wanted to provide ESM support.\nThese library authors would either have to break compatibility with CommonJS users, &#8220;dual-publish&#8221; their libraries (providing separate entry-points for ESM and CommonJS), or just stay on CommonJS indefinitely.\nWhile dual-publishing might sound like a good middle-ground, it is a complex and error-prone process that also roughly doubles the amount of code within a package.<\/p>\n<p>Node.js 22 relaxes some of these restrictions and permits <code>require(\"esm\")<\/code> calls from CommonJS modules to ECMAScript modules.\nNode.js still does not permit <code>require()<\/code> on ESM files that contain a top-level <code>await<\/code>, but most other ESM files are now consumable from CommonJS files.\nThis presents a major opportunity for library authors to provide ESM support without having to dual-publish their libraries.<\/p>\n<p>TypeScript 5.8 supports this behavior under the <code>--module nodenext<\/code> flag.\nWhen <code>--module nodenext<\/code> is enabled, TypeScript will avoid issuing errors on these <code>require()<\/code> calls to ESM files.<\/p>\n<p>Because this feature may be back-ported to older versions of Node.js, there is currently no stable <code>--module nodeXXXX<\/code> option that enables this behavior;\nhowever, we predict future versions of TypeScript may be able to stabilize the feature under <code>node20<\/code>.\nIn the meantime, we encourage users of Node.js 22 and newer to use <code>--module nodenext<\/code>, while library authors and users of older Node.js versions should remain on <code>--module node16<\/code> (or make the minor update to <a href=\"#--module-node18\"><code>--module node18<\/code><\/a>).<\/p>\n<p>For more information, <a href=\"https:\/\/github.com\/microsoft\/TypeScript\/pull\/60761\">see our support for require(&#8220;esm&#8221;) here<\/a>.<\/p>\n<h2 id=\"module-node18\"><code>--module node18<\/code><\/h2>\n<p>TypeScript 5.8 introduces a stable <code>--module node18<\/code> flag.\nFor users who are fixed on using Node.js 18, this flag provides a stable point of reference that does not incorporate certain behaviors that are in <code>--module nodenext<\/code>.\nSpecifically:<\/p>\n<ul>\n<li><code>require()<\/code> of ECMAScript modules is disallowed under <code>node18<\/code>, but allowed under <code>nodenext<\/code><\/li>\n<li>import assertions (deprecated in favor of import attributes) are allowed under <code>node18<\/code>, but are disallowed under <code>nodenext<\/code><\/li>\n<\/ul>\n<p>See more at both <a href=\"https:\/\/github.com\/microsoft\/TypeScript\/pull\/60722\">the <code>--module node18<\/code> pull request<\/a> and <a href=\"https:\/\/github.com\/microsoft\/TypeScript\/pull\/60761\">changes made to <code>--module nodenext<\/code><\/a>.<\/p>\n<h2 id=\"the---erasablesyntaxonly-option\">The <code>--erasableSyntaxOnly<\/code> Option<\/h2>\n<p>Recently, Node.js 23.6 unflagged <a href=\"https:\/\/nodejs.org\/api\/typescript.html#type-stripping\">experimental support for running TypeScript files directly<\/a>;\nhowever, only certain constructs are supported under this mode.\nNode.js has unflagged a mode called <code>--experimental-strip-types<\/code> which requires that any TypeScript-specific syntax cannot have runtime semantics.\nPhrased differently, it must be possible to easily <em>erase<\/em> or &#8220;strip out&#8221; any TypeScript-specific syntax from a file, leaving behind a valid JavaScript file.<\/p>\n<p>That means constructs like the following are not supported:<\/p>\n<ul>\n<li><code>enum<\/code> declarations<\/li>\n<li><code>namespace<\/code>s and <code>module<\/code>s with runtime code<\/li>\n<li>parameter properties in classes<\/li>\n<li><code>import =<\/code> aliases<\/li>\n<\/ul>\n<p>Here are some examples of what does not work:<\/p>\n<pre class=\"prettyprint language-typescript\" style=\"padding: 10px; border-radius: 10px;\"><code>\/\/ \u274c error: A namespace with runtime code.\r\nnamespace container {\r\n    foo.method();\r\n\r\n    export type Bar = string;\r\n}\r\n\r\n\/\/ \u274c error: An `import =` alias\r\nimport Bar = container.Bar;\r\n\r\nclass Point {\r\n    \/\/ \u274c error: Parameter properties\r\n    constructor(public x: number, public y: number) { }\r\n}\r\n\r\n\/\/ \u274c error: An enum declaration.\r\nenum Direction {\r\n    Up,\r\n    Down,\r\n    Left,\r\n    Right,\r\n}\r\n<\/code><\/pre>\n<p>Similar tools like <a href=\"https:\/\/github.com\/bloomberg\/ts-blank-space\">ts-blank-space<\/a> or <a href=\"https:\/\/github.com\/nodejs\/amaro\">Amaro<\/a> (the underlying library for type-stripping in Node.js) have the same limitations.\nThese tools will provide helpful error messages if they encounter code that doesn&#8217;t meet these requirements, but you still won&#8217;t find out your code doesn&#8217;t work until you actually try to run it.<\/p>\n<p>That&#8217;s why TypeScript 5.8 introduces the <code>--erasableSyntaxOnly<\/code> flag.\nWhen this flag is enabled, TypeScript will error on most TypeScript-specific constructs that have runtime behavior.<\/p>\n<pre class=\"prettyprint language-typescript\" style=\"padding: 10px; border-radius: 10px;\"><code>class C {\r\n    constructor(public x: number) { }\r\n    \/\/          ~~~~~~~~~~~~~~~~\r\n    \/\/ error! This syntax is not allowed when 'erasableSyntaxOnly' is enabled.\r\n    }\r\n}\r\n<\/code><\/pre>\n<p>Typically, you will want to combine this flag with the <code>--verbatimModuleSyntax<\/code>, which ensures that a module contains the appropriate import syntax, and that import elision does not take place.<\/p>\n<p>For more information, <a href=\"https:\/\/github.com\/microsoft\/TypeScript\/pull\/61011\">see the implementation here<\/a>.<\/p>\n<h2 id=\"the---libreplacement-flag\">The <code>--libReplacement<\/code> Flag<\/h2>\n<p>In TypeScript 4.5, we introduced the possibility of substituting the default <code>lib<\/code> files with custom ones.\nThis was based on the possibility of resolving a library file from packages named <code>@typescript\/lib-*<\/code>.\nFor example, you could lock your <code>dom<\/code> libraries onto a specific version of <a href=\"https:\/\/www.npmjs.com\/package\/@types\/web?activeTab=readme\">the <code>@types\/web<\/code> package<\/a> with the following <code>package.json<\/code>:<\/p>\n<pre class=\"prettyprint language-json\" style=\"padding: 10px; border-radius: 10px;\"><code>{\r\n    \"devDependencies\": {\r\n       \"@typescript\/lib-dom\": \"npm:@types\/web@0.0.199\"\r\n     }\r\n}\r\n<\/code><\/pre>\n<p>When installed, a package called <code>@typescript\/lib-dom<\/code> should exist, and TypeScript will currently always look it up when <code>dom<\/code> is implied by your settings.<\/p>\n<p>This is a powerful feature, but it also incurs a bit of extra work.\nEven if you&#8217;re not using this feature, TypeScript always performs this lookup, and has to watch for changes in <code>node_modules<\/code> in case a <code>lib<\/code>-replacement package <em>begins<\/em> to exist.<\/p>\n<p>TypeScript 5.8 introduces the <code>--libReplacement<\/code> flag, which allows you to disable this behavior.\nIf you&#8217;re not using <code>--libReplacement<\/code>, you can now disable it with <code>--libReplacement false<\/code>.\nIn the future <code>--libReplacement false<\/code> may become the default, so if you currently rely on the behavior you should consider explicitly enabling it with <code>--libReplacement true<\/code>.<\/p>\n<p>For more information, <a href=\"https:\/\/github.com\/microsoft\/TypeScript\/issues\/61023\">see the change here<\/a>.<\/p>\n<h2 id=\"preserved-computed-property-names-in-declaration-files\">Preserved Computed Property Names in Declaration Files<\/h2>\n<p>In an effort to make computed properties have more predictable emit in declaration files, TypeScript 5.8 will consistently preserve entity names (<code>bareVariables<\/code> and <code>dotted.names.that.look.like.this<\/code>) in computed property names in classes.<\/p>\n<p>For example, consider the following code:<\/p>\n<pre class=\"prettyprint language-typescript\" style=\"padding: 10px; border-radius: 10px;\"><code>export let propName = \"theAnswer\";\r\n\r\nexport class MyClass {\r\n    [propName] = 42;\r\n\/\/  ~~~~~~~~~~\r\n\/\/ error!\r\n\/\/ A computed property name in a class property declaration must have a simple literal type or a 'unique symbol' type.\r\n}\r\n<\/code><\/pre>\n<p>Previous versions of TypeScript would issue an error when generating a declaration file for this module, and a best-effort declaration file would generate an index signature.<\/p>\n<pre class=\"prettyprint language-typescript\" style=\"padding: 10px; border-radius: 10px;\"><code>export declare let propName: string;\r\nexport declare class MyClass {\r\n    [x: string]: number;\r\n}\r\n<\/code><\/pre>\n<p>In TypeScript 5.8, the example code is now allowed, and the emitted declaration file will match what you wrote:<\/p>\n<pre class=\"prettyprint language-typescript\" style=\"padding: 10px; border-radius: 10px;\"><code>export declare let propName: string;\r\nexport declare class MyClass {\r\n    [propName]: number;\r\n}\r\n<\/code><\/pre>\n<p>Note that this does not create statically-named properties on the class.\nYou&#8217;ll still end up with what is effectively an index signature like <code>[x: string]: number<\/code>, so for that use case, you&#8217;d need to use <code>unique symbol<\/code>s or literal types.<\/p>\n<p>Note that writing this code was and currently is an error under the <code>--isolatedDeclarations<\/code> flag;\nbut we expect that thanks to this change, computed property names will generally be permitted in declaration emit.<\/p>\n<p>Note that it&#8217;s possible (though unlikely) that a file compiled in TypeScript 5.8 may generate a declaration file that is not backward compatible in TypeScript 5.7 or earlier.<\/p>\n<p>For more information, <a href=\"https:\/\/github.com\/microsoft\/TypeScript\/pull\/60052\">see the implementing PR<\/a>.<\/p>\n<h2 id=\"optimizations-on-program-loads-and-updates\">Optimizations on Program Loads and Updates<\/h2>\n<p>TypeScript 5.8 introduces a number of optimizations that can both improve the time to build up a program, and also to update a program based on a file change in either <code>--watch<\/code> mode or editor scenarios.<\/p>\n<p>First, TypeScript now <a href=\"https:\/\/github.com\/microsoft\/TypeScript\/pull\/60812\">avoids array allocations that would be involved while normalizing paths<\/a>.\nTypically, path normalization would involve segmenting each portion of a path into an array of strings, normalizing the resulting path based on relative segments, and then joining them back together using a canonical separator.\nFor projects with many files, this can be a significant and repetitive amount of work.\nTypeScript now avoids allocating an array, and operates more directly on indexes of the original path.<\/p>\n<p>Additionally, when edits are made that don&#8217;t change the fundamental structure of a project, <a href=\"https:\/\/github.com\/microsoft\/TypeScript\/pull\/60754\">TypeScript now avoids re-validating the options provided to it<\/a> (e.g. the contents of a <code>tsconfig.json<\/code>).\nThis means, for example, that a simple edit might not require checking that the output paths of a project don&#8217;t conflict with the input paths.\nInstead, the results of the last check can be used.\nThis should make edits in large projects feel more responsive.<\/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\"><code>lib.d.ts<\/code><\/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\/issues\/60985\">see linked issues related to DOM and <code>lib.d.ts<\/code> updates for this version of TypeScript<\/a>.<\/p>\n<h3 id=\"restrictions-on-import-assertions-under---module-nodenext\">Restrictions on Import Assertions Under <code>--module nodenext<\/code><\/h3>\n<p>Import assertions were a proposed addition to ECMAScript to ensure certain properties of an import (e.g. &#8220;this module is JSON, and is not intended to be executable JavaScript code&#8221;).\nThey were reinvented as a proposal called <a href=\"https:\/\/github.com\/tc39\/proposal-import-attributes\">import attributes<\/a>.\nAs part of the transition, they swapped from using the <code>assert<\/code> keyword to using the <code>with<\/code> keyword.<\/p>\n<pre class=\"prettyprint language-typescript\" style=\"padding: 10px; border-radius: 10px;\"><code>\/\/ An import assertion \u274c - not future-compatible with most runtimes.\r\nimport data from \".\/data.json\" assert { type: \"json\" };\r\n\r\n\/\/ An import attribute \u2705 - the preferred way to import a JSON file.\r\nimport data from \".\/data.json\" with { type: \"json\" };\r\n<\/code><\/pre>\n<p>Node.js 22 no longer accepts import assertions using the <code>assert<\/code> syntax.\nIn turn when <code>--module nodenext<\/code> is enabled in TypeScript 5.8, TypeScript will issue an error if it encounters an import assertion.<\/p>\n<pre class=\"prettyprint language-typescript\" style=\"padding: 10px; border-radius: 10px;\"><code>import data from \".\/data.json\" assert { type: \"json\" };\r\n\/\/                             ~~~~~~\r\n\/\/ error! Import assertions have been replaced by import attributes. Use 'with' instead of 'assert'\r\n<\/code><\/pre>\n<p>For more information, <a href=\"https:\/\/github.com\/microsoft\/TypeScript\/pull\/60761\">see the change here<\/a><\/p>\n<h2 id=\"whats-next\">What&#8217;s Next?<\/h2>\n<p>The next version of TypeScript will be TypeScript 5.9, and we&#8217;ll have more details through an upcoming iteration plan <a href=\"https:\/\/github.com\/microsoft\/TypeScript\/issues\">on our issue tracker<\/a>.\nThat will have precise target dates and more details on upcoming features that we plan to work on.\nIn the meantime, you can try out early versions of TypeScript 5.9 today by installing our <a href=\"https:\/\/www.typescriptlang.org\/docs\/handbook\/nightly-builds.html\">nightly builds<\/a> from npm with <code>npm install typescript@next<\/code>, or by using the <a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=ms-vscode.vscode-typescript-next\">VS Code TypeScript Nightly<\/a> extension.<\/p>\n<p>Until then, TypeScript 5.8 is here and ready to use, and we&#8217;re excited for you to install it today!\nWe hope that this release makes your day-to-day coding a joy.<\/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.8! If you&#8217;re not familiar with TypeScript, it&#8217;s a language that builds on top of JavaScript by adding syntax for types. Writing types in our code allows us to explain intent and have other tools check our code to catch mistakes like typos, issues with null [&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-4644","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.8! If you&#8217;re not familiar with TypeScript, it&#8217;s a language that builds on top of JavaScript by adding syntax for types. Writing types in our code allows us to explain intent and have other tools check our code to catch mistakes like typos, issues with null [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/typescript\/wp-json\/wp\/v2\/posts\/4644","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=4644"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/typescript\/wp-json\/wp\/v2\/posts\/4644\/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=4644"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/typescript\/wp-json\/wp\/v2\/categories?post=4644"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/typescript\/wp-json\/wp\/v2\/tags?post=4644"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}