-
-
Save yogain123/b73384c117c79116399ec89996d258b3 to your computer and use it in GitHub Desktop.
hola |
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:
-
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.
-
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.
-
requiredVersion
:- Specifies the version range the shared library must satisfy.
- If the required version isn’t met, Webpack throws an error.
-
eager
:- Forces the shared dependency to be eagerly loaded.
- Useful for critical libraries but may increase the initial bundle size.
-
version
:- Specifies the exact version of the library to use.
- Automatically detected if not manually provided.
Shared Deps in webpack
Shared Deps in Webpack
-
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" }, },
- 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.
- 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.
- 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.
- The host app dynamically imports the MFE’s
- 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.
- Why Use Singletons?
- Ensures shared state, reduces bundle size, and avoids version conflicts.
- Add fallbacks for missing dependencies to ensure MFE functionality.
Fallback Mechanism
Fallback Mechanism Summary
- Key Line Enabling Fallback
The fallback is enabled whenstrictVersion: true
is not set in theshared
configuration:
shared: {
lodash: {
singleton: true,
requiredVersion: "^4.17.0",
},
}
- 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.
-
Is lodash Always Bundled in the MFE?
Yes, the fallback version is always bundled unless markedeager: true
or explicitly excluded. -
Why Bundle the Fallback?
To ensure the MFE works independently, even if the host is missing or provides an incompatible dependency. -
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.
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:
-
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") ); }),
- All modules (
-
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");
- Module caching (
- The bootstrap code is at the top of the file and manages:
-
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
- A Webpack bundle does include everything in one file if configured to produce a single bundle.
- The runtime code (
__webpack_require__
) and module definitions (__webpack_modules__
) exist in the same file. - 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.
Chunking in Webpack
-
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. -
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'
}
}
}
}
}
- 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'));
- 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:
-
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
-
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
-
Vendor Chunk:
- Catches all remaining node_modules code
- Lower priority (-10) means it runs after React and Router rules
- Results in:
vendors.[hash].js
-
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:
-
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
-
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
- 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
.
- Webpack runtime inserts a
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.
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
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.
- Note: There are several options for the
-
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 tofalse
:// webpack.prod.js module.exports = { // ...other config options... devtool: false, // This disables source map generation };
- In your production webpack configuration, you can disable source maps by setting the
- 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.
- You can disable source map generation by setting an environment variable when you run the build command:
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.
- You can configure your build process or post-process your bundled file to remove the
- 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.
- Configure your web server or CDN to restrict access to
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 };
- Set the
- 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.
- By default, a production build generates source maps unless you disable it. So just run:
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.
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 inremoteEntry.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.
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:
Locally Scoped Styles:
.button
without worrying about conflicts with other.button
classes elsewhere in the app.button__xyz123
).Dynamic Class Names:
Importing CSS as an Object:
Easier Maintenance:
File Naming Convention:
.module.css
or.module.scss
are treated as CSS Modules (e.g.,styles.module.css
).Example: Using CSS Modules in React
Button.module.css
:Button.jsx
:How It Works:
styles.button
is used, the actual class applied might look something likebutton__xyz123
..button
class in the app can override or conflict with it.