Lesson 40-Module Splitting and Lazy Loading

Code Splitting: SplitChunksPlugin

Webpack’s SplitChunksPlugin is a powerful tool for optimizing application load times and performance. By splitting code into smaller chunks, it reduces initial load times and enhances user experience.

SplitChunksPlugin Overview

Introduced in Webpack 4, SplitChunksPlugin automatically splits code into chunks, extracting common code into separate files for reuse and caching.

SplitChunksPlugin Parameters

SplitChunksPlugin uses configurable parameters to determine how code is split:

  • chunks: Specifies the types of chunks to process: 'initial' (initial chunks), 'async' (on-demand chunks), or 'all' (all chunks).
  • minSize: Minimum module size before compression, in bytes.
  • minChunks: Minimum number of times a module must be referenced to be split.
  • maxAsyncRequests: Maximum number of on-demand loading requests, controlling split granularity.
  • maxInitialRequests: Maximum number of initial loading requests, controlling initial load speed.
  • name: Name of the split chunks, customizable or defaults to a rule-based name.
  • cacheGroups: Cache groups defining shared splitting rules.

Configuration Example

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

Dynamic Imports

Combining SplitChunksPlugin with dynamic import() enables on-demand and lazy loading, further optimizing performance.

src/index.js

import React, { lazy, Suspense } from 'react';

const AsyncComponent = lazy(() => import('./AsyncComponent'));

function App() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <AsyncComponent />
      </Suspense>
    </div>
  );
}

export default App;

Analysis and Debugging

Inspecting the generated webpack-stats.json file provides insights into how SplitChunksPlugin splits chunks, including their sizes and dependencies.

Best Practices

  • Extract Common Modules: Move third-party libraries and frameworks to separate chunks to leverage browser caching.
  • On-Demand Loading: Use dynamic imports and lazy loading to load code only when needed.
  • Balance Splitting Granularity: Avoid excessive splitting, which can lead to too many small files and impact performance.

Code Example Analysis

Assume a simple React application with an entry file and multiple components.

webpack.config.js

module.exports = {
  //...
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true,
        },
      },
    },
  },
};

src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));

src/App.js

import React from 'react';
import ComponentA from './ComponentA';
import ComponentB from './ComponentB';

function App() {
  return (
    <div>
      <ComponentA />
      <ComponentB />
    </div>
  );
}

export default App;

Running Webpack generates multiple files in the output directory, including main.js, vendors.js, and other on-demand chunks.

SplitChunksPlugin is a powerful Webpack tool for code splitting, significantly improving application load speed and performance when configured appropriately. Understanding its principles and configuration options helps optimize the build process, enabling efficient, responsive frontend applications. In real-world projects, combining dynamic imports and lazy loading further enhances user experience, making it a critical part of modern frontend engineering.

Lazy Loading (Code Splitting): import() Syntax

Lazy loading, or code splitting, is an optimization technique that loads code on-demand at runtime instead of loading the entire application upfront. Webpack supports this through the dynamic import() syntax, significantly improving application startup speed and performance.

Dynamic import() Syntax

The dynamic import() is an ECMAScript feature for asynchronous module loading, returning a Promise that resolves to the loaded module.

import(modulePath)
  .then((module) => {
    // Use the module
  })
  .catch((error) => {
    // Handle loading errors
  });

Difference from Static Imports

Static imports are resolved at compile time, while dynamic import() is resolved at runtime, allowing conditional module loading based on runtime conditions.

Dynamic import() with Webpack

Webpack recognizes dynamic import() and converts it into a code-splitting point, generating additional chunk files for on-demand loading.

Usage Example

Assume a React application where a component is lazily loaded.

src/index.js

import React, { lazy, Suspense } from 'react';
import ReactDOM from 'react-dom';

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

ReactDOM.render(
  <Suspense fallback={<div>Loading...</div>}>
    <LazyComponent />
  </Suspense>,
  document.getElementById('root')
);

src/LazyComponent.js

import React from 'react';

const LazyComponent = () => <div>This is a lazy-loaded component.</div>;

export default LazyComponent;

Internal Mechanism of Dynamic import()

Webpack transforms dynamic import() into a special function call that requests and loads the module at runtime. It also generates an additional chunk file, which is asynchronously loaded when import() is called.

Combining with SplitChunksPlugin

SplitChunksPlugin can be used with dynamic import() to further optimize code splitting, such as extracting commonly used third-party libraries into a separate chunk.

Best Practices

  • On-Demand Loading: Load code only when needed to reduce initial load time.
  • Use Suspense Component: Display a loading indicator while lazy-loaded components are being fetched.
  • Avoid Over-Splitting: Excessive splitting may increase network requests, impacting performance.

Code Example Analysis

Let’s break down the example code step-by-step.

Step 1: Define the Lazy-Loaded Component

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

Here, LazyComponent is defined as a lazy-loaded component, loaded asynchronously during the first render.

Step 2: Use the Suspense Component

<Suspense fallback={<div>Loading...</div>}>
  <LazyComponent />
</Suspense>

The Suspense component displays a placeholder while the lazy-loaded component is being loaded.

Step 3: Render the Application

ReactDOM.render(
  <Suspense fallback={<div>Loading...</div>}>
    <LazyComponent />
  </Suspense>,
  document.getElementById('root')
);

The application is rendered, and if LazyComponent is not yet loaded, Suspense shows the loading indicator.

Share your love