Skip typechecking; only emit (support `--transpileOnly` in `tsc`, re-open of #4176)
See original GitHub issueSearch Terms
isolatedModules, incremental build slow, allowJs, transpileOnly. #4176
Suggestion
Support a compilation mode where files are only transpiled without typechecking. This can greatly improve compilation speed. Similar to the transpileOnly
flag in ts-node
and ts-loader
.
Use Cases
At @taskworld, we are trying to migrate our project to TypeScript. We have 1400 source files.
As we try to get our .js
files to be processed and transpiled by tsc
, setting "allowJs": true
makes tsc
take a painfully long time (40 seconds) to complete, even in --watch
mode. tsc --diagnostics
shows that lots of time is spent in typechecking phase.
I have checked these issues:
-
https://github.com/Microsoft/TypeScript/issues/7808 No resolution.
-
https://github.com/Microsoft/TypeScript/issues/10018 Irrelevant — compiler speed has since been improved but that did not solve our problem.
-
https://github.com/Microsoft/TypeScript/issues/21221 I used
tsc --listFiles
to check the files included in the compilation unit. Looks normal. In that issue, they solved the problem by renaming all.js
to.ts
, which we can’t do yet without causing errors all over the place, due to our JS files are still CommonJS modules, which is only recognized when using JS files. I try to migrate with minimal code changes, so I avoid mass-converting CommonJS to ES modules for now. -
https://github.com/Microsoft/TypeScript/issues/13538 People at Google solved this problem by separating their project into libraries and creates their own build system (based on Bazel). Since I tried to add the tooling with minimal code change, we don’t want to “split our code base into multiple units” right now.
-
https://github.com/Microsoft/TypeScript/issues/10157 OP solved this problem by using
transpileOnly
mode ints-loader
, which uses the “single-module transpilation mode” (thets.transpileModule
API). However, we aren’t usingwebpack
and we’re directly usingtsc
to compile.ts
files to.js
files. -
https://github.com/Microsoft/TypeScript/issues/22953 No resolution.
I tried to profile the tsc
process, and found that a lot of time is spent in resolveCallSignature
.
If we can skip the type-checking process, this compilation phase should be faster.
This seems to be supported in both ts-node
and ts-loader
, since TypeScript provides the “single-module transpilation mode” (the ts.transpileModule
API). So, I looked for a way to do it using tsc
. Turns out, it is not available, and we have to somehow use the ts.transpileModule
API directly.
https://github.com/Microsoft/TypeScript/issues/4176#issuecomment-128505179
A fancier solution would be to use the compiler’s
transpile
API directly.
https://github.com/Microsoft/TypeScript/issues/13538#issuecomment-273695261
If you are willing to get your entire project compiling under the
isolatedModules
switch, then you can safely wire up your build system to do a simple emit of only changed files, which should be practically instant, followed by a re-typecheck.
Examples
All evidence so far suggests that we have to build our own tooling which behaves like babel -d build-dir source-dir
(e.g. compiles each file separately) but for TypeScript. And so we implemented our own workaround:
// tsc-fast.js
const args = require('yargs')
.options({
force: {
alias: 'f',
description: 'Recompiles even if output file is newer.',
type: 'boolean',
},
watch: {
alias: 'w',
description: 'Watches for file changes.',
type: 'boolean',
},
})
.strict()
.help()
.parse()
const watch = require('gulp-watch')
const ts = require('gulp-typescript')
const newer = require('gulp-newer')
const tsProject = ts.createProject('tsconfig.json', {
isolatedModules: true,
})
const vfs = require('vinyl-fs')
const debug = require('gulp-debug')
const sourcemaps = require('gulp-sourcemaps')
function main() {
let compiling = false
let pending = false
function compile() {
if (compiling) {
pending = true
return
}
compiling = true
const rawInput = tsProject.src()
const input = args.force
? rawInput
: rawInput.pipe(
newer({
dest: 'dist',
map: f => f.replace(/\.ts$/, '.js'),
})
)
input
.pipe(sourcemaps.init())
.pipe(tsProject())
.pipe(sourcemaps.write('.'))
.on('error', () => {
/* Ignore compiler errors */
})
.pipe(debug({ title: 'tsc:' }))
.pipe(vfs.dest('dist'))
.on('end', () => {
compiling = false
if (pending) {
pending = false
compile()
}
})
}
compile()
if (args.watch) {
watch(['app/**/*.js', 'app/**/*.ts', '!app/vpc-admin/front/**/*'], compile)
}
}
main()
To typecheck in separate step, we simply run tsc --noEmit
in a separate CI job. Also, VS Code takes care of typechecking in the editor, so we already get instant feedback for type errors.
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, etc.)
- This feature would agree with the rest of TypeScript’s Design Goals.
Issue Analytics
- State:
- Created 5 years ago
- Reactions:205
- Comments:38 (7 by maintainers)
Top GitHub Comments
This should be included in tsc as
--transpileOnly
flagOn Mon, Jun 24, 2019, 07:50 Ben Lu notifications@github.com wrote:
Another reason this would be great is compiling in the production environment. In production we don’t install dev dependencies, so running
tsc
on production results in an error because it can’t find type definitions. But our code is checked before it reaches to production, so in production, we know its fine type wise.