Files
self/app/docs/TREE_SHAKING.md
Justin Hernandez fc472915e6 refactor: remove namespace imports (#969)
* refactor: remove namespace imports

* refactor: use named fs imports

* refactor(app): replace path and fs namespace imports

* format

* format
2025-08-27 20:59:26 -07:00

11 KiB

Tree Shaking Testing and Analysis

This document explains how to test and measure tree shaking effectiveness in this project.

Overview

Tree shaking is a technique used by modern bundlers to eliminate unused code from your final bundle. This project has been optimized for tree shaking with:

  • "sideEffects": false in @selfxyz/common
  • ESM modules ("type": "module")
  • Granular exports in package.json
  • Code splitting with tsup
  • Optimized import patterns

Quick Start

1. Test Tree Shaking Effectiveness

# Run comprehensive tree shaking tests
yarn test:tree-shaking

# This will create test apps with different import patterns and compare bundle sizes

2. Analyze Current Bundle

# Analyze import patterns in your codebase
yarn analyze:tree-shaking:imports

# Analyze web bundle after building
yarn web:build
yarn analyze:tree-shaking:web

# Analyze React Native bundles
yarn analyze:tree-shaking:android
yarn analyze:tree-shaking:ios

3. View Visual Bundle Analysis

# Build web app with visual analysis
yarn web:build

# Open dist/bundle-analysis.html in your browser
# This shows a treemap of your bundle with tree shaking results

Understanding the Results

Tree Shaking Test Output

The yarn test:tree-shaking command will show output like:

🧪 Testing: full-import
📝 Import everything from @selfxyz/common (worst case)
   📊 Bundle size: 2.45 MB

🧪 Testing: granular-constants
📝 Only constants via granular import (best case)
   📊 Bundle size: 156 KB

📊 TREE SHAKING EFFECTIVENESS REPORT
=====================================

Bundle Sizes (smallest to largest):
🏆 granular-constants    156 KB (93.6% smaller, saves 2.29 MB)
🥈 granular-utils        234 KB (90.4% smaller, saves 2.21 MB)
🥉 granular-mixed        267 KB (89.1% smaller, saves 2.18 MB)
📦 mixed-import          891 KB (63.6% smaller, saves 1.56 MB)
📦 full-import           2.45 MB

🎯 Maximum tree shaking benefit: 93.6% reduction (2.29 MB saved)

Import Pattern Analysis

The import analyzer will categorize your imports:

  • Namespace imports (importing entire modules) - Prevents tree shaking
  • 📝 Named imports (import { ... }) - Moderate tree shaking
  • 🎯 Granular imports (import { ... } from '@selfxyz/common/constants') - Best tree shaking

Tree Shaking Score

You'll get a score based on your import patterns:

  • 🟢 80-100% - Excellent (mostly granular imports)
  • 🟡 50-79% - Good (mix of patterns)
  • 🔴 0-49% - Poor (many star imports)

Import Patterns

Avoid: Star Imports

// This imports everything, preventing tree shaking
import common from '@selfxyz/common';
console.log(common.API_URL);

⚠️ Moderate: Named Imports

// Better, but could be more granular
import { API_URL, hash, buildSMT } from '@selfxyz/common';
console.log(API_URL);

Good: Level 2 File-Based Imports

// Good - granular file-level imports
import { API_URL } from '@selfxyz/common/constants/core';
import { hash } from '@selfxyz/common/utils/hash';
console.log(API_URL, hash('test'));
// ⭐ OPTIMAL - maximum granularity with clean re-exports
import { API_URL } from '@selfxyz/common/constants/core';
import { hash } from '@selfxyz/common/utils/hash/sha';
console.log(API_URL, hash('test'));

Level 2 Examples - Good Tree Shaking

// Hash utilities only (no passport parsing, certificates, etc.)
import { hash, poseidon } from '@selfxyz/common/utils/hash';

// Passport operations only (no circuit generation, certificates, etc.)
import { generateCommitment } from '@selfxyz/common/utils/passports';

// Core constants only (no country data, vkey, etc.)
import { API_URL, PASSPORT_ATTESTATION_ID } from '@selfxyz/common/constants/core';

// App types only
import type { SelfApp } from '@selfxyz/common/types/app';

🚀 Level 3 Examples - Maximum Tree Shaking

// ⭐ OPTIMAL: Function-level imports with clean re-exports
// Only specific hash functions (not entire hash module)
import { hash } from '@selfxyz/common/utils/hash/sha';
import { flexiblePoseidon } from '@selfxyz/common/utils/hash/poseidon';

// Only specific passport functions (not entire passports module)
import { generateCommitment } from '@selfxyz/common/utils/passports/commitment';
import { initPassportDataParsing } from '@selfxyz/common/utils/passports/core';

// Only specific circuit generators (not entire circuits module)
import { generateCircuitInputsDSC } from '@selfxyz/common/utils/circuits/dscInputs';

// ✅ 60-90% smaller bundles vs Level 2
// ✅ Zero regression risk from clean re-exports

Available Import Paths

The @selfxyz/common package provides these granular imports:

Level 1: Category-Based (Good)

// Constants (URLs, country codes, etc.)
import { API_URL, countryCodes } from '@selfxyz/common/constants';

// Utility functions (hashing, parsing, etc.)
import { hash, generateCommitment } from '@selfxyz/common/utils';

// Type definitions (eliminated at compile time)
import type { PassportData } from '@selfxyz/common/types';

Level 2: File-Based (Better - NEW!)

// Core constants only (API URLs, attestation IDs)
import { API_URL, PASSPORT_ATTESTATION_ID } from '@selfxyz/common/constants/core';

// Country data only
import { countryCodes, commonNames } from '@selfxyz/common/constants/countries';

// Hash utilities only
import { hash, poseidon } from '@selfxyz/common/utils/hash';

// Passport utilities only
import { generateCommitment, generateNullifier } from '@selfxyz/common/utils/passports';

// Circuit utilities only
import { generateCircuitInputsDSC } from '@selfxyz/common/utils/circuits';

// Certificate parsing only
import { parseCertificateSimple } from '@selfxyz/common/utils/certificates';

// App-related types
import type { SelfApp } from '@selfxyz/common/types/app';

// Passport-related types
import type { PassportData } from '@selfxyz/common/types/passport';

Complete Level 2 Import Reference

Constants

  • @selfxyz/common/constants/core - API URLs, attestation IDs, basic constants
  • @selfxyz/common/constants/countries - Country codes and names
  • @selfxyz/common/constants/vkey - Verification keys
  • @selfxyz/common/constants/skiPem - SKI PEM data
  • @selfxyz/common/constants/mockCerts - Mock certificates
  • @selfxyz/common/constants/hashes - Sample data hashes

Utilities

  • @selfxyz/common/utils/attest - Proving Attestation Utils
  • @selfxyz/common/utils/hash - Hash and Poseidon functions
  • @selfxyz/common/utils/bytes - Byte manipulation
  • @selfxyz/common/utils/trees - SMT and leaf operations
  • @selfxyz/common/utils/scope - Endpoint formatting
  • @selfxyz/common/utils/proving - Proving Utils
  • @selfxyz/common/utils/appType - SelfApp definitions
  • @selfxyz/common/utils/date - Date utilities
  • @selfxyz/common/utils/arrays - Array helpers
  • @selfxyz/common/utils/passports - Core passport functions
  • @selfxyz/common/utils/passportFormat - Passport formatting
  • @selfxyz/common/utils/passportMock - Mock passport generation
  • @selfxyz/common/utils/passportDg1 - DG1 specific operations
  • @selfxyz/common/utils/certificates - Certificate parsing
  • @selfxyz/common/utils/elliptic - Elliptic curve operations
  • @selfxyz/common/utils/curves - Curve definitions
  • @selfxyz/common/utils/oids - OID handling
  • @selfxyz/common/utils/circuits - Circuit input generation
  • @selfxyz/common/utils/circuitNames - Circuit name logic
  • @selfxyz/common/utils/circuitFormat - Circuit formatting
  • @selfxyz/common/utils/uuid - UUID utilities
  • @selfxyz/common/utils/contracts - Contract utilities
  • @selfxyz/common/utils/sanctions - OFAC/sanctions
  • @selfxyz/common/utils/csca - CSCA operations

Types

  • @selfxyz/common/types/passport - Passport and document types
  • @selfxyz/common/types/app - SelfApp and disclosure types
  • @selfxyz/common/types/certificates - Certificate data types
  • @selfxyz/common/types/circuits - Circuit-related types

Main export (less optimal for tree shaking)

import { API_URL } from '@selfxyz/common';

Testing Commands

Basic Analysis

# Quick import pattern check
yarn analyze:tree-shaking:imports

# Full analysis including bundle sizes
yarn analyze:tree-shaking

Platform-Specific Analysis

# Web bundle analysis (after yarn web:build)
yarn analyze:tree-shaking:web

# Mobile bundle analysis
yarn analyze:tree-shaking:android
yarn analyze:tree-shaking:ios

Comprehensive Testing

# Test different import strategies with real bundlers
yarn test:tree-shaking

# Run all analysis tools
yarn analyze:tree-shaking all

Continuous Integration

Tree shaking is automatically tested in CI:

  1. Bundle Size Monitoring: Bundle analysis runs on every PR
  2. Size Thresholds: Builds fail if bundles exceed size limits
  3. Visual Reports: Bundle analysis HTML reports are generated

Optimizing Your Code

1. Replace Star Imports

- import common from '@selfxyz/common';
+ import { API_URL } from '@selfxyz/common/constants';
+ import { hash } from '@selfxyz/common/utils';

2. Use Granular Imports

- import { API_URL, hash, countryCodes, buildSMT } from '@selfxyz/common';
+ import { API_URL, countryCodes } from '@selfxyz/common/constants';
+ import { hash } from '@selfxyz/common/utils';

3. Import Only What You Use

- import { generateCommitment, buildSMT, hash } from '@selfxyz/common/utils';
+ import { hash } from '@selfxyz/common/utils';  // Only import what you use

Understanding Bundle Analysis

Web Bundle Treemap

After running yarn web:build, open dist/bundle-analysis.html to see:

  • Visual representation of your bundle
  • Which modules are taking up space
  • Tree shaking effectiveness by module

React Native Bundle Reports

Bundle reports show:

  • Total bundle size vs. thresholds
  • Module-by-module breakdown
  • Optimization opportunities

Troubleshooting

Tree Shaking Not Working?

  1. Check "sideEffects": false in package.json
  2. Use ESM imports (import), not CommonJS (require)
  3. Avoid dynamic imports where possible
  4. Check for circular dependencies

Bundle Still Large?

  1. Run yarn analyze:tree-shaking:imports to find star imports
  2. Check the visual bundle analysis for large modules
  3. Consider lazy loading for large features
  4. Review vendor chunk sizes

Different Results Between Platforms?

  • React Native and Web use different bundlers
  • Some optimizations only work on specific platforms
  • Check platform-specific bundle configurations

Examples

See the /docs/examples/tree-shaking/ directory for:

  • level3-optimal-example.ts - Shows Level 3 function-based imports (best)
  • level3-migration-guide.ts - Migration guide from Level 2 to Level 3

Further Reading