Core concepts

  • Entry →An entry point indicates which module webpack should use to begin building out its internal dependency graph.
  • Output →The output property tells webpack where to emit the bundles it creates and how to name these files. It defaults to
  • Loaders →webpack only understands JavaScript and JSON files. Loaders allow webpack to process other types of files and convert them into valid modules that can be consumed by your application and added to the dependency graph.
  • Plugins →While loaders are used to transform certain types of modules, plugins can be leveraged to perform a wider range of tasks like bundle optimization, asset management and injection of environment variables.
  • Mode →By setting the mode parameter to either developmentproduction or none, you can enable webpack’s built-in optimizations that correspond to each environment. The default value is production.

tapable → backbone of the webpack plugin system

webpack 4 support css default

7 tapable instance

  1. compiler
  2. dependcey graph
  3. Resolver
  4. Module factories → take resolved obj and create module obj of it
  5. parser → convert to AST
  6. template

Modules

  1. ESM support browser default → it is very slow in browser because it need to resolve module in runtime
  2. import { } from “.” → pull only what we using
  3. commonjs in nodejs came first

Tree shaking

Remove the unused exports it will only be used in ES6 module it not work with commonjs and it will default work on production.

	optimization: {
	    minimize: true,
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: {
            unused: true,
            dead_code: true,
          },
        },
      }),
    ],
  },

1. Basic Setup

Let’s start with a minimal React app.

Project Structure:

my-react-app/
├── src/
│   ├── index.js       # Entry point for React
│   └── App.jsx        # React Component
├── public/
│   └── index.html     # Basic HTML template
├── package.json       # NPM configuration
└── webpack.config.js  # Webpack configuration file

src/index.js (React entry point):

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
 
ReactDOM.render(<App />, document.getElementById('root'));

src/App.jsx (A simple React component):

import React from 'react';
 
const App = () => {
  return <h1>Hello, Webpack and React!</h1>;
};
 
export default App;

public/index.html (HTML template):

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Webpack React App</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

package.json (Dependencies and scripts):

{
  "name": "my-react-app",
  "version": "1.0.0",
  "main": "index.js",
  "dependencies": {
    "react": "^18.0.0",
    "react-dom": "^18.0.0"
  },
  "devDependencies": {
    "webpack": "^5.60.0",
    "webpack-cli": "^4.8.0",
    "babel-loader": "^8.2.2",
    "babel-core": "^7.0.0-bridge.0",
    "babel-preset-react": "^7.0.0",
    "html-webpack-plugin": "^5.3.2",
    "webpack-dev-server": "^4.5.0"
  },
  "scripts": {
    "start": "webpack serve --open",
    "build": "webpack --mode production"
  }
}

2. Webpack Configuration

Now, let’s configure Webpack to bundle our React app.

webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
 
module.exports = {
  entry: './src/index.js', // Entry file for Webpack to start bundling
  output: {
    filename: 'bundle.js',  // The output bundle file name
    path: path.resolve(__dirname, 'dist'),  // Output directory
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,  // Rule to handle .js and .jsx files
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader', // Use Babel to transpile JSX and ES6 code
          options: {
            presets: ['@babel/preset-env', '@babel/preset-react'], // Babel presets for React and modern JS
          },
        },
      },
    ],
  },
  resolve: {
    extensions: ['.js', '.jsx'],  // Resolve .js and .jsx file extensions
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html',  // Use the HTML template
    }),
  ],
  devServer: {
    contentBase: path.resolve(__dirname, 'dist'),
    port: 3000,  // Webpack dev server runs on this port
    open: true,  // Automatically open the browser
  },
};

3. Understanding the Webpack Build Process

When you run Webpack (via npm run build or npm start), here’s what happens under the hood:

Step 1: Entry Point

Webpack starts by looking at the entry defined in webpack.config.js. In this case, it’s src/index.js.

  1. Webpack resolves the entry file:
    • It loads index.js (which imports App.jsx).
    • It starts parsing index.js to find dependencies.

Step 2: Module Parsing & Dependency Graph

Webpack recursively builds a dependency graph by following all import or require statements.

  1. index.js imports App.jsx.
  2. Webpack sees that App.jsx is a .jsx file and applies the corresponding loader (babel-loader).
  3. Babel then transpiles the JSX code (and modern JavaScript) into browser-compatible JavaScript.

This process repeats for each file, recursively resolving dependencies.

Step 3: Loader Processing

For every file, Webpack uses the appropriate loader to transform the content.

  1. Babel Loader: Transpiles JSX and modern JavaScript (e.g., ES6 imports) into vanilla JavaScript.
  2. HTML Webpack Plugin: Takes the index.html template and injects the bundle (bundle.js) into it.

Step 4: Bundling

Once all dependencies are resolved and transformed:

  1. Webpack combines the transformed files into a single or multiple bundle(s).
  2. It injects these bundles into an index.html file (handled by HtmlWebpackPlugin).

Step 5: Output

Webpack writes the final bundle and other assets (like index.html) to the dist/ folder:

  • bundle.js: The main JavaScript file containing all bundled modules.
  • index.html: The HTML file with a <script> tag to load bundle.js.

4. Webpack Output Structure

After running npm run build, Webpack outputs the following:

dist/
├── index.html       # Injects <script src="bundle.js">
├── bundle.js        # The final JavaScript bundle (compiled React app)
└── ...              # Other files like source maps, if enabled

5. What Happens When You Run the Build

  1. index.html: The HTML file gets injected with a <script> tag for the final JavaScript bundle (bundle.js).

    Example of how the output index.html will look:

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Webpack React App</title>
    </head>
    <body>
      <div id="root"></div>
      <script src="bundle.js"></script>
    </body>
    </html>
  2. bundle.js: The JavaScript file will include:

    • The React app logic (compiled JSX code turned into JavaScript).

    • The React and ReactDOM dependencies (if not shared).

    • A runtime that handles module loading (via Webpack’s custom require logic).

    Example of a small part of bundle.js:

    !function(modules) {
      var installedModules = {};  // Cache of loaded modules
     
      function __webpack_require__(moduleId) {
        if(installedModules[moduleId]) return installedModules[moduleId].exports;
        var module = installedModules[moduleId] = { i: moduleId, l: false, exports: {} };
        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
        module.l = true;
        return module.exports;
      }
     
      __webpack_require__.e = function(chunkId) { /* Chunk loading logic */ };
     
      __webpack_require__(0);  // Initial entry point (index.js)
    }({
      0: function(module, exports) {
        // Module code for index.js (React rendering)
      }
    });

6. Development Mode (webpack-dev-server)

If you’re running webpack-dev-server (via npm start), Webpack serves the app from memory and automatically reloads the page when files change. In this case:

  1. HMR (Hot Module Replacement): Webpack will not do a full page reload; it will only update the changed modules.

  2. Output: The bundles are served from memory, so you won’t see them in the dist/ folder, but Webpack will still inject them into your HTML.

Tree shaking

Tree shaking is a term commonly used in the JavaScript context for dead-code elimination. It relies on the static structure of ES2015 module syntax, i.e., import and export. The name and concept have been popularized by the ES2015 module bundler Rollup.GitHub

In Webpack, the term “tree shaking” is used somewhat inconsistently, often broadly referring to various optimizations aimed at dead code elimination. This broad usage can lead to confusion, as different optimizations operate at different levels of granularity.GitHub


⚙️ Key Optimizations in Webpack Tree Shaking

Webpack’s tree shaking encompasses several distinct optimizations:GitHub+1GitHub+1

  1. usedExports Optimization:

    • Identifies and removes unused export variables from modules.

    • Further eliminates related side-effect-free statements.

    • Example: In a module where variable b is unused, both b and its associated code are excluded from the final bundle.GitHub+3GitHub+3GitHub+3

  2. sideEffects Optimization:

    • Removes entire modules from the module graph when their export variables are not utilized.

    • This optimization depends on the sideEffects flag in package.json to determine if a module can be safely excluded.

    • Example: If util.js exports functions that aren’t used elsewhere, and it’s marked as side-effect-free, the entire module is omitted from the final output.GitHub

  3. Dead Code Elimination (DCE):

    • Typically handled by minification tools, DCE removes code that is never executed.

    • Webpack’s ConstPlugin can also perform similar dead code removal.

    • Example: A console.log statement inside a conditional block that never runs will be stripped out during the build process.GitHub+1GitHub+1

These optimizations operate at different levels:GitHub+2GitHub+2GitHub+2

  • usedExports focuses on individual export variables.

  • sideEffects targets entire modules.

  • DCE addresses specific JavaScript statements.

Resources

  1. How We Reduced Replay SDK Bundle Size by

    • They used DefinePlugin in webpack to pass the value true or false on build time if the module used they send true if it false by default webpack will remove the dead code.

      if(__SENTRY_REPLAY__){
      	//dead code
      }
  2. [How Webpack works? under the hood] https://blog.lyearn.com/how-webpack-works-236f8cc43ae7

  3. https://survivejs.com/webpack/introduction/

  4. How treeshaking works https://github.com/orgs/web-infra-dev/discussions/17

Rspack

  • Rspack is designed to be a drop-in replacement for webpack.
  • It is written in Rust.
  • Rspack keeps getting faster and faster.

Performance

  • Rspack is about 11 times faster than webpack.
  • Comparing build speeds, Rspack can complete tasks in milliseconds (e.g., 230 milliseconds) versus seconds for webpack (e.g., 700 milliseconds to two seconds).
  • The speed has recently been improved further, dropping by another 10 to 15% about a week before the talk.

Relation to Module Federation

  • Rspack supports Federation.
  • The Module Federation V2 version is designed to be bundler agnostic and has been extracted into a library, meaning bundlers like Rspack implement the library and fill out an object for it, rather than being tied to a specific compiler plugin. This allows Federation to work with Rspack.
  • RS Doctor is a tool that works for both webpack and Rspack14. It helps understand build times, identify ineffective tree shaking or unused exports, inspect timing of loaders and resolvers, show flame charts, and provide warnings and type inspection