, 6 min read, typescript, compilers, tooling
TypeScript 7 is fast. Plan for two compilers anyway
I timed TypeScript 6 against 7 on this site's repo: about 6x, once I stopped timing npx by accident. The new defaults and the missing API matter more.
, 6 min read, typescript, compilers, tooling
I timed TypeScript 6 against 7 on this site's repo: about 6x, once I stopped timing npx by accident. The new defaults and the missing API matter more.
"A 10x faster native port of TypeScript" is the first thing the TypeScript 7.0 announcement (opens in new tab) tells you. 7.0 shipped on July 8th, and it's the Go port of the compiler, finally stable. The body of the post is a bit more careful: full builds "typically" get 8x to 12x faster, and its own benchmark table runs from 7.7x on tldraw to 11.9x on the VS Code codebase.
I didn't want to repeat a number I hadn't seen, so I timed both compilers on this site's repo. The first result I got was wrong, and the way it was wrong turned out to be the most useful part.
Two projects from this repo, both checked with --noEmit: the app (tsconfig.app.json, 252 of my own files plus about 1,200 declaration files from lib and node_modules) and the smaller admin project. TypeScript 6 came from the @typescript/typescript6 package, which gives you a tsc6 binary (the package is 6.0.2, the compiler inside it is 6.0.3, more on that below). TypeScript 7 was typescript@7.0.2. I called both binaries directly with node, did one warm-up run, then five timed runs each on an 18-core Mac, with wall time and peak memory from /usr/bin/time -l. Medians:
--checkers 8: 0.39s, 934 MB--checkers 1: 0.62s, 467 MBThat's about 6x on both projects. Neither compiler reported a single error on either one.
Six times faster is great. It's also not 10x, and my first attempt said it was. I had run TypeScript 6 through npx -y -p @typescript/typescript6@6.0.2 tsc6 and TypeScript 7 straight out of node_modules, so every TS 6 run also paid for npx starting up. On this machine npx takes 1.2 to 2 seconds just to print tsc6 -v, against 0.03s for calling the same binary directly. Through npx the TS 6 check took 3.2 to 4.0 seconds, which made the ratio look like 9x or more. Once both compilers were launched the same way, it came down to 6.2x.1
So yes, measure your own repo. But measure both compilers the same way, or you'll end up benchmarking your package manager.
The announcement puts the speed down to "native code speed, shared memory multithreading, and a number of new optimizations", and says parsing, type-checking and emitting now run in parallel. Type-checking is split across 4 workers by default, and --checkers changes that.
The checker numbers above are the interesting part. Going from 4 to 8 checkers did nothing for my repo (0.37s to 0.39s) and cost about 200 MB of memory. Dropping to 1 checker made it slower (0.62s) and cut memory by about a third. In Microsoft's table, every project gets around 20 to 30 percent faster at --checkers 8, even tldraw, which takes 1.46s at the default. My whole check finishes in a quarter of that. My guess, and it is only a guess, is that at this size there just isn't enough checking left to split up, and the fixed costs of starting a process and reading 1,400 files take up more of the total. The post does warn that more checkers "will typically come at the cost of increased memory usage", and that part I can confirm.
The 7.0 post lists the new defaults: strict is on, types is [] instead of every @types package it can find, rootDir is ./, and module is esnext. It also turns a set of options into hard errors, including baseUrl, moduleResolution set to node10 or classic, target: es5, and the amd, umd and systemjs module formats. Microsoft thinks the rootDir and types changes "may be the most 'surprising'".
What I didn't realise until I tried it is that most of this already landed in TypeScript 6.0, back in March. The 6.0 announcement (opens in new tab) made strict default to true and types default to [], and deprecated baseUrl, node10 resolution and target: es5. The difference is that 6.0 let you set "ignoreDeprecations": "6.0" and carry on. 7.0 takes that away.
To see it for myself, I made a tiny project with an old-style config (moduleResolution: "node10", baseUrl: ".", no strict, no types) and one file with an untyped function that reads process.version:
$ tsc6 -p tsconfig.json
tsconfig.json(4,25): error TS5107: Option 'moduleResolution=node10' is deprecated and will stop functioning in TypeScript 7.0. Specify compilerOption '"ignoreDeprecations": "6.0"' to silence this error.
Visit https://aka.ms/ts6 for migration information.
tsconfig.json(5,5): error TS5101: Option 'baseUrl' is deprecated and will stop functioning in TypeScript 7.0. Specify compilerOption '"ignoreDeprecations": "6.0"' to silence this error.
Visit https://aka.ms/ts6 for migration information.
$ tsc -p tsconfig.json
tsconfig.json(4,25): error TS5108: Option 'moduleResolution=node10' has been removed. Please remove it from your configuration.
tsconfig.json(5,5): error TS5102: Option 'baseUrl' has been removed. Please remove it from your configuration.
Use '"paths": {"*": ["./*"]}' instead.With ignoreDeprecations added, TypeScript 6 got past the config and reported two implicit any parameters (that's strict on by default) and "Cannot find name 'process'" (that's types: []). TypeScript 7 ignored the setting and printed the same two "has been removed" errors.
So how much the config side hurts depends on where you're coming from. If you're on 6.0 and it's quiet without ignoreDeprecations, 7.0 is close to free here. If you skipped 6.0, you get both releases' worth of changes at once. If you silenced the warnings, that bill is now due. My repo had no errors because its tsconfig files already say everything out loud: strict: true, an explicit types list, moduleResolution: "bundler", and no baseUrl.
This is the part that keeps TypeScript 6 around. The announcement doesn't hedge: "While TypeScript 7.0 is here, it does not ship with an API." It expects 7.1 to bring "a new (and different) API", and until then tools that need programmatic access, with typescript-eslint as its example, should keep running on 6.0.
I opened the 7.0.2 package.json to see what that means in practice. The main entry point exports two version strings and nothing else. The rest is under typescript/unstable/* (sync, async, ast and friends). So there is something in there, but it's labelled unstable, and it isn't the API your linter is calling.
Same cause, different symptom. These tools embed TypeScript through its API, and the post spells it out: "Projects using Vue, MDX, Astro, Svelte, and others will need to continue using TypeScript 6.0 for now." Angular is split: TypeScript 7 for fast checking at the command line, 6 for the editor, because template checking probably won't use 7 yet. If the new language server gives you trouble, VS Code has a "Disable TypeScript 7 Language Server" command that puts you back on 6.0.
If your repo has a single .vue or .svelte file, this applies to you the day you install TypeScript 7.
The announcement's recipe for keeping both is a pair of npm aliases:
{
"devDependencies": {
"@typescript/native": "npm:typescript@^7.0.2",
"typescript": "npm:@typescript/typescript6@^6.0.2"
}
}I installed exactly that in an empty directory and checked what each name resolves to. npx tsc -v printed 7.0.2, tsc6 -v printed 6.0.3, and a script that did require("typescript") got version 6.0.3 with createProgram on it. So your build gets the fast compiler while anything that imports typescript keeps getting the API it expects.
One small trap: the 6.0.2 in @typescript/typescript6@6.0.2 is the wrapper's version. It depends on typescript@^6, and the compiler it actually ran for me was 6.0.3. Run tsc6 -v before you put a version number in a bug report.
Install both
put `@typescript/typescript6` and `typescript@7` in devDependencies or a scratch directory, not behind npx
Launch them the same way
`node path/to/tsc6` and `node path/to/tsc`, same project, same flags
Repeat
one throwaway run, then five, and take the median
Check versions
`tsc6 -v` and `tsc -v`, since package names and compiler versions don't always match
The speed is real. On a small repo I got 6x, not 10x, and that's still the difference between a check I wait for and one I don't notice. I'd stop expecting much from --checkers below a certain size, though, and I'd keep an eye on memory if I raised it.
What I'd actually budget time for is everything else: config that 6.0 was only warning about, whatever in the toolchain needs the API, and any embedded-language files. For most repos that means tsc and tsc6 living side by side until 7.1 ships an API and the ecosystem moves onto it. That's fine, as long as you know which one is answering.
npx -y -p @typescript/typescript6@6.0.2 tsc6 -p tsconfig.app.json --noEmit took 4.03s, 3.23s and 3.29s, against a 0.37s median for TypeScript 7. That's where a "9x" comes from on a repo that's really 6x. ↩
Comments