Understanding relation of scam and treeshakability

I’ve created this simple app to investigate treeshaking; modules; scams …

GITHUB REPO

(for some nicer navigation/ overview)
STACKBLITZ OF REPO

  • architecture

       app.module
    
          feature1.module
    
              shared.module
                  (declares)
                  sc1-component
                  sc2-component
    
              shared.scam.module
                  (imports)
                  scam1
                  scam2   
    
  • I dont use any components (sc1, sc2, scam1, scam2) in the app

  • I run ng build --prod

  • Every component has something with string "works!" in it. To search it later.

  • I expect to not find "works!" at all in main.js.

  • I global search all my --PROD build .js files (main-BLABLABLA.js), and I find the string “works!” only for the scams.

  • That is exactly the opposite of what I expected. I expect to use a SCAM in order to be able to shake it if I dont use this component.

  • On the other hand I dont find any "works!"-strings for the normally declared components sc1; sc2;

I hope its clear what the question is. Please help me what im doing wrong here.

TLDR;
Why are sc-scam1; sc-scam2 not treeshaken ?

Hi Andre,

Your shared modules are not SCAMs.

Anyways, this problem is not because of SCAMs.

The Angular CLI Build Optimizer should remove the components that are not marked as entry components and are not used in any templates, in your case, the scam1 and scam2 components.

For some reason, the Build Optimizer doesn’t do this for unused declarables in the main bundle. So you’re experiencing this problem because you’re importing the Feature1Module eagerly. If you lazy-load it, you will find that the scam1 and scam2 components are removed from all of your bundles.

// feature1.module.ts
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

import { SharedScamModule } from '../shared-scam/shared-scam.module';
import { SharedModule } from '../shared/shared.module';
import { Feature1Component } from './feature1.component';

const routes: Routes = [
  {
    path: '',
    component: Feature1Component,
  },
];

@NgModule({
  declarations: [
    Feature1Component,
  ],
  imports: [
    RouterModule.forChild(routes),
    CommonModule,
    SharedModule,
    SharedScamModule,
  ]
})
export class Feature1Module { }

// feature1.component.ts
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-feature1',
  template: `
    <p>
      feature1 works!
    </p>
  `,
  styles: [
  ],
})
export class Feature1Component implements OnInit {

  a = 'Feature1Component works!TS';

  constructor() { }

  ngOnInit(): void {
  }
}

// app.component.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule, Routes } from '@angular/router';

import { AppComponent } from './app.component';

const routes: Routes = [
  {
    path: '',
    loadChildren: () => import('./feature1/feature1.module')
      .then(esModule => esModule.Feature1Module),
  },
];

// app.module.ts
@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    RouterModule.forRoot(routes),
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: '<router-outlet></router-outlet>',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  title = 'treeshake-scam';
}

1 Like

Hi Andre,

The solution proposed by Lars about lazy-load is absolutely correct,
but I think that your original treeshake problem with SCAM module is generated by another error: you DON’T have to include the scam component in the bootstrap of the scam module, or it will be marked as an entry-component so it’ll be always included in the build bundle (as pointed by Lars)!

I’ve made a PR to your repo to fix it, and I modified even your SharedScam module to be used as a group / re-export of scam module to simulate something like your FeatureModule1 and then included it in the main AppModule

If you try it (ng build --prod), you will get all the scam component correctly treeshaked from the main bundle, until you use it in the app-component template.

Hope this can help to solve your doubt about treeshake SCAM

2 Likes

You’re absolutely right, Daniele. I didn’t notice components being marked to be bootstrapped. That’s really unusual. Indeed, this marks them as entry components, preventing them from being tree shaked, similar to routed components.

1 Like

Hey guys, thank you alot, I will have a closer look and will reflect on your input. :slight_smile:

1 Like