├── .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 |
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 |
--------------------------------------------------------------------------------