Skip to main content

Angular Tree Shaking

Introduction

Tree shaking is a critical performance optimization technique in modern JavaScript applications, including Angular. At its core, tree shaking is a form of dead code elimination that removes unused code from your final bundle. The name "tree shaking" comes from the visualization of your application as a tree, where unused branches (code) are shaken off during the build process.

For Angular applications, which can become quite large, tree shaking is particularly important as it can significantly reduce the size of your application bundles, resulting in faster download times, parsing, and execution for your users.

How Tree Shaking Works in Angular

Angular, in combination with the webpack bundler, analyzes your code during the build process to determine which parts are actually used. It then excludes any code that isn't referenced, keeping only what's necessary for your application to function.

The Process

  1. Static Analysis: The build tool analyzes your import/export statements
  2. Dependency Graph Creation: It builds a graph of all your module dependencies
  3. Usage Marking: It marks which exports are actually used in your code
  4. Dead Code Elimination: It removes any code that isn't marked as used

Prerequisites for Effective Tree Shaking

For tree shaking to work effectively in your Angular application, several conditions must be met:

  1. Use ES modules syntax: Tree shaking relies on the static nature of ES modules
  2. Enable production mode: Use the --prod flag with Angular CLI
  3. Use side-effect free code: Avoid code with side effects that can't be safely eliminated
  4. Use proper imports/exports: Import exactly what you need, not entire modules

Practical Examples

Example 1: Basic Tree Shaking in Action

Consider a utility module with multiple functions:

typescript
// utils.ts
export function add(a: number, b: number): number {
return a + b;
}

export function subtract(a: number, b: number): number {
return a - b;
}

export function multiply(a: number, b: number): number {
return a * b;
}

export function divide(a: number, b: number): number {
if (b === 0) throw new Error("Cannot divide by zero");
return a / b;
}

Now, in your component, if you only import and use the add function:

typescript
// my-component.ts
import { add } from './utils';

@Component({
selector: 'app-calculator',
template: `<div>{{ add(5, 3) }}</div>`
})
export class CalculatorComponent {
constructor() {}

add(a: number, b: number): number {
return add(a, b);
}
}

After tree shaking, your final bundle will only include the add function, while the subtract, multiply, and divide functions will be excluded from the bundle.

Example 2: Library Imports

Libraries often offer tree-shaking capabilities. Consider RxJS, a common library used in Angular:

Bad practice (imports the entire library):

typescript
import * as rxjs from 'rxjs';

@Component({
// ...
})
export class MyComponent {
data$ = rxjs.of(1, 2, 3);
}

Good practice (only imports what's needed):

typescript
import { of } from 'rxjs';

@Component({
// ...
})
export class MyComponent {
data$ = of(1, 2, 3);
}

With the second approach, tree shaking can remove all other RxJS operators and functions that aren't used in your application.

Common Issues that Prevent Effective Tree Shaking

1. Side Effects

Code that performs side effects (like modifying global state) can't be safely tree-shaken:

typescript
// This might not be tree-shaken because it has a side effect
export function initialize() {
window.appInitialized = true;
}

2. Importing Entire Modules

Importing entire modules limits tree shaking effectiveness:

typescript
// Bad for tree shaking
import * as _ from 'lodash';

// Better for tree shaking
import { map, filter } from 'lodash';

3. Using Dynamic Imports Incorrectly

Dynamic imports should be used for code-splitting, not to circumvent tree shaking:

typescript
// This may prevent proper tree shaking
const moduleToUse = someCondition ? './module-a' : './module-b';
import(moduleToUse).then(module => {
// Use module
});

Best Practices for Angular Tree Shaking

  1. Use explicit imports:

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

    // Not ideal for tree shaking
    import * as ng from '@angular/core';
  2. Avoid side effects in your modules:

    typescript
    // Avoid this pattern
    console.log('Module loaded'); // Side effect!

    export class MyService { }
  3. Use Angular's build optimizer: The --build-optimizer flag (included in production builds) enhances tree shaking capabilities.

  4. Use Angular paths for imports:

    typescript
    // Good
    import { HttpClient } from '@angular/common/http';

    // Not ideal
    import { HttpClient } from '@angular/common';
  5. Properly configure your tsconfig.json:

    json
    {
    "compilerOptions": {
    "module": "esnext",
    // Other options...
    }
    }

Real-world Application: Creating a Feature Module

Let's see how tree shaking works in a real-world scenario with feature modules:

typescript
// feature.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FeatureComponent } from './feature.component';
import { OptionalComponent } from './optional.component';

@NgModule({
imports: [CommonModule],
declarations: [FeatureComponent, OptionalComponent],
exports: [FeatureComponent]
})
export class FeatureModule { }

If OptionalComponent is only used within the feature module but never referenced by your application, tree shaking will remove it from your production bundle.

Analyzing Bundle Size

To see the effects of tree shaking, you can analyze your bundle size:

  1. Use the source-map-explorer tool:

    bash
    npm install --save-dev source-map-explorer
    ng build --prod --source-map
    npx source-map-explorer dist/your-app/*.js
  2. Use the webpack-bundle-analyzer:

    bash
    npm install --save-dev webpack-bundle-analyzer

    Then add to your angular.json:

    json
    "scripts": [
    "node_modules/webpack-bundle-analyzer/lib/bin/analyzer.js"
    ]

Summary

Tree shaking is a powerful optimization technique in Angular that significantly reduces your application's bundle size by removing unused code. By following the best practices outlined in this guide, you can ensure that your Angular applications are as lean and fast as possible:

  • Use ES modules syntax and explicit imports
  • Avoid side effects in your modules
  • Configure your build process correctly
  • Import only what you need from libraries
  • Analyze your bundle size regularly

Remember that effective tree shaking is just one part of a comprehensive performance optimization strategy for Angular applications.

Additional Resources

  1. Angular Build Performance Guide
  2. Webpack Tree Shaking Documentation
  3. RxJS Pipeable Operators - examples of tree-shakeable imports

Exercises

  1. Create a utility module with multiple functions and verify that only the functions you use are included in your final bundle.
  2. Analyze an existing Angular application using source-map-explorer to identify opportunities for bundle size reduction.
  3. Refactor a component that imports an entire library to use specific imports and measure the impact on bundle size.
  4. Create a feature module with lazy loading and observe how tree shaking affects the main bundle and feature bundle sizes.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)