This past March we unveiled our efforts to port the TypeScript compiler and toolset to native code.
This port has achieved a 10x speed-up on most projects – not just by using a natively-compiled language (Go), but also through using shared memory parallelism and concurrency where we can benefit.
Since then, we have made several strides towards running on large complex real-world projects.
Today, we are excited to announce broad availability of TypeScript Native Previews.
As of today, you will be able to use npm to get a preview of the native TypeScript compiler.
Additionally, you’ll be able to use a preview version of our editor functionality for VS Code through the Visual Studio Marketplace.
To get the compiler over npm, you can run the following command in your project:
npm install -D @typescript/native-preview
This package provides an executable called tsgo
.
This executable runs similarly to tsc
, the existing executable that the typescript
package makes available:
npx tsgo --project ./src/tsconfig.json
Eventually we will rename tsgo
to tsc
and move it to the typescript
package.
For now, it lives separately for easier testing.
The new executable is still a work in progress, but is suitable to type-check and build many real-world projects.
But we know that a command-line compiler is only half the story.
We’ve heard teams are eager to see what the new editing experience is like too, and so you can now install the new “TypeScript (Native Preview)” extension in Visual Studio Code.
You can easily install it off of the VS Code Extension Marketplace.
Because the extension is still in early stages of development, it defers to the built-in TypeScript extension in VS Code.
For that reason, the extension will need to be enabled even after installation.
You can do this by opening VS Code’s command palette and running the command “TypeScript Native Preview: Enable (Experimental)”.
Alternatively, you can toggle this in your settings UI by configuring “TypeScript > Experimental: Use Tsgo”
or by adding the following line to your JSON settings:
"typescript.experimental.useTsgo": true,
Updates, Release Cadence, and Roadmap
These previews will eventually become TypeScript 7 and will be published nightly so that you can easily try the latest developments on the TypeScript native port effort.
If you use the VS Code extension, you should get automatic updates by default.
If for whatever reason you find any sort of disruption, we encourage you to file an issue and temporarily disable the new language service with the command “TypeScript Native Preview: Disable”:
or by configuring any of the settings mentioned above.
Keep in mind, these native previews are missing lots of functionality that stable versions of TypeScript have today.
That includes command-line functionality like --build
(though individual projects can still be built with tsgo
), --declaration
emit, and certain downlevel emit targets.
Similarly, editor functionality like auto-imports, find-all-references, and rename are still pending implementation.
But we encourage developers to check back frequently, as we’ll be hard at work on these features!
What’s New?
Since our initial announcement, we have made some notable strides in type-checking support, testability, editor support, and APIs.
We would also love to give a brief update of what we’ve accomplished, plus what’s on the horizon.
Note that while the native preview will eventually be called TypeScript 7, we’ve casually been referring to it as “Project Corsa” in the meantime.
We’ve also been more explicit in referring to the codebase that makes up TypeScript 5.8 as our current JS-based codebase, or “Strada”.
So in our updates, you’ll see us differentiate the native and stable versions of TypeScript as “Corsa” (TS7) and “Strada” (TS 5.8).
Fuller Type-Checking Support
The majority of the type-checker has been ported at this time.
That is to say, most projects should see the same errors apart from those affected by some intentional changes (e.g. this change to the ordering of types) and some stale definitions in lib.d.ts
.
If you see any divergences and differences, we encourage you to file an issue to let us know.
It is worth calling out support for two major type-checking features that have been added since our initial announcement: JSX and JavaScript+JSDoc.
JSX Checking Support
When developers first got access to the TypeScript native port, we had to temper expectations.
While type-checking was pretty far along, some constructs were still not fully checked yet.
For many developers, the most notable omission was JSX.
While Corsa was able to parse JSX, it would mostly just pass over JSX expressions when type-checking and note that JSX was not-yet supported.
Since then, we’ve actually added type-checking support for JSX and we can get a better sense of how fast a real JSX project can be built.
As an example codebase, we looked at the codebase for Sentry.
If you run TypeScript 5.8 from the repository root with --extendedDiagnostics --noEmit
, you’ll get something like the following:
$ tsc -p . --noEmit --extendedDiagnostics
Files: 9306
Lines of Library: 43159
Lines of Definitions: 352182
Lines of TypeScript: 1113969
Lines of JavaScript: 1106
Lines of JSON: 304
Lines of Other: 0
Identifiers: 1956007
Symbols: 3563371
Types: 999619
Instantiations: 3675199
Memory used: 3356832K
Assignability cache size: 944737
Identity cache size: 43226
Subtype cache size: 110171
Strict subtype cache size: 430338
I/O Read time: 1.40s
Parse time: 3.48s
ResolveModule time: 1.88s
ResolveTypeReference time: 0.02s
ResolveLibrary time: 0.01s
Program time: 7.78s
Bind time: 1.77s
Check time: 63.26s
printTime time: 0.00s
Emit time: 0.00s
Total time: 72.81s
That’s over a minute to type-check this codebase!
Let’s see how the native port fares with some minimal changes.
$ tsgo -p . --noEmit --extendedDiagnostics
...
Files: 9292
Lines: 1508361
Identifiers: 1954236
Symbols: 5011565
Types: 1689528
Instantiations: 6524885
Memory used: 3892267K
Memory allocs: 61043466
Parse time: 0.712s
Bind time: 0.133s
Check time: 5.882s
Emit time: 0.012s
Total time: 6.761s
There are some discrepancies in results, but Corsa brings build times down from over a minute to just under 7 seconds on the same machine.
Your results may vary, but in general we’ve seen consistent speed-ups of over 10x on this specific example.
You can try introducing an error in existing JSX code and tsgo
will catch it.
You can see more over at our PR to port JSX type-checking, plus some follow-on support for tslib and JSX factory imports.
JavaScript Checking
TypeScript supports parsing and type-checking JavaScript files.
Because valid JavaScript/ECMAScript doesn’t support type-specific syntax like annotations or interface
and type
declarations, TypeScript looks at JSDoc comments in JS source code for type analysis.
The native previews of TypeScript now also support type-checking JS files.
In developing JS type-checking for Corsa, we revisited our early decisions in our implementation.
JavaScript support was built up in a very organic way and, in turn, analyzed very specific patterns that may no longer be (or may never have been) in widespread use.
In order to simplify the new codebase, JavaScript support has been rewritten rather than ported.
As a result, there may be some constructs that may need to be rewritten, or to use a more idiomatic/modern JS style.
If this causes difficulties for your project, we are open to feedback on the issue tracker.
Editor Support & LSP Progress
When we released the Corsa codebase, it included a very rudimentary LSP-based language server.
While most tangible development has been on the compiler itself, we have been iterating on multiple fronts to port our editor functionality in this new system.
Because the Strada codebase communicates with editors via the TSServer format that predates LSP, we are not aiming to do a perfect 1:1 port between the codebases.
This means that porting code often requires more manual porting and has required a bit more up-front thought in how we generate type definitions that conform to LSP.
Gathering errors/diagnostics, go-to-definition, and hover work in very early stages.
Most recently, we have hit another milestone: we have enabled completions!
While auto-imports and other features around completions are not fully ported, this may be enough for many teams in large codebases.
Going forward, our priorities are in porting over our existing language server test suite, along with enabling find-all-references, rename, and signature help.
API Progress
A big challenge as part of this port will be continuity with API consumers of TypeScript.
We have the initial foundation of an API layer that can be leveraged over standard I/O.
This work means that API consumers can communicate with a TypeScript process through IPC regardless of the consuming language.
Since we know that many API consumers will be writing TypeScript and JavaScript code, we also have JavaScript-based clients for interacting with the API.
Because so much TypeScript API usage today is synchronous, we wanted to make it possible to communicate with this process in a synchronous way.
Node.js unfortunately doesn’t provide an easy way to communicate synchronously with a child process, so we developed a native Node.js module in Rust (which should make lots of people happy) called libsyncrpc.
We are still in the early days of API design here, but we are open to thoughts and feedback on the matter.
More details about the current API server are available here.
Known and Notable Differences
As we’ve mentioned, the Corsa compiler and language service may still have some differences from Strada.
There are some differences that you may hit early on in trying out tsgo
and the Native Preview VS Code extension.
Some of those come from eventual TypeScript 6.0 deprecations, like node
/node10
resolution (in favor of node16
, nodenext
, and bundler
).
If you use --moduleResolution node
or --module commonjs
, you may see some errors like:
Cannot find module 'blah' or its corresponding type declarations.
Module '"module"' has no exported member 'Thing'.
You will get consistent errors if you switch your tsconfig.json
settings to use
{
"compilerOptions": {
// ...
"module": "preserve",
"moduleResolution": "bundler",
}
}
or
{
"compilerOptions": {
// ...
"module": "nodenext"
}
}
These can be manually fixed depending on your configuration, though often you can remove the imports and leverage auto-imports to do “the right thing”.
Beyond deprecations, downlevel emit to older targets is limited, and JSX emit only works as far as preserving what you wrote.
Declaration emit is currently not supported either.
--build
mode and language service functionality around project references is still not available, though project dependencies can be built through tsc
, and the native preview language service can often leverage generated .d.ts
files.
What’s Next?
By later this year, we will aim to have a more complete version of our compiler with major features like --build
, along with most language service features for editors.
But we don’t expect you to wait that long!
As TypeScript Native Previews are published nightly, we’ll aim to provide periodic updates on major notable developments.
So give the native previews a shot!
Happy Hacking!
– Daniel Rosenwasser and the TypeScript Team
The post Announcing TypeScript Native Previews appeared first on TypeScript.