├── .gitignore ├── README.md ├── app1 ├── package-lock.json ├── package.json ├── public │ └── index.html ├── src │ ├── App.js │ ├── bootstrap.js │ └── index.js └── webpack.config.js ├── app2 ├── package-lock.json ├── package.json ├── public │ └── index.html ├── src │ ├── App.css │ ├── App.js │ ├── bootstrap.js │ └── index.js └── webpack.config.js └── app3 ├── package-lock.json ├── package.json ├── public └── index.html ├── src ├── App.js ├── bootstrap.js └── index.js └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | .idea 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # TypeScript v1 declaration files 46 | typings/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Microbundle cache 58 | .rpt2_cache/ 59 | .rts2_cache_cjs/ 60 | .rts2_cache_es/ 61 | .rts2_cache_umd/ 62 | 63 | # Optional REPL history 64 | .node_repl_history 65 | 66 | # Output of 'npm pack' 67 | *.tgz 68 | 69 | # Yarn Integrity file 70 | .yarn-integrity 71 | 72 | # dotenv environment variables file 73 | .env 74 | .env.test 75 | 76 | # parcel-bundler cache (https://parceljs.org/) 77 | .cache 78 | 79 | # Next.js build output 80 | .next 81 | 82 | # Nuxt.js build / generate output 83 | .nuxt 84 | dist 85 | .next 86 | buildClient 87 | buildServer 88 | 89 | # Gatsby files 90 | .cache/ 91 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 92 | # https://nextjs.org/blog/next-9-1#public-directory-support 93 | # public 94 | 95 | # vuepress build output 96 | .vuepress/dist 97 | 98 | # Serverless directories 99 | .serverless/ 100 | 101 | # FuseBox cache 102 | .fusebox/ 103 | 104 | # DynamoDB Local files 105 | .dynamodb/ 106 | 107 | # TernJS port file 108 | .tern-port 109 | /streamed-federation/.s3rver/ 110 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Micro front-end implementation using Webpack 5 Module Federation and Bit 2 | 3 | This example shows how you can create a component library application that exposes components for other apps to use. 4 | 5 | For more information, visit the article [explaining this project](https://blog.bitsrc.io/revolutionizing-micro-frontends-with-webpack-5-module-federation-and-bit-99ff81ceb0) 6 | 7 | - `app1` and `app2` are standalone applications that exposes its App file for reuse. 8 | - `app3` is the master Application that consumes both `app1` and `app2` 9 | 10 | ## Running Demo 11 | 12 | Run `npm install` and `npm start` inside each repo respectively. This will build and serve your apps on ports 3001, 3002 and 3003 13 | 14 | - [localhost:3001](http://localhost:3001/) 15 | - [localhost:3002](http://localhost:3002/) 16 | - [localhost:3003](http://localhost:3003/) 17 | 18 | Example referenced from https://github.com/module-federation/module-federation-examples/tree/master/basic-host-remote 19 | -------------------------------------------------------------------------------- /app1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@bit-module-federation/app1", 3 | "version": "0.0.0", 4 | "private": true, 5 | "devDependencies": { 6 | "@babel/core": "7.10.3", 7 | "@babel/preset-react": "7.10.1", 8 | "babel-loader": "8.1.0", 9 | "bundle-loader": "0.5.6", 10 | "css-loader": "^3.6.0", 11 | "html-webpack-plugin": "git://github.com/ScriptedAlchemy/html-webpack-plugin#master", 12 | "serve": "11.3.2", 13 | "style-loader": "^1.2.1", 14 | "webpack": "5.0.0-beta.18", 15 | "webpack-cli": "3.3.11", 16 | "webpack-dev-server": "3.11.0" 17 | }, 18 | "scripts": { 19 | "start": "webpack-dev-server", 20 | "build": "webpack --mode production", 21 | "serve": "serve dist -p 3001", 22 | "clean": "rm -rf dist" 23 | }, 24 | "dependencies": { 25 | "@bit/nsebhastian.design-system.card": "0.0.3", 26 | "react": "^16.13.0", 27 | "react-dom": "^16.13.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app1/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | -------------------------------------------------------------------------------- /app1/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Card from '@bit/nsebhastian.design-system.card'; 3 | 4 | const App = props => { 5 | const buttonClick = () => { 6 | const onClick = props.onClick; 7 | if (onClick) { 8 | onClick(); 9 | } else { 10 | console.log('button is clicked'); 11 | } 12 | }; 13 | return ( 14 |
15 | buttonClick()} 20 | /> 21 | buttonClick()} 26 | /> 27 | buttonClick()} 32 | /> 33 |
34 | ); 35 | }; 36 | 37 | export default App; 38 | -------------------------------------------------------------------------------- /app1/src/bootstrap.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import App from "./App"; 4 | ReactDOM.render(, document.getElementById("root")); 5 | -------------------------------------------------------------------------------- /app1/src/index.js: -------------------------------------------------------------------------------- 1 | import("./bootstrap"); 2 | -------------------------------------------------------------------------------- /app1/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const { ModuleFederationPlugin } = require("webpack").container; 3 | const path = require("path"); 4 | 5 | module.exports = { 6 | entry: "./src/index", 7 | mode: "development", 8 | devServer: { 9 | contentBase: path.join(__dirname, "dist"), 10 | port: 3001, 11 | }, 12 | output: { 13 | publicPath: "http://localhost:3001/", 14 | }, 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.jsx?$/, 19 | loader: "babel-loader", 20 | exclude: /node_modules/, 21 | options: { 22 | presets: ["@babel/preset-react"] 23 | }, 24 | }, 25 | { 26 | test: /\.css$/i, 27 | use: ['style-loader', 'css-loader'], 28 | }, 29 | ], 30 | }, 31 | plugins: [ 32 | new ModuleFederationPlugin({ 33 | name: "app1", 34 | library: { type: "var", name: "app1" }, 35 | filename: "remoteEntry.js", 36 | exposes: { 37 | // expose each component 38 | "./App": "./src/App", 39 | }, 40 | shared: ["react", "react-dom"], 41 | }), 42 | new HtmlWebpackPlugin({ 43 | template: "./public/index.html", 44 | }), 45 | ], 46 | }; 47 | -------------------------------------------------------------------------------- /app2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@bit-module-federation/app2", 3 | "version": "0.0.0", 4 | "private": true, 5 | "devDependencies": { 6 | "@babel/core": "7.10.3", 7 | "@babel/preset-react": "7.10.1", 8 | "babel-loader": "8.1.0", 9 | "html-webpack-plugin": "git://github.com/ScriptedAlchemy/html-webpack-plugin#master", 10 | "serve": "11.3.2", 11 | "webpack": "5.0.0-beta.18", 12 | "webpack-cli": "3.3.11", 13 | "webpack-dev-server": "3.11.0" 14 | }, 15 | "scripts": { 16 | "start": "webpack-dev-server", 17 | "build": "webpack --mode production", 18 | "serve": "serve dist -p 3002", 19 | "clean": "rm -rf dist" 20 | }, 21 | "dependencies": { 22 | "@bit/nsebhastian.design-system.button": "0.0.1", 23 | "css-loader": "^3.6.0", 24 | "react": "^16.13.0", 25 | "react-datepicker": "^3.1.3", 26 | "react-dom": "^16.13.0", 27 | "style-loader": "^1.2.1" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app2/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | -------------------------------------------------------------------------------- /app2/src/App.css: -------------------------------------------------------------------------------- 1 | .container { 2 | margin: 3em auto; 3 | max-width: 720px; 4 | } 5 | 6 | .column { 7 | margin: 0 auto; 8 | display: flex; 9 | justify-content: space-between; 10 | flex-direction: column; 11 | width: 350px; 12 | height: 500px; 13 | border: 2px solid blue; 14 | } 15 | 16 | .column-header { 17 | text-align: center; 18 | } 19 | 20 | .column-content { 21 | margin: 0 auto; 22 | margin-bottom: 100px; 23 | } 24 | 25 | .form-group { 26 | margin-bottom: 1em; 27 | } 28 | 29 | .form-label { 30 | padding: 0 1em; 31 | } 32 | 33 | .react-datepicker-wrapper { 34 | float: right; 35 | } -------------------------------------------------------------------------------- /app2/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import DatePicker from 'react-datepicker'; 3 | import Button from '@bit/nsebhastian.design-system.button'; 4 | import 'react-datepicker/dist/react-datepicker.css'; 5 | import './App.css'; 6 | 7 | export default class App extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = {startDate: '', endDate: ''}; 11 | } 12 | 13 | render() { 14 | const {startDate, endDate} = this.state; 15 | return ( 16 |
17 |
18 |
19 |

Book the room

20 |
21 |
22 |
23 |
24 | 25 | this.setState({startDate: date})} 28 | /> 29 |
30 |
31 | 32 | this.setState({endDate: date})} 35 | /> 36 |
37 |
38 |
39 |
44 |
45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app2/src/bootstrap.js: -------------------------------------------------------------------------------- 1 | import App from "./App"; 2 | import React from "react"; 3 | import ReactDOM from "react-dom"; 4 | 5 | ReactDOM.render(, document.getElementById("root")); 6 | -------------------------------------------------------------------------------- /app2/src/index.js: -------------------------------------------------------------------------------- 1 | import("./bootstrap"); 2 | -------------------------------------------------------------------------------- /app2/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const { ModuleFederationPlugin } = require("webpack").container; 3 | const path = require("path"); 4 | 5 | module.exports = { 6 | entry: "./src/index", 7 | mode: "development", 8 | devServer: { 9 | contentBase: path.join(__dirname, "dist"), 10 | port: 3002, 11 | }, 12 | output: { 13 | publicPath: "http://localhost:3002/", 14 | }, 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.jsx?$/, 19 | loader: "babel-loader", 20 | exclude: /node_modules/, 21 | options: { 22 | presets: ["@babel/preset-react"], 23 | }, 24 | }, 25 | { 26 | test: /\.css$/i, 27 | use: ['style-loader', 'css-loader'], 28 | }, 29 | ], 30 | }, 31 | plugins: [ 32 | new ModuleFederationPlugin({ 33 | name: "app2", 34 | library: { type: "var", name: "app2" }, 35 | filename: "remoteEntry.js", 36 | exposes: { 37 | "./App": "./src/App", 38 | }, 39 | shared: ["react", "react-dom"], 40 | }), 41 | new HtmlWebpackPlugin({ 42 | template: "./public/index.html", 43 | }), 44 | ], 45 | }; 46 | -------------------------------------------------------------------------------- /app3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@bit-module-federation/app3", 3 | "version": "0.0.1", 4 | "private": true, 5 | "devDependencies": { 6 | "@babel/core": "7.10.3", 7 | "@babel/preset-react": "7.10.1", 8 | "@babel/plugin-proposal-class-properties": "^7.10.4", 9 | "babel-loader": "8.1.0", 10 | "html-webpack-plugin": "git://github.com/ScriptedAlchemy/html-webpack-plugin#master", 11 | "serve": "11.3.2", 12 | "webpack": "5.0.0-beta.18", 13 | "webpack-cli": "3.3.11", 14 | "webpack-dev-server": "3.11.0" 15 | }, 16 | "scripts": { 17 | "start": "webpack-dev-server", 18 | "build": "webpack --mode production", 19 | "serve": "serve dist -p 3003", 20 | "clean": "rm -rf dist" 21 | }, 22 | "dependencies": { 23 | "@bit/nsebhastian.design-system.navbar": "0.0.2", 24 | "css-loader": "^3.6.0", 25 | "react": "^16.13.0", 26 | "react-dom": "^16.13.0", 27 | "style-loader": "^1.2.1" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app3/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /app3/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Navbar from '@bit/nsebhastian.design-system.navbar'; 3 | 4 | const ExploreHotel = React.lazy(() => import('app1/App')); 5 | const BookRoom = React.lazy(() => import('app2/App')); 6 | 7 | export default class App extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = {view: 1}; 11 | this.bookTheRoom = this.bookTheRoom.bind(this); 12 | } 13 | 14 | bookTheRoom() { 15 | this.setState({view: 2}); 16 | } 17 | 18 | render() { 19 | const {view} = this.state; 20 | let component = ( 21 | 22 | 23 | 24 | ); 25 | if (view === 2) { 26 | component = ( 27 | 28 | 29 | 30 | ); 31 | } 32 | return ( 33 | <> 34 | 35 | {component} 36 | 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app3/src/bootstrap.js: -------------------------------------------------------------------------------- 1 | import App from "./App"; 2 | import React from "react"; 3 | import ReactDOM from "react-dom"; 4 | 5 | ReactDOM.render(, document.getElementById("root")); -------------------------------------------------------------------------------- /app3/src/index.js: -------------------------------------------------------------------------------- 1 | import('./bootstrap'); -------------------------------------------------------------------------------- /app3/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const { ModuleFederationPlugin } = require("webpack").container; 3 | const path = require("path"); 4 | 5 | module.exports = { 6 | entry: "./src/index", 7 | mode: "development", 8 | devServer: { 9 | contentBase: path.join(__dirname, "dist"), 10 | port: 3003, 11 | }, 12 | output: { 13 | publicPath: "http://localhost:3003/", 14 | }, 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.jsx?$/, 19 | loader: "babel-loader", 20 | exclude: /node_modules/, 21 | options: { 22 | presets: ["@babel/preset-react"] 23 | }, 24 | }, 25 | { 26 | test: /\.css$/i, 27 | use: ['style-loader', 'css-loader'], 28 | }, 29 | ], 30 | }, 31 | plugins: [ 32 | new ModuleFederationPlugin({ 33 | name: "app3", 34 | library: { type: "var", name: "app3" }, 35 | remotes: { 36 | app1: "app1", 37 | app2: "app2", 38 | }, 39 | shared: ["react", "react-dom"], 40 | }), 41 | new HtmlWebpackPlugin({ 42 | template: "./public/index.html", 43 | }), 44 | ], 45 | }; 46 | --------------------------------------------------------------------------------