Skip to content

Skip typechecking; only emit (support --transpileOnly in tsc, re-open of #4176) #29651

@dtinth

Description

@dtinth
Contributor

Search 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:

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.

#4176 (comment)

A fancier solution would be to use the compiler's transpile API directly.

#13538 (comment)

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.

Activity

changed the title [-]Skip typechecking; only emit (--transpileOnly, re-open of #4176)[/-] [+]Skip typechecking; only emit (support `--transpileOnly` in `tsc`, re-open of #4176)[/+] on Jan 30, 2019
dtinth

dtinth commented on Feb 27, 2019

@dtinth
ContributorAuthor

I noticed the questions in #30117 and would like to give some answers for context:

Is this just piping errors to /dev/null ?

If this has to do with .on('error', () => { /* Ignore compiler errors */ }), this only prevents the script from crashing Node.js when a compiler error occurs. gulp-typescript outputs error messages to console already.

Why is this user's project so slow?

I realize that I haven’t put the diagnostic information in the issue.

This is the diagnostic info for incremental compilation.

Files:           2099
Lines:         967290
Nodes:        2998862
Identifiers:   983959
Symbols:      1266945
Types:         211713
Memory used: 1473430K
I/O read:       0.01s
I/O write:      0.98s
Parse time:     0.57s
Bind time:      0.02s
Check time:    16.95s
Emit time:      7.99s
Total time:    25.54s

Here’s the corresponding CPU profile:

image

As you can see, about 80% of “Check time” is spent in checkCallExpression function. So maybe there might be some slow stuff going on there — I may have to do a more thorough investigation.

Is this a global project or a module project?

This is a module project, but allowJs is on and most of JS files are CommonJS modules.

(cc: @RyanCavanaugh)

added
Awaiting More FeedbackThis means we'd like to hear from more people who would be helped by this feature
and removed on Apr 1, 2019
RyanCavanaugh

RyanCavanaugh commented on Apr 1, 2019

@RyanCavanaugh
Member

Discussed. We felt like we had a good handle on this and then totally deadlocked on the question of whether this mode would imply --noResolve:

  • If the idea is simply to transpile ASAP, then not doing module resolution is a huge time savings
  • ... but this means you have no straightforward path to migrate an existing project to transpileOnly
  • Some healthy mix of users wants to a) transpile everything under some folder vs b) do a fast no-check build to get a faster build output

Separately, the problem that this mode only "works" if --isolatedModules is on was a point against it.

Ultimately a tool that just calls ts.transpileModule on some defined set of files is quite trivial to write, and would have clearer semantics than what we might provide out of the box.

We were curious if things got better for you with --incremental.

biels

biels commented on May 7, 2019

@biels

I would add another reason c) treat all ts errors as warnings and still emit the results.
This is available on browser projects when using webpack with ts-loader using transpileOnly: true.

The advantage of this workflow is that it enables faster prototyping when combined with hot reload / run on save. You can see type errors alongside runtime errors and get the best of both worlds. You still care about ts errors as they show up in your IDE and build logs but they do not necessarily break your build.

This may not be desired on all projects or environments but that's where the flag comes in. I would suggest to add a transpileOnly boolean flag to the compliler so that it supports this behavior natively and can be used for projects that do not target the browser.

ayroblu

ayroblu commented on Jun 24, 2019

@ayroblu

tsc can emit even if there are errors (how we quickly fix bugs by trying things)
Looking at this cause --incremental doesn't re emit files that haven't changed, nor does it remove files that have been deleted (which is why I want this)
ts-node already has a fast flag, so it'd be nice to have this here

ayroblu

ayroblu commented on Jun 24, 2019

@ayroblu

Discovered typescript-transpile-only guess this solves my problems:
https://github.com/cspotcode/typescript-transpile-only

biels

biels commented on Jun 24, 2019

@biels
canvural

canvural commented on Sep 27, 2019

@canvural

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.

sheerun

sheerun commented on Oct 3, 2019

@sheerun

My use case is quickly transiling typescript snippet so I can use it in non-typescript project. Currently I need to use online playground for this...

63 remaining items

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    SuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @ahamid@martinjlowm@dtinth@sheerun@y-nk

        Issue actions

          Skip typechecking; only emit (support `--transpileOnly` in `tsc`, re-open of #4176) · Issue #29651 · microsoft/TypeScript