TypeScript 6.0 RC: features, breaking changes and how to prepare

Última actualización: 03/17/2026
  • TypeScript 6.0 RC is the last JS-based compiler release and aligns behavior, defaults and ordering with the upcoming Go-based TypeScript 7.0.
  • The release tightens modern defaults (strict, ESNext modules, ES2025), introduces Temporal, ES2025 APIs and new Map upsert and RegExp.escape typings.
  • Key config changes include an empty default types array, rootDir defaulting to the config directory, and the deprecation of ES5, old module systems, baseUrl and legacy resolution modes.
  • Teams are encouraged to upgrade to 6.0, fix deprecations and optionally use --stableTypeOrdering to ensure a smoother migration path to TypeScript 7.0.

TypeScript 6.0 release candidate

TypeScript 6.0 has officially hit the Release Candidate (RC) milestone, and it’s not just another incremental update. This is the last major version running on the long‑standing JavaScript implementation of the compiler and language service, right before the project jumps to a brand‑new, Go‑based engine in TypeScript 7.0. That alone makes 6.0 a pivotal release: it’s the bridge you’re meant to cross before everything under the hood changes.

You can start trying the RC today by installing it from npm with:

npm install -D typescript@rc

The core idea behind TypeScript 6.0 is preparation and alignment. This version smooths the path from 5.9 to 7.0, tightening defaults, deprecating historical baggage, and adding a few targeted features that either mirror future behavior or expose upcoming JavaScript capabilities like Temporal, ES2025 APIs, and Map “upsert” methods. Along the way, there are some subtle type system tweaks, new compiler flags, and configuration defaults that will absolutely affect real projects—especially around types, rootDir, and strictness.

TypeScript 6.0 as the bridge to the Go‑based 7.0

TypeScript 6.0 is explicitly designed as the final major release on the existing JavaScript codebase. The TypeScript team has been rewriting the compiler and language service into a motor nativo en Go, taking advantage of native performance and shared‑memory parallelism. That new engine will debut as TypeScript 7.0 and beyond, and 6.0 sits right in front of it as a transitional stage.

Most of the breaking changes and deprecations in 6.0 are there to make your future 7.0 upgrade survivable. Options that can’t be supported efficiently in the native architecture, old module systems, and long‑standing quirks are either being removed or clearly marked as deprecated with an escape hatch: you can set "ignoreDeprecations": "6.0" in your tsconfig.json to suppress deprecation diagnostics in 6.0. But that flag won’t help you in 7.0—those options are planned to disappear completely.

If you’re tempted to “wait for 7.0” before doing any config clean‑up, that’s a trap. The 6.0 RC is the version where you’re supposed to fix your tsconfig, normalize types, and deal with deprecated flags. Making two giant jumps (5.x → 7.0) will always hurt more than going 5.x → 6.0 → 7.0 with smaller, controlled changes.

What changed since the 6.0 beta

Between the beta and the RC, the TypeScript team mainly focused on aligning behavior with the future 7.0 engine, plus a few targeted tweaks in type checking and DOM typings.

One important change impacts type checking of function expressions passed to generic calls, especially in JSX contexts. When generic functions are invoked with callbacks (for instance in React or other JSX components), the RC tightens inference so it more accurately reflects what 7.0 will do. In practice, you may notice that some calls which relied on implicit inference now require an explicit type argument to keep type checking happy, but you’ll also catch more genuine bugs in existing code.

Import assertion syntax deprecation has been extended as well. TypeScript was already warning against the old import ... assert {...} syntax in static imports due to the ECMAScript proposal shifting to import attributes with with. Now, that deprecation also applies to dynamic imports using import() with assertion objects like import(..., { assert: {...}}). The direction is clear: move to import attributes everywhere.

DOM library types have been refreshed to match current web platform standards, including updates to the Temporal‑related APIs in web contexts. If you’re building browser apps, you benefit from more accurate typings and better editor tooling around these newer APIs.

Less context sensitivity for functions that never use this

TypeScript 6.0 introduces a subtle but very practical change in how it treats functions without an explicit this usage during type inference. Historically, functions with parameters lacking explicit types could be considered “contextually sensitive”, meaning their parameter and this types depended on where they were used. That impacts generic inference and can lead to odd behavior depending on function syntax.

Consider a generic helper that uses a producer and consumer pair:

declare function callIt<T>(obj: {
  produce: (x: number) => T,
  consume: (y: T) => void,
}): void;

// Arrow functions: everything infers fine
callIt({
  produce: (x: number) => x * 2,
  consume: y => y.toFixed(),
});

// Flipped property order still fine with arrows
callIt({
  consume: y => y.toFixed(),
  produce: (x: number) => x * 2,
});

But with method syntax, the previous behavior could be surprising. The same logic, written as methods, might fail when properties are reordered, because TypeScript skipped over “contextually sensitive” functions when inferring generic arguments. Methods implicitly have a this parameter, which bumped them into that sensitive category even if this was never actually referenced.

In 6.0, functions that never read this are now treated as less contextually sensitive. Put differently, if the compiler sees that this is not used at all inside a function, it won’t penalize that function during inference. That means method syntax and arrow syntax are now much more consistent in generic inference scenarios, and the weird “works in one property order, fails in another” behavior goes away in these cases.

This change improves the prioritization of candidates for type inference: functions without a used this become higher‑priority sources of information when inferring type arguments like T. The effect is fewer mysterious unknown types and more stable inference across refactors. This work was contributed by Mateusz Burzyński.

Support for Node #/ subpath imports

Node’s “subpath imports” feature lets packages define internal import aliases via the imports field in package.json. These aliases simplify imports by avoiding deep relative paths. Previously, every subpath key had to have at least one segment after the initial #, which was a small but annoying limitation for people used to bundler conventions like @/....

TypeScript 6.0 now supports subpath imports that begin with #/, aligning with newer Node 20 behavior. This means you can use a configuration like:

{
  "name": "my-package",
  "type": "module",
  "imports": {
    "#": "./dist/index.js",
    "#/*": "./dist/*"
  }
}

With this setup, modules inside the package—and even consumers—can import via a concise #/... prefix instead of long relative paths like ../../utils.js. TypeScript understands this pattern when --moduleResolution is set to node20, nodenext, or bundler, mirroring the semantics of modern Node. This enhancement was implemented by contributor magic-akari.

Using --moduleResolution bundler with --module commonjs

Previously, --moduleResolution bundler could only be used with --module esnext or preserve. With the deprecation of the older node/node10 module resolution mode, many projects needed a migration path that fit their current CommonJS output.

TypeScript 6.0 now allows combining --moduleResolution bundler with --module commonjs. This combination is often a practical stepping stone for codebases still emitting CommonJS but moving toward bundler‑centric workflows or newer resolution logic. From there, projects can plan a longer‑term migration to either:

  • module: "preserve" with moduleResolution: "bundler" for bundled web apps and similar setups, or
  • module: "nodenext" for environments directly targeting modern Node.js.

This change is especially relevant for teams leaving moduleResolution: node behind but not ready to fully embrace ESM output yet. It gives you a phased route instead of a cliff.

The --stableTypeOrdering flag to emulate 7.0 ordering

One of the major architectural upgrades coming in TypeScript 7.0 is parallel type checking. Running multiple checkers in parallel means different parts of the program can be visited in different orders. If type IDs and symbol ordering depend on visitation order, you can end up with non‑deterministic union ordering, property ordering, and even rare differences in diagnostics.

Older TypeScript versions assign internal type IDs based on encounter order. Those IDs are then used to sort things like union types and properties. That’s why seemingly harmless edits—like introducing a new const before an existing function—can flip the order of literal unions in emitted .d.ts files, or change how some types print in your editor.

TypeScript 7.0 switches to a deterministic, content‑based ordering for internal objects. Every type or symbol is sorted according to its structure, not the incidental order of visitation, so the same program will consistently produce the same ordering regardless of parallelism or compilation order. That eliminates the “why did my union suddenly flip?” mystery.

To help you compare behavior between 6.0 and 7.0, the RC introduces --stableTypeOrdering. When this flag is enabled, TypeScript 6.0 adopts the same deterministic type ordering algorithm that 7.0 uses. The result is far fewer diffs in emitted declaration files and more predictable comparisons between 6.x and 7.x outputs.

However, determinism isn’t free. Enabling --stableTypeOrdering can slow down type checking by up to about 25% depending on your codebase. It’s meant as a diagnostic and migration aid, not a forever‑on performance setting.

If you only see type errors when --stableTypeOrdering is turned on, it usually means your previous code was relying on the old quasi‑accidental ordering of types for inference to “just work”. Fixes typically involve making types explicit—adding a type argument to a problematic generic call, or annotating a variable with a specific interface instead of relying entirely on inference for a complex object.

New es2025 target and lib options

TypeScript 6.0 adds an es2025 option for both target and lib. While ES2025 doesn’t introduce new core syntax compared to prior years, it does fold in several standardized APIs that were previously gated behind esnext.

By targeting or including es2025, you gain updated typings for new built‑ins like RegExp.escape, and some APIs are moved out of esnext into es2025. That includes things like Promise.try, iterator helpers, and extra Set methods that have reached full spec maturity. This work was contributed by Kenta Moriuchi.

The broader story is that the default target in 6.0 now tracks the current ECMAScript year, which at the moment effectively lands you on ES2025 when you don’t specify a target. That better matches the reality of evergreen runtimes and modern tooling.

Built‑in types for the Temporal API

The long‑awaited Temporal proposal has reached stage 3 and is expected to replace the infamous Date API for serious date/time work. TypeScript 6.0 now ships first‑class typings for Temporal, so you can start writing Temporal‑based code with full type safety and editor support.

To enable Temporal types, you can use --target esnext or configure your libs explicitly via something like:

{
  "compilerOptions": {
    "lib": 
  }
}

Or you can opt into just the temporal subset with "esnext.temporal" if you want a more granular configuration. Once enabled, you can write code along the lines of:

let yesterday = Temporal.Now.instant().subtract({
  hours: 24,
});

let tomorrow = Temporal.Now.instant().add({
  hours: 24,
});

console.log(`Yesterday: ${yesterday}`);
console.log(`Tomorrow: ${tomorrow}`);

Temporal is already supported in some runtimes and can be polyfilled in others, so these types are genuinely usable today. Documentation is emerging on MDN (with some gaps still being filled). The typings were contributed by GitHub user Renegade334.

Upsert support: Map.getOrInsert and getOrInsertComputed

JavaScript developers have been manually writing “check‑then‑set‑then‑get” patterns on Map for years. A typical pattern checks if a key exists, sets a default if not, and finally returns a value—boilerplate that’s easy to get wrong or repeat everywhere.

The ECMAScript “upsert” proposal (now stage 4) introduces getOrInsert and getOrInsertComputed on Map and WeakMap. TypeScript 6.0 adds type support for these methods in the esnext lib, so you can start writing more declarative upserts right away.

With getOrInsert, a verbose pattern like this:

function processOptions(compilerOptions: Map<string, unknown>) {
  let strictValue: unknown;
  if (compilerOptions.has("strict")) {
    strictValue = compilerOptions.get("strict");
  } else {
    strictValue = true;
    compilerOptions.set("strict", strictValue);
  }
  // ...
}

Can be collapsed to a single line:

function processOptions(compilerOptions: Map<string, unknown>) {
  let strictValue = compilerOptions.getOrInsert("strict", true);
  // ...
}

The companion getOrInsertComputed is ideal for expensive defaults—it takes a callback that’s only invoked if the key is missing. That callback can even receive the key as a parameter, letting you derive the default from the key itself. TypeScript’s typings capture these behaviors precisely, thanks again to contributions from Renegade334.

RegExp.escape and safer regex building

If you’ve ever concatenated user‑supplied strings into a regular expression, you know you’re supposed to escape special characters first—but most codebases either re‑implement escaping in a helper or forget it entirely. The RegExp Escaping proposal (now stage 4) standardizes this with RegExp.escape.

TypeScript 6.0 exposes types for RegExp.escape under the es2025 lib. That means when you target or include ES2025, you can safely write helpers like:

function matchWholeWord(word: string, text: string) {
  const escapedWord = RegExp.escape(word);
  const regex = new RegExp(`\\b${escapedWord}\\b`, "g");
  return text.match(regex);
}

No need for a hand‑rolled escape function anymore, and TypeScript will fully understand and type‑check the API. This addition, like the ES2025 target work, comes from Kenta Moriuchi.

dom lib now includes iteration and async iteration APIs

Historically, TypeScript split DOM iterator support into dom, dom.iterable, and dom.asynciterable. If you wanted to iterate over NodeList or HTMLCollection with for...of, you had to remember to add dom.iterable in your "lib" configuration alongside dom. This was a common source of confusion, especially since all modern browsers support iterables and async iterables on DOM collections.

In TypeScript 6.0, lib.dom.iterable.d.ts and lib.dom.asynciterable.d.ts are effectively merged into lib.dom.d.ts. That means using "dom" alone now gives you iterable and async iterable behavior by default.

You can still mention dom.iterable and dom.asynciterable in your tsconfig, but those files are now empty shells. If your previous config looked like this:

{
  "compilerOptions": {
    "lib": 
  }
}

You can safely simplify to just "dom", and iteration over DOM collections like document.querySelectorAll("div") will still type‑check:

for (const element of document.querySelectorAll("div")) {
  console.log(element.textContent);
}

This is a small but welcome clean‑up that aligns the default DOM lib with the reality of current browsers and removes another gotcha from project setup.

Defaults, deprecations, and breaking changes in TypeScript 6.0

Beyond shiny APIs, 6.0 makes some of the most opinionated changes to TypeScript’s defaults since 1.0. These changes reflect the current JavaScript ecosystem: evergreen runtimes, ESM as a baseline, widespread use of bundlers, and a strong appetite for strict typing and performance.

The team highlights a few broad trends underpinning these decisions: ES5 and truly legacy browsers are nearly gone; AMD/UMD module systems are niche; almost everything ships as modules now; strict typing is the norm; and performance has to stay front‑and‑center, especially as 7.0 brings parallel checking.

As a result, TypeScript 6.0 and 7.0 are being shaped with modern defaults and fewer “legacy escape valves”. For 6.0 RC, you can temporarily silence deprecations by setting "ignoreDeprecations": "6.0" in your tsconfig, but those options won’t exist in 7.0. Some adjustments can be automated with tools like the experimental ts5to6 codemod, which can help rewrite things like baseUrl and rootDir configurations across a project.

Two adjustments many projects will need immediately

While there’s a long deprecation list, two configuration changes are likely to bite the largest number of codebases:

  • Explicitly set the types array (very often plus your test framework). Without this, you’ll lose auto‑included ambient types from @types/*.
  • Explicitly set rootDir (commonly "./src") if you relied on the old “common root inference”. Otherwise your emitted file structure might shift in subtle ways.

Symptoms of missing types include a flood of errors about globals like process, fs, path, or describe being undefined. Symptoms of a changed rootDir are more about output paths unexpectedly gaining an extra src segment (for example dist/src/index.js instead of dist/index.js).

Updated defaults for modern projects

Several compiler options now have new default values that match how most new apps are actually built:

  • strict now defaults to true. Strict mode is no longer an opt‑in luxury; it’s the baseline. If you previously relied on non‑strict behavior, you’ll need to explicitly set "strict": false (though you’ll miss out on a big category of safety checks).
  • module now defaults to esnext, reflecting that ESM is the dominant module format and plays nicest with bundlers and modern Node.
  • target defaults to the current ECMAScript year (effectively ES2025 right now), acknowledging that most deployments target evergreen environments or go through a bundler that can down‑level further when truly necessary.
  • noUncheckedSideEffectImports is now true by default, helping you catch typos or bad paths in imports that are included for side effects only.
  • libReplacement defaults to false, avoiding a host of extra failed module resolutions and watch overhead until a project explicitly opts into specialized lib behaviors.

If any of these new defaults break your build, they can all be overridden explicitly in tsconfig.json. But the intent is that new projects should “just do the right thing” without extra configuration.

rootDir now defaults to the config directory

Previously, if you didn’t specify rootDir, TypeScript tried to infer a common source root based on all non‑declaration files in the program. That made it harder to reason about project boundaries and required scanning many file paths just to decide where emit should be rooted.

In TypeScript 6.0, the default rootDir is simply the directory containing tsconfig.json. The old inference behavior only applies when you run tsc on the command line without any tsconfig at all.

This change means that projects with source files deeper than the config directory should explicitly set rootDir. A common setup would be:

{
  "compilerOptions": {
    // ...
    "rootDir": "./src"
  },
  "include": 
}

If your config references files above the tsconfig location, you’ll also need to extend rootDir accordingly, for example "rootDir": "../src" for shared source directories.

types now defaults to an empty array

This is arguably the most impactful change for real‑world projects. Historically, if you didn’t specify types in compilerOptions, TypeScript would auto‑include everything under node_modules/@types. That was convenient, but also expensive and brittle: modern repos often pull in hundreds of @types packages transitively.

In TypeScript 6.0, types defaults to [], meaning no ambient type packages are loaded automatically. You now opt in explicitly to the global environments you actually need, for example:

{
  "compilerOptions": {
    "types": 
  }
}

This can shave 20-50% off build times in some projects, because the compiler no longer has to crawl through every declaration file under @types. If you truly want the old “load everything” behavior, you can specify:

{
  "compilerOptions": {
    "types": 
  }
}

If you suddenly see errors like “Cannot find name ‘process’” or “Cannot find module ‘fs’”, that’s your cue to add node (and any other test/runtime types you rely on) to your types array.

Deprecated: target: es5 and --downlevelIteration

Targeting ES5 is now deprecated. With every relevant browser shipping ES2015+ for years and Internet Explorer retired, ES5 output is no longer considered worth the complexity inside TypeScript itself. The lowest supported target going forward is ES2015. If you truly must ship ES5, the recommendation is to use an external transpiler (like Babel or a bundler pipeline) either on your TS source or on TS’s output.

The --downlevelIteration flag is also deprecated, because its only meaningful use case was to adjust emit behavior for ES5 targets. In TypeScript 6.0, setting downlevelIteration at all will produce a deprecation error. If you’re on ES2015 or newer, the flag never had any effect anyway.

Deprecated: --moduleResolution node and legacy classic

The node (a.k.a. node10) module resolution mode is deprecated. It modeled Node 10’s behavior but doesn’t match modern Node’s ESM and resolution semantics. Projects should migrate to either nodenext (for direct Node targets) or bundler (for bundler‑driven environments like web apps or Bun).

The original moduleResolution: classic strategy has also been removed. This was TypeScript’s pre‑Node resolution story. Today, all practical scenarios are better served by nodenext or bundler, so classic is gone to reduce complexity and edge cases.

Deprecated: AMD, UMD, SystemJS, and module: none

The following module values are now deprecated and effectively unsupported:

  • amd
  • umd
  • systemjs
  • none

These formats were critical in the pre‑ESM era, when browsers lacked native module support and developers leaned on AMD, UMD, or SystemJS to fill the gap. Today, ESM plus bundlers or import maps handle virtually all real use cases, and “none” was never particularly well‑defined.

If you’re still targeting these legacy module formats, the recommendation is to migrate toward an ESM‑emitting target and rely on a bundler or alternative compiler for final packaging—or remain on TypeScript 5.x until a migration plan is in place. As part of this cleanup, the old amd-module directive is also dropped.

Deprecated: baseUrl

The baseUrl option has long been a source of weird, hard‑to‑debug module resolution behavior. Many projects used it purely as a prefix for paths entries, but TypeScript also treated it as a general lookup root, causing imports like "someModule" to resolve to src/someModule.js unexpectedly when all the developer meant was to support custom aliases like @app/*.

In 6.0, baseUrl is deprecated and will no longer be used as a lookup root. Path mapping hasn’t required baseUrl for quite some time, so the recommended migration is simply to fold the prefix into each paths entry. For example:

// Before
{
  "compilerOptions": {
    "baseUrl": "./src",
    "paths": {
      "@app/*": ,
      "@lib/*": 
    }
  }
}

// After
{
  "compilerOptions": {
    "paths": {
      "@app/*": ,
      "@lib/*": 
    }
  }
}

Only in rare cases where you truly used baseUrl as a generic lookup root would you need to reproduce that behavior with a catch‑all path mapping like:

"paths": {
  "*": ,
  "@app/*": ,
  "@lib/*": 
}

For most teams, simply removing baseUrl and inlining prefixes in paths will be both clearer and safer.

Interop and strictness: esModuleInterop, allowSyntheticDefaultImports, and alwaysStrict

TypeScript 6.0 also locks in what has long been the recommended default interop behavior. You can no longer set esModuleInterop or allowSyntheticDefaultImports to false. These options were originally opt‑in to avoid breaking older projects, but keeping them off today often leads to subtle runtime issues when mixing CommonJS and ESM.

With interop always enabled, certain import patterns may need to be updated. For example:

// Old style with esModuleInterop: false
import * as express from "express";

// New style with modern interop always on
import express from "express";

The alwaysStrict flag also can’t be set to false anymore. TypeScript now assumes JavaScript strict mode semantics across the board, including how reserved words and this behave. If you have truly old code that used reserved words like await or static as identifiers, you’ll need to rename them.

Deprecated: outFile, legacy namespace module keyword, and import asserts

The --outFile option is removed in 6.0. Concatenating multiple inputs into a single JS bundle is a job better handled by Webpack, Rollup, esbuild, Vite, Parcel, or similar tools. TypeScript is doubling down on type checking and declaration emit instead of trying to be a bundler.

The legacy use of module to declare namespaces is now a hard error. Early TypeScript allowed:

module Foo {
  export const bar = 10;
}

The modern, supported syntax uses namespace:

namespace Foo {
  export const bar = 10;
}

This change is necessary to avoid clashing with potential future ECMAScript module blocks. Ambient module declarations like declare module "some-module" { ... } remain fully supported.

Import assertions using asserts are also deprecated, because the underlying proposal evolved into import attributes using with. Code like:

import blob from "./data.json" asserts { type: "json" };

Should be migrated to the new form:

import blob from "./data.json" with { type: "json" };

Deprecated: no-default-lib references and command‑line file lists with tsconfig

The /// <reference no-default-lib="true" /> directive is no longer supported. It was often misunderstood. If you need to exclude the default lib, use --noLib or --libReplacement instead, which more clearly express the intent at the configuration level.

Another long‑standing source of confusion is how tsc treats explicit file arguments when a tsconfig.json is present. Previously, running tsc foo.ts in such a directory would silently ignore the config file. In 6.0, that scenario produces an explicit error:

error TS5112: tsconfig.json is present but will not be loaded if files are specified on commandline. Use '--ignoreConfig' to skip this error.

If you truly want to bypass the config and just compile a single file with defaults, you can now use tsc --ignoreConfig foo.ts to make that intention clear.

Getting ready for TypeScript 7.0

TypeScript 6.0 is feature‑complete and mostly in stabilization mode. From here to general availability, the team expects only critical bug fixes. Nightly builds are published regularly, and the team also ships nightly versions of the upcoming native (Go‑based) TypeScript 7.0 along with a dedicated VS Code extension for experimenting with the new engine.

The roadmap is intentionally tight: 7.0 is expected to follow 6.0 soon after, so the feedback loop on upgrade pain and migration issues will be short. If you’re seeing deprecation warnings in 6.0, the strong recommendation is to tackle them now instead of waiting until 7.0 forces the issue.

The practical workflow for most teams looks like this: upgrade to TypeScript 6.0 RC, fix your types and rootDir, address deprecations (or temporarily gate them behind "ignoreDeprecations": "6.0" while you iterate), and run with --stableTypeOrdering if you need to compare outputs or prepare CI pipelines for 7.0’s deterministic ordering. Once that’s in place, the jump to the Go‑based compiler should feel like a performance upgrade rather than a breaking rewrite.

Taken together, TypeScript 6.0 RC is less about shiny syntax and more about setting the stage: native speed in 7.0, cleaner configs, modern defaults, and standardized APIs like Temporal and ES2025 built‑ins that make day‑to‑day coding easier. If you adopt it now, fix the noisy bits, and lean into the new defaults, you’ll be in a much better spot when the native compiler lands.

novedades de software
Artículo relacionado:
Latest Software Developments and Emerging Tech Trends
Related posts: