ng-packagr
ng-packagr copied to clipboard
Ability to disable sub entry points
Type of Issue
[ ] Bug Report
[x] Feature Request
Description
As a library developer, I would like to have the possibility not to build sub entry points (for dev builds).
Details
We are experiencing build performance issues with our Angular library (30+ sub entries) even after migrating to Angular 10 (related to #1222).
lib build time (seconds) | ng 8 | non-Ivy (ng 10) |
---|---|---|
with sub-entries | 194 | 90.93 |
without sub-entries | 36 | 32.08 |
This is why we decided to have 2 separate builds:
- a production one with sub-entries so that the library is tree-shaked
- a development one without sub-entries - an
index.dev.ts
barrel that exports the sub-entries.
In order allow both builds without chaing the code, we created a custom Angular builder that:
- generates
package.json
files for each sub-entry - builds
- cleans-up previously generated
package.json
file.
Expected Behaviour
I would like to have a configuration flag so that ng-packagr ignores sub-entries. This way we can run ng-packagr for the dev build without the pre/post build processing.
Version Information
$ node_modules/.bin/ng-packagr --version
ng-packagr: 10.1.0
@angular/compiler: 10.0.14
typescript: 3.9.7
rxjs: 6.6.3
node: 12.13.1
yarn: 1.21.1
Interesting find. If what you are trying to solve is a performance problem, the approach is not to ignore sub-entry points, because you will run into issues with the dependency graph. You should provide a reproduction repository with generated code where this difference in performance is visible, maybe the overhead of entry points could be reduced.
Hi @SchnWalter
I generated a skeleton library and added 50 modules.
yarn && yarn run build // build time ~ 8 seconds
git checkout sub-entries && yarn run build // build time ~47 seconds
From what I can tell, scheduleEntryPoints
is creating a lot of class instances for each entry point. We might be able to reduce the resource usage and the build duration by reusing some of the instances, but this will be tricky when it comes to external packages like the mainNgcc process
from @angular/compiler-cli/ngcc
:
https://github.com/ng-packagr/ng-packagr/blob/2f470e0b7742b1a517ddd2bb33c603344c3294c5/src/lib/ngc/ngcc-processor.ts#L57-L65
For any change, we'll need to make sure that we don't introduce regressions and we'll need to start adding benchmarks and tests. And we'll need to check with the Angular Core team if the ngcc
process can be reused, it probably can, because we have support for live-reload.
@victorsc I looked into a profiler trace for your reproduction and one thing stood out: TypeScript type checking was taking ~35s of a total of ~50s. The reason is that the tsconfig.lib.json
does not specify "skipLibCheck": true
and that is not configured by default in ng-packagr, so all entry-points pay the cost of type-checking the .d.ts files of included libraries and default libraries. This adds a huuuuuuge overhead to processing the packages; setting that flag reduces the execution from ~50s to ~18s and significantly drops retained memory when used with the --watch
flag from ~1030MB to just ~255MB.
@SchnWalter ngcc is already somewhat optimized to be smart about whether it needs to run or not, but it still does add some overhead. I'm seeing about 4s being in spent in ngcc in total in the above reproduction, just under 100ms per entry-point. Although that is noticeable, it's also not the end of the world. I think we can do something to reduce the overhead per entry-point to almost nil, though, if ng-packagr would maintain a shared cache of which modules have been processed.
I have incorporated the above changes and some more improvements in #1764 which reduces the total time spent for @victorsc's repro from ~50s to just ~12s. Note that the repro does not represent a real-world scenario; as all entry-points are very small. It does show however that the overhead of an entry-point is significantly reduced.
I briefly talked to @alan-agius4 and he rightfully mentioned that skipLibCheck
will also disabled type-checking of local .d.ts files, which is not something we want to do by default. That means that you'll only get the largest perf improvement if you configure it yourself.
I think on future we might consider building all the entry points with a single TS program.
The only problem that we need to overcome is rootDirs, which at the moment we set so users don’t accidentally, reference entry-points using relative paths.
Thanks @JoostK. Nice work! The build time for a basic library[1] is down to ~18% of what I saw with the latest version of the master branch. About ~13s instead of ~72s
@victorsc, in my last comment I forgot to mention that in the sub-entries
branch of your example library you are actually building all the files 3 times: the first time in the package root, then once more in a /lib
sub package and one last time in the sub package containing the module.
[1] a generated library that included a basic component and a basic service in each of the 90 sub packages
The change to enable skipLibCheck
by default has been retracted from #1764. It's not universally safe as it also causes local declaration files not to be checked.
For projects with a lot of entry-points where the performance penalty is deemed unacceptable it is possible to enable the "skipLibCheck"
flag in tsconfig.lib.json
.