Alex Lockhart
npm i react-app-rewired webpack-merge
// config-overrides.js const { merge } = require("webpack-merge"); const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); const { dependencies } = require("./package.json"); module.exports = function override(config) { return merge(config, { output: { uniqueName: "remote_app_1", publicPath: "http://localhost:3001/", }, plugins: [ new ModuleFederationPlugin({ name: "remote_app_1", filename: "remoteEntry.js", exposes: { "./App": "./src/bootstrap/remote", }, shared: { ...dependencies, react: { singleton: true, requiredVersion: dependencies["react"], }, "react-dom": { singleton: true, requiredVersion: dependencies["react-dom"], }, }, }), ], }); };
// config-overrides.js const { merge } = require("webpack-merge"); const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); const { dependencies } = require("./package.json"); module.exports = function override(config) { return merge(config, { output: { uniqueName: "remote_app_1", publicPath: "http://localhost:3001/", }, plugins: [ new ModuleFederationPlugin({ name: "remote_app_1", filename: "remoteEntry.js", exposes: { "./App": "./src/bootstrap/remote", }, shared: { ...dependencies, react: { singleton: true, requiredVersion: dependencies["react"], }, "react-dom": { singleton: true, requiredVersion: dependencies["react-dom"], }, }, }), ], }); };
// config-overrides.js const { merge } = require("webpack-merge"); const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); const { dependencies } = require("./package.json"); module.exports = function override(config) { return merge(config, { output: { uniqueName: "remote_app_1", publicPath: "http://localhost:3001/", }, plugins: [ new ModuleFederationPlugin({ name: "remote_app_1", filename: "remoteEntry.js", exposes: { "./App": "./src/bootstrap/remote", }, shared: { ...dependencies, react: { singleton: true, requiredVersion: dependencies["react"], }, "react-dom": { singleton: true, requiredVersion: dependencies["react-dom"], }, }, }), ], }); };
// config-overrides.js const { merge } = require("webpack-merge"); const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); const { dependencies } = require("./package.json"); module.exports = function override(config) { return merge(config, { output: { uniqueName: "remote_app_1", publicPath: "http://localhost:3001/", }, plugins: [ new ModuleFederationPlugin({ name: "remote_app_1", filename: "remoteEntry.js", exposes: { "./App": "./src/bootstrap/remote", }, shared: { ...dependencies, react: { singleton: true, requiredVersion: dependencies["react"], }, "react-dom": { singleton: true, requiredVersion: dependencies["react-dom"], }, }, }), ], }); };
// config-overrides.js const { merge } = require("webpack-merge"); const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); const { dependencies } = require("./package.json"); module.exports = function override(config) { return merge(config, { output: { uniqueName: "remote_app_1", publicPath: "http://localhost:3001/", }, plugins: [ new ModuleFederationPlugin({ name: "remote_app_1", filename: "remoteEntry.js", exposes: { "./App": "./src/bootstrap/remote", }, shared: { ...dependencies, react: { singleton: true, requiredVersion: dependencies["react"], }, "react-dom": { singleton: true, requiredVersion: dependencies["react-dom"], }, }, }), ], }); };
remote
Entrypoint// src/bootstrap/remote.tsx import { App } from "../App"; export default App;
// src/bootstrap/remote.tsx import { App } from "../App"; export default App;
// config-overrides.js const { merge } = require("webpack-merge"); const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); const { dependencies } = require("./package.json"); module.exports = function override(config) { return merge(config, { output: { uniqueName: "remote_app_1", publicPath: "http://localhost:3001/", }, plugins: [ new ModuleFederationPlugin({ name: "remote_app_1", filename: "remoteEntry.js", exposes: { "./App": "./src/bootstrap/remote", }, shared: { ...dependencies, react: { singleton: true, requiredVersion: dependencies["react"], }, "react-dom": { singleton: true, requiredVersion: dependencies["react-dom"], }, }, }), ], }); };
{ "name": "remote-app-1", "version": "0.1.0", "private": true, "homepage": "/module-federation-example/remote-app-1", "dependencies": { "@emotion/react": "^11.11.0", "@emotion/styled": "^11.11.0", "@mui/icons-material": "^5.11.16", "@mui/material": "^5.12.3", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "@types/jest": "^27.5.2", "@types/node": "^16.18.26", "@types/react": "^18.2.6", "@types/react-dom": "^18.2.4", "react": "^18.2.0", "react-app-rewired": "^2.2.1", "react-dom": "^18.2.0", "react-scripts": "5.0.1", "source-map-explorer": "^2.5.3", "typescript": "^4.9.5", "web-vitals": "^2.1.4", "webpack-merge": "^5.8.0" }, "scripts": { "start": "react-app-rewired start", "build": "react-app-rewired build", "deploy": "gh-pages -d build -e remote-app-1", "test": "react-app-rewired test", "eject": "react-app-rewired eject", "analyze": "npx source-map-explorer 'build/static/js/*.js' --html build/source-map-explorer.html" }, "eslintConfig": { "extends": ["react-app", "react-app/jest"] }, "browserslist": { "production": [">0.2%", "not dead", "not op_mini all"], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] }, "devDependencies": { "gh-pages": "^5.0.0" } }
// config-overrides.js const { merge } = require("webpack-merge"); const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); const { dependencies } = require("./package.json"); module.exports = function override(config) { return merge(config, { output: { uniqueName: "remote_app_1", publicPath: "http://localhost:3001/", }, plugins: [ new ModuleFederationPlugin({ name: "remote_app_1", filename: "remoteEntry.js", exposes: { "./App": "./src/bootstrap/remote", }, shared: { ...dependencies, react: { singleton: true, requiredVersion: dependencies["react"], }, "react-dom": { singleton: true, requiredVersion: dependencies["react-dom"], }, }, }), ], }); };
// config-overrides.js const { merge } = require("webpack-merge"); const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); const { dependencies } = require("./package.json"); module.exports = function override(config) { return merge(config, { output: { uniqueName: "remote_app_1", publicPath: "http://localhost:3001/", }, plugins: [ new ModuleFederationPlugin({ name: "remote_app_1", filename: "remoteEntry.js", exposes: { "./App": "./src/bootstrap/remote", }, shared: { ...dependencies, react: { singleton: true, requiredVersion: dependencies["react"], }, "react-dom": { singleton: true, requiredVersion: dependencies["react-dom"], }, }, }), ], }); };
// config-overrides.js const { merge } = require("webpack-merge"); const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); const { dependencies } = require("./package.json"); module.exports = function override(config) { return merge(config, { output: { uniqueName: "remote_app_1", publicPath: "http://localhost:3001/", }, plugins: [ new ModuleFederationPlugin({ name: "remote_app_1", filename: "remoteEntry.js", exposes: { "./App": "./src/bootstrap/remote", }, shared: { ...dependencies, react: { singleton: true, requiredVersion: dependencies["react"], }, "react-dom": { singleton: true, requiredVersion: dependencies["react-dom"], }, }, }), ], }); };
Module Federation ❌
build ├── index.html └── static ├── css │ └── main.8a685450.css └── js ├── 787.83a184bd.chunk.js └── main.d2804cea.js
build ├── index.html └── static ├── css │ └── main.8a685450.css └── js ├── 787.83a184bd.chunk.js └── main.d2804cea.js
build ├── index.html └── static ├── css │ └── main.8a685450.css └── js ├── 787.83a184bd.chunk.js └── main.d2804cea.js
Module Federation + Sharing ✅
build ├── index.html └── static ├── css │ └── 735.19f42a5c.chunk.css └── js ├── 164.d173b888.chunk.js ├── 184.658dae2a.chunk.js ├── 192.eeafc2fa.chunk.js ├── 225.77016a9a.chunk.js ├── 357.e948afb0.chunk.js ├── 361.5b8c06a6.chunk.js ├── 677.5bffb570.chunk.js ├── 702.bc15c451.chunk.js ├── 73.23dbfaa2.chunk.js ├── 735.693974f1.chunk.js ├── 783.97111ac3.chunk.js ├── 787.1912ef9c.chunk.js ├── 791.6276d6ee.chunk.js ├── 938.c91fb019.chunk.js └── main.24d3dcb7.js
build ├── index.html └── static ├── css │ └── 735.19f42a5c.chunk.css └── js ├── 164.d173b888.chunk.js ├── 184.658dae2a.chunk.js ├── 192.eeafc2fa.chunk.js ├── 225.77016a9a.chunk.js ├── 357.e948afb0.chunk.js ├── 361.5b8c06a6.chunk.js ├── 677.5bffb570.chunk.js ├── 702.bc15c451.chunk.js ├── 73.23dbfaa2.chunk.js ├── 735.693974f1.chunk.js ├── 783.97111ac3.chunk.js ├── 787.1912ef9c.chunk.js ├── 791.6276d6ee.chunk.js ├── 938.c91fb019.chunk.js └── main.24d3dcb7.js
build ├── index.html └── static ├── css │ └── 735.19f42a5c.chunk.css └── js ├── 164.d173b888.chunk.js ├── 184.658dae2a.chunk.js ├── 192.eeafc2fa.chunk.js ├── 225.77016a9a.chunk.js ├── 357.e948afb0.chunk.js ├── 361.5b8c06a6.chunk.js ├── 677.5bffb570.chunk.js ├── 702.bc15c451.chunk.js ├── 73.23dbfaa2.chunk.js ├── 735.693974f1.chunk.js ├── 783.97111ac3.chunk.js ├── 787.1912ef9c.chunk.js ├── 791.6276d6ee.chunk.js ├── 938.c91fb019.chunk.js └── main.24d3dcb7.js
npm run build
npx source-map-explorer 'build/static/js/*.js'
// config-overrides.js const { merge } = require("webpack-merge"); const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); const { dependencies } = require("./package.json"); module.exports = function override(config) { return merge(config, { output: { uniqueName: "remote_app_1", publicPath: "http://localhost:3001/", }, plugins: [ new ModuleFederationPlugin({ name: "remote_app_1", filename: "remoteEntry.js", exposes: { "./App": "./src/bootstrap/remote", }, shared: { ...dependencies, react: { singleton: true, requiredVersion: dependencies["react"], }, "react-dom": { singleton: true, requiredVersion: dependencies["react-dom"], }, }, }), ], }); };
// config-overrides.js const { merge } = require("webpack-merge"); const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); const { dependencies } = require("./package.json"); module.exports = function override(config) { return merge(config, { output: { uniqueName: "remote_app_1", publicPath: "http://localhost:3001/", }, plugins: [ new ModuleFederationPlugin({ name: "remote_app_1", filename: "remoteEntry.js", exposes: { "./App": "./src/bootstrap/remote", }, shared: { ...dependencies, react: { singleton: true, requiredVersion: dependencies["react"], }, "react-dom": { singleton: true, requiredVersion: dependencies["react-dom"], }, }, }), ], }); };
// config-overrides.js const { merge } = require("webpack-merge"); const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); const { dependencies } = require("./package.json"); module.exports = function override(config) { return merge(config, { output: { uniqueName: "remote_app_1", publicPath: "http://localhost:3001/", }, plugins: [ new ModuleFederationPlugin({ name: "remote_app_1", filename: "remoteEntry.js", exposes: { "./App": "./src/bootstrap/remote", }, shared: { ...dependencies, react: { singleton: true, requiredVersion: dependencies["react"], }, "react-dom": { singleton: true, requiredVersion: dependencies["react-dom"], }, }, }), ], }); };
npm i react-app-rewired webpack-merge
// config-overrides.js const { merge } = require("webpack-merge"); const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); const { dependencies } = require("./package.json"); module.exports = function override(config) { return merge(config, { plugins: [ new ModuleFederationPlugin({ remotes: { remote_app_1: "remote_app_1@http://localhost:3001/remoteEntry.js", remote_app_2: "remote_app_2@http://localhost:3002/remoteEntry.js", }, shared: { ...dependencies, react: { singleton: true, requiredVersion: dependencies["react"], }, "react-dom": { singleton: true, requiredVersion: dependencies["react-dom"], }, }, }), ], }); };
// config-overrides.js const { merge } = require("webpack-merge"); const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); const { dependencies } = require("./package.json"); module.exports = function override(config) { return merge(config, { plugins: [ new ModuleFederationPlugin({ remotes: { remote_app_1: "remote_app_1@http://localhost:3001/remoteEntry.js", remote_app_2: "remote_app_2@http://localhost:3002/remoteEntry.js", }, shared: { ...dependencies, react: { singleton: true, requiredVersion: dependencies["react"], }, "react-dom": { singleton: true, requiredVersion: dependencies["react-dom"], }, }, }), ], }); };
// config-overrides.js const { merge } = require("webpack-merge"); const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); const { dependencies } = require("./package.json"); module.exports = function override(config) { return merge(config, { plugins: [ new ModuleFederationPlugin({ remotes: { remote_app_1: "remote_app_1@http://localhost:3001/remoteEntry.js", remote_app_2: "remote_app_2@http://localhost:3002/remoteEntry.js", }, shared: { ...dependencies, react: { singleton: true, requiredVersion: dependencies["react"], }, "react-dom": { singleton: true, requiredVersion: dependencies["react-dom"], }, }, }), ], }); };
remote_app_1@http://localhost:3001/remoteEntry.js
remote_app_1@http://localhost:3001/remoteEntry.js
{name}@{publicPath}{filename}
// config-overrides.js const { merge } = require("webpack-merge"); const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); const { dependencies } = require("./package.json"); module.exports = function override(config) { return merge(config, { output: { uniqueName: "remote_app_1", publicPath: "http://localhost:3001/", }, plugins: [ new ModuleFederationPlugin({ name: "remote_app_1", filename: "remoteEntry.js", exposes: { "./App": "./src/bootstrap/remote", }, shared: { ...dependencies, react: { singleton: true, requiredVersion: dependencies["react"], }, "react-dom": { singleton: true, requiredVersion: dependencies["react-dom"], }, }, }), ], }); };
const Remote1App = React.lazy(() => import("remote_app_1/App")); <ErrorBoundary fallback={<h1>🤷</h1>}> <React.Suspense fallback={<CircularProgress />}> <Remote1App /> </React.Suspense> </ErrorBoundary>;
const Remote1App = React.lazy(() => import("remote_app_1/App")); <ErrorBoundary fallback={<h1>🤷</h1>}> <React.Suspense fallback={<CircularProgress />}> <Remote1App /> </React.Suspense> </ErrorBoundary>;
const Remote1App = React.lazy(() => import("remote_app_1/App")); <ErrorBoundary fallback={<h1>🤷</h1>}> <React.Suspense fallback={<CircularProgress />}> <Remote1App /> </React.Suspense> </ErrorBoundary>;
const Remote1App = React.lazy(() => import("remote_app_1/App")); <ErrorBoundary fallback={<h1>🤷</h1>}> <React.Suspense fallback={<CircularProgress />}> <Remote1App /> </React.Suspense> </ErrorBoundary>;
const Remote1App = React.lazy(() => import("remote_app_1/App")); <ErrorBoundary fallback={<h1>🤷</h1>}> <React.Suspense fallback={<CircularProgress />}> <Remote1App /> </React.Suspense> </ErrorBoundary>;
const Remote1App = React.lazy(() => import("remote_app_1/App")); <ErrorBoundary fallback={<h1>🤷</h1>}> <React.Suspense fallback={<CircularProgress />}> <Remote1App /> </React.Suspense> </ErrorBoundary>;
host
Entrypoint// src/bootstrap/host.tsx import React from "react"; import ReactDOM from "react-dom/client"; import { App } from "../App"; import "../index.css"; const root = ReactDOM.createRoot(document.getElementById("root")); root.render( <React.StrictMode> <App /> </React.StrictMode> );
host
Entrypoint// src/index.ts // ❌ This won't work once we turn on Module Federation! // import "./bootstrap/host"; // 🪄 magic // Import the component, create the React root, render... import("./bootstrap/host").catch((e) => console.error(e)); // TS wants an import, export, or an // empty 'export {}' statement to make it a module. export {};
// src/index.ts // ❌ This won't work once we turn on Module Federation! // import "./bootstrap/host"; // 🪄 magic // Import the component, create the React root, render... import("./bootstrap/host").catch((e) => console.error(e)); // TS wants an import, export, or an // empty 'export {}' statement to make it a module. export {};
// src/index.ts // ❌ This won't work once we turn on Module Federation! // import "./bootstrap/host"; // 🪄 magic // Import the component, create the React root, render... import("./bootstrap/host").catch((e) => console.error(e)); // TS wants an import, export, or an // empty 'export {}' statement to make it a module. export {};
Alex Lockhart