-
-
Notifications
You must be signed in to change notification settings - Fork 433
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
tsloader's project reference support seems to broken #1174
Comments
Thanks for your detailed benchmarking @desmap. If you'd like to look into submitting a PR to resolve this we'll happily take a look. |
Are you serious? I don't know the code base, I spent now 48h in benchmarking your lib and asking me for a PR is—no offense—borderline. I could offer you to make a PR on the docs which states 'project ref support is highly experimental', so other users don't fall in the same rabbit hole like I did. It's your lib and you hurt your and |
@desmap this is open source. No-one is paying me to build The lib is not perfect but it's very useful to many people. You are not entitled to anything, but you are welcome to join in. Perhaps you could help make |
@desmap, I forked the demo repo as you suggested and added webpack.config.js to test the various options. In the readme I have detailed what I found. I won't repeat that all here but in summary, ts-loader, project references, transpileOnly & ForkTsCheckerWebpackPlugin are all working as expected. I am not seeing a bug in ts-loader here. You can see the repo at: https://github.com/appzuka/project-references-demo It is certainly possible that with larger projects we will see a significant performance slowdown. I will try to expand the repo to test this although I have another project using all of ts-loader, project references, transpileOnly & ForkTsCheckerWebpackPlugin and it works fine (initial build is around 30 seconds). Hopefully, you can see where this code differs from yours and understand why you are seeing something different. If you can share a repo showing the behaviour you benchmarked I'll take a look. |
@appzuka nice that you took the effort and take this seriously, expected and appreciated. To bloat the files, you have to add something to all files and import external modules, otherwise you won't see a difference. I've put following 1,000 arrow functions in an array (the extraError is just to add type errors if needed, otherwise it doesn't have any relevance) in every file. I also imported react, react-dom, reflect-metadata and type-graphql's browser-shim to bloat even more.
So after you bloated the files you have to compare BROKEN 1 and BASE. BROKEN 1's ref compiles on ref changes must be faster than BASE because of To create even more drama just run your tests with babel-loader and you will be shocked how much faster babel is and project reference worked out of the box without any setup with babel-loader (however, there is even a dedicated ms plugin). Again: Important are only the compile times on ref changes. The rest (initial compile, compile on non-ref change) is nice-to-have but I would focus just on the ref changes to isolate the cause better. The rest looks ok to me, however initial compile should be also faster in BROKEN 1 (they are indeed a bit faster but this could be also vps tolerance) but again, just focus on ref changes for the sake of simplicity.
I respectfully and highly doubt this. Pls bloat your files, do proper time measurements on ref changes and but also non-ref changes, also for a babel setup and share quantitative data. Otherwise it's more difficult to follow. I'd like to help you guys wherever I can but also I am limited on time/have to move on and my peers already think I am nuts doing such pedantic time measurements but it was worth, I learned a lot and with babel-loader I now save 5 (!) sec on avg per ref compile and still have perfect and async type checks. And most of my code base are refs. Again, I can't stress this enough, refs are the very best feature of TS and allow truly amazing monorepos, so yeah. I am super happy to have now everything at amazing compile times. To push babel even further (than in OPTION 3) just exclude Finally a comment I got from that guy on reddit who hinted me to babel, pls don't see this as a rant or offense, just as valuable feedback and an opportunity to improve (more options are always good and hope to see ts-loader back in the ring soon again): I've been using TS for 3+ years and since babel v7.0 (with ts support) released, ts-loader, ts-jest etc. never performed as good as tsc+babel. Microsoft officially promotes the babel approach in some of its sub-projects and internal projects [...] people coming to the TS community are mislead towards [ts-loader, ts-jest, etc]. Wish microsoft/typescript or webpack/webpack were more vocal and opiniated about the topic. Many people try to show babel as a monster, but in the long term, it's much simpler to use than ts-*-loader, ts-jest etc and less hassle. And preset-env with auto polyfills is really dope. I have no idea how people live without it. Wish you guys a nice weekend and keep pushing! |
@desmap, I'll bloat the source files and report back. Please confirm your setup using babel-loader. I assume you are using @babel/preset-typescript, but that does not support building project references - you would need to build the references separately. ts-loader does build project references but transpileOnly will not affect the way project references are built. Project references will still be built with syntax checking. Therefore I would not expect your Broken1 settings to be faster than Base. |
Yep. I use babel-loader and it compiles refs out of the box. There is also https://github.com/microsoft/webpack-project-references-alias but I didnt need it, think it's if you want to import the refs module-style but IDK Here is the config:
babel.config.json
Ok, is this caused by tsc or ts-loader? And if former why is then even Edited: I mean OPTION 1 in the last sentence |
i remember an issue on the ts issues where users wanted to have noEmit to work with tsc -b or incremental builds and this wasn't possible because tsc -b need d.ts files but then the ts team offered emitDeclarationOnly and also intro'd composite false. I am not sure if tsc needs even with latter still a compile step for refs. If yes then we found the culprit and it would make sense that fork-ts cannot fork because yeah, this can't be parallelized, or can it? so ts-loader would be innocent but idk, this is getting complicated 😉 still OPTION 1 shouldn't be faster in any case |
fwiw I run the dedicated type check process in OPTION 3 with
|
@desmap, I used your babel setup but still cannot see it build project references. You can repeat my findings using the 'babel' branch of my repo:
This discussion indicates that this is normal for babel-loader - it simply strips off types similar to ts-loader's transpileOnly mode: microsoft/TypeScript-Babel-Starter#35 If this is correct it would explain why your benchmark winner only takes 1 second after a reference change - it is not rebuilding the reference. Even |
tldr babel builds refs, there were some config/structural errors in your repo here you go: https://github.com/desmap/project-references-demo/tree/babel just compare with Github's compare feature the babel branch with yours; there are more changes bc of code formatting etc to get the babel compile run do i changed a few things just to give a better understanding; the project-reference-demo structure is ok to grok project references but it's not perfect (a dedicated packages folder would make it easier to understand but yeah) and naming is here and there odd:
if you want to compile backend related packages you do run this is just an example structure, you could also pipe webpack stuff into a public folder which gives a cleaner architecture. It's important to understand that you now have two build pipelines coexisting neatly. The beauty is now if you have some lib which doesn't get along at all with project references (like Nextjs): you could still add a Next project as a sibling and import any package from dist/ with eg tsconfig path or baseUrl: '..' final note: i am thinking also about to setup the same fast compiles for backend matters but webpack is frontend and IDK if this would add more complexity than benefits; but it would be worth a try because this setup is so fast; but it would suffer drawbacks like no option to let others like Next import single composite packages because webpack creates one big file and you don't want to setup for every tiny component/package a webpack config/build step but just for front-facing apps. ps there will be a type error or warning which is ok, so we see that type checking is working |
@desmap, thanks for sharing the repo. I can see that you are not actually using project references in your front end build. In your backend build you execute For the front end, when you run
The first builds the project with webpack and babel-loader. Babel-loader just strips out the types, it does not check for syntax errors (there are other aspects of Typescript it does not handle either, such as const enums). For this reason, you run the second command, which should report Typescript syntax errors. Both of these commands run, and a bundle is produced in dist, but neither use project references and the project references are not compiled. Run So to be clear if anyone comes across this thread, babel-loader does not understand project references. You will notice that babel-loader produces a warning Webpack with babel-loader does build the project, even if you have not compiled the references. This is because you are building from the typescript source files, not the compiled references. In your
If you are using project references and you want to benefit from using the precompiled projects you should be importing from the compiled javascript:
This gives you the benefit of using the pre-compiled files when building your project, which will reduce the warm-start time. It does mean that if you make a change to a reference you will need to re-build the reference. You would need to be running In your benchmarks, you see essentially the same time to rebuild after a change in references and non-references for your Option3. This is because Option3 does not use references so it is doing the same thing. The settings with ts-loader (your Broken2) takes the same time for non-references but much longer for changes to a reference. This is because you are asking ts-loader to do everything that babel-loader does and then build the references, even though they are not used. If you set ts-loader to run with I don't believe it is fair to say that there were some config/structural errors in my repo. We have made different choices and those choices make different performance tradeoffs. If rebuild time is most critical to you then perhaps your configuration is best for you. The babel-loader vs ts-loader is not the relevant thing to look at here - they both have fast rebuild times. The issue here is comparing webpack in watch mode vs I did bloat the codebase as you suggested and found ts-loader is behaving exactly as expected. Its performance with project references is the same as running Your benchmarks have uncovered some interesting points about different ways to configure projects. I don't believe they show that there is a bug in ts-loader or that ts-loader's performance is worse than alternative methods. It is also clear that using project references with webpack is not straightforward and documentation is badly needed. I intend to add some documentation and examples to ts-loader so that others can get the most of out this great feature which has been added to tsc. |
tldr I like your thoughts, tested and benchmarked them but (i think) babel-loader still excels at perf and ux @appzuka thanks and to summarize your thoughts: (1) tsc, (2) webpack+ts-loader, (3) webpack+babel-loader all create different kind of builds and we should consider this when benchmarking, i totally agree:
hence you are right, 3 needs a dedicated tsc -b to build the entire project ref structure but even 2 might need a dedicated tsc -b to compile not imported refs of a project ref structure, e.g. pure backend refs. 2 definitely builds more than 3 but not all refs. I just tested this myself: ts-loader with hence...
so babel-loader understands project refs, creates proper builds which run but does not compile all project refs, tsloader neither. besides, that babel-loader strips away types is intended and for any ts-related matter we have the dedicated tsc type checking process. to reframe aforementioned:
A benefit on this setup is that if I just work on frontend stuff and don't need to get refs to be compiled as dedicated files but just need my main.js bundle I dont face any overhead of checking/recompiling potentially affected refs. then you stress that dist js files should be imported and not the ts sources. i haven't seen this in the wild, even Ryan's reference demo repo imports ts sources. however, i think that your thought is actually quite good, so i benchmarked it. i changed in my bloated 1,000 function repo from this structure...
to this...
and changed the imports from eg ../p1 to ../p1/dist you find the benchmark results in the og post at the top in the last rows. first for tsloader and then pure tsc -b. so if we look at the figures, they don't make sense. since the warmup times are just slightly faster and the rest not, i assume that tsc -b or --incremental knows what have been compiled also with importing ts sources and does not recompile them (hint: tsbuildinfo), so I still believe importing ts sources is good too. i like also the import ux more with a sibling dist folder. but i might be wrong and maybe someone from the ts team knows more and can chime in here. in general, i think it's nice that ts-loader compiles all its imported packages as dedicated files (better than babel-loader) but i still need a dedicated tsc -b process for all refs. so if i need anyway a dedicated tsc -b process, i prefer really fast builds for webpack frontend matters and then we talk again about babel-loader.[1] [1] in a full dev setup you would have three processes: webpack+babel-loader with parallel tsc -w --incremental type-checking, another parallel tsc -b -w for the rest Edit: I tested again last two rows in watch mode to just stay consistent (which two new rows before, so you have now 4 new rows). Now, definitely warm-up is faster (so what you wrote) but the ref compiles not so much compared to the ts import variants, I also edited the respective parts of the text |
@desmap, I think if you have a solution which you are comfortable with you should go for it. This issue started with a report that ts-loader's project reference support is broken. I have not seen any confirmation of this. The rows named 'Broken' are not comparing like with like. If you still believe there is a problem I suggest you share a repo which demonstrates this. I created a repo with 100 files each with 100 blocks of code to transpile and I saw that ts-loader with transpileOnly is as fast as babel-loader and ts-loader with projectReferences is as fast as running tsc -b in a separate process. |
pls check top post: tsloader+transpileOnly transpiles refs exactly as slow as tsloader omitting transpileOnly (compare BROKEN 1 with BASE). here we could debate (again) if we blame tsloader or tsc. then we see that even OPTION 1's (pure tsc -b -w+webpack) ref times are faster than BROKEN 1 which shouldn't be the case because tsloader uses tsc under the hood, so why is it slower (ref times!) than tsc+webpack? and OPTION 1 builds all refs while BROKEN 1 builds only the imported ones?
re babel yes, re BROKEN 1 vs BASE and BROKEN 1 vs OPTION 1 (ref times!) no, compare aforementioned
Would have been helpful if you shared quantitative data such as time measurements for respective setup and build variants.
Nice closure, I think the same. We spent way too much time here and should do some real work now, both with their preferred setup. 😉 Wish you all the best and I enjoyed our conversation. |
I submitted a PR to for ts-loader's docs to make it clear that The code I used to confirm ts-loader is running correctly on a large codebase is available in the https://github.com/appzuka/project-references-demo/tree/benchmark The steps to run the benchmarks and the times I obtained on my system (WSL2, i7 3.4GHz) are in the readme. |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
Closing as stale. Please reopen if you'd like to work on this further. |
Expected Behaviour
Project references compile/transpile way too slow, this can be tested and reproduced, I put extensive benchmarking below.
transpileOnly: true
doesn't make any difference on ref compile times so there must be something wrong, more details:tldr ATM it's recommended to go for
babel-loader
for fast project reference transpilation and a dedicatedtsc -b -w --noEmit
process for type checking until tsloader's bug is fixedCC @johnnyreilly, @sheetalkamat, @piotr-oles, @appzuka
thanks to r/keiser_sozze for hinting me to babel+tsc noEmit.
Here again the updated comparison table, pls focus on OPTION 3 and BROKEN 1 and 2 and there, the bold figures highlight the broken transpilations:
rm -r dist
(cold start)tsc -b -w
+ webpack-dev-server just bundlingtsc
's output, aggregateTimeout: 0tsc -b -w
without bundlingtsc
's output, aggregateTimeout: 750tsc
's output, aggregateTimeout: 0Actual Behaviour
BROKEN 1 should be much faster than BASE
Steps to Reproduce the Problem
git clone https://github.com/RyanCavanaugh/project-references-demo, take your preferred webpack config and play around with the settings in the benchmark table.
Location of a Minimal Repository that Demonstrates the Issue.
https://github.com/RyanCavanaugh/project-references-demo + settings from the table
The text was updated successfully, but these errors were encountered: