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

Extremely high compilation time for production build on v5 #453

Closed
IlCallo opened this issue Jun 18, 2020 · 31 comments
Closed

Extremely high compilation time for production build on v5 #453

IlCallo opened this issue Jun 18, 2020 · 31 comments

Comments

@IlCallo
Copy link

IlCallo commented Jun 18, 2020

Current behavior

We are about to release Quasar Framework CLI v2 (@quasar/app), and really want to integrate fork-ts-checker new version.
We are testing it on real world (private) projects and I noticed an extreme (talking about some minutes, 18s vs 183s) slowdown of production compilation in a particular scenario, for which I cannot pin-point the cause.

I could use some guidance, because the behaviour I'm experiencing is pretty random and don't know how do debug it further.
Right now my instinct tells me there is something wrong with our configuration of v5 and the tsconfig.json file, probably because we add and configure the plugin into @quasar/app and not into a webpack config file into the project root.

Or some kind of incompatibility with @typescript-eslint pre v3
EDIT: nope, upgraded to v3 and problem persist. Also, clean test project didn't had problems with pre v3

Enabling typescript.profile using the only configuration which avoids the slowdown (see below) produce no output.
Enabling typescript.profile when the problem is present I got this

Parse Configuration:          0.03 s
Create Watch Compiler Host:   0.00 s
I/O Read:                     0.13 s
Parse:                        0.93 s
ResolveTypeReference:         0.04 s
ResolveModule:                0.90 s
Program:                      2.12 s
Bind:                         0.84 s
Check:                        0.90 s
transformTime:              143.05 s

Reverting to v4.1.6 fixes the problem, so I'm fairly sure the problem is in the new major version (or our configuration).

This is how we internally enable TS support into the Quasar webpack chain: https://github.com/quasarframework/quasar/blob/9133883bb3f2a3764b697bad8ffc5e313502c4ea/app/lib/webpack/create-chain.js#L154-L182

Notice we always include vue ts support into the options via webpack chain as

typescript: {
  extensions: {
    vue: true
  }
}

In Quasar configuration we allow the user to customize your plugin options like

supportTS: {
  tsCheckerConfig: {
     // ts-checker options
  }
},

Into our starter kit we automatically setup the configuration depending on initial user choices: https://github.com/quasarframework/quasar-starter-kit/blob/19bd2ec30086d1086b7e44929bed71273d3d41a4/template/quasar.conf.js#L26-L33

Experiments till now

supportTS: true

With no eslint configuration, the problem persist


supportTS: {
  tsCheckerConfig: {
     eslint: {
          enabled: true,
          files: ['./src/**/*.ts', './src/**/*.js', './src/**/*.vue']
        }
  }
},

With eslint enabled and this eslint.files glob patterns the problem is solved but I get the error below error, no lint error is displayed for Vue and TS files and typescript.profile output is not shown.
All this stuff makes me think nothing is actually being processed and that's why there's no slowdown.

Error: No files matching '/home/----/src/**/*.js' were found.
Error: No files matching '/home/----/src/**/*.js' were found.
    at FileEnumerator.iterateFiles (/home/----/node_modules/eslint/lib/cli-engine/file-enumerator.js:272:27)
    at iterateFiles.next (<anonymous>)
    at CLIEngine.executeOnFiles (/home/----/node_modules/eslint/lib/cli-engine/cli-engine.js:761:48)
    at /home/----/node_modules/fork-ts-checker-webpack-plugin/lib/eslint-reporter/reporter/EsLintReporter.js:36:41
    at Generator.next (<anonymous>)
    at /home/----/node_modules/fork-ts-checker-webpack-plugin/lib/eslint-reporter/reporter/EsLintReporter.js:8:71

supportTS: {
  tsCheckerConfig: {
     eslint: {
          enabled: true,
          files: './src/**/*'
        }
  }
},

With eslint enabled and this eslint.files glob patterns the problem persist and I get an error for any extension @typescript-eslint cannot process and for all Vue files.
This last bit is strange, because Vue files are marked to be processed.
One more point on the "there's something strange with tsconfig.json" path.

Ex.

src/assets/icons/my-icon.svg
[unknown]: Parsing error: "parserOptions.project" has been set for @typescript-eslint/parser.
The file does not match your project config: src/assets/icons/my-icon.svg.
The extension for the file (.svg) is non-standard. It should be added to your existing "parserOptions.extraFileExtensions".
src/components/_fullscreen-dialog.scss
[unknown]: Parsing error: "parserOptions.project" has been set for @typescript-eslint/parser.
The file does not match your project config: src/components/_fullscreen-dialog.scss.
The extension for the file (.scss) is non-standard. It should be added to your existing "parserOptions.extraFileExtensions".
src/components/my-component.vue
[unknown]: Parsing error: "parserOptions.project" has been set for @typescript-eslint/parser.
The file does not match your project config: src/components/my-component.vue.
The file must be included in at least one of the projects provided.

supportTS: {
  tsCheckerConfig: {
     eslint: {
          enabled: true,
          files: ['./src/**/*.ts', './src/**/*.vue']
        }
  }
},

With eslint enabled and this eslint.files glob patterns the problem persist.

Miscellaneous info

The project tsconfig is in the project root folder and extends the preset provided by Quasar (https://github.com/quasarframework/quasar/blob/dev/app/tsconfig-preset.json)

{
  "extends": "@quasar/app/tsconfig-preset",
  "compilerOptions": {
    "baseUrl": "."
  }
}

I'm using Vue Composition API package (v0.5 right now), but the clean Quasar project I created uses it too and doesn't seems to be affected.

Expected behavior

Avoid slowdown.

Steps to reproduce the issue

I'm not able to replicate the problem with a fresh Quasar project using latest unreleased packages and starter kit.

Environment

  • fork-ts-checker-webpack-plugin: 5.0.1
  • typescript: 3.9.5
  • eslint: 6.8.0
  • webpack: 4.43
  • os: Ubuntu 18.04
@IlCallo IlCallo added the bug label Jun 18, 2020
@piotr-oles
Copy link
Collaborator

Thank you for reporting this issue.
I hope this explanation will help you with debugging:

Starting from version 5, eslint has its own process, separate from typescript checker. It means that eslint configuration should not affect typescript check time (except IO as the file system is shared) and vice versa. It can slow down the time to show issues as it uses Promise.all but not the check itself.

Currently, we don't have implemented profiling for vue extension and for eslint. I can add it - maybe it would help you to find the issue.

Also, I think there is lack of documentation on how to set up eslint for .vue files. By default, the plugin sets:

  options: {
      extensions: ['.ts', '.tsx', '.js', '.jsx'],
  }

which doesn't include .vue files. You can overwrite this by passing it as eslint.options.extensions

@IlCallo
Copy link
Author

IlCallo commented Jun 18, 2020

Consider that the problem isn't probably related to ESLint.
As I specified, the problem comes up even when linting isn't even enabled.

That let apart, I think I tried the linter with Vue files and I seem to recall that it worked anyway (probably because it got up the eslint configuration).

After a lot of trial and errors, I noticed that removing some ts aliases solved the issues.
In particular components, pages and layouts.

Gonna study a bit what's happening there and what do they have in common

EDIT: tried to add eslint options as you said, no effect
EDIT2: can confirm Vue linting isn't actually working, even if I add .vue extension into the options as you proposed
EDIT3: note that all 3 those aliases usually references Vue files, there could be some problems in that area
EDIT4: forget about EDIT3, Vue linting is working ok (even without the additional options you wrote before)

@piotr-oles
Copy link
Collaborator

Cool, thanks for digging into this 👏 I would like to help, but it's hard without a reproduction repository.

You can clone the plugin repository and add performance.markStart and performance.markEnd in the vue extension to check if it's a bottleneck. The example usage is in the TypeScriptReporter. Then you can build it (yarn build) and link it to your project (using yarn link).

I have to admit that we don't have end-to-end tests for .vue linting. The assumption is that eslint linter should be capable of doing everything that eslint command is capable of. I'm not a Vue.js developer, so I'm not familiar with the eslint support for .vue files. Is it something that eslint command handles well, but the plugin doesn't?

@IlCallo
Copy link
Author

IlCallo commented Jun 18, 2020

I added a new edit on previous message: seems like I was doing something wrong in tsconfig, Vue linting works as expected even without the extension options you mentioned.

Right now, I managed to make my project work (and it's blazing fast!) with this configuration:

{
  "extends": "@quasar/app/tsconfig-preset",
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "src/*": ["src/*"],
      "app/*": ["*"],
      // "components/*": ["src/components/*"],
      // "layouts/*": ["src/layouts/*"],
      // "pages/*": ["src/pages/*"],
      "assets/*": ["src/assets/*"],
      "boot/*": ["src/boot/*"]
    }
  }
}

We're gonna release q/app v2 (which will support this package both v4 and v5) and try to gather more examples on the wild to pinpoint the issue.
Also, it will be easier for us to provide some repro when v2 is officially out.

Gonna try what you suggested next week.

If this could ring some bell to you, our current investigative trail (lol) brought us to vue-router files and it's dynamic import syntax for layouts and pages. Components could be affected indirectly because loaded by layouts an pages? Maybe it's something not working related to dynamic imports and dependencies of files retrieved in that way?

Example:

const routes: RouteConfig[] = [
  {
    path: '/',
    redirect: () => ({ name: isAuthenticated() ? 'home' : 'login' })
  },
  {
    path: '/',
    component: () => import('layouts/guest.vue'),
    beforeEnter: redirectIfAuthenticated,
    children: [
      {
        path: 'login',
        name: 'login',
        component: () => import('pages/login.vue')
      },
      {
        path: 'forgot-credentials',
        name: 'forgot-credentials',
        component: () => import('pages/forgot-credentials.vue')
      }
    ]
  },
  {
    path: '/',
    component: () => import('layouts/authenticated.vue'),
    beforeEnter: redirectIfGuest,
    children: [
      { path: 'home', name: 'home', component: () => import('pages/home.vue') },
      {
        path: 'statistics',
        name: 'statistics',
        component: () => import('pages/statistics.vue')
      },
      {
        path: 'registry',
        name: 'registry',
        component: () => import('pages/registry.vue')
      },
      {
        path: 'administration',
        name: 'administration',
        component: () => import('pages/administration.vue'),
        beforeEnter(to, from, next) {
          if (!isAdmin()) {
            next(false);
          } else {
            next();
          }
        }
      }
    ]
  }
];

@piotr-oles
Copy link
Collaborator

piotr-oles commented Jun 19, 2020

Hmm... Hard to tell if dynamic imports can be an issue. We use standard TypeScript API, with a custom fileExists and readFile functions which interpret src/some-file.vue.ts as src/some-file.vue (because TypeScript doesn't support custom extensions and import 'src/some-file.vue'; can be resolved only to src/some-file.vue.ts, src/some-file.vue.tsx, or src/some-file.vue.js). I've opened microsoft/TypeScript#38736 proposal to allow compiler plugins for better Vue.js support, so you can give a 👍 there to let know TypeScript team that this is a desired feature. So we don't hack around import statements.

"app/*": ["*"], looks suspicious for me - maybe it makes other aliases hard to resolve in the TypeScript?

@IlCallo
Copy link
Author

IlCallo commented Jun 19, 2020

Liked the proposal 👍
Will let you know next week if I discover something more.

I don't think it's about "app/*": ["*"], the problem was still there when I disabled everything except the 3 paths I mentioned yesterday. Also, it's a pretty popular path alias, even if you usually find it as ~/* or @/* instead

@jods4
Copy link

jods4 commented Jun 25, 2020

@piotr-oles

Also, I think there is lack of documentation on how to set up eslint for .vue files. By default, the plugin sets:

  options: {
      extensions: ['.ts', '.tsx', '.js', '.jsx'],
  }

which doesn't include .vue files. You can overwrite this by passing it as eslint.options.extensions

I'm hijacking this issue but wanted to say: this ☝️
Just randomly noticed my vue files aren't linted and was trying to figure out why. Glad I found this.
It would be nice to add a note in the Vue section of the readme, or even better, add .vue automatically to the default extensions when Vue support is enabled.

@johnnyreilly
Copy link
Member

It would be nice to add a note in the Vue section of the readme,

Would you like to submit a PR?

@IlCallo
Copy link
Author

IlCallo commented Jul 3, 2020

Finally got little time to come back at this. I see you added some items to the time stats, here what do I get now with dev mode

Parse Configuration:                  0.10 s
Create Watch Compiler Host:           0.00 s
I/O Read:                             0.15 s
Parse:                                2.98 s
ResolveTypeReference:                 0.07 s
ResolveModule:                        1.35 s
Program:                              4.77 s
Bind:                                 3.77 s
Check:                               10.06 s
transformTime:                      150.53 s
Emit:                               153.74 s
Semantic Diagnostics:               164.09 s
Create Watch Program:               172.81 s
Poll And Invoke Created Or Deleted:   0.07 s
Queued Tasks:                         0.00 s

and build mode

Parse Configuration:                  0.07 s
Create Watch Compiler Host:           0.00 s
I/O Read:                             0.24 s
Parse:                                2.68 s
ResolveTypeReference:                 0.08 s
ResolveModule:                        1.45 s
Program:                              4.66 s
Bind:                                 2.84 s
Check:                                7.49 s
transformTime:                      139.46 s
Emit:                               142.29 s

Will continue my debugging next week and let you know if I discover something new

@IlCallo
Copy link
Author

IlCallo commented Jul 6, 2020

Some comments on DX:

  • yarn link give some problems (mostly about "I cannot find module X, Y and Z"), got better luck with yarn add
  • by using "@semantic-release", it's not possible to extract the version of your code when using the project locally: both package.json > version and the exported version const are placeholders. Not a major problem, but it could cause confusion during development of tools (as Quasar) which tries to detect the version of the package

I tried to add a Vue-related performance measurements, but seems like I'm missing something obvious:

Meanwhile I enabled all diagnostic options like this

        typescript: {
          extensions: {
            vue: true
          },
          diagnosticOptions: {
            syntactic: true,
            semantic: true,
            declaration: true,
            global: true
          },
          profile: true
        },
        eslint: {
          files: './**/*.{ts,js,vue}'
        },
        logger: {
          infrastructure: 'console'
        }

and got

Parse Configuration:                  0.06 s
Create Watch Compiler Host:           0.00 s
I/O Read:                             0.14 s
Parse:                                2.49 s
ResolveTypeReference:                 0.06 s
ResolveModule:                        1.14 s
Program:                              4.02 s
Bind:                                 2.67 s
Syntactic Diagnostics:                0.23 s
Global Diagnostics:                   0.01 s
Check:                                7.81 s
transformTime:                      307.87 s
Emit:                               155.59 s
Semantic Diagnostics:               163.66 s

I cannot find some marks name in the codebase (transformTime, Emit, etc) so I guess those comes directly from TS via getDiagnosticsOfBuilderProgram method calls.
Notice that Declaration Diagnostics isn't present even if should. Dunno what does this mean.

Any idea on how to proceed further?

@IlCallo
Copy link
Author

IlCallo commented Jul 6, 2020

I'm exploring TS perf issues world, seems there are quite a bit slowdown problems around.
First two I found:

It may be something related to Quasar types? Does this ring any bell?
Dunno why the problem appears only on v5 and not v4 tho

Running ./node_modules/.bin/tsc --noEmit false --declaration --emitDeclarationOnly --extendedDiagnostics finish in 13s only, so I'm pretty sure the problem is in Vue files (which are checked only after webpack transformation)

@johnnyreilly
Copy link
Member

Fascinating - I hadn't heard of "Quasar types"

@IlCallo
Copy link
Author

IlCallo commented Jul 6, 2020

I mean, Quasar Framework typings:

(Or was it some pun I didn't get 🤔 )

@IlCallo
Copy link
Author

IlCallo commented Jul 6, 2020

More specific question: any idea on how I can start profiling TS stuff while is being processed by the plugin (or before, or after, anything), both in v4 and v5?

@johnnyreilly
Copy link
Member

Although it's primitive - simply hacking in console.log statements will get you a long way. It's surprisingly effective even if you feel that "there must be a better way"

@bodinsamuel
Copy link

Hi team,
+1 on the compilation time (but there is so much involved I'm not sure where it comes from).
Once thing I share with the initial issue is that it reports error for .scss which really should not be parsed by eslint as the default extensions suggest.
I suspect some glob is too greedy.

  new ForkTsCheckerWebpackPlugin({
        typescript: {
          enabled: true,
          configFile: 'tsconfig.json',
          build: true,
          mode: 'readonly',
          diagnosticsOptions: false,
          compilerOptions: {
            skipLibCheck: true,
          }
        },
        eslint: {
          enabled: production ? false : true,
          files: './src/**/*',
        },
      })
[...100s of...]
ERROR in src/views/[...]/index.scss
[unknown]: Parsing error: "parserOptions.project" has been set for @typescript-eslint/parser.
The file does not match your project config: src/views/[...]/index.scss.
The extension for the file (.scss) is non-standard. You should add "parserOptions.extraFileExtensions" to your config.

@IlCallo
Copy link
Author

IlCallo commented Jul 24, 2020

On Quasar framework part, we decided to wait a bit to see if someone else bump into this, as I'm just running in circles when trying to debug this problem and we must focus on some other features.
Will come back to this problem in some months and start debugging again!

@nfour
Copy link

nfour commented Oct 10, 2020

Just wanted to chime in and bump this.

This plugin seemed fine on 5.2.0 for a while till my own project grew, and then some kind of threshold occurred and it exploded in compile time.

Seems related to MobxStateTree & MaterialUI, which are known for their complex types.

Edit:

tsc builds in under 12s, while the plugin takes ~30minutes now (120% cpu).

tsc --watch also hangs for me, so this could be an upstream issue microsoft/TypeScript#25023

Seems related to MobxStateTree & MaterialUI, which are known for their complex types.

Would be ideal to see if [email protected]'s extended perf profiling features can be integrated here to map long

@piotr-oles
Copy link
Collaborator

We use the same API as the tsc --watch so it seems to be a source of the problem

@nfour
Copy link

nfour commented Oct 11, 2020

We use the same API as the tsc --watch so it seems to be a source of the problem

@piotr-oles have you looked into supporting microsoft/TypeScript#40124 4.1.0's new profiling outputs?

More info here microsoft/TypeScript#40063

A projects file paths could be mapped to the types.json/trace.json output, then rendered as a ordered list or tree based on costs.

Might even make sense to emit a warning based on a threshold for type resolution time to let the user know when a new dependency or interface has a large impact.

@piotr-oles
Copy link
Collaborator

@nfour I've added a support for "generateTrace" in 6.0.0-alpha.2 :)

@IlCallo
Copy link
Author

IlCallo commented Nov 3, 2020

Good! We're working on Quasar v2 (with Vue3 support) which means we'll have to migrate to 5+ version of fork-ts-checker.
I'll probably have to come back on this problem in next couple months and check if I can finally find the root cause, any additional tracing tool could be useful!

@nfour
Copy link

nfour commented Dec 23, 2020

@nfour I've added a support for "generateTrace" in 6.0.0-alpha.2 :)

https://github.com/nfour/ts-rank whipped this up to quickly parse generateTrace and show some useful numbers. Still early tho but I've used it to fix some perf issues already.

@kelunik
Copy link

kelunik commented Mar 30, 2021

For my setup this slow down seems to mainly come from eslint, I guess it doesn't work fine with webpack 5's cache.

Initial build Second build
Entire plugin disabled 54689 ms 6217 ms
eslint.enabled: false 54045 ms 16519 ms
eslint.enabled: true 114185 ms 112230 ms

@piotr-oles
Copy link
Collaborator

@IlCallo The newest TypeScript Beta version contains a fix that could potentially address some of these performance issues. Could you test it?

@IlCallo
Copy link
Author

IlCallo commented Apr 22, 2021

Hey there, you mean TS 4.3 beta? What fix in particular should be useful in our case?

Also, with are in the verge of releasing Quasar v2 (and @quasar/app v3) based on Vue3 and webpack 5, we updated this package dependency to v6 directly.
The project I used for this issue reproduction unluckily still have some blockers while migrating to Quasar v2 so I cannot test it right now.
Since we released Quasar v2 beta, only a person reported this same problem, but he too could not pin-point the problem.

Will try to finish my project migration to Quasar v2 and try it out again

@piotr-oles
Copy link
Collaborator

The fix: microsoft/TypeScript#43314 :)

@uccmen
Copy link

uccmen commented Apr 30, 2021

@piotr-oles @johnnyreilly , is the latest version of fork-ts-checker-webpack-plugin compatible with webpack 5 ?

@piotr-oles
Copy link
Collaborator

yes, we have e2e tests that uses webpack 5

@caiquelsousa
Copy link

I believe that this is related #612

@IlCallo
Copy link
Author

IlCallo commented Sep 29, 2021

I cannot reproduce anymore the problem after migrating to Quasar v2, which uses Webpack5 and latest version of this package
Closing this since my original problem seems to be solved

@IlCallo IlCallo closed this as completed Sep 29, 2021
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

9 participants