angular-cli
angular-cli copied to clipboard
`externalPackagesPlugin` slows down `ng serve` substantially
Command
serve
Is this a regression?
- [x] Kinda, as the
externalPackagesPlugin
was just added via https://github.com/angular/angular-cli/pull/26923
The previous version in which this bug was not present was
Before https://github.com/angular/angular-cli/pull/26923 existed, which was part of 17.1.1, so I guess angular 17.1.0
Description
angular 17.2.0 vite/esbuild powered application builder
Using the loader
option in angular.json
like:
"loader": { ".webp": "file", ".svg": "text" },
to support images as ecmascript imports or svgs as inline. This causes angular-cli to add the externalPackagesPlugin
which is a giant hit on build performance. For our apps:
angular version | watch rebuild time |
---|---|
angular 17.1.0 { "loader": { ".webp": "file" } } |
0.8 seconds |
angular 17.1.1 { "loader": { ".webp": "file" } } |
6.0 seconds |
angular 17.2.0 { "loader": { ".webp": "file" } } |
6.0 seconds |
Those additional 5.2 seconds are also added to the initial build.
My rough idea of the issue is, that the externalPackagesPlugin
looks at every single import that doesn't start with .
or /
and doesn't cache the results. So tons of @angular/...
, rxjs
, @ngrx/...
and similar imports are resolved over and over again. Even if this function only needs 2ms to run, this quickly adds up.
Minimal Reproduction
https://github.com/sod/ng-serve-external-deps-slowdown
The only difference between the development
and development-slow
configurations in the angular.json are:
"development": {
},
"development-slow": {
"loader": {
".webp": "file"
}
},
Your Environment
@angular-devkit/architect 0.1702.0
@angular-devkit/build-angular 17.2.0
@angular-devkit/core 17.2.0
@angular-devkit/schematics 17.2.0
@angular/cli 17.2.0
@schematics/angular 17.2.0
rxjs 7.8.1
typescript 5.3.3
zone.js 0.14.4
As as side note. If you intend to just make the externalPackagesPlugin
faster but keep the /^[^./]/
regex, this still would be a regression compared to angular 17.1.0.
E.g. if all that the externalPackagesPlugin
did was return null
, like:
build.onResolve({ filter: /^[^./]/ }, async (args) => {
return null;
});
Then the watcher in our project would still need 1.4 seconds (instead of the 0.8 seconds in 17.1.0 without this plugin).
I guess having esbuild calling a javascript method on nearly every import doesn't come for free. For us it executes this function 7458 times on every change.
I'd prefer if we could stick to buildOptions.packages = 'external';
without the plugin, as that seemed to work fine. Maybe the options.loaderExtension
doesn't need to be part of the bail:
After all, loaderExtension
are all build-in esbuild loaders. esbuild doesn't support custom loaders like webpack does. Custom stuff must all be implemented as plugins in esbuild.