ivogabe / gulp-typescript

A TypeScript compiler for gulp with incremental compilation support.
MIT License
841 stars 129 forks source link

Very slow incremental compilation (2.6x slower) compared to tsc #549

Open esprehn opened 6 years ago

esprehn commented 6 years ago

With "gulp-typescript": "3.2.3",

Expected behavior:

Should be as fast as tsc -w

Synchronizing program
Files:           289
Lines:        121510
Nodes:        524773
Identifiers:  184342
Symbols:      244975
Types:         51810
Memory used: 263018K
I/O read:      0.03s
I/O write:     0.09s
Parse time:    2.55s
Bind time:     0.63s
Check time:    4.13s
Emit time:     2.57s
Total time:    9.87s
9:21:12 PM - Compilation complete. Watching for file changes.

9:21:20 PM - File change detected. Starting incremental compilation...
Synchronizing program
...
Total time:    3.34s
9:21:23 PM - Compilation complete. Watching for file changes.
9:21:29 PM - File change detected. Starting incremental compilation...
Synchronizing program
...
Total time:    2.81s

Actual behavior:

Very slow, gulp does:

[21:17:49] Finished 'compileSources' after 9.31 s
... change a file ...
[21:18:04] Finished 'compileSources' after 7.4 s

Your gulpfile:

const ts = require('gulp-typescript');
const tsProject = ts.createProject('tsconfig.json');
gulp.task('compileSources', () =>
  tsProject.src()
      .pipe(tsProject())
      .js.pipe(gulp.dest('artifacts'))
);

tsconfig.json

Include your tsconfig, if related to this issue.

{
    "compilerOptions": {
        "target": "es6",
        "module": "commonjs",
        "allowJs": true,
        "jsx": "react",
        "moduleResolution": "node",
        "importHelpers": true,
        "outDir": "artifacts",
        "rootDir": ".",
        "rootDirs": [
            "src",
            "node_modules/include-common"
        ],
        "sourceMap": true,
        "noImplicitAny": true,
        "suppressImplicitAnyIndexErrors": true,
        "strictNullChecks": true,
        "lib": ["dom", "es2017", "es2016", "es2015"],
        "baseUrl": ".",
        "paths": {
            "escodegen": ["node_modules/escodegen"],
            "esprima": ["node_modules/esprima"],
            "esquery": ["node_modules/esquery"],
            "firebase": ["node_modules/firebase"],
            "firebase-admin": ["node_modules/firebase-admin"],
            "firebase-safekey": ["node_modules/firebase-safekey"],
            "include-common/*": ["node_modules/include-common/*"]
        },
        "skipLibCheck": true,
        "types": [
            "color",
            "d3-format",
            "d3-scale",
            "esprima",
            "estree",
            "react",
            "react-native",
            "react-native-fs",
            "react-navigation",
            "react-redux",
            "redux-actions"
        ]
    },
    "compileOnSave": false,
    "include": [
        "ourTypings/**/*.d.ts",
        "src/**/*.ts",
        "src/**/*.tsx",
        "node_modules/include-common/**/*.ts",
        "node_modules/include-common/**/*.tsx"
    ],
    "exclude": [
        "android",
        "artifacts",
        "gruntfile.js",
        "node_modules/include-common/artifacts",
        "node_modules/include-common/node_modules"
    ]
}
ivogabe commented 6 years ago

I'm afraid I cannot do anything without seeing the project. I do see that you have a complicated configuration with rootDir, rootDirs, paths, include and exclude, that might have some influence on the performance. Could you maybe check whether the performance problems are caused by tsProject.src()?

esprehn commented 6 years ago

Sure! How would I do that?

ivogabe commented 6 years ago

You could create a task which only invokes tsProject.src() and measure its running time.

gulp.task('foo', () =>
  tsProject.src()
);
ivogabe commented 6 years ago

@esprehn Have you tried to measure the performance of tsProject.src()?

GregRos commented 6 years ago

I have a simpler example. I haven't really seen a difference between the first time gulp-typescript compiles and the next.

Runtimes

tsc -w

Initial: 9 seconds Change in 1 file: <2s

gulp-typescript

Initial: ~9 seconds Change in 1 file: ~9 seconds

The snippet you posted

Instantaneous

Details

Versions

node: v8.2.1 gulp: 3.9.1 typescript: 2.7.1 gulp-typescript: 4.0.1, also tried with 3.* and got similar results

gulpfile.js

const appProject = ts.createProject('tsconfig.json', {

});

gulp.task('build', function() {
    let appStream = appProject.src()
        .pipe(appProject())
        .pipe(gulp.dest("dist/"));

    return appStream;
});

gulp.task('watch', ['build'], function() {
    gulp.watch(["src/**/*.ts"], ["build"]);
});

I tried changing the src() to various glob patterns, but nothing changed anything.

tsconfig.json

{
  "compilerOptions": {
    "module": "commonjs",
    "moduleResolution": "node",
    "noImplicitAny": false,
    "noImplicitThis": true,
    "preserveConstEnums": true,
    "removeComments": true,
    "sourceMap": true,
    "skipLibCheck": true,
    "target": "ES6",
    "lib": [
      "dom",
      "es2015"
    ],
    "allowUnreachableCode": true,
    "outDir": "dist",
    "baseUrl": "."
  },
  "include": [
    "src/**/*",
    "src/*"
  ],
  "exclude": [
    "dist",
    "node_modules",
    "src/apps/guest-subsystem/**/*"
  ]
}

tsc -w --diagnostics

Initial

15:02:29 - Starting compilation in watch mode...

Synchronizing program
Files:          1020
Lines:        106017
Nodes:        482795
Identifiers:  157375
Symbols:      151459
Types:         19056
Memory used: 233559K
I/O read:      0.26s
I/O write:     0.46s
Parse time:    3.71s
Bind time:     0.90s
Check time:    2.91s
Emit time:     1.77s
Total time:    9.29s
15:02:39 - Compilation complete. Watching for file changes.

After change in one file

15:03:03 - File change detected. Starting incremental compilation...

Synchronizing program
Files:          1019
Lines:        106005
Nodes:        482730
Identifiers:  157353
Symbols:       98221
Types:           288
Memory used: 241529K
I/O read:      0.00s
I/O write:     0.01s
Parse time:    2.03s
Bind time:     0.04s
Check time:    0.04s
Emit time:     0.06s
Total time:    2.18s
15:03:06 - Compilation complete. Watching for file changes.
esprehn commented 6 years ago

@ivogabe Sorry I didn't get a chance to look at this. We switched all our projects to calling tsc directly instead since it was so much faster. I'll see if I can try using gulp-typescript again later this week,

const { execSync, spawn } = require('child_process');

gulp.task('compileSources', () => new Promise((resolve, reject) => {
  const tsc = spawn('tsc', [], { stdio: 'inherit' });
  tsc.on('exit', (code) => {
    if (!code) {
      resolve();
    } else {
      reject();
    }
  });
}));

gulp.task('watchSources', () => {
  spawn('tsc', ['-w'], { stdio: 'inherit' });
});
ivogabe commented 6 years ago

@GregRos could you measure the running time of appProject.src()? You can just modify your build task to:

gulp.task('build', function() {
    return  appProject.src();
});

@esprehn I understand that. Could you add a simple task to measure the performance of .src()? That way you don't have to bring back full compilation using gulp-typescript. You can use something like this:

const tsProject = ts.createProject('tsconfig.json');
gulp.task('foo', () =>
  tsProject.src()
);
esprehn commented 6 years ago

That completes in 100ms for me. Testing it looks like tsc 2.7.2 takes the same amount of time (if not longer) on the first compile, and then is nearly instant for later compiles. It's a lot more than 2.6x now, close to 100x faster (0.07 seconds vs 6.6 seconds). gulp-typescript seems to always take 6-8 seconds.

Looking at the diagnostics difference I think tsc is not reloading all the types for each incremental compile (57770 for first compile, 238 for second, see below) while gulp-typescript is.

gulpfile.js

const gulp = require('gulp');
const { spawn } = require('child_process');
const ts = require('gulp-typescript');

const tsProject = ts.createProject('./tsconfig.json');

gulp.task('compileWithGulpTypeScript', () => {
  const t1 = Date.now();
  const src = tsProject.src();
  console.log('.src()', Date.now() - t1);
  const t2 = Date.now();
  const transformer = tsProject();
  console.log('transformer', Date.now() - t2);
  return src
    .pipe(transformer)
    .pipe(gulp.dest('artifacts'));
});

gulp.task('compileWithGulpTypeScript-watch', () => {
  gulp.watch(['src/**/*.ts'], ['compileWithGulpTypeScript'])
});

gulp.task('watchSources', () => {
    // tsc --watch clears the shell, so we use a gross hack around it.
    // https://github.com/Microsoft/TypeScript/issues/21295#issuecomment-367571059
    spawn(`tsc -w | awk '{ gsub(/\\033c/, "") system("")}1'`, {
        stdio: 'inherit',
        shell: true,
    });
});

Output from gulp-typescript

$ gulp compileWithGulpTypeScript-watch
[13:00:00] Using gulpfile /Checkouts/grasshopper/packages/app/gulpfile.js
[13:00:00] Starting 'compileWithGulpTypeScript-watch'...
[13:00:00] Finished 'compileWithGulpTypeScript-watch' after 79 ms
[13:00:59] Starting 'compileWithGulpTypeScript'...
.src() 135
transformer 0
[13:01:09] Finished 'compileWithGulpTypeScript' after 9.27 s
[13:01:11] Starting 'compileWithGulpTypeScript'...
.src() 100
transformer 0
[13:01:18] Finished 'compileWithGulpTypeScript' after 7.28 s
[13:01:21] Starting 'compileWithGulpTypeScript'...
.src() 87
transformer 0
[13:01:28] Finished 'compileWithGulpTypeScript' after 6.71 s
[13:01:46] Starting 'compileWithGulpTypeScript'...
.src() 101
transformer 0
[13:01:53] Finished 'compileWithGulpTypeScript' after 6.64 s

Output from tsc

$ tsc -v
Version 2.7.2
$ gulp watchSources
[13:02:41] Using gulpfile /Checkouts/grasshopper/packages/app/gulpfile.js
[13:02:41] Starting 'watchSources'...
[13:02:41] Finished 'watchSources' after 3.8 ms
13:02:42 - Starting compilation in watch mode...
13:02:52 - Compilation complete. Watching for file changes.
13:02:59 - File change detected. Starting incremental compilation...
13:02:59 - Compilation complete. Watching for file changes.
13:03:05 - File change detected. Starting incremental compilation...
13:03:05 - Compilation complete. Watching for file changes.
13:03:14 - File change detected. Starting incremental compilation...
13:03:14 - Compilation complete. Watching for file changes.
13:03:17 - File change detected. Starting incremental compilation...
13:03:17 - Compilation complete. Watching for file changes.

tsc with --diagnostics turned on

$ gulp watchSources
[13:08:24] Using gulpfile /Checkouts/grasshopper/packages/app/gulpfile.js
[13:08:24] Starting 'watchSources'...
[13:08:24] Finished 'watchSources' after 4.03 ms
13:08:24 - Starting compilation in watch mode...

Synchronizing program
Files:           367
Lines:        161504
Nodes:        628499
Identifiers:  217633
Symbols:      276782
Types:         57770
Memory used: 296777K
I/O read:      0.05s
I/O write:     0.18s
Parse time:    2.68s
Bind time:     0.86s
Check time:    4.35s
Emit time:     2.93s
Total time:   10.82s
13:08:35 - Compilation complete. Watching for file changes.

13:08:43 - File change detected. Starting incremental compilation...

Synchronizing program
Files:           367
Lines:        161504
Nodes:        628499
Identifiers:  217633
Symbols:      129934
Types:           238
Memory used: 238081K
I/O read:      0.00s
I/O write:     0.00s
Parse time:    0.01s
Bind time:     0.00s
Check time:    0.04s
Emit time:     0.01s
Total time:    0.07s
13:08:43 - Compilation complete. Watching for file changes.
GregRos commented 6 years ago

@ivogabe By the following part of my message:

The snippet you posted Instantaneous

I meant that the .src thing runs instantaneously pretty much.

marcghorayeb commented 6 years ago

Hello - we're seeing this as well. The incremental builds are an order of magnitude slower than what tsc gives. Any news regarding this?

GregRos commented 6 years ago

@marcghorayeb I've pretty much lost interest in this project personally and switched to invoking tsc and gulp separately.

marcghorayeb commented 6 years ago

@GregRos That's what I'd like to avoid ;) On a side note, how do you handle launching both at the same time?

GregRos commented 6 years ago

@marcghorayeb I don't. I keep tsc running and rerun gulp whenever I want to publish/start the application.

ivogabe commented 6 years ago

I haven't been able to replicate this yet. Could you try this with the latest alpha release? You can install it with npm install gulp-typescript@5.0.0-alpha.1.

marcghorayeb commented 6 years ago

@ivogabe I tried the alpha.1, unfortunately the results are the same (even worse in most cases). Previous build during watch (when saving a file, without modifying its content) was taking ~10 seconds, it's taking up twice as long with the alpha.

goloveychuk commented 6 years ago

Maybe it's possible to use new typescript watch api with gulp? Example of usage https://github.com/s-panferov/awesome-typescript-loader/blob/0d39ed51dc02665b1c573c4038d83287c738f997/src/checker/runtime.ts#L142

marcghorayeb commented 6 years ago

I've stopped debugging/using this plugin... Lack of time to debug it unfortunately. @goloveychuk I'm actually using the tsc command line in my gulp workflow. Works quite well. Here's my setup: https://gist.github.com/marcghorayeb/a092cdac015a960dca6dd87abe8c7b00

mashaalmemon commented 5 years ago

Any progress on this issue? It is quite slow for me as well. I'm using:

"gulp-typescript": "^5.0.0-alpha.3"
mrogunlana commented 5 years ago

Is it safe to assume that this project is abandoned?

ivogabe commented 5 years ago

I personally don't have enough interest in this issue to invest my time in it, currently. We probably need to adapt a newer API from TypeScript. If someone wants to work on this, I'd suggest to dive into the TypeScript api and we could chat if you run into problems.

mheiber commented 5 years ago

I'm interested in this issue, but a prerequisite is understanding what is causing performance issues. I asked this StackOverflow question about how the incremental compilation currently works: https://stackoverflow.com/questions/54716548/how-does-gulp-typescript-incremental-compilation-work.

If someone following this issue has the knowledge needed to answer the question, I appreciate your help!

ivogabe commented 5 years ago

I've posted an answer on your StackOverflow question, let me know if anything is unclear.

mheiber commented 5 years ago

Thanks @ivogabe , this is very helpful. Do you think it would help if the TS Compiler API enabled us to do async io instead of relying on fs.readFileSync for files not available in the vinyl stream?

ivogabe commented 5 years ago

I think that the compilation is compute-bound, not IO-bound. Most of the time is spent on the real type checking and the emit, the cost for IO is probably only a small part. Async IO would then not give a (major) improvement in compile times, but it would increase the complexity of the compiler. But that's just my guess, maybe you can somehow profile how much time is spent doing IO stuff. You could maybe benchmark the compiler on a SSD and a HDD and take a look at the difference in compile time.

DanielRosenwasser commented 5 years ago

@ivogabe I'd watch https://github.com/Microsoft/TypeScript/issues/29978, and avoid spinning off a full language service if you don't need to. That can provide incremental subsequent compilations for both --build and non-build mode.

mheiber commented 5 years ago

TS Compiler API now supports --incremental 🎉 🎉 !

cc @DanielRosenwasser

Lej commented 4 years ago

Any progress being made?

If not, is there any easy replacements for gulp-typescript?

wcldyx commented 4 years ago

Any progress being made?

If not, is there any easy replacements for gulp-typescript?

pdemro commented 3 years ago

Thanks to the team for creating gulp-typescript. Would love to see the incremental compile feature fixed! I wish our friends at Microsoft would help pony up a bounty since this is their recommended method for using TypeScript with Gulp

https://www.typescriptlang.org/docs/handbook/gulp.html

Thanks again