- Book: TypeScript in Production
- Also by me: The TypeScript Library — the 5-book collection
- My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools
- Me: xgabriel.com | GitHub
On July 8, 2026, TypeScript 7.0 went GA, with full builds typically 8x to 12x faster than TypeScript 6 on the projects Microsoft benchmarked. The same day, someone opened issue #12518 on typescript-eslint. npm ci refused the upgrade because the published peer range only allows TypeScript >=4.8.4 <6.1.0, and forcing past it ended in TypeError: Cannot read properties of undefined (reading 'Cjs').
A maintainer closed it as "not planned" that day, with one sentence that explains the next few months of TypeScript tooling: "typescript-eslint isn't compatible with TS 7 at this time, because there is no TS 7 API at this time."
That reply is about architecture, and ts-morph and ts-jest run into it too, because they import the compiler API the way typescript-eslint does.
What TypeScript 7.0 actually shipped
TypeScript 7 is the Go port of the compiler. You install it from the plain typescript package and run it as tsc, same as always. The 7.0 announcement is direct about the gap: "While TypeScript 7.0 is here, it does not ship with an API." It adds that Microsoft expects "TypeScript 7.1 to ship with a new (and different) API."
You can see the gap in the package itself. The typescript@7.0.2 manifest on the npm registry has no main field, and its root export "." points at ./lib/version.cjs. The JavaScript API surfaces that do exist sit under subpaths named ./unstable/sync, ./unstable/async, ./unstable/ast and so on. So import * as ts from "typescript" no longer hands you a compiler. It hands you a version module.
The announcement also says Vue, MDX, Astro and Svelte projects need to stay on TypeScript 6.0 for now, because tools like Volar embed the compiler and "can only currently rely on TypeScript 6.0."
How the old tools talked to the compiler
Before the port, the compiler was a JavaScript library. Microsoft's original native-port post calls that codebase "Strada" and the Go effort "Corsa". Strada ran inside your Node process, so any tool could build a ts.Program, grab its type checker and call into it as often as it liked.
typescript-eslint's typed rules are built on that. The custom rules docs describe a parser-services object with the full ts.Program, an esTreeNodeToTSNodeMap from ESTree nodes to TypeScript nodes, and a getTypeAtLocation wrapper. A typed rule looks roughly like the docs' own example:
import { ESLintUtils } from "@typescript-eslint/utils";
import * as ts from "typescript";
// inside a rule's create(context), on a ForOfStatement:
const services = ESLintUtils.getParserServices(context);
const type = services.getTypeAtLocation(node.right);
if (type.symbol.flags & ts.SymbolFlags.Enum) {
// report
}
Every one of those calls is a plain function call into objects on the same heap. A rule can ask for the type of every node in every file, because each ask stays inside one process: no serialization, no round trip. ESLint also expects parsing and rules to be synchronous; Josh Goldberg listed "ESLint does not yet support asynchronous parsers" among the difficulties when he opened #10940 in March 2025.
According to #12521, code at the top level of typescript-estree reads members of ts.Extension the moment the module loads. Against 7.0, ts is the version module, ts.Extension is undefined, and reading .Cjs off it throws before typescript-eslint's own version warning can print. That issue, which asked for a readable message in place of the raw crash, was closed as completed on July 18 by PR #12529.
Why a Go binary can't hand you the same objects
A Go compiler keeps its AST, symbols and types on the Go heap, in its own process. Your Node process can't hold a pointer to a Go struct. Whatever API 7.1 ships, a JavaScript tool will be talking to the compiler across a boundary.

In-process, every type lookup is a function call. Across a process boundary, every lookup is a round trip.
The design work is public. In typescript-go PR #711, merged in April 2025, Andrew Branch scaffolded an IPC-based API: tsgo api starts a server "communicating over STDIO", and the Node client "uses libsyncrpc to block during IPC calls", which keeps the client synchronous for tools like ESLint. Projects, symbols and types live in server-side maps keyed by IDs. The client receives "IDs plus bits of easily serializable info, like name and flags", and calls .dispose() to release them.
The PR is also candid about cost. Branch reported IPC as "very fast on Apple Silicon", with more per-call overhead on Windows, and said overhead starts to matter at hundreds of thousands of back-to-back calls, such as per-identifier symbol lookups. That's the shape of a typed lint run, or of a codemod walking every node.
The ts-morph tracker has a concrete example. In ts-morph #1685, a user profiled a job at roughly 158s on TypeScript 5 in-process and roughly 199s on TypeScript 7 over RPC, and argued the slowdown came from ts-morph's chatty call pattern, not the Go checker. The maintainer closed it as not planned, saying "I'm waiting for TypeScript to stabilize and make certain APIs available so that I don't need to maintain a fork." His progress on TS 7 support lives in #1621, where in September he reported a native backend that is "faster than ts-morph on ts 6."
typescript-eslint's tracker tells a similar story. In a September 29 update on #10940, Goldberg wrote that work-in-progress 7.1 support is "looking more and more stable", most of the time measures slightly faster than the TS 6 baseline, and that the team is not expecting TS 7 to be "significantly better" than TS 6 for this first IPC-based version.
The setup that works today: tsc for builds, tsc6 for tools
Microsoft shipped a bridge for this window. The @typescript/typescript6 package re-exports the 6.0 API and installs one binary, tsc6. The 7.0 announcement recommends npm aliases so that tools importing typescript keep getting 6.0, while tsc runs 7.0:
{
"devDependencies": {
"@typescript/native": "npm:typescript@^7.0.2",
"typescript": "npm:@typescript/typescript6@^6.0.2"
}
}
Read it from the resolver's point of view. Anything that does require("typescript") or import "typescript" (typescript-eslint, ts-jest, Volar) resolves to the 6.0 compatibility package. The tsc binary comes from the @typescript/native alias, which is the real 7.0 package. The compatibility package only installs tsc6, so the two never fight over the name. The announcement notes that with only the second alias, you're left with just tsc6.
ts-jest documents the identical layout in its TypeScript 7 guide, with two extra rules. Don't set ts-jest's compiler option to @typescript/native; ts-jest stops early with a configuration error if you do. And run npx tsc --noEmit separately, locally and in CI, because transform diagnostics "can differ from TypeScript 7."
One way this layout rots: a teammate runs npm i -D typescript@latest, the alias is replaced by 7.0, and lint breaks with the Cjs error again. A one-line guard catches it, because only the 6.0 API reports 6.0:
test "$(node -p "require('typescript').versionMajorMinor")" = "6.0"

Two lanes in one repo: TypeScript 7's tsc for the type check and build, the 6.0 API for every tool that imports typescript.
A CI layout that keeps both
Put the 7.0 checker on the path that benefits from it, and keep the tooling on 6.0 until it moves:
name: ci
on: [push, pull_request]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
cache: npm
- run: npm ci
- name: tooling sees TypeScript 6.0
run: |
v=$(node -p "require('typescript').versionMajorMinor")
test "$v" = "6.0"
- run: npx tsc --version
- run: npx tsc --noEmit
- run: npx eslint .
- run: npm test
npx tsc is the 7.0 checker, the part Microsoft's speed numbers describe. eslint and jest load the 6.0 API through the alias, so their speed is unchanged. The build gets fast; typed linting stays where it was.
Knowing when to drop tsc6
Two things have to be true before the alias can go: TypeScript 7.1 stable is on latest with the new API, and typescript-eslint publishes a peer range that admits it.
The first is weeks away, and slipping. The 7.1 iteration plan lists the beta for October 6, the RC for November 10 and stable for November 24, 2026, but the beta has already slipped. On September 1 Daniel Rosenwasser wrote that the team was "looking at pushing out the beta about 2 weeks further," with RC and final dates to follow, and on October 5 that "we'll be at least a few days late on beta." As of mid-October, the beta tag on npm still points at 6.0.0-beta. Andrew Branch's API status issue from October 9 says the /unstable prefix is gone from export paths, so typescript/unstable/sync becomes typescript/sync. It also says the team believes the exposed APIs "are stable and forward-compatible", while watch, incremental and build-orchestrator APIs won't be stable in 7.1. You can already see it in the typescript@next manifest: 7.1.0-dev.20261015.1 exports ./sync and ./async at the top level.
That gives you a cheap weekly check:
#!/usr/bin/env bash
# scripts/ts7-readiness.sh
set -euo pipefail
if npm view typescript@latest exports --json \
| grep -q '"./sync"'; then
echo "::notice::typescript@latest ships the 7.1 API"
fi
range=$(npm view @typescript-eslint/parser \
peerDependencies.typescript)
echo "typescript-eslint peer range: $range"
if npx -y semver -r "$range" 7.1.0 >/dev/null; then
echo "::warning::typescript-eslint accepts 7.1. Try dropping tsc6."
fi
Run it from a scheduled workflow with cron: "0 7 * * 1". The grep matches "./sync" with its leading quote, so 7.0.2's "./unstable/sync" doesn't trip it. The semver call fails quietly while the range is still <6.1.0, and the annotation shows up in the Actions UI the week it changes.
When both lines fire, delete the typescript alias, rename @typescript/native back to typescript, and run lint and tests on a branch first. Until then, keep the alias and leave the guard step in CI.
If this was useful
Two compilers in one package.json, aliases that the resolver has to read correctly, a CI guard against your own teammates: this is the unglamorous side of shipping TypeScript at work, and it's what TypeScript in Production is about. tsconfig, build tools, monorepos and library authoring, written for the person who owns the toolchain when an upgrade like 7.0 lands.
It's one of five books in The TypeScript Library. If you're new to the language, start with the first two; if you're coming from the JVM or PHP, swap those for your bridge book; if you ship TypeScript for a living, the last one is yours:
- TypeScript Essentials — types, narrowing, modules, async, daily-driver tooling
- The TypeScript Type System — generics, mapped/conditional types, infer, template literals, branded types
- Kotlin and Java to TypeScript — JVM developers moving to TypeScript
- PHP to TypeScript — PHP 8+ developers moving to TypeScript
- TypeScript in Production — tsconfig, build tools, monorepos, library authoring, dual ESM/CJS, JSR
All five books ship in ebook, paperback, and hardcover.

Top comments (0)