Skip to content

Instantly share code, notes, and snippets.

@yogain123
Last active June 15, 2025 07:20
Show Gist options
  • Save yogain123/b73384c117c79116399ec89996d258b3 to your computer and use it in GitHub Desktop.
Save yogain123/b73384c117c79116399ec89996d258b3 to your computer and use it in GitHub Desktop.
webpack 5
@yogain123
Copy link
Author

yogain123 commented Jan 6, 2025

CSS Modules

CSS Modules are a popular approach in modern frontend development that allows you to write CSS that is scoped to a particular component. This avoids naming collisions and ensures that styles are applied only where they are intended.

Key Features of CSS Modules:

  1. Locally Scoped Styles:

    • By default, CSS rules in a CSS Module are scoped to the specific component where they are imported. This means you can use simple class names like .button without worrying about conflicts with other .button classes elsewhere in the app.
    • The scoping is achieved by automatically generating unique class names during the build process (e.g., button__xyz123).
  2. Dynamic Class Names:

    • The build tools (like Webpack) dynamically generate unique class names for CSS selectors to ensure isolation.
  3. Importing CSS as an Object:

    • CSS classes are exported as a JavaScript object, and you access them using keys.
  4. Easier Maintenance:

    • Since the styles are scoped to components, you don't need to worry about globally impacting styles or inheritance issues.

File Naming Convention:

  • Files named with .module.css or .module.scss are treated as CSS Modules (e.g., styles.module.css).
  • This naming tells the build tool (like Webpack or Vite) to process the file as a CSS Module.

Example: Using CSS Modules in React

Button.module.css:

/* This CSS is scoped only to the component importing it */
.button {
  background-color: blue;
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 5px;
  cursor: pointer;
}

.button:hover {
  background-color: darkblue;
}

Button.jsx:

import React from "react";
import styles from "./Button.module.css"; // Importing the CSS Module

const Button = ({ label, onClick }) => {
  return (
    <button className={styles.button} onClick={onClick}>
      {label}
    </button>
  );
};

export default Button;

How It Works:

  1. When styles.button is used, the actual class applied might look something like button__xyz123.
  2. This ensures no other .button class in the app can override or conflict with it.

@yogain123
Copy link
Author

yogain123 commented Jan 19, 2025

Shared Dependency

1. Understanding Shared Dependencies in Webpack

In a Micro-Frontend setup, each MFE and the Host application often rely on shared libraries like React. The goal of sharing dependencies is to:

  • Avoid downloading multiple versions of the same library.
  • Reduce application size and improve load time.
  • Ensure consistency in behavior (e.g., avoid React hooks issues when multiple React versions are loaded).

2. Configuring Shared Dependencies

When configuring ModuleFederationPlugin, you define shared dependencies in the shared property. The shared configuration can be flexible depending on version constraints and use cases.

Basic Configuration

new ModuleFederationPlugin({
  name: 'host',
  remotes: {
    mfe: 'mfe@http://localhost:3001/remoteEntry.js',
  },
  shared: {
    react: { singleton: true, strictVersion: true, requiredVersion: '^17.0.2' },
    'react-dom': { singleton: true, strictVersion: true, requiredVersion: '^17.0.2' },
  },
})

3. Key Attributes of Shared Dependencies

Here are the critical attributes of the shared object:

  1. singleton:

    • Ensures only one instance of the library is loaded.
    • Useful for libraries like React to avoid version conflicts and issues like multiple React renderers.
  2. strictVersion:

    • Ensures the exact version of the library is used.
    • Prevents conflicts when multiple versions exist but can break if an exact version isn't available.
  3. requiredVersion:

    • Specifies the version range the shared library must satisfy.
    • If the required version isn’t met, Webpack throws an error.
  4. eager:

    • Forces the shared dependency to be eagerly loaded.
    • Useful for critical libraries but may increase the initial bundle size.
  5. version:

    • Specifies the exact version of the library to use.
    • Automatically detected if not manually provided.

@yogain123
Copy link
Author

yogain123 commented Jan 25, 2025

Shared Deps in webpack

Shared Deps in Webpack

  1. Does the MFE Bundle Contain React?

    • Singleton React: React is not bundled with the MFE; it uses the host app’s React instance.
    • Non-Singleton React: React is bundled, causing potential multiple instances and broken state/context sharing.
    shared: {
      react: { singleton: true, requiredVersion: "^18.0.0" },
      "react-dom": { singleton: true, requiredVersion: "^18.0.0" },
    },

  1. Host App Has React. What Happens?
    • With shared dependencies, the MFE uses the host React instance, ensuring a single version is loaded for consistent behavior.

  1. How Does the MFE Bundle Run?
    • The MFE bundle is downloaded, parsed, and executed by the V8 engine, resolving shared dependencies (like React) via the host’s shared scope.

  1. MFE React App Loading
    • The host app dynamically imports the MFE’s remoteEntry.js, resolves shared dependencies, and mounts the MFE’s components to the DOM using React.

  1. When is React Bundled with MFE?
    • React is bundled if it’s not a singleton or the MFE uses a different React version. This should be avoided to prevent duplicate instances.

  1. Why Use Singletons?
    • Ensures shared state, reduces bundle size, and avoids version conflicts.
    • Add fallbacks for missing dependencies to ensure MFE functionality.

@yogain123
Copy link
Author

yogain123 commented Jan 25, 2025

Fallback Mechanism

Fallback Mechanism Summary

  1. Key Line Enabling Fallback
    The fallback is enabled when strictVersion: true is not set in the shared configuration:
shared: {
  lodash: {
    singleton: true,
    requiredVersion: "^4.17.0",
  },
}
  1. How It Works
  • If the host provides a compatible lodash version, it is used.
  • If the host provides no version or an incompatible one, the MFE uses its own bundled version.
  1. Is lodash Always Bundled in the MFE?
    Yes, the fallback version is always bundled unless marked eager: true or explicitly excluded.

  2. Why Bundle the Fallback?
    To ensure the MFE works independently, even if the host is missing or provides an incompatible dependency.

  3. Runtime Example

  • Host provides lodash@4.18.0 → Host version is used.
  • Host provides lodash@3.10.0 (incompatible) → Fallback version in MFE is used.
  • Host provides no lodash → Fallback version in MFE is used.

@yogain123
Copy link
Author

yogain123 commented Jan 25, 2025

Webpack JS Bundle Working

Here is how everything would look when combined into a single bundle file:

// Webpack Bootstrap Runtime
/******/ (() => {
    var __webpack_modules__ = ({
        /* 0 */
        "./src/index.js": ((module, __unused_webpack_exports, __webpack_require__) => {
            const React = __webpack_require__("react");
            const ReactDOM = __webpack_require__("react-dom");
            const App = __webpack_require__("./src/App.js");

            ReactDOM.render(
                React.createElement(App, null),
                document.getElementById("root")
            );
        }),

        /* 1 */
        "./src/App.js": ((module, __unused_webpack_exports, __webpack_require__) => {
            const React = __webpack_require__("react");

            const App = () => React.createElement(
                "div",
                null,
                React.createElement("h1", null, "Hello from Webpack")
            );

            module.exports = App;
        }),

        /* 2 */
        "react": ((module) => {
            module.exports = window.React; // React provided via CDN
        }),

        /* 3 */
        "react-dom": ((module) => {
            module.exports = window.ReactDOM; // ReactDOM provided via CDN
        })
    });

    // Module cache
    var __webpack_module_cache__ = {};

    // Function to require a module
    function __webpack_require__(moduleId) {
        if (__webpack_module_cache__[moduleId]) {
            return __webpack_module_cache__[moduleId].exports;
        }

        var module = __webpack_module_cache__[moduleId] = {
            i: moduleId,
            l: false,
            exports: {}
        };

        __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
        module.l = true;

        return module.exports;
    }

    // Start the app by executing the entry point
    __webpack_require__("./src/index.js");
})();

Explanation of the Combined Structure

In this single file:

  1. Modules and Code:

    • All modules (index.js, App.js, react, react-dom) are defined as properties of the __webpack_modules__ object.
    • Each module is wrapped in a function, enabling isolation and the use of module, exports, and __webpack_require__.

    Example of a module (./src/index.js):

    "./src/index.js": ((module, __unused_webpack_exports, __webpack_require__) => {
        const React = __webpack_require__("react");
        const ReactDOM = __webpack_require__("react-dom");
        const App = __webpack_require__("./src/App.js");
    
        ReactDOM.render(
            React.createElement(App, null),
            document.getElementById("root")
        );
    }),
  2. Webpack Runtime:

    • The bootstrap code is at the top of the file and manages:
      • Module caching (__webpack_module_cache__).
      • Module execution (__webpack_require__).
      • Starting the app by executing the entry point:
        __webpack_require__("./src/index.js");
  3. Single File:

    • All the logic (runtime, modules, dependencies) exists in this single JavaScript file.

Why Everything is in One File

By default, when Webpack bundles your app without optimizations like code splitting or externalizing libraries, it combines:

  • Your app code (e.g., React components).
  • Third-party libraries (like React and ReactDOM).
  • Webpack runtime (e.g., __webpack_require__ logic).

This is to ensure that the app runs as a standalone file without needing external scripts (except externalized libraries, if configured).


Key Takeaways

  1. A Webpack bundle does include everything in one file if configured to produce a single bundle.
  2. The runtime code (__webpack_require__) and module definitions (__webpack_modules__) exist in the same file.
  3. The last step in the bundle is the execution of the entry point, like:
    __webpack_require__("./src/index.js");
  • The bundle isn’t just blindly executed from top to bottom.
  • It simulates how your files run during development, except everything is packed neatly in one file (or multiple chunks).
  • The webpack_modules registry and webpack_require function act like the glue, ensuring the correct behavior of imports/exports, even in a minified bundle.

@yogain123
Copy link
Author

yogain123 commented Jan 29, 2025

Chunking in Webpack

  1. What are chunks?
    Think of chunks like separate packages of JavaScript code. Instead of putting all your code into one big file, Webpack can split it into smaller, more manageable pieces. This is like splitting a large book into chapters that can be read independently.

  2. How Webpack decides to create chunks:

A. Automatic Splitting:

  • Webpack looks at your import statements
  • When it sees dynamic imports (using import() syntax), it automatically creates a new chunk
// This will create a separate chunk
const MyComponent = React.lazy(() => import('./MyComponent'));

// Or using direct dynamic import
import('./MyComponent').then(component => {
  // Use the component
});

B. Manual Configuration:
You can tell Webpack explicitly how to split chunks using the splitChunks option:

module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all', // Split all chunks
      minSize: 20000, // Minimum size in bytes to create a chunk
      maxSize: 244000, // Maximum size target for chunks
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all'
        }
      }
    }
  }
}
  1. Common scenarios where Webpack creates chunks:

Note: Webpack automatically put all node_modules in a separate vendor chunk when chunk: "all" we explicitly don't have to write custom regex for the same

A. Node Modules Splitting:

// This is good because third-party code changes less frequently
import React from 'react';
import lodash from 'lodash';

B. Route-based Splitting:

// Each route can become its own chunk
const HomePage = React.lazy(() => import('./pages/Home'));
const AboutPage = React.lazy(() => import('./pages/About'));
  1. Real-world example of how it works:

Let's say you have this React app structure:

// App.js
import React, { Suspense } from 'react';
import { BrowserRouter, Route } from 'react-router-dom';

// These will be in the main chunk
import Header from './components/Header';
import Footer from './components/Footer';

// These will be in separate chunks
const HomePage = React.lazy(() => import('./pages/Home'));
const DashboardPage = React.lazy(() => import('./pages/Dashboard'));
const SettingsPage = React.lazy(() => import('./pages/Settings'));

function App() {
  return (
    <BrowserRouter>
      <Header />
      <Suspense fallback={<div>Loading...</div>}>
        <Route path="/" exact component={HomePage} />
        <Route path="/dashboard" component={DashboardPage} />
        <Route path="/settings" component={SettingsPage} />
      </Suspense>
      <Footer />
    </BrowserRouter>
  );
}

In this case, Webpack will create:

  • A main chunk with App.js, Header, and Footer
  • Separate chunks for HomePage, DashboardPage, and SettingsPage
  • A vendor chunk with React, React Router, and other node_modules

The benefits of this approach:

  • Users only download what they need when they need it
  • Initial page load is faster because it loads less code
  • Better caching because separate chunks can be cached independently

Let me point out exactly where each type of chunk is defined in the configuration. These are all defined in the cacheGroups section of the splitChunks optimization:

splitChunks: {
  chunks: 'all',  // This tells webpack to split all types of chunks (async and initial)
  minSize: 20000, // Minimum size for a chunk to be generated
  cacheGroups: {
    // 1. React and React DOM chunk
    react: {
      test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,  // Matches react and react-dom packages
      name: 'react',
      chunks: 'all',
      priority: 40,  // Higher priority means this rule is applied first
    },

    // 2. Router packages chunk
    router: {
      test: /[\\/]node_modules[\\/](react-router|react-router-dom)[\\/]/,  // Matches router packages
      name: 'router',
      chunks: 'all',
      priority: 30,
    },

    // 3. Other vendor code (remaining node_modules)
    defaultVendors: {
      test: /[\\/]node_modules[\\/]/,  // Matches all other node_modules
      priority: -10,
      reuseExistingChunk: true,
      name: 'vendors',
    },

    // 4. Common code used across pages
    commons: {
      name: 'commons',
      minChunks: 2,  // Must be used in at least 2 places to be included
      priority: -20,
      reuseExistingChunk: true,
    }
  }
}

How these work:

  1. React Chunk:

    • Matches files in node_modules/react and node_modules/react-dom
    • High priority (40) ensures React gets its own chunk before other rules apply
    • Results in: react.[hash].js
  2. Router Chunk:

    • Matches files in node_modules/react-router and react-router-dom
    • Lower priority than React (30) but higher than general vendors
    • Results in: router.[hash].js
  3. Vendor Chunk:

    • Catches all remaining node_modules code
    • Lower priority (-10) means it runs after React and Router rules
    • Results in: vendors.[hash].js
  4. Commons Chunk:

    • Doesn't look at node_modules, focuses on your application code
    • Only includes code used in at least 2 different places (minChunks: 2)
    • Results in: commons.[hash].js

Priority is needed to resolve conflicts when a module could match multiple cache groups. Let me explain with a practical example:

splitChunks: {
  cacheGroups: {
    // Priority 40
    react: {
      test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
      priority: 40,
      name: 'react'
    },

    // Priority -10
    defaultVendors: {
      test: /[\\/]node_modules[\\/]/,
      priority: -10,
      name: 'vendors'
    }
  }
}

Here's why priority matters:

  1. Conflict Example:

    • Take react-dom in node_modules
    • It matches TWO rules:
      • react rule (matches /node_modules/react-dom/)
      • defaultVendors rule (matches /node_modules/)
    • Without priority, webpack wouldn't know which chunk to put it in
  2. How Priority Resolves It:

    • react rule has priority 40
    • defaultVendors has priority -10
    • Higher number wins
    • So react-dom goes to the 'react' chunk, not 'vendors'

Real-world example of potential conflict:

import React from 'react';           // Matches both react and vendors rules
import moment from 'moment';         // Only matches vendors rule
import { Route } from 'react-router' // Matches both router and vendors rules

If there were no priorities:

  • React might end up in the general vendors chunk
  • This would defeat the purpose of having separate optimized chunks

Think of it like a hospital emergency room:

  • High priority (40): Emergency cases (React core libraries)
  • Medium priority (30): Urgent cases (Router packages)
  • Low priority (-10): Regular cases (other node_modules)
  • Lowest priority (-20): Non-urgent cases (common code)

The higher priority rules get to "claim" their modules first, ensuring critical libraries go to their intended chunks.


In a browser, when bundle.js is loaded, it knows that it has to download vendor.js or react.js (separate chunks) due to Webpack’s module resolution and chunk loading mechanism.

🔹 How bundle.js Knows About Other Chunks

  1. Script Injection to Load Chunks:
    • Webpack runtime inserts a <script> tag dynamically into the HTML:
      var script = document.createElement('script');
      script.src = 'vendor.js';
      document.head.appendChild(script);
    • This fetches and executes vendor.js.

2 Chunk Resolution:

  • Once vendor.js is loaded, Webpack updates its internal module registry.
  • Now, bundle.js can access other dependencies.

optimization: {
  splitChunks: {
    chunks: 'all',
    minChunks: 2,
  }
}

So here minChunks: 2 applies only to shared application code, while node_modules always get split when chunks: 'all' is used.


When You Don't Define Chunking (Without splitChunks):

  • Webpack still does chunking automatically for dynamic imports but doesn’t optimize the chunking process for things like shared code or libraries.
  • Chunk names will be generic (e.g., 0.js, 1.js), which might make it harder to manage caching and efficient loading.
  • No control over splitting logic: You won't be able to fine-tune how Webpack splits specific dependencies like React, Lodash, etc., without defining a custom splitChunks configuration.

@yogain123
Copy link
Author

yogain123 commented Feb 9, 2025

dist and lib

when you run npm run build, you might generally see this 2 folder auto genrated

  • dist: Your final, production-ready bundle (minified code + source maps) for direct use in a browser.
  • lib: Your transpiled source code, usually kept in a modular format for developers to import or further process. like lets say your node code you upload in npm regsitrry, someone when try to use it, its from lib, like its transpiled version

Note: the dist folder is your production equivalent of a public folder—it contains all the static assets that will be delivered to users when they access your site.

A public folder (sometimes called a static folder) is a directory in your project where you store static assets/files that don't change dynamically on each request. These assets can include HTML files, CSS stylesheets, JavaScript files, images, fonts, and other resources that should be served directly to browsers without additional processing.

Anything in the public folder is accessible via HTTP requests. For example, if you have an image at public/logo.png, it could be accessed at https://yourdomain.com/logo.png

@yogain123
Copy link
Author

yogain123 commented Feb 9, 2025

sourcemap

1. What Is a Source Map?

  • Definition:
    A source map is a file that maps the transformed (bundled/minified) code that runs in production back to your original source code. This means that when something goes wrong in your production environment, you can see exactly where the issue is in your original files, not in a compressed or bundled file.

  • Why It Exists:
    In production, we typically bundle, transpile, and minify our code for performance. This process changes the code structure, making it hard to read and debug. A source map bridges that gap by keeping track of how each piece of your original code maps to the final code.


2. Why Use Source Maps?

  • Easier Debugging:
    When errors occur in production, the browser’s developer tools (or error tracking services like Sentry) can use the source map to display the error in terms of your original code. This makes it much easier to diagnose and fix issues.

  • Error Reporting:
    Many modern error tracking tools can automatically use source maps to show you the context of an error (like which line in your original code caused a problem).

  • Improved Developer Experience:
    Without source maps, you’d have to work with minified code (one or two long lines of code), which is almost impossible to debug.


3. How Are Source Maps Generated?

  • Build Tools & Bundlers:
    Tools like webpack, Rollup, or Parcel can generate source maps for you. In React projects (especially those created with Create React App), the production build process usually generates source maps automatically.

  • Configuration Example (Webpack):
    In your webpack configuration, you might see something like this:

    module.exports = {
      // ... other configuration ...
      devtool: 'source-map', // This enables generation of source maps.
    };
    • Note: There are several options for the devtool setting that balance build speed and the quality/size of the source map. For production, "source-map" is a common choice because it provides full mapping.
  • How It Works Internally:
    When the bundler processes your files, it keeps track of where every segment of your original code goes in the bundled file. It then creates a JSON file (with a .map extension) that contains this mapping.

  • What It Looks Like:
    A source map file is a JSON object with several fields, including:

    • version: The version of the source map format.
    • file: The name of the generated file.
    • sources: An array of original file names.
    • mappings: A string that encodes the actual mapping between the original and transformed code.

    While the contents are not human-friendly, browsers and error reporting tools can interpret them automatically.


4. How Do You Use Source Maps?

  • Automatic Usage in Browsers:
    When you generate a production build, your bundled JavaScript file will often have a comment at the end like:

    //# sourceMappingURL=main.abcdef123456.js.map

    This tells the browser where to find the source map. When you open your developer tools and view an error or set a breakpoint, the browser automatically uses the source map to show the original code.

  • Using with Error Tracking Tools:
    If you’re using an error tracking service (like Sentry), you can upload your source maps along with your production code. When an error is reported, the service will use the corresponding source map to display the error in your original code.


5. Do You Need to Push Source Maps to GitHub?

  • Typically, No:
    Source maps are generated artifacts. You usually don’t commit generated files like source maps to your version control system (like GitHub). Instead, they’re created as part of your build process (using CI/CD or locally).

1. If You DON'T Want to Expose Source Maps Publicly

a. Disable Source Map Generation

  • Using Webpack Directly:
    • In your production webpack configuration, you can disable source maps by setting the devtool option to false:
      // webpack.prod.js
      module.exports = {
        // ...other config options...
        devtool: false, // This disables source map generation
      };
  • Using Create React App:
    • You can disable source map generation by setting an environment variable when you run the build command:
      GENERATE_SOURCEMAP=false npm run build
    • This tells Create React App not to generate source maps for your production build.

b. Generate Source Maps but Prevent Public Access

Sometimes you want to generate source maps (for internal debugging or error reporting) but not have them publicly available:

  • Build Normally, Then Remove the Mapping Reference:
    • You can configure your build process or post-process your bundled file to remove the //# sourceMappingURL=... comment. Without that comment, browsers won’t try to load the source map automatically.
  • Store Source Maps in a Secure Location:
    • Instead of deploying them to the same publicly accessible folder as your bundled assets, you can upload them to a secure location or an error tracking service (like Sentry).
  • Server-Level Restrictions:
    • Configure your web server or CDN to restrict access to .map files. For example, in an Nginx configuration you might restrict them by IP or add authentication:
      location ~ \.js\.map$ {
        allow 192.168.1.0/24;  # Only allow your internal IP range
        deny all;
      }
    • This way, even if the source maps exist on the server, only authorized users (or systems) can access them.

2. If You DO Want to Expose Source Maps

a. Ensure They’re Generated and Referenced

  • For Webpack:
    • Set the devtool option to "source-map" in your production config:
      // webpack.prod.js
      module.exports = {
        // ...other config options...
        devtool: "source-map", // Generates separate .map files
      };
  • For Create React App:
    • By default, a production build generates source maps unless you disable it. So just run:
      npm run build
    • The generated minified files will include a comment like:
      //# sourceMappingURL=main.abcdef.js.map
    • Make sure that both the JS bundle and the .map file are deployed together.

@yogain123
Copy link
Author

yogain123 commented Jun 10, 2025

Complete MFE Architecture

Core Concept

Micro Frontend (MFE) architecture using Webpack Module Federation where a shell application dynamically loads and renders multiple independent frontend applications at runtime, all served from Amazon S3/CDN without needing running servers.

1. Configuration Setup

Shell Webpack Configuration

// shell/webpack.config.js
const ModuleFederationPlugin = require('@module-federation/webpack');

module.exports = {
  mode: 'production',
  entry: './src/index.js',
  plugins: [
    new ModuleFederationPlugin({
      name: 'shell',
      remotes: {
        productMFE: 'productMFE@https://my-bucket.s3.amazonaws.com/product-mfe/remoteEntry.js',
        cartMFE: 'cartMFE@https://my-bucket.s3.amazonaws.com/cart-mfe/remoteEntry.js',
        userMFE: 'userMFE@https://my-bucket.s3.amazonaws.com/user-mfe/remoteEntry.js'
      },
      shared: {
        react: { singleton: true },
        'react-dom': { singleton: true }
      }
    })
  ]
};

MFE Webpack Configuration

// product-mfe/webpack.config.js
const ModuleFederationPlugin = require('@module-federation/webpack');

module.exports = {
  mode: 'production',
  entry: './src/index.js',
  plugins: [
    new ModuleFederationPlugin({
      name: 'productMFE',
      filename: 'remoteEntry.js',
      exposes: {
        './ProductList': './src/components/ProductList',
        './ProductDetail': './src/components/ProductDetail',
        './ProductCard': './src/components/ProductCard'
      },
      shared: {
        react: { singleton: true },
        'react-dom': { singleton: true }
      }
    })
  ]
};

2. Shell Application Code

Shell App Component

// shell/src/App.js
import React, { Suspense } from 'react';
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';

const ProductList = React.lazy(() => import('productMFE/ProductList'));
const CartWidget = React.lazy(() => import('cartMFE/CartWidget'));
const UserProfile = React.lazy(() => import('userMFE/UserProfile'));

function App() {
  return (
    <BrowserRouter>
      <div className="app">
        <header>
          <h1>My E-Commerce Store</h1>
          <nav>
            <Link to="/">Home</Link>
            <Link to="/products">Products</Link>
            <Link to="/profile">Profile</Link>
          </nav>
          <Suspense fallback={<div>Loading cart...</div>}>
            <CartWidget />
          </Suspense>
        </header>
        
        <main>
          <Routes>
            <Route path="/" element={
              <Suspense fallback={<div>Loading...</div>}>
                <ProductList />
              </Suspense>
            } />
            <Route path="/profile" element={
              <Suspense fallback={<div>Loading profile...</div>}>
                <UserProfile />
              </Suspense>
            } />
          </Routes>
        </main>
      </div>
    </BrowserRouter>
  );
}

export default App;

Shell Index File

// shell/src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './styles.css';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

Shell HTML Template

<!-- shell/public/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My E-Commerce Store</title>
</head>
<body>
    <div id="root"></div>
</body>
</html>

3. MFE Application Code

Product MFE Component

// product-mfe/src/components/ProductList.js
import React, { useState, useEffect } from 'react';

const ProductList = () => {
  const [products, setProducts] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch('https://api.mystore.com/products')
      .then(res => res.json())
      .then(data => {
        setProducts(data);
        setLoading(false);
      })
      .catch(err => {
        console.error('Failed to fetch products:', err);
        setLoading(false);
      });
  }, []);

  if (loading) return <div>Loading products...</div>;

  return (
    <div className="product-list">
      <h2>Our Products</h2>
      <div className="product-grid">
        {products.map(product => (
          <div key={product.id} className="product-card">
            <img src={product.image} alt={product.name} />
            <h3>{product.name}</h3>
            <p className="price">${product.price}</p>
            <button onClick={() => addToCart(product)}>
              Add to Cart
            </button>
          </div>
        ))}
      </div>
    </div>
  );
};

const addToCart = (product) => {
  window.dispatchEvent(new CustomEvent('addToCart', { 
    detail: product 
  }));
};

export default ProductList;

4. What's Inside remoteEntry.js

// Actual content of remoteEntry.js (simplified)
var productMFE;
(() => {
"use strict";

var __webpack_modules__ = ({
  "./src/components/ProductList": ((module, __webpack_exports__) => {
    // Actual ProductList component code
    const ProductList = () => {
      // Component implementation
    };
    module.exports = ProductList;
  })
});

var __webpack_module_cache__ = {};

function __webpack_require__(moduleId) {
  var cachedModule = __webpack_module_cache__[moduleId];
  if (cachedModule !== undefined) {
    return cachedModule.exports;
  }
  var module = __webpack_module_cache__[moduleId] = {
    exports: {}
  };
  
  __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
  return module.exports;
}

// Module Federation Container
var moduleMap = {
  "./ProductList": () => {
    return __webpack_require__.e("src_components_ProductList_js").then(() => 
      () => (__webpack_require__("./src/components/ProductList"))
    );
  },
  "./ProductDetail": () => {
    return __webpack_require__.e("src_components_ProductDetail_js").then(() => 
      () => (__webpack_require__("./src/components/ProductDetail"))
    );
  }
};

var get = (module, getScope) => {
  __webpack_require__.R = getScope;
  getScope = (
    __webpack_require__.o(moduleMap, module)
      ? moduleMap[module]()
      : Promise.resolve().then(() => {
          throw new Error('Module "' + module + '" does not exist in container.');
        })
  );
  __webpack_require__.R = undefined;
  return getScope;
};

var init = (shareScope, initScope) => {
  if (!__webpack_require__.S) __webpack_require__.S = {};
  var name = "default";
  __webpack_require__.S[name] = shareScope;
  return __webpack_require__.I(name, initScope);
};

__webpack_require__.d(__webpack_exports__, {
  get: () => (get),
  init: () => (init)
});

productMFE = __webpack_exports__;
})();

5. S3 Bucket Structure

my-ecommerce-bucket/
├── shell/
│   ├── index.html
│   ├── static/
│   │   ├── css/
│   │   │   └── main.css
│   │   └── js/
│   │       ├── main.js
│   │       └── vendor.js
├── product-mfe/
│   ├── remoteEntry.js
│   ├── static/
│   │   └── js/
│   │       ├── src_components_ProductList_js.js
│   │       └── src_components_ProductDetail_js.js
├── cart-mfe/
│   ├── remoteEntry.js
│   └── static/
│       └── js/
│           └── src_components_CartWidget_js.js
└── user-mfe/
    ├── remoteEntry.js
    └── static/
        └── js/
            └── src_components_UserProfile_js.js

6. S3 CORS Configuration

{
  "CORSRules": [
    {
      "AllowedOrigins": [
        "https://mystore.com",
        "https://www.mystore.com",
        "http://localhost:3000"
      ],
      "AllowedHeaders": ["*"],
      "AllowedMethods": ["GET", "HEAD"],
      "MaxAgeSeconds": 3600,
      "ExposeHeaders": ["ETag"]
    }
  ]
}

7. Deployment Scripts

Shell Deployment

#!/bin/bash
# deploy-shell.sh

echo "Building shell application..."
npm run build

echo "Uploading to S3..."
aws s3 sync build/ s3://my-ecommerce-bucket/shell/ \
  --delete \
  --cache-control "public, max-age=31536000" \
  --exclude "*.html" \
  --exclude "service-worker.js"

aws s3 sync build/ s3://my-ecommerce-bucket/shell/ \
  --delete \
  --cache-control "public, max-age=0, must-revalidate" \
  --include "*.html" \
  --include "service-worker.js"

echo "Invalidating CloudFront..."
aws cloudfront create-invalidation \
  --distribution-id E1234567890123 \
  --paths "/shell/*"

echo "Shell deployed successfully!"

MFE Deployment

#!/bin/bash
# deploy-product-mfe.sh

echo "Building product MFE..."
npm run build

echo "Uploading to S3..."
aws s3 sync dist/ s3://my-ecommerce-bucket/product-mfe/ \
  --delete \
  --cache-control "public, max-age=31536000"

echo "Invalidating CloudFront..."
aws cloudfront create-invalidation \
  --distribution-id E1234567890123 \
  --paths "/product-mfe/*"

echo "Product MFE deployed successfully!"

8. Runtime Flow Example

User Visits Site

// 1. Browser loads shell from S3
GET https://my-bucket.s3.amazonaws.com/shell/index.html
GET https://my-bucket.s3.amazonaws.com/shell/static/js/main.js

// 2. Shell renders with lazy-loaded components
// React.lazy(() => import('productMFE/ProductList'))

// 3. Browser fetches MFE remoteEntry
GET https://my-bucket.s3.amazonaws.com/product-mfe/remoteEntry.js

// 4. remoteEntry.js executes, creates window.productMFE

// 5. Browser fetches actual component
GET https://my-bucket.s3.amazonaws.com/product-mfe/static/js/src_components_ProductList_js.js

// 6. Component executes, makes API call
fetch('https://api.mystore.com/products')

// 7. Component renders with data

9. Package.json Examples

Shell Package.json

{
  "name": "ecommerce-shell",
  "version": "1.0.0",
  "scripts": {
    "start": "webpack serve --mode development",
    "build": "webpack --mode production",
    "deploy": "./deploy-shell.sh"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router-dom": "^6.8.1"
  },
  "devDependencies": {
    "@module-federation/webpack": "^0.0.5",
    "webpack": "^5.76.0",
    "webpack-cli": "^5.0.1",
    "webpack-dev-server": "^4.11.1"
  }
}

MFE Package.json

{
  "name": "product-mfe",
  "version": "1.0.0",
  "scripts": {
    "start": "webpack serve --mode development --port 3001",
    "build": "webpack --mode production",
    "deploy": "./deploy-product-mfe.sh"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "devDependencies": {
    "@module-federation/webpack": "^0.0.5",
    "webpack": "^5.76.0",
    "webpack-cli": "^5.0.1"
  }
}

Summary

This complete architecture enables:

  • Serverless frontend deployment to S3/CDN
  • Independent team development and deployment
  • Runtime composition of micro-frontends
  • Shared dependencies for optimal bundle sizes
  • Scalable, cost-effective hosting solution

Each MFE can be developed, tested, and deployed independently while the shell orchestrates them into a cohesive user experience, all without requiring any running servers for the frontend layer.

@yogain123
Copy link
Author

yogain123 commented Jun 15, 2025

RemoteEntry.js

You can define multiple entry points and expose multiple modules, but they will all be served through a single remoteEntry.js file, unless you deliberately split your application using multiple Webpack configurations.

🔹 Role of Entry Points

Entry points (entry) define the startup file(s) for your application — they are primarily used when your remote is running in standalone mode (like during local development or direct browser access). These files are what Webpack uses to bootstrap and render your full app.

🔹 Role of Exposed Modules

Exposed modules are defined in the exposes section of ModuleFederationPlugin.
This configuration determines what parts of your app are made available to host applications via remoteEntry.js.

  • Every module listed under exposes gets a corresponding entry in remoteEntry.js.
  • Each exposed module is bundled into its own chunk, even if it's not used anywhere in your entry point or the standalone app itself.

💡 Final Insight

Entry points and exposed modules are completely independent of each other.
You can expose any file, from anywhere in your project, regardless of whether it's used in the entry point or not.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment