TypeScript 7.0 RC – the new Go compiler and a migration that does not have to hurt
TypeScript 7.0 introduces a new Go compiler. The migration is mostly about cleaning up tsconfig.json, removing deprecated options, and testing hard.
It happened! On June 18, 2026, Microsoft released the Release Candidate for TypeScript 7.0. This time, however, the most important change is not new syntax, new operators, or even smarter type inference. This time, the changes go all the way down to the foundations.
The previous compiler was written in TypeScript, compiled to JavaScript, and executed in Node.js. In TypeScript 7.0, its code has been rewritten in Go. Thanks to native execution, shared memory, and multi-threaded processing, the new compiler is — according to Microsoft’s reports — up to around ten times faster than TS 6.0. Importantly, this is not a complete rewrite of the type system from scratch, but a methodical port of the existing implementation. The type-checking logic is supposed to remain structurally backward-compatible.
In short: it is the same TypeScript, the same tsconfig.json, the same tsc command — just with a completely new engine under the hood. But does that mean we can simply install the new version and forget about the problem of a slow compiler? Unfortunately, not quite.
TypeScript 7.0 - new compiler, old command
For quite some time, the native version of TypeScript was published as a separate @typescript/native-preview package, and we invoked its compiler with the tsgo command.
Starting with the RC, we no longer have to remember that name. TypeScript 7.0 once again uses the standard typescript package and exposes the regular tsc command.
npm install -D typescript@rc
npx tsc --version
npx tsc --noEmitThis matters in everyday work — we do not have to rewrite any scripts, change CI configuration, or explain to the whole team that tsgo is now the proper compiler.
From the user’s perspective, everything looks familiar. The biggest differences only start under the hood.
And under the hood?
The new compiler performs many things in parallel, including:
parsing files,
type-checking,
generating code,
building projects that use project references.
While parsing and emitting code can be split across files relatively easily, type-checking is more complicated, because information from one file may depend on types from many other parts of the codebase.
TypeScript 7.0 solves this problem with a fixed number of independent type-checker workers. By default it starts four of them, but we can change that number with the --checkers flag.
npx tsc --checkers 6More workers can speed up a large project, but they also increase memory usage. On a small CI runner, setting this value too high may therefore produce the opposite result.
For monorepositories using project references, there is also a --builders flag, which defines how many projects may be built at the same time.
npx tsc --build --builders 2 --checkers 4In this case, at most two parallel builders may run, and each of them can use up to four checkers. In the extreme case, that gives us eight checkers working at the same time.
There is also a single-threaded mode available:
npx tsc --singleThreadedIt can be useful for diagnosing issues, comparing performance, or working in an environment with very limited memory.
Is this really the end of slow tsc?
According to Microsoft, TS 7.0 is often around ten times faster than TS 6.0. The team also reports major improvements in --watch mode and in the editor experience. The new language server is based on the Language Server Protocol, which makes it easier to integrate with code editors.
The important thing to stress here is that it is true “often”, not “always”.
The biggest difference should be visible in large projects where the compiler processes thousands of files and can actually distribute the work across many CPU cores. In small applications that take one second to check, even a tenfold speedup will not meaningfully change the developer experience.
On top of that, tsc is only one part of the entire application build process. If most of the time is spent bundling, testing, generating code, or doing other expensive tasks, the new compiler will not automatically speed all of those things up.
So we can say that TS 7.0 has a real chance to end the era of slow tsc. It does not mean that every pipeline will suddenly become ten times faster, though. First, we need to measure whether this is actually our bottleneck.
time npx tsc --noEmitIt is best to run such measurements several times, both locally and in CI. In very fast projects, a single result can easily be distorted by the operating system cache, CPU load, or other running processes.
TypeScript 6.0 and the migration to TypeScript 7.0
The safest migration to TypeScript 7.0 does not start by installing that version. It starts by cleaning up the previous one.
Microsoft explicitly describes TS 6.0 as a bridge release between the existing implementation and the new compiler. It is the last major version based on the old codebase written in TypeScript and JavaScript. Most of the new defaults, deprecations, and breaking changes were introduced precisely to prepare projects for TS 7.0.
The first step should therefore be installing stable TypeScript 6 and getting the project to compile cleanly.
npm install -D typescript@^6
npx tsc --noEmitIf errors about deprecated options appear, we can temporarily add:
{
"compilerOptions": {
"ignoreDeprecations": "6.0"
}
}ignoreDeprecations does not fix the configuration. It only postpones the migration. TypeScript 7.0 does not implement the options removed in TS 6.0, so this kind of setting will not save the project after the actual upgrade.
A good definition of a project ready for TS 7.0 is therefore a project that:
compiles with TypeScript 6.0,
does not use
ignoreDeprecations,passes the check with
stableTypeOrdering,explicitly defines the most important options in
tsconfig.json.
Explicit tsconfig.json
In older projects, we can often find a very short tsconfig.json that relies on compiler defaults.
For example:
{
"compilerOptions": {
"outDir": "./dist",
"baseUrl": "./src",
"paths": {
"@/*": ["*"]
}
},
"include": ["src"]
}This file does not tell us any of these things:
whether the project runs in
strictmode,which JavaScript version we compile to,
which module system we generate,
how imports should be resolved,
which global types should be loaded,
which directory is the actual source directory.
In TypeScript 5, some of these decisions were made for us. In TS 6 and 7, however, several defaults changed, which can matter for many legacy projects:
strictdefaults totrue,moduledefaults toesnext,targetpoints to the newest stable ECMAScript version beforeesnext— in TS 6, that ises2025,noUncheckedSideEffectImportsdefaults totrue,rootDirpoints to the directory containingtsconfig.json,typesdefaults to an empty array.
It is safer not to rely on these values and to write the most important decisions directly in the configuration.
An example project using a bundler could look like this:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"noUncheckedSideEffectImports": true,
"rootDir": "./src",
"outDir": "./dist",
"types": ["node", "jest"],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"]
}This is not a universal tsconfig.json that should be blindly copied into every repository. The values of target, types, and the way code is emitted must match the environment in which a given application actually runs.
The most important thing is that the configuration clearly describes its assumptions.
strict
Since TS 6.0, the strict option is enabled by default.
If the project already used:
{
"compilerOptions": {
"strict": true
}
}then nothing changes.
If, however, an older project relied on the previous default value of false, after the upgrade it may suddenly receive a large number of errors. In that case, we can explicitly preserve the old behavior:
{
"compilerOptions": {
"strict": false
}
}This is not the final ideal, but it lets us separate the compiler migration from a separate project of introducing full strict mode. First we stabilize the configuration, and only then can we gradually tighten the typing.
module and moduleResolution
For an application executed directly in Node.js, the right pair will most often be:
{
"compilerOptions": {
"module": "NodeNext",
"moduleResolution": "NodeNext"
}
}For an application processed by a bundler:
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "Bundler"
}
}The old moduleResolution: "node" — also known as node10 — describes the old module-resolution behavior from the Node.js 10 era. In TS 6 it was marked as deprecated, while TS 7 does not support it at all. Microsoft recommends "nodenext" for applications executed in Node.js and "bundler" for applications processed by a bundler or Bun.
target
The default target is now a moving value. In TypeScript 6 it points to es2025, but in later compiler versions it may point to a newer standard.
This is convenient for new projects that only support current environments, but less convenient for reproducible builds.
That is why it is worth defining target explicitly:
{
"compilerOptions": {
"target": "ES2022"
}
}This does not mean that ES2022 is always the best choice. We need to choose the standard supported by the oldest environments where the application actually has to run. The important part is that this is a conscious decision, not an accidental side effect of upgrading TypeScript.
types now defaults to an empty array
In TypeScript 5 and earlier, the compiler automatically scanned available node_modules/@types directories and added the packages found there to the global type scope. Thanks to that, a project could use names such as:
process,Buffer,describe,it,
although the relevant packages were not explicitly listed in tsconfig.json.
In TS 6, the default value of types is []. That means the required global declarations should be listed manually:
{
"compilerOptions": {
"types": ["node", "jest"]
}
}Names are provided without the @types/ prefix. So @types/node is written as node, and @types/jest as jest. If necessary, the old behavior can be restored with:
{
"compilerOptions": {
"types": ["*"]
}
}This is not recommended as the target solution, though. An explicit list makes compilation more predictable and does not pull random global types from dependencies into the project. According to Microsoft’s observations, simply setting types appropriately improved compilation time in some projects by 20-50%.
The new default rootDir
Previously, TypeScript tried to infer the common source directory of the project.
For the structure:
project/
├── src/
│ └── index.ts
├── dist/
└── tsconfig.jsonthe compiler could treat src as the root directory and generate:
dist/index.jsSince TS 6, the default rootDir is the directory containing tsconfig.json. In the same project, the result may therefore look like this:
dist/src/index.js
If we want to preserve the previous structure, we should also add explicitly:
{
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist"
},
"include": ["src"]
}This change simplifies the compiler’s work, because it no longer has to analyze all files first to determine the common source directory. It also helps the language server determine more quickly which project a given file may belong to.
The end of baseUrl
baseUrl is one of the most subtle changes in this migration.
In many projects, this option was used only as a shared prefix for entries in paths:
{
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"@app/*": ["app/*"],
"@lib/*": ["lib/*"]
}
}
}The problem is that baseUrl was not only a prefix — it also became an additional place where TypeScript tried to resolve all imports without a relative path.
An import:
import * as helpers from "helpers.js";could therefore be found as:
src/helpers.js
although the configuration author only wanted to create aliases beginning with @app/ and @lib/.
In TS 7, baseUrl has not been reimplemented. Instead, we should remove the option and write the full prefixes directly in paths:
{
"compilerOptions": {
"paths": {
"@app/*": ["./src/app/*"],
"@lib/*": ["./src/lib/*"]
}
}
}If the project really used baseUrl as a place to resolve all imports, we can preserve that behavior with a mapping:
{
"compilerOptions": {
"paths": {
"*": ["./src/*"],
"@app/*": ["./src/app/*"],
"@lib/*": ["./src/lib/*"]
}
}
}That is a much rarer case, though. Most often, it is enough to remove baseUrl and fill in the paths.
Other deprecated options and constructs
target: "es5"
TypeScript 7 no longer supports generating ES5 code. The lowest target is now ES2015.
If an application still has to run in an environment that requires ES5, TypeScript should generate newer code that is then processed by an external toolchain.
Together with ES5, there is no longer much point in using:
{
"compilerOptions": {
"downlevelIteration": true
}
}That option was specifically about emitting older code.
outFile
outFile was already removed in TypeScript 6.0.
Its job was to combine many input files into a single output file. Today, that work is done by bundlers, which have far more capabilities around optimization, code splitting, and asset handling.
TypeScript should focus primarily on type-checking and generating declarations. Bundling belongs to a bundler — webpack, Vite, etc.
Legacy module systems
TS 7 does not support the values:
amd
umd
systemjs
nonefor the module option.
Modern projects should use ESM, CommonJS through an appropriate Node configuration, or code that is passed further to a bundler.
esModuleInterop: false
The options:
{
"compilerOptions": {
"esModuleInterop": false,
"allowSyntheticDefaultImports": false
}
}can no longer be set to false. Safer interoperability between CommonJS and ESM is always enabled.
This may require fixing some older imports:
// Before
import * as express from "express";
// Now
import express from "express";module used as a namespace
The old syntax:
module Application {
export const version = "1.0";
}should be replaced with:
namespace Application {
export const version = "1.0";
}This does not apply to external module declarations:
declare module "some-package" {
export function run(): void;
}That syntax remains valid.
Import assertions
The older import assertions syntax:
import data from "./data.json" assert { type: "json" };has been replaced by import attributes:
import data from "./data.json" with { type: "json" };It is worth searching for such imports across the whole repository, because they may also appear in dynamic import() calls.
Watch out for tsc file.ts
Older CI scripts or developer commands may run TypeScript like this:
tsc src/index.tsIf there is a tsconfig.json in the current directory, TypeScript 6 will report an error. Passing files directly on the command line used to make the project configuration completely ignored, which often led to surprising results.
Normally, we should run the whole project:
tsc --project tsconfig.jsonIf we really want to ignore the configuration, we have to make that explicit:
tsc --ignoreConfig src/index.tsThat way, the intention behind the command is clear.
Automatic migration with ts5to6
Some of the changes around baseUrl and rootDir can be performed automatically by the ts5to6 tool. If we are not afraid of automated tools performing tricky migrations, it is worth trying.
npx @andrewbranch/ts5to6 --fixBaseUrl .
npx @andrewbranch/ts5to6 --fixRootDir .For a configuration with a different name, we can pass a specific file:
npx @andrewbranch/ts5to6 --fixBaseUrl ./tsconfig.app.json
npx @andrewbranch/ts5to6 --fixRootDir ./tsconfig.app.jsonThe tool can walk through references and look for related configurations using extends; it is especially useful in monorepositories, where manually changing a dozen files can easily lead to mistakes.
It is still an experimental tool, though. After running it, it is worth checking whether our application still transpiles correctly and behaves as expected. A codemod should do the mechanical part of the work, but it is up to us to confirm that the result matches expectations.
stableTypeOrdering – compatibility test for TS 6 and TS 7
One of the trickier changes is about type ordering.
In TypeScript 6, internal type identifiers were assigned in the order in which the compiler encountered particular constructs. This could affect the order of union members generated in declaration files.
For example:
export function getValue(condition: boolean) {
return condition ? 100 : 500;
}could generate:
export declare function getValue(condition: boolean): 100 | 500;After adding an unrelated declaration earlier in the same file, the order could change to:
export declare function getValue(condition: boolean): 500 | 100;For TypeScript, both types are equivalent — but the difference creates noise in snapshots and .d.ts comparisons. In TypeScript 7, type-checking runs in parallel. If each worker assigned identifiers based on its own processing order, the result could become nondeterministic. That is why TS 7 uses stable sorting based on the content of types and symbols.
TypeScript 6 provides a flag that lets us simulate this behavior earlier:
npx tsc --noEmit --stableTypeOrdering
It is also worth running declaration generation:
npx tsc \
--declaration \
--emitDeclarationOnly \
--stableTypeOrdering \
--outDir .tmp/types-ts6Then we can compare the errors and .d.ts files with the TypeScript 7 result.
The flag is not meant to be used permanently in TS 6 — depending on the project, it may slow type-checking down by around 25%. Its main purpose is to detect migration differences. In TS 7, stable ordering is always enabled and cannot be disabled.
If enabling the flag reveals an inference error, explicitly providing the type often helps:
// Before
someFunction(value);
// After migration
someFunction<ExpectedType>(value);or adding an annotation to the variable:
const value: ExpectedType = createValue();TypeScript 6 and 7 side by side
TypeScript 7.0 RC is ready to be used as a CLI compiler, but its stable programmatic API is expected no earlier than TS 7.1.
This matters for tools that do not only invoke tsc, but import the typescript package directly and use the Compiler API. This includes some linting integrations, code generators, and custom analysis scripts.
Microsoft provides the @typescript/typescript6 package, which lets us install both versions at the same time:
{
"devDependencies": {
"typescript": "npm:@typescript/typescript6@^6.0.0",
"typescript-7": "npm:typescript@rc"
}
}The compatibility package exposes the tsc6 command, while TypeScript 7 still uses tsc.
npx tsc6 --noEmit
npx tsc --noEmitThanks to this, tools that require the TypeScript 6 API can continue importing typescript, while the project itself can be checked in parallel with the new compiler.
Migration step by step
The safest process may look like this.
Step 1: save a baseline
Run tests, the full build, and declaration generation. Also record the compilation time:
time npx tsc --noEmit
Without a baseline, we will not know whether the new compiler actually improved the situation or whether we are dealing with little more than a placebo effect.
Step 2: move to TypeScript 6
npm install -D typescript@^6
npx tsc --noEmitAt this stage, we do not install TS 7 yet.
Step 3: set explicit values
Check above all:
strict
target
module
moduleResolution
types
rootDir
noUncheckedSideEffectImportsNot all of them have to change, but we need to know which values are actually declared.
Step 4: remove deprecated options
Search for:
baseUrl
target: es5
downlevelIteration
moduleResolution: node
moduleResolution: node10
moduleResolution: classic
outFile
module: amd
module: umd
module: systemjs
esModuleInterop: false
allowSyntheticDefaultImports: false
alwaysStrict: falseUse ts5to6 if needed.
Step 5: remove ignoreDeprecations
The project should compile without this option. Otherwise, some of the problems are still only hiding under the carpet.
Step 6: run stableTypeOrdering
npx tsc --noEmit --stableTypeOrderingAlso compare generated declarations, especially if you publish a library.
Step 7: run TypeScript 7.0 RC
npm install -D typescript@rc
npx tsc --noEmitIn larger projects, it is safer to start by doing this in a separate CI job that does not block the main pipeline.
Step 8: compare the results
Check:
type errors,
.d.tsfiles,generated JavaScript,
tests,
linter integrations,
scripts using the Compiler API,
editor behavior.
Step 9: tune performance only now
Start with the defaults. Then test different --checkers values, and in a monorepo, --builders.
Do not automatically assume that the highest number gives the best result. Faster type-checking paid for with excessive memory usage may instead make CI worse.
Final thoughts
TypeScript 7.0 is one of the biggest infrastructure changes in the history of the language. The code we write still looks familiar, but the mechanism responsible for analyzing it has moved to a completely new foundation.
The compiler written in Go may genuinely end years of complaining about slow tsc, especially in large repositories and monorepos. The biggest migration risk, however, does not come from Go or from parallel type-checking, but from years of relying on implicit values in tsconfig.json.
That is why the safest path to TypeScript 7.0 goes through a cleaned-up TypeScript 6.0 setup: deprecated options removed, explicit types, rootDir, moduleResolution, and target, and finally a test with stableTypeOrdering. Once a project passes that stage without errors, replacing the old compiler with the new one stops being a major migration and becomes a matter of running a single command.