Performance Optimization - JS-CSS Code Minification

  • Terser is a toolset for JavaScript parsing, mangling, and compressing.
  • In the early days, we used uglify-js to minify and uglify our JavaScript code. However, it is no longer maintained and does not support ES6+ syntax.
  • Terser is a fork of uglify-es and retains most of its original APIs, compatible with uglify-es and uglify-js@3, etc.

webpack-terser

JavaScript Code Minification

Webpack provides the terser-webpack-plugin plugin for code optimization and minification.

In production mode, TerserPlugin is used by default for code processing.

const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  // Configure other Webpack options...

  optimization: {
    minimizer: [new TerserPlugin()],
  },
};

CSS Code Minification

Apart from JavaScript code, CSS code can also be minified using Webpack. Use css-minimizer-webpack-plugin to compress CSS code.

const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

module.exports = {
  // Configure other Webpack options...

  optimization: {
    minimizer: [
      new CssMinimizerPlugin(),
      // You can continue adding other compression plugins...
    ],
  },
};

Tree Shaking Implementation in Webpack

Tree shaking is a term commonly used to describe the removal of dead code in JavaScript context.

Tree Shaking in Webpack

In modern front-end development, optimizing code size is a crucial topic. Tree shaking is an optimization technique used to eliminate unused JavaScript modules in a project, reducing the size of the bundled files. Webpack provides built-in support, making it easy to implement tree shaking in projects.

Enable ES Module Syntax

First, ensure your JavaScript code follows ES module syntax, as Webpack’s tree shaking feature only works with ES modules. Use import and export syntax to define modules in your project.

// math.js
export function square(x) {
  return x * x;
}

export function cube(x) {
  return x * x * x;
}

Webpack Configuration

In the Webpack configuration file, ensure the following settings to enable tree shaking:

Set mode to 'production'. Webpack will automatically enable related optimizations, including tree shaking.

Implementing Tree Shaking for JavaScript

Webpack implements tree shaking using two different approaches:

  • usedExports: Marks certain functions as used, and later optimizes them with Terser.
  • sideEffects: Skips entire modules/files and checks if the file has side effects.

Using usedExports to Implement Tree Shaking

Set the mode to production:

module.exports = {
  mode: 'production',
  // ...other configurations
};

Configure usedExports in the optimization section:

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
 mode: 'development',
 optimization: {
   usedExports: true,
 },
};

Using sideEffects to Implement Tree Shaking

Set the sideEffects field in package.json:

  • Set it to false to inform Webpack that it can safely remove unused exports.
  • If there are specific files you want to keep, set it as an array.
{
  "name": "your-project",
  "sideEffects": ["./src/some-side-effectful-file.js"]
}

Webpack Side Effects

Understanding Tree Shaking and sideEffects

sideEffects and usedExports (more commonly considered tree shaking) are two different optimization techniques.

sideEffects is more efficient as it allows skipping entire modules/files and their entire subtree.

usedExports depends on terser to detect side effects in statements. It’s a more complex JavaScript task and is not as straightforward as sideEffects. Also, it cannot skip subtrees/dependencies because side effects need to be evaluated. While exported functions work as usual, higher-order functions (HOC) in the React framework can have issues in this scenario.

CSS Tree Shaking Implementation

For CSS tree shaking, additional plugins are required.

In the past, PurifyCss plugin was used for CSS tree shaking, but it’s no longer maintained (last update was 4 years ago).

A different library, PurgeCSS, can now be used for CSS tree shaking, helping remove unused CSS.

File Compression in Webpack

What is HTTP Compression

HTTP compression is a technique used between servers and clients to improve transmission speed and bandwidth utilization.
The process of HTTP compression is as follows:

  1. Data is compressed on the server before being sent. (Can be done in Webpack)
  2. Compatible browsers inform the server about supported compression formats during requests.
  3. The server returns the corresponding compressed file to the browser, indicating it in the response headers.

Popular Compression Formats

There are several popular compression formats:

  • compress: Method used by UNIX’s “compress” program (historical reasons, not recommended for most applications, use gzip or deflate instead).
  • deflate: Compression based on the deflate algorithm (defined in RFC 1951) and encapsulated in zlib data format.
  • gzip: GNU zip format (defined in RFC 1952), widely used compression algorithm.
  • br: A new open-source compression algorithm designed specifically for HTTP content encoding.

Webpack Configuration for File Compression

Webpack essentially performs the first step of HTTP compression. You can use the CompressionPlugin for this purpose.

Step 1: Install CompressionPlugin:

npm install compression-webpack-plugin -D

Step 2: Use CompressionPlugin in your Webpack configuration:

module.exports = {
  plugins: [
    new CompressionPlugin({
      test: /\.js(\?.*)?$/i,
    }),
  ],
};

Note: This article is a translated version of the original post. For the most accurate and up-to-date information, please refer to the original source.
```

I tried migrating a real project from Vite to Rspack. The build time reduced from 125 seconds to 17 seconds, and the page refresh speed during development increased by 64%. However, the HMR (Hot Module Replacement) in Rspack is much slower compared to Vite.

If you frequently trigger HMR during development and refresh the page less often, Vite still offers a better development experience. For complex projects where refreshing the page is more common, Rspack provides a better development experience.

Comparison

There are so many frontend build tools out there: RollDown, Rollup, Rspack, Vite…

Just a sneak peek; stay tuned for the detailed comparison.


Note: This article is a translated version of the original post. For the most accurate and up-to-date information, please refer to the original source.
```

Introduction

Let’s talk about why optimization is necessary. If your project is small and builds quickly, you might not need to worry too much about performance optimization. However, as your project grows with more pages, features, and business logic, the build time of webpack, the underlying build tool, also increases. At this point, optimizing performance becomes crucial.

Webpack offers various avenues for performance optimization, which can be broadly categorized into two areas:

Optimization One: Optimizing the built result for production, focusing on performance during deployment (e.g., code splitting, reducing bundle size, using CDN servers, etc.).
Optimization Two: Optimizing build speed for development or production build, enhancing the speed of the build process (e.g., using exclusion, cache loaders, etc.).
The performance during production directly affects user experience, whereas build time is closely related to developers’ daily workflow. If the local development server or production build takes too long, it significantly hampers productivity.

Performance Optimization - Code Splitting

Code splitting is a critical feature in webpack:

Its primary purpose is to separate code into different bundles, which can be loaded on demand or in parallel.
By default, all JavaScript code (business logic, third-party dependencies, and modules not immediately used) is loaded on the initial page load, impacting the loading speed.
Code splitting allows creating smaller bundles and controlling resource loading priorities, thereby enhancing code loading performance.

Webpack provides three common approaches to code splitting:

  • Entry Points: Manually splitting code using entry configuration
  • Preventing Duplication: Avoiding duplicate code using Entry Dependencies or SplitChunksPlugin
  • Dynamic Imports: Splitting code using inline functions in modules

Optimizing Entry Points - Entry Dependencies

When a project has multiple entry points, there might be issues with duplicate dependencies. Some modules might be referenced in multiple entry points, causing redundancy in the final output, increasing the output file size.

module.exports = {
  entry: {
    page1: {
      import: './src/page1.js',
      dependOn: 'shared',
    },
    page2: {
      import: './src/page2.js',
      dependOn: 'shared',
    },
    shared: './src/shared.js',
  },
  output: {
    filename: '[name].bundle.js',
    path: __dirname + '/dist',
  },
}; 

Dynamic Imports

Dynamic imports are a technique in webpack for lazy loading, allowing modules to load asynchronously at runtime instead of bundling all modules into a large initial file. This approach improves the initial loading speed and reduces the initial bundle size.

const path = require('path');

module.exports = {
  entry: {
    main: './src/index.js',
  },
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist'),
    publicPath: '/',
  },
  module: {
    rules: [
      // Add your loader rules here
    ],
  },
  optimization: {
    splitChunks: {
      chunks: 'all',
    },
  },
};

In the above configuration, code splitting is achieved using optimization.splitChunks with the option chunks: 'all'.

Then, dynamic imports can be used in the code like this:

// Dynamically import modules where needed
const loadModule = () => import('./Module');

loadModule().then(module => {
  // Use the loaded module
});

Webpack will split the modules imported using import() into separate files. These files will be loaded asynchronously when needed during runtime.

Custom Bundle Splitting - SplitChunks

Bundle splitting is an optimization strategy that allows breaking down code into smaller pieces, enabling faster content display during loading.

Webpack provides various strategies for bundle splitting, one of which involves using the SplitChunksPlugin plugin. This strategy is known as splitChunks.

module.exports = {
  // ...other configurations
  optimization: {
    splitChunks: {
      chunks: 'all',
      minSize: 30000, // Minimum size of the module before splitting
      minChunks: 1, // Minimum number of times a module should be duplicated before splitting
      maxAsyncRequests: 5, // Maximum number of parallel requests when loading modules on demand
      maxInitialRequests: 3, // Maximum number of parallel requests at an entry point
      automaticNameDelimiter: '~',
      name: true,
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          reuseExistingChunk: true,
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true,
        },
      },
    },
  },
};

For more information, refer to the webpack-split-chunks-plugin documentation.


Performance Optimization - CDN

CDN, or Content Delivery Network, refers to a network of interconnected servers strategically placed to deliver content to users efficiently. It ensures faster and more reliable delivery of resources such as music, images, videos, applications, and other files by utilizing servers closest to each user, providing high performance, scalability, and low-cost content delivery.

In development, CDN is typically used in two ways:

  • All static resources are bundled and stored on a CDN server, and users load resources exclusively through the CDN.
  • Some third-party resources are hosted on CDN servers.

Utilizing a Content Delivery Network (CDN) is a highly effective performance optimization strategy, especially within Webpack. CDN accelerates website loading speed, reduces server load, and enhances user experience. Here’s how you can configure and use CDN in Webpack:

Using CDN for Third-Party Libraries

Integrate third-party libraries used in your project (such as React, Vue, jQuery, etc.) through CDN links directly in the HTML file:

<script src="https://cdn.jsdelivr.net/npm/react@version/dist/react.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/react-dom@version/dist/react-dom.min.js"></script>

Configuring Externals in Webpack

In your Webpack configuration, utilize the externals field to inform Webpack about externally referenced modules that shouldn’t be bundled:

module.exports = {
  // ...other configurations
  externals: {
    react: 'React',
    'react-dom': 'ReactDOM',
  },
};

Then, include the CDN links in the HTML file using script tags:

<script src="https://cdn.jsdelivr.net/npm/react@version/dist/react.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/react-dom@version/dist/react-dom.min.js"></script>

Configuring CDN’s publicPath

In the Webpack output field, set the publicPath to specify the URL prefix for resource imports, typically set to the CDN’s address:

module.exports = {
  // ...other configurations
  output: {
    // ...other output configurations
    publicPath: 'https://cdn.example.com/',
  },
};

This way, during Webpack build, all resource paths will be prefixed with the CDN’s address.

Performance Optimization - Extracting CSS Files

Extracting CSS files from JavaScript bundles is a common performance optimization strategy. This approach reduces the size of JavaScript files, speeds up page loading, and allows browsers to download CSS and JavaScript files in parallel, enhancing loading performance. In Webpack, you can achieve this using the mini-css-extract-plugin plugin.

Webpack Configuration

In your Webpack configuration file, include the mini-css-extract-plugin plugin and configure the module.rules to handle CSS files:

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  // ...other configurations

  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
          // Additional CSS loaders like postcss-loader and sass-loader can be added here
        ],
      },
    ],
  },

  plugins: [
    new MiniCssExtractPlugin({
      filename: 'styles.css', // Filename for the extracted CSS file
    }),
  ],
};

Including CSS Files

In your JavaScript files or entry point file, import the CSS file:

import './styles.css';

Alternatively, in the HTML file, use the link tag to include the extracted CSS file:

<link rel="stylesheet" href="styles.css">

Performance Optimization - Bundling File Naming (Hash, ContentHash, ChunkHash)

In Webpack, how files are named during bundling is a crucial performance optimization strategy. Proper naming ensures that browsers can cache files correctly, avoiding unnecessary network requests and improving application loading speed. Here are three common bundling file naming techniques: Hash, ContentHash, and ChunkHash.

Hash

Hash is generated based on file content. When file content changes, its corresponding hash value also changes. In Webpack, you can use the [hash] placeholder to represent the hash value.

output: {
  filename: 'bundle.[hash].js',
}

ContentHash

ContentHash is generated based on file content as well, but unlike Hash, it’s solely influenced by file content and remains unaffected by file name or path changes. In Webpack, you can use the [contenthash] placeholder to represent the ContentHash value.

output: {
  filename: 'bundle.[contenthash].js',
}

ChunkHash

ChunkHash is generated based on module content. Different module contents result in different ChunkHash values. In Webpack, you can use the [chunkhash] placeholder to represent the ChunkHash value.

output: {
  filename: '[name].[chunkhash].js',
}

Performance Optimization - Implementing Tree Shaking in Webpack

JavaScript Tree Shaking:

Tree Shaking in JavaScript originates from the rollup bundler, a build tool. It relies on the static syntax analysis of ES Modules (no code execution) to determine module dependencies.

Webpack 2 introduced native support for ES2015 modules, enhancing tree shaking capabilities. Webpack 4 extended this ability and introduced the sideEffects property in package.json to indicate which files have no side effects, allowing webpack to safely remove unused code.

In Webpack 5, partial CommonJS tree shaking support was introduced.

CommonJS Tree Shaking

Implementing Tree Shaking in JavaScript

Webpack implements Tree Shaking through two methods:

  • usedExports: Marking certain functions as used and optimizing them using Terser.
  • sideEffects: Skipping entire modules/files and checking if they have side effects.

Tree Shaking for CSS

Tree Shaking for CSS involves using additional plugins. While PurifyCss was used previously, it’s no longer maintained. An alternative is PurgeCSS, a tool for removing unused CSS.


Note: This translation includes placeholder strings like [hash], [contenthash], and [chunkhash] to represent dynamic values. Please replace these placeholders with appropriate values based on your specific use case.


Note: This article is a translated version of the original post. For the most accurate and up-to-date information, please refer to the original source.
```

Introduction

When learning to write web pages, you usually start with HTML and CSS, which are responsible for creating and beautifying the layout. Once you have a solid foundation, you start learning JavaScript to create interactive effects. In addition to user and browser interactions, don’t forget about the interaction between the client and server, which means you must learn how to use JavaScript to retrieve data from the backend server. Otherwise, your web page data will be static.

The main target audience of this article is beginners in web front-end development. I hope that after reading this article, readers who do not understand how to exchange data with the server or how to connect to APIs can have a better understanding of how to connect to the backend.

Read More