I've created a Bootstrap component library for angular along with a demo application. However when building the application, the main bundle size is massive. More remarkably, adding components to the library, and adding pages (lazy-loaded) to the application which load these modules, recently increased my main bundle size from 650kB to 840kB.
I already checked other questions like this one too, but it doesn't give me an answer.
I recreated the project in an angular workspace, with dist/lib-name
refs in the tsconfig.json
. Even here I can come to a very simple conclusion:
AppModule
, the main bundle is simple and clean, as expected (328 kB):BsAlertComponent
:I recreated my project in an angular workspace, with the expected result:
Total main bundle size (js): 509 kB.
The main bundle only contains the navbar
chunk
Then for the NX workspace with a single library:
Total main bundle size (js): 695 kB.
Note all the unnecessary chunks.
I'm pretty sure there are no index-imports (import {} from '../navbar'
instead of import {} from '../navbar/navbar.module'
) in my project.
I seem to have found a catch here. While recreating my workspace step-by-step, here's what I see:
When there's no animations on my component
This is the common bundle (lazy-loaded) *
Contains BsListGroup and BsCard
And the lib/components chunk from the main.xxx.js bundle looks like this: Contains only the navbar
Now let's put an animation on the BsAlertComponent
:
My common bundle (containing BsListGroup and BsCard) looks exactly the same as *
However, the components chunk from the main bundle looks like this
And clearly contains the entire BsAlertComponent
(which I don't need) and tons of garbage from other components in the project...
PS. Please fix the SO file-uploader...
I created a minimal angular workspace (even without a library) and I can see all the same behavior as I described in my old question (see below).
The app contains 3 components each with their respective module, 2*2 pages. The BsNavbarModule
is loaded on the AppModule
because I'm using the BsNavbarComponent
in the root.
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
BsNavbarModule,
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Each page loads the respective component.
Carousel 1 page:
@NgModule({
declarations: [
CarouselOneComponent
],
imports: [
CommonModule,
BsCarouselModule,
CarouselOneRoutingModule
]
})
export class CarouselOneModule { }
Offcanvas 1 page:
@NgModule({
declarations: [
OffcanvasOneComponent
],
imports: [
CommonModule,
BsOffcanvasModule,
OffcanvasOneRoutingModule
]
})
export class OffcanvasOneModule { }
So what would you expect to see here?
What do we get?
BsCarouselModule
and BsOffcanvasModule
(this is not what I expect it to be)Why are all these components bundled together into a single bundle? When the user visits a page, it's not necessary to download the js for all these components... In this example it's only about 2 components, but in my real application there's like 30 components, of which most are bundled together for no reason.
So to rephrase, the pages are lazy-loaded obviously:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
const routes: Routes = [
...
{ path: 'range', loadChildren: () => import('./range/range.module').then(m => m.RangeModule) },
{ path: 'select', loadChildren: () => import('./select/select.module').then(m => m.SelectModule) }
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class BasicRoutingModule { }
And only in these pages I'm loading the modules from my library. I ran the webpack bundle analyzer on the main bundle, and noticed that all modules from my library are bundled in my main bundle, just because I'm loading the BsNavbarModule
in the AppModule
.
npm run build:wba
npm run analyze:wba
The Stuff in red is all part of the main bundle, even though I never import
the component's module in the AppModule
, only in lazy-loaded modules. The BsNavbarComponent
is used on the AppComponent
, so the BsNavbarModule
on the other hand needs to be in the main bundle. I'm also under the impression that because of all this, the @angular/core
and @angular/common
bundles are a lot bigger than they actually need to be.
Another catch is that I'm using ngx-highlighjs
in some lazy-loaded page, and this library requires you to specify the HIGHLIGHT_OPTIONS
at root level. Because of this, you can also see the entire ngx-highlightjs library packaged in the main bundle, even though I only load the module on yet another lazy-loaded page...
The module
of my library is specified as "esnext"
.
I tried adding "sideEffects": false
to the package.json
, with the following result:
Which still doesn't solve my problem...
How can I solve this? Why are angular modules that aren't loaded in the AppModule
bundled in the main bundle?
I created a blank angular app:
Installed my library and added the global styles:
And created a page (ng g module demo --module app --route demo
) and added the BsNavbarComponent
on it:
This immediately increased my main bundle size with 50kB...
You can follow this approach: The folder for sub-entrypoints MUST be next to the src folder. Addressing this design problem involves structing the project, optimizing module dependency management, and improving lazy loading behavior to enhance performance, maintainability, and modularity.