Lesson 08-Modular Performance Optimization

Module Loading Performance Optimization

Reducing Network Requests

HTTP/2 Optimization Strategies:

  1. Multiplexing: Leverages HTTP/2’s multiplexing to eliminate HTTP/1.1 request number limitations.
  2. Header Compression: Uses the HPACK algorithm to compress request headers, reducing transmission overhead.
  3. Server Push: Proactively pushes critical resources to the client’s cache.

Implementation Example:

// Webpack configuration for HTTP/2 optimization
output: {
  filename: '[name].[contenthash].js',
  chunkFilename: '[name].[contenthash].chunk.js',
  publicPath: 'https://cdn.example.com/assets/' // Use CDN for acceleration
},
performance: {
  hints: 'warning',
  maxAssetSize: 244 * 1024, // 244KB
  maxEntrypointSize: 244 * 1024 // 244KB
}

Resource Merging Strategies:

  1. Critical CSS Inlining: Inline critical CSS for the first screen directly into HTML.
  2. JS/CSS Merging: Combine small files into larger chunks.
  3. On-Demand Merging: Avoid excessive merging to prevent cache invalidation.
// Webpack configuration for merging small files
optimization: {
  concatenateModules: true, // Scope hoisting
  splitChunks: {
    minSize: 30000, // Split chunks larger than 30KB
    maxSize: 244000 // Attempt to split chunks larger than 244KB into smaller ones
  }
}

Module Preloading and Prefetching

Preloading Technology Comparison:

TechnologyPurposePriorityUse Case
preloadResources essential for the current pageHighCritical rendering path resources
prefetchResources likely needed in the futureLowPredictive loading
preconnectEstablish connections in advanceMediumThird-party resource domains
dns-prefetchPre-resolve DNSLowCross-domain resources

Implementation Example:

<!-- Preload critical resources -->
<link rel="preload" href="critical.js" as="script">
<link rel="preload" href="main.css" as="style">

<!-- Predictive prefetching -->
<link rel="prefetch" href="next-page.js" as="script">
<link rel="prefetch" href="user-profile.jpg" as="image">

<!-- Pre-establish connections -->
<link rel="preconnect" href="https://api.example.com">
<link rel="dns-prefetch" href="https://cdn.example.com">

Dynamic Preloading:

// Predictive preloading based on user behavior
document.addEventListener('mousemove', throttle(e => {
  if (e.clientX > window.innerWidth - 100) {
    // User may scroll, preload next page module
    import(/* webpackPrefetch: true */ './next-page-module');
  }
}, 200));

// Route-based preloading
const routes = [
  {
    path: '/dashboard',
    component: () => import(/* webpackPrefetch: true */ './Dashboard')
  }
];

Module Caching Strategies

Cache Control Best Practices:

  1. Strong Caching Configuration:
Cache-Control: public, max-age=31536000, immutable
  1. Negotiated Caching Configuration:
ETag: "xyz123"
Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT
  1. Service Worker Caching:
// sw.js
const CACHE_NAME = 'v2';
const PRECACHE_URLS = [
  '/index.html',
  '/styles/main.[contenthash].css',
  '/scripts/main.[contenthash].js'
];

self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => cache.addAll(PRECACHE_URLS))
  );
});

self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request)
      .then(response => response || fetchAndCache(event.request))
  );
});

Caching Strategy Matrix:

Resource TypeCaching StrategyExample
Core JS/CSSStrong caching + content hashmain.[contenthash].js
Images/FontsStrong caching + CDN/images/logo.[hash].png
API ResponsesNegotiated cachingETag/Last-Modified
Third-Party LibrariesLong-term cachingreact.[version].js

Concurrent Module Loading Control

Concurrent Loading Optimization Strategies:

  1. HTTP/2 Concurrency Advantages:
  • Supports multiplexing by default (typically 100+ parallel streams).
  • Avoids HTTP/1.1 head-of-line blocking issues.
  1. Request Priority Control:
<link rel="preload" href="critical.js" as="script" importance="high">
<link rel="preload" href="non-critical.js" as="script" importance="low">
  1. Resource Loading Order Optimization:
// Use Webpack magic comments to control loading priority
import(/* webpackPreload: true */ 'CriticalModule');
import(/* webpackPrefetch: true */ 'NonCriticalModule');

Concurrency Limitation Solution:

// Custom concurrency controller
class ResourceLoader {
  constructor(maxConcurrent = 6) {
    this.maxConcurrent = maxConcurrent;
    this.queue = [];
    this.activeCount = 0;
  }

  load(resource) {
    return new Promise((resolve, reject) => {
      const task = () => {
        this.activeCount++;
        resource()
          .then(resolve)
          .catch(reject)
          .finally(() => {
            this.activeCount--;
            this.next();
          });
      };

      if (this.activeCount < this.maxConcurrent) {
        task();
      } else {
        this.queue.push(task);
      }
    });
  }

  next() {
    if (this.queue.length > 0 && this.activeCount < this.maxConcurrent) {
      const task = this.queue.shift();
      task();
    }
  }
}

// Usage example
const loader = new ResourceLoader(4); // Max 4 concurrent
const resources = [/* Array of resource loading functions */];
Promise.all(resources.map(r => loader.load(r)));

Monitoring and Analyzing Module Loading Performance

Performance Monitoring Metrics:

  1. FP (First Paint): Time to first render.
  2. FCP (First Contentful Paint): Time to first content render.
  3. FMP (First Meaningful Paint): Time to first meaningful render.
  4. TTI (Time To Interactive): Time to interactivity.
  5. TBT (Total Blocking Time): Total blocking time.

Monitoring Tool Integration:

// Use web-vitals to monitor core web vitals
import { getCLS, getFID, getLCP, getFCP, getTTFB } from 'web-vitals';

function sendToAnalytics(metric) {
  const body = JSON.stringify(metric);
  navigator.sendBeacon('/analytics', body);
}

getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getLCP(sendToAnalytics);
getFCP(sendToAnalytics);
getTTFB(sendToAnalytics);

// Custom module loading performance monitoring
const moduleTimings = {};

function trackModuleLoad(moduleName) {
  moduleTimings[moduleName] = {
    start: performance.now(),
    end: null,
    duration: null
  };

  return {
    end: () => {
      moduleTimings[moduleName].end = performance.now();
      moduleTimings[moduleName].duration = 
        moduleTimings[moduleName].end - moduleTimings[moduleName].start;

      // Send monitoring data
      if (moduleTimings[moduleName].duration > 100) {
        console.warn(`Module ${moduleName} load time: ${moduleTimings[moduleName].duration.toFixed(2)}ms`);
        // Can send to monitoring system
      }
    }
  };
}

// Usage example
const timer = trackModuleLoad('heavy-component');
import('./heavy-component').then(() => {
  timer.end();
});

Performance Analysis Toolchain:

  1. Chrome DevTools Performance Panel
  2. Lighthouse CI Integration
  3. WebPageTest In-Depth Analysis
  4. RUM (Real User Monitoring) Systems

Module Bundle Size Optimization

Deep Application of Tree Shaking

Deep Tree Shaking Configuration:

// webpack.config.js
module.exports = {
  optimization: {
    usedExports: true, // Mark unused code
    minimize: true,    // Enable minification
    concatenateModules: true, // Scope hoisting
    sideEffects: true // Enable side effect analysis
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            plugins: ['@babel/plugin-transform-runtime']
          }
        }
      }
    ]
  }
};

package.json Configuration:

{
  "name": "my-library",
  "sideEffects": [
    "*.css",
    "*.global.js"
  ],
  "exports": {
    ".": {
      "import": "./dist/esm/index.js",
      "require": "./dist/cjs/index.js"
    }
  }
}

Advanced Tree Shaking Techniques:

  1. Dynamic Import Splitting:
// Split less commonly used features into separate modules
const HeavyComponent = React.lazy(() => import('./HeavyComponent'));
  1. Pure Function Marking:
/*#__PURE__*/
const result = expensiveCalculation();
  1. Avoiding Side Effects:
// Avoid side effects at module top level
// Bad
console.log('Module loaded');

// Good
export function init() {
  console.log('Explicit initialization');
}

Code Splitting and Lazy Loading

Code Splitting Strategies:

Entry Splitting:

entry: {
  main: './src/main.js',
  admin: './src/admin.js'
}

Dynamic Import Splitting:

const LazyComponent = React.lazy(() => import('./LazyComponent'));

Common Code Extraction:

optimization: {
  splitChunks: {
    chunks: 'all',
    cacheGroups: {
      vendors: {
        test: /[\\/]node_modules[\\/]/,
        priority: -10
      },
      default: {
        minChunks: 2,
        priority: -20
      }
    }
  }
}

Route-Level Code Splitting:

// React Router example
const routes = [
  {
    path: '/dashboard',
    component: React.lazy(() => import('./Dashboard'))
  },
  {
    path: '/settings',
    component: React.lazy(() => import('./Settings'))
  }
];

function App() {
  return (
    <Suspense fallback={<LoadingSpinner />}>
      <Routes>
        {routes.map(route => (
          <Route 
            key={route.path} 
            path={route.path} 
            element={<route.component />} 
          />
        ))}
      </Routes>
    </Suspense>
  );
}

Compression and Obfuscation

Compression Tool Configuration:

// Webpack Terser configuration
const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  optimization: {
    minimizer: [
      new TerserPlugin({
        parallel: true,
        terserOptions: {
          compress: {
            drop_console: true, // Remove console
            pure_funcs: ['console.log'], // Remove specific functions
            dead_code: true // Remove dead code
          },
          mangle: {
            properties: {
              regex: /^_/ // Only mangle properties starting with underscore
            }
          },
          format: {
            comments: false // Remove comments
          }
        }
      })
    ]
  }
};

Advanced Compression Techniques:

  1. Conditional Compression:
new TerserPlugin({
  terserOptions: {
    compress: {
      conditionals: true,
      comparisons: true,
      evaluate: true
    }
  }
})
  1. Preserving Specific Names:
new TerserPlugin({
  terserOptions: {
    mangle: {
      reserved: ['React', 'Component'] // Do not mangle these names
    }
  }
})
  1. Source Map Optimization:
devtool: 'hidden-source-map' // Use in production

Image and Font Module Optimization

Image Optimization Strategies:

  1. Base64 Inlining:
// Webpack url-loader configuration
module: {
  rules: [
    {
      test: /\.(png|jpg|gif)$/,
      use: [
        {
          loader: 'url-loader',
          options: {
            limit: 8192, // Convert to Base64 if smaller than 8KB
            name: 'images/[name].[hash:8].[ext]'
          }
        }
      ]
    }
  ]
}
  1. Responsive Images:
<picture>
  <source srcset="image.webp" type="image/webp">
  <source srcset="image.jpg" type="image/jpeg"> 
  <img src="image.jpg" alt="Example image">
</picture>
  1. CDN Acceleration:
// Webpack publicPath pointing to CDN
output: {
  publicPath: 'https://cdn.example.com/assets/'
}

Font Optimization Strategies:

// Webpack font file configuration
module: {
  rules: [
    {
      test: /\.(woff|woff2|eot|ttf|otf)$/,
      use: [
        {
          loader: 'file-loader',
          options: {
            name: 'fonts/[name].[hash:8].[ext]'
          }
        }
      ]
    }
  ]
}

Bundle Size Analysis Tools

Webpack Bundle Analyzer:

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: 'server', // or 'static'/'disabled'
      analyzerHost: '127.0.0.1',
      analyzerPort: 8888,
      reportFilename: 'report.html',
      defaultSizes: 'parsed',
      openAnalyzer: true,
      generateStatsFile: false,
      statsFilename: 'stats.json',
      statsOptions: null,
      logLevel: 'info'
    })
  ]
};

Analysis Report Interpretation:

  1. Module Size Proportion: Identify the largest modules.
  2. Dependency Graph: Discover unnecessary dependencies.
  3. Duplicate Dependencies: Find libraries bundled multiple times.
  4. Unused Code: Identify parts eligible for Tree Shaking.

Other Analysis Tools:

  1. source-map-explorer:
npx source-map-explorer dist/main.js
  1. webpack-bundle-analyzer Visualization:
npx webpack-bundle-analyzer stats.json
  1. Lighthouse Performance Audit:
npm install -g lighthouse
lighthouse http://localhost:3000 --view

Modularization and Runtime Performance

Module Initialization Performance

Reducing Initialization Overhead:

  1. Deferring Global Variables:
// Bad: Immediate execution polluting globals
const utils = { /* ... */ };
window.utils = utils;

// Good: Load on demand or defer exposure
export const utils = { /* ... */ };
  1. Staged Initialization:
// Initialize core functionality immediately
initCore();

// Defer non-critical functionality initialization
setTimeout(initNonCritical, 0);
// or
window.requestIdleCallback(initNonCritical);

Optimization Example:

// Module design optimization
// Bad: Immediate heavy computation on module load
export function init() {
  const heavyData = computeHeavyData();
  return { heavyData };
}

// Good: Compute on demand
export function createHeavyDataCalculator() {
  return {
    compute: () => computeHeavyData() // Deferred execution
  };
}

Module Caching and Reuse

Caching Strategy Implementation:

// Module-level caching
const moduleCache = new Map();

export function getModule(moduleName) {
  if (moduleCache.has(moduleName)) {
    return moduleCache.get(moduleName);
  }

  const module = loadModule(moduleName); // Actual loading logic
  moduleCache.set(moduleName, module);
  return module;
}

// Component-level caching
const componentCache = {};

function getCachedComponent(key, factory) {
  if (!componentCache[key]) {
    componentCache[key] = factory();
  }
  return componentCache[key];
}

Memory Management:

// Cache with cleanup mechanism
class ModuleCache {
  constructor(maxSize = 10) {
    this.cache = new Map();
    this.maxSize = maxSize;
  }

  get(key) {
    if (!this.cache.has(key)) return null;
    const value = this.cache.get(key);
    this.cache.delete(key);
    this.cache.set(key, value); // Update access order
    return value;
  }

  set(key, value) {
    if (this.cache.size >= this.maxSize) {
      const oldestKey = this.cache.keys().next().value;
      this.cache.delete(oldestKey);
    }
    this.cache.set(key, value);
  }

  clear() {
    this.cache.clear();
  }
}

Modularization and Virtual DOM Synergy Optimization

Virtual DOM Update Optimization:

  1. Component-Level Caching:
// React.memo for memoized components
const MemoizedComponent = React.memo(function MyComponent(props) {
  /* Render using props */
});

// Memo with custom comparison function
const MemoizedComponent = React.memo(
  MyComponent,
  (prevProps, nextProps) => {
    // Custom props comparison logic
    return prevProps.id === nextProps.id;
  }
);
  1. Key Optimization:
<!-- Use stable unique IDs for keys -->
<ul>
  {items.map(item => (
    <ListItem 
      key={item.id} 
      item={item} 
    />
  ))}
</ul>
  1. Batch Updates:
// React batch updates
ReactDOM.unstable_batchedUpdates(() => {
  setState1();
  setState2();
});

// Or use automatic batching (React 18+)

Modularization and Web Worker Integration

Web Worker Integration Patterns:

  1. Main Thread-Worker Communication:
// Main thread
const worker = new Worker('worker.js');

worker.postMessage({ type: 'CALCULATE', data: inputData });

worker.onmessage = (event) => {
  const result = event.data;
  // Process result
};

// worker.js
self.onmessage = (event) => {
  if (event.data.type === 'CALCULATE') {
    const result = heavyCalculation(event.data.data);
    self.postMessage(result);
  }
};
  1. Modularized Worker:
// Use comlink to simplify Worker communication
import * as Comlink from 'comlink';

// worker.js
const api = {
  heavyCalculation(data) {
    // Computation-intensive task
  }
};

Comlink.expose(api);

// Main thread
const workerApi = Comlink.wrap(new Worker('worker.js'));
const result = await workerApi.heavyCalculation(data);
  1. Worker Pool Management:
class WorkerPool {
  constructor(size, workerPath) {
    this.pool = Array(size).fill().map(() => new Worker(workerPath));
    this.queue = [];
    this.activeCount = 0;
  }

  execute(task) {
    return new Promise((resolve, reject) => {
      const runTask = () => {
        const worker = this.pool[this.activeCount++];
        worker.onmessage = (e) => {
          resolve(e.data);
          this.activeCount--;
          if (this.queue.length > 0) {
            this.queue.shift()();
          }
        };
        worker.onerror = reject;
        worker.postMessage(task);
      };

      if (this.activeCount < this.pool.length) {
        runTask();
      } else {
        this.queue.push(runTask);
      }
    });
  }
}

Modular Performance Testing and Benchmarking

Performance Testing Strategies:

  1. Unit Performance Testing:
// Use benchmark.js for benchmarking
const Benchmark = require('benchmark');
const suite = new Benchmark.Suite();

suite
  .add('Module A', function() {
    moduleA.heavyOperation();
  })
  .add('Module B', function() {
    moduleB.heavyOperation();
  })
  .on('cycle', function(event) {
    console.log(String(event.target));
  })
  .run({ 'async': true });
  1. End-to-End Performance Testing:
// Use Lighthouse CI for performance testing
module.exports = {
  ci: {
    collect: {
      url: ['http://localhost:3000'],
      numberOfRuns: 3
    },
    assert: {
      assertions: {
        'first-contentful-paint': ['error', { maxNumericValue: 1000 }],
        'interactive': ['error', { maxNumericValue: 5000 }]
      }
    },
    upload: {
      target: 'temporary-public-storage'
    }
  }
};

Performance Monitoring Metrics:

  1. Module Loading Time:
// Measure module loading time
const start = performance.now();
import('./module').then(module => {
  const duration = performance.now() - start;
  console.log(`Module load time: ${duration.toFixed(2)}ms`);
});
  1. Initialization Performance:
// Measure component initialization time
class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.initStart = performance.now();
  }

  componentDidMount() {
    const duration = performance.now() - this.initStart;
    console.log(`Component initialization time: ${duration.toFixed(2)}ms`);
  }

  render() {
    return <div>My Component</div>;
  }
}
  1. Runtime Performance:
// Use Performance API to monitor runtime performance
function measurePerformance() {
  performance.mark('start-task');

  // Perform task...

  performance.mark('end-task');
  performance.measure('task-duration', 'start-task', 'end-task');

  const measures = performance.getEntriesByName('task-duration');
  console.log(`Task duration: ${measures[0].duration.toFixed(2)}ms`);
}
Share your love