#Webpack,Front-end

Introduction

A loader is a node module exported as a function. This function is called when transforming resources in the loader. The given function will utilize the Loader API and can be accessed through the this context.

Here is an official link on loader usage and examples, including local development and testing of custom loaders.

Simple Usage of Webpack Loader

When a loader is used in a resource, it can only take one parameter - a string containing the content of the resource file.

Synchronous loaders can return a single value representing the transformed module.

Loaders can return one or two values. The first value is a string or buffer containing JavaScript code. The optional second value is a SourceMap, which is a JavaScript object.

Here’s a simple example of using a loader. It matches all JavaScript files and processes them using loader.js:

// webpack.config.js
const path = require('path');
module.exports = {
  //...
  module: {
    rules: [
      {
        test: /\.js$/,
        use: [
          {
            loader: path.resolve('path/to/loader.js'),
            options: {
              /* ... */
            },
          },
        ],
      },
    ],
  },
};

From the above, we can understand how loaders are used. But this only scratches the surface. What does a specific loader look like?

For example, a simple loader could be like this:

module.exports = function (content) {
	// content is the source content string passed in
  return content
}

A loader is just a node module exposing a function that can only receive one parameter: a string containing the content of the resource file. The function’s return value is the processed content.

Creating a Custom Webpack Loader

Guidelines for Using Custom Loaders

When writing loaders, you should follow these guidelines. They are listed in order of importance, and some apply only to specific scenarios. Please read the detailed sections below for more information.

  • Keep it simple.
  • Use chaining.
  • Output should be modular.
  • Ensure it’s stateless.
  • Use loader utilities.
  • Record loader dependencies.
  • Resolve module dependencies.
  • Extract common code.
  • Avoid absolute paths.
  • Use peer dependencies.

Step 1: Create Project Directory and Files

First, create the following files in a folder within your webpack project directory:

  • src/loader/custom-loader.js: The source file for your custom loader.
  • src/index.js: JavaScript entry file for testing the custom loader.

Step 2: Write the Custom Loader

In the custom-loader.js file, write your custom loader code. This loader adds a comment at the top of each loaded JavaScript file.

// src/loader/custom-loader.js
module.exports = function(source) {
    // Add a custom comment at the top of the source code
    const updatedSource = `/** Custom Comment added by Custom Loader */\n${source}`;
    return updatedSource;
};

Step 3: Configure Webpack

Create a Webpack configuration file webpack.config.js in the project root directory. Use the custom loader you just created in the configuration file.

// webpack.config.js
const path = require('path');

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist'),
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                use: ['custom-loader'], // Use the custom loader to process .js files
                exclude: /node_modules/,
            },
        ],
    },
};

This configuration achieves a simple functionality. Now let’s discuss how to test the local loader. There are two ways to do this: one is through Npm link for testing, a convenient method where you can create a symbolic link for local testing. Here is a link to npm-link. Another way is to configure the path directly in the project:

Single Loader Configuration

// webpack.config.js
{
  test: /\.js$/
  use: [
    {
      loader: path.resolve('path/to/custom-loader.js'),
      options: {/* ... */}
    }
  ]
}

Multiple Loader Configuration

You can also configure it using an array:

// webpack.config.js
resolveLoader: {
  // Look for loaders first in the node_modules directory; if not found, search in the loaders directory
  modules: [
    'node_modules',
    path.resolve(__dirname, 'custom-loader')
  ]
}

Step 4: Test the Custom Loader

In the index.js file, write some JavaScript code, for example:

// src/index.js
console.log('Hello, Webpack Loader!');

Step 5: Run Webpack Build

Run the following command to build your project:

npx webpack --config webpack.config.js

After the build is complete, you will find the generated bundle.js file in the dist folder. In this file, you can see JavaScript code with a custom comment added at the top.



## Simple Usage of Webpack Plugin

Plugins provide complete control over the webpack engine for third-party developers. By introducing custom behaviors into the webpack build process through stage-based build callbacks, developers can customize webpack's behavior.

Here's the simplest example:

```javascript
// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: __dirname + '/dist',
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html', // Specify the HTML template file
            filename: 'index.html', // Generated HTML file name
        }),
        // You can add more plugins here
    ],
};

In this example, the HtmlWebpackPlugin is used. It generates a new HTML file based on the specified HTML template and automatically adds the bundled JavaScript file to the generated HTML file.

A basic webpack plugin consists of the following components:

  • A JavaScript named function or JavaScript class.

  • Define an apply method on the plugin function’s prototype. The apply method is called when webpack loads the plugin and is passed the compiler object.

  • Specify an event hook bound to webpack itself.

  • Process specific data from webpack’s internal instances.

  • Call the callback provided by webpack after the functionality is completed.

A plugin structure looks like this:

class HelloWorldPlugin {
  apply(compiler) {
    compiler.hooks.done.tap(
      'Hello World Plugin',
      (
        stats /* After binding the done hook, stats is passed as a parameter. */
      ) => {
        console.log('Hello World!');
      }
    );
  }
}

module.exports = HelloWorldPlugin;

Compiler and Compilation

The two most important resources in plugin development are the compiler and compilation objects. Plugin development revolves around hooks on these objects.

The compiler object is essentially bound to the entire webpack environment. It contains all the environment configurations, including options, loaders, and plugins. When webpack starts, this object is instantiated and it is globally unique. The parameters passed into the apply method are properties of this object.

The compilation object is created each time resources are built. It represents the current module resources, compiled generated resources, changed files, and tracked dependency status. It also provides many hooks.

Creating a Custom Webpack Plugin

Step 1: Create Project Directory and Files

First, create the following file in a folder within your webpack project directory:

  • src/plugins/CustomPlugin.js: Source file for your custom plugin.

Step 2: Write the Custom Plugin

In the CustomPlugin.js file, write a plugin that outputs a message when the webpack build process is completed.

// src/plugins/CustomPlugin.js
class CustomPlugin {
    apply(compiler) {
        compiler.hooks.done.tap('CustomPlugin', () => {
            console.log('CustomPlugin: Webpack build process is done!');
        });
    }
}

module.exports = CustomPlugin;

Step 3: Configure Webpack

In the configuration file, use the custom plugin you just created.

// webpack.config.js
const CustomPlugin = require('./src/plugins/CustomPlugin');

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: __dirname + '/dist',
    },
    plugins: [
        new CustomPlugin(),
        // You can add more plugins here
    ],
};

Step 4: Run Webpack Build

Now, run the webpack build:

npx webpack --config webpack.config.js

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.
```

Webpack v4 introduced a new feature called sideEffects, which allows you to declare in your package.json whether a package/module contains side effects or not. This declaration provides more optimization space for tree-shaking.

In the conventional understanding of side effects, if we are certain that the modules within our package have no side effects, we can mark the package in npm with "sideEffects": false in package.json. This allows us to offer a better bundling experience for consumers. The principle behind this is that Webpack can transform imports like import {a} from xx into import {a} from 'xx/a' for packages marked as side-effects-free, automatically trimming unnecessary imports, similar to babel-plugin-import.

Tree Shaking and Side Effects

Tree shaking, first introduced and implemented by Rollup in the frontend community, has been a topic of discussion in various articles about optimizing bundling.

Principles of Tree Shaking

ES6 module imports are statically analyzable, meaning the compiler can accurately determine what code is loaded during compilation. The program flow is analyzed to identify unused or unreferenced variables, which are then removed from the code.

The principle sounds perfect, so why do we sometimes find that unnecessary code in our projects isn’t eliminated? The reason is side effects.

Side Effects

For those familiar with functional programming, the term “side effect” is not unfamiliar. It can be broadly understood as any action of a function that might or might not affect variables outside its scope.

For example, consider this function:

function go(url) {
  window.location.href = url;
}

This function modifies the global variable location and even triggers a browser redirect, making it a function with side effects.

// components.js
export class Person {
  constructor({ name }) {
    this.className = 'Person';
    this.name = name;
  }
  getName() {
    return this.name;
  }
}
export class Apple {
  constructor({ model }) {
    this.className = 'Apple';
    this.model = model;
  }
  getModel() {
    return this.model;
  }
}
// main.js
import { Apple } from './components';

const appleModel = new Apple({
  model: 'IphoneX'
}).getModel();

console.log(appleModel);

In this code, the Person class is clearly unused. However, why can other tools like Rollup successfully eliminate unused code, while Webpack cannot?

The answer lies in Babel compilation + Webpack bundling.

I’ll provide a link here that explains in detail how Babel compilation + Webpack bundling might prevent effective code elimination: Your Tree-Shaking Isn’t Working.

If you don’t want to read the article, here’s a brief explanation: Babel compilation wraps the Person class in an IIFE (Immediately Invoked Function Expression) and returns a constructor, introducing a side effect.

There’s an issue related to this: Class declarations inside IIFEs are considered side effects.

When I declare a class inside an IIFE and don’t use the class, UglifyJS doesn’t remove it because it’s considered a side effect.

var V6Engine = (function () {
    function V6Engine() {
    }
    V6Engine.prototype.toString = function () {
        return 'V6';
    };
    return V6Engine;
}());

During compilation, you might receive this warning: WARN: Side effects in initialization of unused variable V6Engine [./dist/car.bundle.js:74,4].

The reason is that UglifyJS doesn’t perform complete program flow analysis. It doesn’t remove code because you noticed a side effect. If you want a more sophisticated tree shaking, go check out Rollup!

Summarizing some key points from the issue:

  • If a function’s parameter is a reference type, any operations on its properties could potentially have side effects. This is because it’s a reference type, and any modification to its properties affects data outside the function. Additionally, accessing or modifying its properties triggers getter or setter, which are opaque and may have side effects.

  • UglifyJS lacks complete program flow analysis. It can simple judge whether a variable is later referenced or modified but cannot determine the complete modification process of a variable. It doesn’t know if it points to an external variable, so many potentially side-effect-causing code cannot be removed.

  • Rollup has the ability to perform program flow analysis, making it better at determining whether code truly has side effects.

However, these issues were prevalent in older versions. The current Webpack tree shaking has undergone many optimizations and can perform sufficient program flow analysis for tree shaking.

The purpose of Webpack’s tree shaking is to mark unused exported members as unused and not export them in the modules where they are re-exported. It sounds complicated, but looking at the code makes it clearer:

// a.js
export function a() {}
// b.js
export function b(){}
// package/index.js
import a from './a'
import b from './b'
export { a, b }
// app.js
import {a} from 'package'
console.log(a)

When using app.js as the entry point, the code after tree shaking becomes:

// a.js
export function a() {}
// b.js is no longer exported: function b(){}
function b() {}
// package/index.js does not export module b anymore
import a from './a'
import './b'
export { a }
// app.js
import {a} from 'package'
console.log(a)

After combining Webpack’s scope hoisting and uglify, all traces of module b will be completely eliminated.

But what if module b contains some side effects, such as a simple log:

// b.js
export function b(v) { return v }
console.log(b(1))
After webpack, the content of module `b` becomes:

// b.js
console.log(function (v){return v}(1))

Although the export of module b is ignored, the code with side effects is retained.

Due to various strange operations introduced by the transformer after compilation, which may cause side effects, we often find that even with tree shaking, our bundle size doesn’t significantly decrease.

Usually, we expect that if module b is not being used, none of its code should be included.

This is where the role of sideEffects becomes apparent: if the imported package/module is marked as "sideEffects: false", regardless of whether it truly has side effects, as long as it’s not being referenced, the entire module/package will be completely removed.

Taking mobx-react-devtools as an example, we often use it like this:

import DevTools from 'mobx-react-devtools';

class MyApp extends React.Component {
  render() {
    return (
      <div>
        ...
        { process.env.NODE_ENV === 'production' ? null : <DevTools /> }
      </div>
    );
  }
}

This is a common scenario of importing modules on demand. However, without the sideEffects: false configuration, even if NODE_ENV is set to production, the bundled code will still include the mobx-react-devtools package. Although we haven’t used any of its exported members, mobx-react-devtools will still be imported because it “might” have side effects.

But when we add sideEffects: false, tree shaking can safely remove it entirely from the bundle.

Use Cases of sideEffects

As mentioned earlier, it’s often difficult to guarantee whether packages/modules published on npm contain side effects (it could be the code’s fault or the transformer’s fault). However, we can usually ensure whether a package/module will affect objects outside of it, such as modifying properties on the window object or overwriting native object methods. If we can guarantee this, we can determine whether a package can have "sideEffects: false". Whether it truly has side effects is not that important for Webpack; it’s acceptable as long as it’s marked.

This explains why packages with inherent side effects, like vue, can still have "sideEffects: false" applied.

So, in Webpack, "sideEffects: false" doesn’t mean that the module truly has no side effects. It’s just a way to tell Webpack during tree shaking: “I designed this package with the expectation that it has no side effects, even if it ends up having side effects after being bundled.”


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.
```

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.
```

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.
```