DEV Community

Cover image for tsc Is Now Written in Go. Your tsconfig Is Probably Wrong
Gabriel Anhaia
Gabriel Anhaia

Posted on

tsc Is Now Written in Go. Your tsconfig Is Probably Wrong


A team upgrades TypeScript from 5.8 to 6.0 on a Friday because the Renovate PR has been open for two weeks. CI finishes. Half their tsconfig.json files across the monorepo now emit deprecation warnings. The frontend package will not type-check until somebody adds "types": ["node", "vite/client"] to its config, because the old default that pulled in everything under node_modules/@types is gone. Two services that emitted AMD modules for a legacy bundler refuse to compile at all.

Then somebody installs @typescript/native-preview to try the Go rewrite. The tsgo binary does not start on the package that uses --outFile. It runs cleanly on the others and finishes in roughly a tenth of the time. The team ends the week with a Slack thread, three open RFCs, and the realisation that their tsconfig.json was written for a compiler that no longer ships.

This is a normal week in April 2026. TypeScript 6.0 (March 2026) was the cleanup release. TypeScript 7.0 beta (April 21 2026) is the Go port — Microsoft positions it as roughly 10x faster across most projects in A 10x Faster TypeScript. Both releases changed what a working tsconfig.json looks like. The migration is short. The catch is that most configs on npm right now are wrong for at least one of the two compilers.

What TypeScript 6.0 actually changed

Microsoft kept the language behaviour mostly stable in 6.0 and used the release as a defaults reset. The four changes that hit existing projects hardest:

strict now defaults to true. If you relied on the old false default, you will see new errors immediately. A lot of older codebases did, especially ones partway through a migration from JavaScript. To keep the old behaviour, write it down explicitly: "strict": false. The compiler will not assume it for you any more.

module now defaults to esnext. ESM is the assumed format. Modern projects already had this set; projects that relied on the old default get ESM emit they did not ask for.

target floats with the current ECMAScript spec. In TS 6.0 that means es2025. The old behaviour of pinning to a much older target is gone. If you emit for a runtime that does not support the current spec, set the target explicitly.

types defaults to []. The one most teams trip on. The old default enumerated every package under node_modules/@types and pulled them all into the type-check graph — a real chunk of why large projects were slow. The new default loads nothing. If your code uses process, Buffer, or any Node global, you now need:

{
  "compilerOptions": {
    "types": ["node"]
  }
}
Enter fullscreen mode Exit fullscreen mode

Vite client types, Vitest globals, or any test runner ambient types go in the array too. The error you see when this is missing is Cannot find name 'process' — clear once you know the rule, confusing if you do not.

Smaller changes on top of those: noUncheckedSideEffectImports defaults to true (catches typos like import "./styls.css"). libReplacement defaults to false for faster resolution. rootDir defaults to the directory containing tsconfig.json rather than being inferred from source files.

The hard removals matter more than the new defaults, because defaults are recoverable in one line and removals are not.

Module emit for amd, umd, systemjs, and none is gone. TypeScript will not produce those formats any more. Move that responsibility to a bundler — most projects already had one for everything else; the AMD output was vestigial.

--outFile is removed. It concatenated input files into one output. Bundlers do this faster and with more control.

--moduleResolution classic is removed. Switch to nodenext or bundler. They replace node (now also deprecated as node10) cleanly.

--target es5 is deprecated, with es2015 as the new minimum. --downlevelIteration goes with it (it only ever did anything with ES5 emit).

esModuleInterop and allowSyntheticDefaultImports cannot be set to false any more. Remove the lines.

For the deprecations, TS 6.0 ships an escape hatch: "ignoreDeprecations": "6.0" in your config silences the warnings. You can buy yourself one release of breathing room that way. TypeScript 7.0 will not honour the flag — the deprecated options are gone.

What TypeScript 7.0 beta brings (the Go rewrite)

The April 21 2026 announcement is the moment the Go port stopped being a research preview. The numbers Microsoft published in A 10x Faster TypeScript:

Project Lines of code tsc (current) tsgo (Go) Speedup
VS Code 1,505,000 77.8s 7.5s 10.4x
Playwright 356,000 11.1s 1.1s 10.1x
TypeORM 270,000 17.5s 1.3s 13.5x
date-fns 104,000 6.5s 0.7s 9.5x

Source: Microsoft, A 10x Faster TypeScript, TypeScript DevBlog.

For VS Code's editor experience specifically, project load time drops from about 9.6 seconds to about 1.2 seconds, an 8x improvement on time-to-language-service. Memory usage is roughly half the JavaScript implementation, with the caveat that Microsoft has not tuned for it yet.

The TS 7.0 beta announcement frames this as "often about 10 times faster" and notes the Go codebase was a methodical port rather than a clean-room rewrite. Type-checking logic is structurally identical to TS 6.0. Behaviour parity is the design goal; a faster runtime is the only outcome that should change.

The binary is tsgo, installed via:

npm install -D @typescript/native-preview@beta
Enter fullscreen mode Exit fullscreen mode

It runs side by side with tsc. The recommended pattern during the beta is to keep TS 6.0 as your emit compiler and use tsgo as a checker, pinning each from npm:

npm install -D typescript@^6.0.0
npm install -D @typescript/native-preview@beta
Enter fullscreen mode Exit fullscreen mode

You now have tsc (TS 6.0) and tsgo (TS 7.0 beta) on disk, callable independently from package.json scripts.

The beta has known parity gaps. The flags worth knowing today:

  • --checkers <n> (default 4) — parallel type-check workers. The Go port uses real OS threads, which is the second piece of the speedup story alongside native code.
  • --builders <n> — parallel project-reference builders.
  • --singleThreaded — useful when debugging a difference between tsc and tsgo.

What Microsoft lists as in-flight for the beta: a more efficient --watch, declaration emit from .js files, "find file references" in editors, and granular import-cleanup commands. The beta is good enough for tsgo --noEmit in CI today on most projects. Not yet the place to do production emit if you depend on the flagged-as-missing features.

The five lines in your tsconfig.json that are probably wrong

Across the codebases I have looked at since 6.0 shipped, the same handful of lines need touching. If you change these five things, your config is back to compiling cleanly on 6.0 and ready for tsgo.

1. Add types explicitly

Before:

{
  "compilerOptions": {
    "module": "esnext",
    "target": "es2022"
  }
}
Enter fullscreen mode Exit fullscreen mode

After:

{
  "compilerOptions": {
    "module": "esnext",
    "target": "es2022",
    "types": ["node"]
  }
}
Enter fullscreen mode Exit fullscreen mode

Add the ambient type packages your code actually consumes. Common entries: node, vite/client, vitest/globals, jest, bun, @cloudflare/workers-types. Anything you imported transitively under the old default and never thought about.

2. Replace moduleResolution: classic and node

Before:

{
  "compilerOptions": {
    "moduleResolution": "node"
  }
}
Enter fullscreen mode Exit fullscreen mode

After (libraries and Node apps):

{
  "compilerOptions": {
    "module": "nodenext",
    "moduleResolution": "nodenext"
  }
}
Enter fullscreen mode Exit fullscreen mode

After (apps that ship through a bundler):

{
  "compilerOptions": {
    "module": "esnext",
    "moduleResolution": "bundler"
  }
}
Enter fullscreen mode Exit fullscreen mode

Pick nodenext when Node will resolve your code at runtime. Pick bundler when Vite, esbuild, Rollup, or Webpack does the resolving for you. The old node is deprecated and classic is gone.

3. Drop outFile, module: amd|umd|systemjs, and the dead siblings

If your config still has any of this, it does not compile on TS 6.0 at all:

{
  "compilerOptions": {
    "outFile": "./dist/bundle.js",
    "module": "amd"
  }
}
Enter fullscreen mode Exit fullscreen mode

Delete both options and run a bundler. If you genuinely need a UMD or AMD output for a legacy consumer, that is the bundler's job now: esbuild, Rollup, and Webpack all produce them.

4. Stop disabling interop

Two flags that used to be opt-out are now mandatory. The legacy form:

// Won't compile on TS 6.0:
{
  "compilerOptions": {
    "esModuleInterop": false,
    "allowSyntheticDefaultImports": false
  }
}
Enter fullscreen mode Exit fullscreen mode

Delete those two lines and your config is correct:

// Compiles on TS 6.0:
{
  "compilerOptions": {}
}
Enter fullscreen mode Exit fullscreen mode

Both options can no longer be set to false. If you had a reason for the old config, that reason is now compiler-managed.

5. Either accept strict: true or set strict: false explicitly

Before (relying on the old default):

{
  "compilerOptions": {}
}
Enter fullscreen mode Exit fullscreen mode

After (one of these two):

{
  "compilerOptions": {
    "strict": true
  }
}
Enter fullscreen mode Exit fullscreen mode
{
  "compilerOptions": {
    "strict": false
  }
}
Enter fullscreen mode Exit fullscreen mode

The default flipped. If you do nothing, you get strict mode. Write down the choice you actually want so future readers do not have to guess.

The recovery flag for any deprecation noise during the transition:

{
  "compilerOptions": {
    "ignoreDeprecations": "6.0"
  }
}
Enter fullscreen mode Exit fullscreen mode

Use it as a temporary mute, not as a fix. TS 7.0 ignores it.

The two-binaries strategy

Until tsgo reaches feature parity with tsc, the workable production setup is to run both. tsgo does the cheap, frequent work; tsc does the work that needs the features Go has not caught up with yet.

In package.json:

{
  "scripts": {
    "typecheck": "tsgo --noEmit",
    "build": "tsc -b",
    "build:watch": "tsc -b --watch"
  },
  "devDependencies": {
    "typescript": "^6.0.0",
    "@typescript/native-preview": "beta"
  }
}
Enter fullscreen mode Exit fullscreen mode

pnpm typecheck runs against tsgo for the speed; CI gets the 10x benefit on the step that runs on every PR. pnpm build stays on tsc for emit, declaration files, project-reference watching, and anything else the beta has not closed yet.

The CI pipeline that benefits most:

- name: Typecheck (tsgo)
  run: pnpm typecheck

- name: Build (tsc)
  run: pnpm build
Enter fullscreen mode Exit fullscreen mode

On VS Code's 1.5M-LOC project, Microsoft measured 77.8 seconds dropping to 7.5 seconds. On a project of any size, that order-of-magnitude shift is the difference between PR feedback in a minute and PR feedback in ten.

The risk is bounded. tsgo is structurally identical to tsc for type-checking, but "structurally identical" is not "byte identical". You will find edge cases. Fall back to tsc --noEmit for any package where tsgo reports something you cannot reconcile, and file the diff upstream. Pin tsgo to a specific beta version. Do not track latest.

Monorepo project references

Project references are where the migration gets the most awkward, because the speedup is biggest there and the parity gap is widest there.

A typical monorepo tsconfig.json at the root looks like this:

{
  "files": [],
  "references": [
    { "path": "packages/core" },
    { "path": "packages/api" },
    { "path": "packages/web" },
    { "path": "packages/cli" }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Each package config sets composite: true and emits its own declaration files. The build uses tsc -b to walk the graph in dependency order.

tsgo understands the references and parallelises the build with --builders. The dependency graph still bottlenecks the linear path: a downstream package cannot start type-checking until its upstream is done. Anything that can run in parallel does.

What works in the beta:

  • tsgo --noEmit against the root tsconfig type-checks the whole graph, in parallel where the dependencies allow.
  • tsgo --build (alias -b) walks the graph the same way tsc -b does.
  • Per-package tsgo runs are fine and fast.

What I would not commit to in the beta yet:

  • --watch on a large reference graph. Microsoft has flagged this as still being optimised. Use tsc -b --watch for now.
  • Declaration emit from .js source. If any of your packages have JSDoc-typed JavaScript and emit .d.ts from it, stay on tsc for that package's build.

The pragmatic monorepo recipe is the same as the single-project one: tsgo for --noEmit on every package and on the root, tsc -b for the actual build. CI runs tsgo --noEmit first because it is the cheap gate; if it passes, tsc -b does the emit. If the type-check is going to fail, you find out in seconds instead of minutes.

What to do this week

  1. Run tsc --noEmit against your repo on TS 6.0. Fix the deprecation warnings. The list above covers most of them. The fix is almost always one of: add types: [...], change moduleResolution, delete a removed option, set strict explicitly. If the warnings are noisy and you need a pause, set "ignoreDeprecations": "6.0" and come back to it.

  2. Install @typescript/native-preview@beta next to your existing typescript package. Add a typecheck script that runs tsgo --noEmit. Run it locally and in CI. Compare what it reports against tsc. For most projects the diff is empty; for the rest, the diff is small and worth filing upstream.

  3. Pin both versions. typescript to a specific 6.x patch. @typescript/native-preview to a specific beta tag. Auto-upgrades on either right now will land you on a moving target, and the moving target is the whole point of the beta.

Close the five lines this week and you will be ready for tsgo whenever it ships as tsc.


If this was useful

tsconfig.json compounds. Five lines defensible in 2018 are wrong by now, and the cost shows up as slow CI, surprise emit, brittle module resolution, type-check times that gate every PR. TypeScript in Production is the book for that layer.

The full TypeScript Library, five books:

  • TypeScript Essentials — daily-driver TS across Node, Bun, Deno, and the browser: Amazon
  • The TypeScript Type System — generics, mapped/conditional types, template literals, branded types: Amazon
  • Kotlin and Java to TypeScript — bridge for JVM developers: Amazon
  • PHP to TypeScript — bridge for modern PHP 8+ developers: Amazon
  • TypeScript in Production — tsconfig, build, monorepos, library authoring: Amazon

Books 1 and 2 are the core path. Books 3 and 4 substitute for readers coming from JVM or PHP. Book 5 is for shipping TS at work.

The TypeScript Library — five-book collection on TypeScript essentials, types, migration, and production tooling

Top comments (0)