Claude
Skills
Sign in
Back

bundle-optimization

Included with Lifetime
$97 forever

Comprehensive guide to Angular bundle optimization, code splitting, tree shaking, and lazy loading strategies

General

What this skill does


# Angular Bundle Optimization

Complete guide to reducing Angular bundle sizes through code splitting, tree shaking, and lazy loading.

## Table of Contents

1. [Bundle Analysis](#bundle-analysis)
2. [Lazy Loading Strategies](#lazy-loading-strategies)
3. [Tree Shaking](#tree-shaking)
4. [Code Splitting](#code-splitting)
5. [Library Optimization](#library-optimization)
6. [Build Configuration](#build-configuration)
7. [Image Optimization](#image-optimization)
8. [Caching Strategies](#caching-strategies)

---

## Bundle Analysis

### Generate Bundle Stats

```bash
# Build with statistics
ng build --configuration production --stats-json

# Output: dist/<project>/stats.json
```

### Analyze with Tools

```bash
# Webpack Bundle Analyzer
npm install --save-dev webpack-bundle-analyzer
npx webpack-bundle-analyzer dist/<project>/stats.json

# Source Map Explorer
npm install --save-dev source-map-explorer
ng build --configuration production --source-map
npx source-map-explorer dist/**/*.js

# Bundle Buddy
npx bundle-buddy dist/<project>/stats.json
```

### Reading the Analysis

```
main.js (1.2 MB)
├── @angular/core (280 KB)
├── @angular/common (150 KB)
├── lodash (287 KB) ⚠️ Can optimize
├── moment (67 KB) ⚠️ Can replace
├── rxjs (98 KB)
└── application code (318 KB)
```

---

## Lazy Loading Strategies

### Route-Based Lazy Loading

```typescript
// app.routes.ts
export const routes: Routes = [
  {
    path: 'dashboard',
    loadComponent: () => import('./dashboard/dashboard.component')
      .then(m => m.DashboardComponent)
  },
  {
    path: 'users',
    loadChildren: () => import('./users/users.routes')
      .then(m => m.USERS_ROUTES)
  },
  {
    path: 'admin',
    loadChildren: () => import('./admin/admin.routes')
      .then(m => m.ADMIN_ROUTES),
    canActivate: [AuthGuard] // Only loads if authorized
  }
];
```

**Impact**: Main bundle reduced by 40-60%

### Component Lazy Loading

```typescript
// Before: Imported at module level
import { HeavyChartComponent } from './chart/heavy-chart.component';

@Component({
  template: `
    <app-heavy-chart *ngIf="showChart" [data]="chartData" />
  `
})
export class DashboardComponent {
  showChart = false;
}

// After: Dynamic import
@Component({
  template: `
    <ng-container *ngIf="chartComponent">
      <ng-container *ngComponentOutlet="chartComponent; inputs: chartInputs" />
    </ng-container>
  `
})
export class DashboardComponent {
  chartComponent: any;
  chartInputs = { data: [] };
  
  async loadChart() {
    const { HeavyChartComponent } = await import('./chart/heavy-chart.component');
    this.chartComponent = HeavyChartComponent;
  }
}
```

### Preloading Strategies

```typescript
// app.config.ts
import { PreloadAllModules, NoPreloading } from '@angular/router';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(
      routes,
      // Option 1: No preloading (default)
      withPreloading(NoPreloading),
      
      // Option 2: Preload all
      withPreloading(PreloadAllModules),
      
      // Option 3: Custom preloading
      withPreloading(CustomPreloadingStrategy)
    )
  ]
};

// Custom preloading strategy
@Injectable({ providedIn: 'root' })
export class CustomPreloadingStrategy implements PreloadingStrategy {
  preload(route: Route, load: () => Observable<any>): Observable<any> {
    // Preload only routes with data.preload = true
    return route.data?.['preload'] ? load() : of(null);
  }
}

// Route configuration
{
  path: 'important',
  loadChildren: () => import('./important/routes'),
  data: { preload: true } // Will preload
}
```

### Lazy Load on Interaction

```typescript
@Component({
  template: `
    <button (click)="openDialog()">Open Settings</button>
  `
})
export class AppComponent {
  async openDialog() {
    // Load dialog only when button clicked
    const { SettingsDialogComponent } = await import(
      './settings/settings-dialog.component'
    );
    
    const dialogRef = this.dialog.open(SettingsDialogComponent);
  }
}
```

---

## Tree Shaking

### How Tree Shaking Works

Tree shaking removes unused code during the build process.

```typescript
// library.ts
export function usedFunction() { /* ... */ }
export function unusedFunction() { /* ... */ }

// app.ts
import { usedFunction } from './library';

usedFunction();
// unusedFunction is removed from bundle ✂️
```

### Optimize Imports

```typescript
// ❌ BAD: Imports entire library
import * as _ from 'lodash';
import * as moment from 'moment';

_.debounce(fn, 300);
moment().format('YYYY-MM-DD');

// ✅ GOOD: Import only what you need
import { debounce } from 'lodash-es';
import { format } from 'date-fns';

debounce(fn, 300);
format(new Date(), 'yyyy-MM-dd');
```

### Material Components

```typescript
// ❌ BAD: Import entire Material module
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatTableModule } from '@angular/material/table';
import { MatPaginatorModule } from '@angular/material/paginator';
// ... 20+ more imports in a shared module

// ✅ GOOD: Import per component/feature
@Component({
  standalone: true,
  imports: [
    MatButtonModule, // Only button needed here
    CommonModule
  ]
})
export class SimpleComponent { }
```

### providedIn: 'root' for Services

```typescript
// ❌ BAD: Service in module providers
@NgModule({
  providers: [DataService] // Always in bundle
})

// ✅ GOOD: Tree-shakeable service
@Injectable({
  providedIn: 'root' // Only in bundle if used
})
export class DataService { }
```

### Side-Effect-Free Code

```typescript
// ❌ BAD: Side effects prevent tree shaking
export class Logger {
  constructor() {
    console.log('Logger initialized'); // Side effect!
  }
}

// Even if unused, stays in bundle

// ✅ GOOD: No side effects
export class Logger {
  log(message: string) {
    console.log(message);
  }
}
```

---

## Code Splitting

### Manual Chunks

```typescript
// angular.json
{
  "projects": {
    "app": {
      "architect": {
        "build": {
          "configurations": {
            "production": {
              "optimization": {
                "scripts": true,
                "styles": {
                  "minify": true
                }
              },
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "500kb",
                  "maximumError": "1mb"
                }
              ],
              "namedChunks": false,
              "outputHashing": "all"
            }
          }
        }
      }
    }
  }
}
```

### Vendor Chunking

Angular automatically creates vendor chunks:

```
dist/
├── main.js        (Your code)
├── vendor.js      (node_modules)
├── polyfills.js   (Browser polyfills)
└── runtime.js     (Webpack runtime)
```

### Custom Webpack Config

```typescript
// custom-webpack.config.ts
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendor',
          priority: 10
        },
        common: {
          minChunks: 2,
          priority: 5,
          reuseExistingChunk: true
        },
        // Separate heavy libraries
        charts: {
          test: /[\\/]node_modules[\\/](chart\.js|d3)[\\/]/,
          name: 'charts',
          priority: 15
        }
      }
    }
  }
};

// angular.json
{
  "architect": {
    "build": {
      "builder": "@angular-builders/custom-webpack:browser",
      "options": {
        "customWebpackConfig": {
          "path": "./custom-webpack.config.ts"
        }
      }
    }
  }
}
```

---

## Library Optimization

### Replace Heavy Libraries

```typescript
// ❌ Moment.js (67 KB gzipped)
import * as moment from 'moment';
const date = moment().format('YYYY-MM-DD');

// ✅ date-fns (6 KB gzipped)
import { format } from 'date-fns';
const date = format(new Date(), 'yyyy-MM-dd');

// ✅ Native Intl API (0 KB - built-in)
const dat

Related in General