Static imports of lazy-loaded libraries are forbidden.
If you are using Nx, you might have already encountered this issue. This Eslint rule comes from the package @nx/enforce-module-boundaries
developed by the Nx team.
If you don’t know or you are not using Nx, this article can still be valuable and you might learn something from it.
Context
Nx is a workspace organizer. You will create folders called libraries to organize your code, and you will expose components and functions to be used outside of the library. This error happens when you try to lazy import an element and at the same time you eager import another element from the same library.
In this article, we will see what this error really means and the side effect that this error is preventing. Lastly we will see how to resolve it.
Setup
First let’s create a small example to better illustrate the issue.
We create a dashboard application with the following architecture:
In the apps
folder, we have our application entry point. AppComponent
is the root component.
In the libs
folder, we have one library called users
containing two components:
UsersDashboardComponent
is a table listing all users. (It uses Angular Material MatTable.)UserComponent
is a component displaying an icon and the user’s fullname. (It uses MatIcon)index.ts
is our library entry point where both component are exposed.
// index.ts
export { UserComponent } from './lib/user.component';
export { default } from './lib/users-dashboard.component';
AppComponent
is eager referencing UserComponent
and it’s lazy-loading UsersDashboardComponent
as shown below:
import {
UserComponent,
type User,
} from '@angular-challenges/static-dynamic-import/users';
// ^Where the ESlint error is shown!!!
@Component({
imports: [UserComponent, RouterOutlet],
template: `
Author:
<sdi-user [user]="author" />
<router-outlet />
`,
...
})
export class AppComponent {
author: User = {...};
}
Where the router is defined as follow:
provideRouter([
{
path: '',
loadComponent: () =>
import('@angular-challenges/static-dynamic-import/users'),
},
]),
We can now clearly see that users
library is loaded statically and dynamically.
However at serve/build time, Typescript is not showing errors and the application is running correctly. So, why do we need to create an ESlint error to guard us from this issue and what is the side effect of importing a library statically and dynamically at the same time ?
Side-Effect
To better understand the issue, we will inspect the bundle of the application’s build. The tool source-map-explorer
will be of great help.
Let’s build our application using the production configuration and setting sourceMap
to true.
After the build has completed, we can see this summary printed in the console:
Let’s explore what is inside the main bundle by opening the chunk inside source-map-explorer
by re-running the following command:
npx source-map-explorer [PATH_DIST_FOLDER]/*.js
This command will give you the following visualization:
By opening the source-map of the main chunk, we can see that MatTable
and MatIcon
are bundled in the same chunk, although MatTable
is only used inside UserDashboardComponent
which is lazy-loaded. We would have expected that this component would be bundled inside the lazy-loaded one.
This small example clearly shows that the compiler is bundling the entire library inside the main bundle despite using only a small portion of it.
Solution
The solution is straight forward. We only have to create a new library and move our UserComponent
. We will now have two libraries. One which will be lazy loaded, and a shared one which can be eager loaded anywhere in our code.
Let’s rebuild our refactored application.
and rerun source-map-explorer
The compiler divided our component successfully. MatTable
is now located inside the lazy-loaded component and it will not increase the main bundle unnecessarily.
If you want to play with this example and try it on your own, I created a challenge inside AngularChallenges.