Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Slow Performance on Incremental Runs (Project References) #463

Closed
nathanforce opened this issue Jun 22, 2020 · 32 comments
Closed

Slow Performance on Incremental Runs (Project References) #463

nathanforce opened this issue Jun 22, 2020 · 32 comments

Comments

@nathanforce
Copy link

Current behavior

Incremental typechecks are very slow.

We have a project that leverages Project References in Typescript. As the latest release of your plugin supports references we tried to upgrade. We are seeing an unusable drop in performance when type-checking this code.

image

For comparison, here is the same code change ran with tsc --build --incremental

image

Here is our config:

      new ForkTsCheckerWebpackPlugin({
        async: true,
        typescript: {
          mode: 'write-references',
          configFile: path.resolve(APP_ROOT, 'tsconfig.json'),
          configOverwrite: {
            compilerOptions: {
              incremental: true
            }
          },
          profile: true,
          build: true
        }
      }),

Expected behavior

Incremental typechecks should be quick.

Environment

  • fork-ts-checker-webpack-plugin: 5.0.0
  • typescript: 3.7.2
  • eslint: n/a
  • webpack: 4.39.3
  • os: macos
@piotr-oles
Copy link
Collaborator

piotr-oles commented Jun 23, 2020

Hi,
Thanks for reporting this issue. Could you check timings using tsc --build --incremental --watch? This is the API we use and the performance drop is not in the initial compilation but in the next one.

@nathanforce
Copy link
Author

nathanforce commented Jun 23, 2020

Sure thing. I've attached the comparison.

tsc --build --incremental --watch
Screen Shot 2020-06-23 at 2 49 46 PM

Webpack, showing both initial compilation and then after a file change.
Screen Shot 2020-06-23 at 2 48 02 PM

One interesting thing I see is that tsc reports 1318 files while webpack reports 2111 modules. Not sure if that mismatch indicates anything.

@piotr-oles
Copy link
Collaborator

Is there any chance of getting a reproduction repository?

@nathanforce
Copy link
Author

https://github.com/nathanforce/monorepo-ts-projects-webpack

@piotr-oles Here's my best attempt. It is very simplistic but follows the same structure/patterns as the real repo, just with far less files. Even in this tiny example I am seeing slower numbers on the webpack side when running the two commands:

webpack:

npx webpack-dev-server --mode=development --config ./apps/alpha/webpack.config.js

tsc:

npx tsc --build ./apps/alpha/tsconfig.json --extendedDiagnostics --incremental --watch

As is expected in such a contrived reproduction, the differences here are much much smaller but maybe it can help you spot something. Sorry for the delay!

Cheers

@piotr-oles
Copy link
Collaborator

I will try to take a look at this in a few days :)

@nathanforce
Copy link
Author

@piotr-oles #494 seems to have helped a bit but the typecheck times using Webpack/plugin are still much higher than using tsc. Below screenshot shows the two running side by side for the same change.

tsc:

image

image

@piotr-oles
Copy link
Collaborator

Cool that there is a progress - I will profile it again :)

@quicksnap
Copy link

Thanks for looking into this. I just upgraded to this version on a branch and I believe we're experiencing the same problems.

@cristian-spiescu
Copy link

Hi guys!

I have an application built with create-react-app which uses fork-ts-checker-webpack-plugin v3.1.1. I have noticed that lately the fork-ts-checker-webpack-plugin portion of the build process (both in dev/incremental and production build) take a huge amount of time to complete. I think there is some kind of dependency that the compiler "doesn't like" that got added at some point in time. E.g. I saw people that reported that adding "material icons" introduced this kind of behavior.

I'd like to debug the compilation phase, and some pointers would help me a lot. E.g. where could I insert a console.log() so that I can see, file by file, the files being processed? Or maybe I'm being naive, and the plugin doesn't actually have such fine grained control (i.e. at file level), and in this case I should somehow work at the level of tsc?

Thanks a lot in advance!
Cristian.

@piotr-oles
Copy link
Collaborator

We don't process file-by-file - we pass configuration and host to the TypeScript API and we use this API to check for errors. I would suggest using debugger or console.log in the node_modules/typescript/lib/typescript.js file :)

@cristian-spiescu
Copy link

Thanks Piotr.

The problem may be around the TS code; i.e. some corner case that the TS compiler doesn't "like". However, in a create-react-app (CRA) TS is practically invoked twice.

  • The first time during the compilation phase (via babel), where TSC actually transpiles the code, outputting JS code
  • The second time via fork-ts-checker.

The first step is executed reasonably fast. It's the second step that seems to put TS to the knees.

I will somehow extract the exact parameters which are passed to the TS API. CompilerHost.js seems promising. So that I can try to translate them into a tsc ... command. Maybe the configuration has something dubious and/or somehow a lot of files are sent to processing (maybe stuff from node_modules, etc?).

ESLint is also a player in the game; I didn't yet understand which is its exact relation to TS. Maybe the issue it's not in the TS code actually, but rather related to ESLint.

@phryneas
Copy link
Contributor

* The first time during the compilation phase (via babel), where TSC actually transpiles the code, outputting JS code

Just FYI: babel is not running tsc, but just stripping away type information, so when you start debugging in the typescript compiler, this step will never hit anything there.

@johnnyreilly
Copy link
Member

ESLint is also a player in the game; I didn't yet understand which is its exact relation to TS

In the case of create-react-app, fork-ts-checker-webpack-plugin is not used for ESLint purposes; eslint-loader is. ( fork-ts-checker-webpack-plugin was added to create react app only once CRA opted to support TypeScript - ESLint support was already implemented prior to this)

facebook/create-react-app#7036

@cristian-spiescu
Copy link

cristian-spiescu commented Aug 26, 2020

Thanks guys for your kind hints!

It does seem to be related to tsc. Running tsc => about 10 sec. W/ TS v3.7.2, running tsc --incremental => 70 sec on a small project. And 500 sec on a bigger one. Even worse w/ TS v4.0. Unfortunately these issues don't seem to be rare. I have commented here. This issue seems to be also similar.

To be honest, coming from the Java world I was amazed by the power of the types system in TS. And I was very suspicious at first, having concerns related to compiler performance. It seems that at some points the "type magic" does have a non negligible cost.

@desmap
Copy link

desmap commented Aug 26, 2020

fwiw, i did some extensive benchmarking[1] and fork-ts-checker struggles the most with reference files in project references (but not main files which import the references)

[1] TypeStrong/ts-loader#1157 (comment)

@cristian-spiescu
Copy link

Interesting. Because I just discovered the reference / composite structure recommended by TS as a performance improvement (+ reusability flow) mechanism.

@desmap
Copy link

desmap commented Aug 26, 2020

performance improvement (+ reusability flow) mechanism.

what do you mean exactly? that you are more productive? yes absolutely, it's TS killer feature and if you change references a lot pure tsc is the fastest followed by ts-loader

@desmap
Copy link

desmap commented Aug 26, 2020

@piotr-oles do you have already any ideas how to fix fork-ts-checker's perf issues with project references or is it something which can't be fixed because of architectural reasons?

@piotr-oles
Copy link
Collaborator

I don't have any ideas yet - first we need to find the root cause of this issue. I'm not sure why it's slower for references. If someone has some free time, feel free to profile it to find where is the bottleneck :)

@cristian-spiescu
Copy link

My (temporary ?) solution for my issue:

running tsc --incremental => 70 sec

was to monkeypatch, within CRA, the creation of ForkTsCheckerWebpackPlugin, and to force

useTypescriptIncrementalApi = false;

@piotr-oles
Copy link
Collaborator

CRA uses an older version of the ForkTsCheckerWebpackPlugin which is no longer developed. I don't think this version supports project references at all :/ I think we should drive CRA to update to a newer version

@cristian-spiescu
Copy link

There is indeed facebook/create-react-app#6799

To be honest, given the time I invested in CRA for monkeypatching => personally I don't find CRA a robust enough solution. Because of our attempt to reuse a common library among several projects, we were pushed towards using some symlinking. And because of this absolutely every new feature that we added: was done with a lot of pain.

Initially I saw in CRA a set of best practices; a reference. But even the Jest testing mechanism is slow and we were forced to ... more monkeypatch for mocha, which has the advantage of executing tests instantaneously.

@desmap
Copy link

desmap commented Aug 27, 2020

feel free to profile it to find where is the bottleneck

@piotr-oles profiles are below, interestingly there's almost no output from your profiler with reference files, so i guess fork-ts-checker is not even touching those files, hence not properly supporting project references.

what should i do next?

change in non-reference file:

Poll And Invoke Created Or Deleted: 0.25 s
I/O Read:                           0.00 s
Parse:                              0.11 s
ResolveModule:                      0.00 s
ResolveTypeReference:               0.00 s
Program:                            0.11 s
Bind:                               0.04 s
Check:                              0.86 s
transformTime:                      0.00 s
Emit:                               0.03 s
Semantic Diagnostics:               0.91 s
Queued Tasks:                       1.10 s

change in reference file:

Poll And Invoke Created Or Deleted: 0.21 s
Queued Tasks:                       0.00 s

@desmap
Copy link

desmap commented Aug 27, 2020

fyi/fwiw, IDK but this slow ref transpile times might come from tsloader and not from fork-ts-checker, I did more benchmarking and added babel to the mix as a comparison and something is wrong with tsloader ref handling

@piotr-oles
Copy link
Collaborator

As a profile I mean basically debugging/finding the root cause of this behaviour using whatever method you like. It will require digging into typescript's codebase which can be a time-consuming task :)

@desmap
Copy link

desmap commented Aug 28, 2020

ok got it, however is it really ts or ts-loader because what I find strange is that fork-ts is not even touching reference files (if you look at the profile data above).

IDK if I can help, I guess I am as busy as you are. However let me know if I can do something, otherwise I'd move on.

@Bnaya
Copy link

Bnaya commented Oct 10, 2020

microsoft/TypeScript#40808
Might be related?

@piotr-oles
Copy link
Collaborator

I've added support for a "generateTrace" option available in typescript 4.1.0-beta in 6.0.0-alpha.2. It should help to debug performance issues :)

@piotr-oles
Copy link
Collaborator

@nathanforce The latest TypeScript Beta version contains a fix that could potentially fix this issue - could you test it?

@dmitry-zaets
Copy link

Just faced the same issue with the project freshly migrated to ts (ejected CRA + SSR build + updated fork-ts-checker-webpack-plugin).
With TypeScript 4.2.3 the build was terribly slow but seems that problem is fixed with Typescript 4.3-beta.

@piotr-oles
Copy link
Collaborator

It seems that the bug was in the TypeScript and as the new version of TypeScript has been released, I'm closing this issue :)

@zofiag
Copy link

zofiag commented Sep 8, 2021

Is it possible that this issue is back? I'm using Typescript 4.4.2 and after introducing a change in a package that is listed in the project tsconfig.json references my webpack-dev-server is incredibly slow (it recompiles couple of times before serving the newest files in the browser). This is my ForkTsCheckerWebpackPlugin setup:

new ForkTsCheckerWebpackPlugin({
      async: isEnvDevelopment,
      typescript: {
        configOverwrite: {
          compilerOptions: {
            sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment,
            skipLibCheck: true,
            inlineSourceMap: false,
            declarationMap: false,
            noEmit: true,
            incremental: true,
            tsBuildInfoFile: paths.appTsBuildInfoFile,
          },
        },
        context: paths.appPath,
        diagnosticOptions: {
          syntactic: true,
        },
        mode: 'write-references',
        build: true,
        profile: true,
      },
      issue: {
        // This one is specifically to match during CI tests,
        // as micromatch doesn't match
        // '../cra-template-typescript/template/src/App.tsx'
        // otherwise.
        include: [{ file: '../**/src/**/*.{ts,tsx}' }, { file: '**/src/**/*.{ts,tsx}' }],
        exclude: [
          { file: '**/src/**/__tests__/**' },
          { file: '**/src/**/?(*.){spec}.*' },
          { file: '**/src/**/?(*.){stories}.*' },
          { file: '**/src/setup-tests.*' },
        ],
      },
      logger: {
        infrastructure: 'silent',
      },
    }),

I'm using "fork-ts-checker-webpack-plugin": "6.3.3", version. My project is an ejected create-react-app with some updates to support webpack 5.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

10 participants