Description
Suggestion
🔍 Search Terms
transpileModule
skip typechecking
no typechecking
Semi-related issues found:
- Skip typechecking; only emit (support --transpileOnly in tsc, re-open of Skip typechecking; only emit #4176) Skip typechecking; only emit (support
--transpileOnly
intsc
, re-open of #4176) #29651 - [FEATURE REQUEST] Please supply a minified version with only transpilation functionality [FEATURE REQUEST] Please supply a minified version with only transpilation functionality #9245
- Allow transformers to run without type-checking Allow transformers to run without type-checking #41986
✅ Viability Checklist
My suggestion meets these guidelines:
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- This feature would agree with the rest of TypeScript's Design Goals.
⭐ Suggestion
Modify the transpileModule
API to mostly do the following:
- Parses a source string
- Runs the corresponding transforms to get to the target ES version
- Emits a resulting string with a sourceMap string
The API itself would be unchanged but would be guaranteed to not hit the type-checker if a certain set of compatible compiler options is set. (deoptimizing options TBD)
Achieving this without modifying TypeScript is currently not possible with the current TypeScript API: there is currently no way to just apply the transforms without running the checker as a Program
has to be created in the process.
Alternatively, if changing transpileModule
is not possible we would be interested in exposing more minimal API primitives so we could build up this API ourselves:
- A public
Printer
API that can issue sourcemaps - A way to run transforms on TS ASTs without the need to build up a
Program
(and run the checker...)
📃 Motivating Example
In our build system, for certain workflows, we introduced a new build mode that runs transpileModule
instead of compiling and type-checking the full project. transpileModule
is run on a per-file basis as we are reading files into our build stream, meaning there are no inter-file ordering dependencies.
Approximative `transpileModule` API usage
const result = ts.transpileModule(tsFileStr, {
compilerOptions, // <- with an ESNext target
reportDiagnostics: true,
moduleName: tsFilePath,
fileName: tsFilePath,
});
if (result.diagnostics) {
// report syntax errors
if (result.diagnostics.length > 0) {
return;
}
}
const jsFilePath = tsFilePath.replace(/\.tsx?$/, ".js");
const jsFileStr = result.outputText;
const sourceMapPath = jsFilePath + ".map";
const sourceMapStr = result.sourceMapText;
// ...
With this new mode, we managed to get 4x faster than an equivalent fully-typechecked build! This is an amazing speedup that is appreciated for running common workflows (while VSCode continues to type-check things in the editor).
We are still trying to get faster: most of the time is now spent type-checking inside transpileModule
, as seen in the CPU flamecharts.
We researched using alternative build tools such as swc or esbuild but using their transpile API alone in our setup is not significantly better than using transpileModule
! (I could do a writeup on this if anyone is interested in that research) However, those other tools do tend to get really fast when they are run standalone and handle file I/O but unfortunately this does not fit our use case.
At this point, a pure js-based transform might be the better option and simply using the TypeScript compiler infrastructure would make sense since all the tools to create a relatively fast module type-stripping system should be there but are not exposed through the current API.
💻 Use Cases
Our goal is to be able to keep transpileModule
and have it go faster if the options don't require checking:
// ✅ doesn't require checking, fast path:
ts.transpileModule(tsFileStr, {
compilerOptions: {
target: "esnext",
},
});
// ❌ requires checking, slow path:
ts.transpileModule("class c { @x f: string };", {
compilerOptions: {
target: "esnext",
emitDecoratorMetadata: true,
noEmitHelpers: true,
},
});
// emits with some type system info:
class c { f }
__decorate([], x, __metadata("design:type", String), c.prototype, "f", void 0);
By avoiding type-checking code paths when possible (deoptimizing options TBD), we could speed things up significantly again for the users of transpileModule
.
In the future, it could also be the base to then introduce a tsc
flag for fast unchecked builds as suggested in #29651.This would permit tsc
to be significantly faster if opted into with the flag.
Deoptimizing options
This is a temporary list that we intend to build up over time:
emitDecoratorMetadata
- ... TBD ...
If you can provide some guidance on whether this would be a good thing to change/fix, along with any implementation constraints/tips, I would be happy to attempt an implementation.