├── .gitignore ├── README.md ├── architecture.dio ├── child1 ├── .babelrc ├── package-lock.json ├── package.json ├── postcss.config.js ├── src │ ├── App.jsx │ ├── Child1Content.jsx │ ├── Feature1_M1.jsx │ ├── Feature2_M1.jsx │ ├── Feature3_M1.jsx │ ├── Feature4_M1.jsx │ ├── IndexPage.jsx │ ├── index.html │ ├── index.js │ ├── index.scss │ └── store.js ├── tailwind.config.js ├── webpack.config.js └── yarn.lock ├── child2 ├── .babelrc ├── package-lock.json ├── package.json ├── postcss.config.js ├── src │ ├── App.jsx │ ├── Child2Content.jsx │ ├── Feature1_M2.jsx │ ├── Feature2_M2.jsx │ ├── Feature3_M2.jsx │ ├── Feature4_M2.jsx │ ├── IndexPage.jsx │ ├── index.html │ ├── index.js │ ├── index.scss │ └── store.js ├── tailwind.config.js ├── webpack.config.js └── yarn.lock └── container ├── .babelrc ├── cypress.json ├── cypress ├── fixtures │ └── example.json ├── integration │ └── e2e_spec.js ├── plugins │ └── index.js └── support │ ├── commands.js │ └── index.js ├── jest.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── src ├── App.jsx ├── MainLayout.jsx ├── NotDeployed.jsx ├── Test3.jsx ├── component │ └── Layout.jsx ├── index.html ├── index.js ├── index.scss ├── reducers │ ├── userReducer.js │ └── utils.js └── store.js ├── tailwind.config.js ├── webpack.config.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | build 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![image](https://user-images.githubusercontent.com/77550580/161375779-de79ffc5-4db3-47d4-8ec0-0ed60b819774.png) 2 | 3 | ![image](https://user-images.githubusercontent.com/77550580/161376322-e191bea4-e5f5-4dc2-97ed-2149956a5c28.png) 4 | 5 | ![image](https://user-images.githubusercontent.com/77550580/161376337-d311d3e9-9fff-4c18-8671-b33225e1396b.png) 6 | 7 | ![image](https://user-images.githubusercontent.com/77550580/161400831-209d96b4-25da-4eca-850c-8c82dddc408f.png) 8 | 9 | ![image](https://user-images.githubusercontent.com/77550580/161400838-0a4ba69f-3739-43a7-9d1e-5eaad9dbdbdc.png) 10 | -------------------------------------------------------------------------------- /architecture.dio: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /child1/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-react", "@babel/preset-env"], 3 | "plugins": [ 4 | ["@babel/transform-runtime"] 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /child1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "child1", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "build": "webpack --mode production", 6 | "build:dev": "webpack --mode development", 7 | "build:start": "cd dist && PORT=3002 npx serve", 8 | "start": "webpack serve --open --mode development", 9 | "start:live": "webpack serve --open --mode development --live-reload --hot" 10 | }, 11 | "license": "MIT", 12 | "author": { 13 | "name": "Syed Ashar", 14 | "email": "saghirashar@gmail.com" 15 | }, 16 | "devDependencies": { 17 | "@babel/core": "^7.15.8", 18 | "@babel/plugin-transform-runtime": "^7.15.8", 19 | "@babel/preset-env": "^7.15.8", 20 | "@babel/preset-react": "^7.14.5", 21 | "autoprefixer": "^10.1.0", 22 | "babel-loader": "^8.2.2", 23 | "css-loader": "^6.3.0", 24 | "html-webpack-plugin": "^5.3.2", 25 | "postcss": "^8.2.1", 26 | "postcss-loader": "^4.1.0", 27 | "style-loader": "^3.3.0", 28 | "tailwindcss": "^2.0.2", 29 | "webpack": "^5.57.1", 30 | "webpack-cli": "^4.9.0", 31 | "webpack-dev-server": "^4.3.1" 32 | }, 33 | "dependencies": { 34 | "@babel/runtime": "^7.13.10", 35 | "react": "^17.0.2", 36 | "react-dom": "^17.0.2", 37 | "react-redux": "^7.2.8", 38 | "react-router-dom": "^5.3.0", 39 | "redux": "^4.1.2", 40 | "redux-thunk": "^2.4.1", 41 | "remixicon": "^2.5.0", 42 | "rxjs": "^7.4.0", 43 | "windowed-observable": "^1.0.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /child1/postcss.config.js: -------------------------------------------------------------------------------- 1 | const autoprefixer = require("autoprefixer"); 2 | const tailwindcss = require("tailwindcss"); 3 | 4 | module.exports = { 5 | plugins: [tailwindcss, autoprefixer], 6 | }; 7 | -------------------------------------------------------------------------------- /child1/src/App.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | 4 | import "remixicon/fonts/remixicon.css"; 5 | import "./index.scss"; 6 | import Child1Content from 'child1/Child1Content' 7 | import { Provider } from 'react-redux' 8 | import store from './store'; 9 | 10 | ReactDOM.render( 11 | 12 | , document.getElementById("app")); 13 | -------------------------------------------------------------------------------- /child1/src/Child1Content.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { BrowserRouter as Router, Route, Switch , Link } from "react-router-dom"; 3 | import Feature1_M1 from "./Feature1_M1"; 4 | import Feature2_M1 from "./Feature2_M1"; 5 | import Feature3_M1 from "./Feature3_M1"; 6 | import Feature4_M1 from "./Feature4_M1"; 7 | import IndexPage from "./IndexPage"; 8 | import { Observable } from 'windowed-observable'; 9 | import { useDispatch } from 'react-redux' 10 | 11 | const observable = new Observable('child1'); 12 | 13 | export default function Child1Content({}) { 14 | 15 | const dispatch = useDispatch() 16 | 17 | const handleNewMessage = (newMessage) => { 18 | console.log(newMessage); 19 | dispatch({ type: 'CHANGE_PERMS', payload: newMessage.modulePerms}); 20 | }; 21 | 22 | useEffect(() => { 23 | observable.subscribe(handleNewMessage); 24 | return () => { 25 | observable.unsubscribe(handleNewMessage) 26 | } 27 | }, [handleNewMessage]); 28 | 29 | 30 | return ( 31 |
32 | 33 | 34 | 35 | } /> 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /child1/src/Feature1_M1.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router-dom' 3 | import { useSelector } from 'react-redux' 4 | 5 | export default function Feature1_M1() { 6 | 7 | const modulePerms = useSelector((state) => state.userLogin.modulePerms); 8 | 9 | return ( 10 |
Module 1 / Feature 1
11 | ) 12 | } -------------------------------------------------------------------------------- /child1/src/Feature2_M1.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router-dom' 3 | import { useSelector } from 'react-redux' 4 | 5 | export default function Feature2_M1() { 6 | 7 | const modulePerms = useSelector((state) => state.userLogin.modulePerms); 8 | 9 | return ( 10 |
Module 1 / Feature 2
11 | ) 12 | } -------------------------------------------------------------------------------- /child1/src/Feature3_M1.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router-dom' 3 | import { useSelector } from 'react-redux' 4 | 5 | export default function Feature3_M1() { 6 | 7 | const modulePerms = useSelector((state) => state.userLogin.modulePerms); 8 | 9 | return ( 10 |
Module 1 / Feature 3
11 | ) 12 | } -------------------------------------------------------------------------------- /child1/src/Feature4_M1.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router-dom' 3 | import { useSelector } from 'react-redux' 4 | 5 | export default function Feature4_M1() { 6 | 7 | const modulePerms = useSelector((state) => state.userLogin.modulePerms); 8 | 9 | return ( 10 |
Module 1 / Feature 4
11 | ) 12 | } -------------------------------------------------------------------------------- /child1/src/IndexPage.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { BrowserRouter as Switch , Link } from "react-router-dom"; 3 | import { useSelector } from 'react-redux' 4 | 5 | export default function IndexPage({}) { 6 | 7 | 8 | const [pre, setPre] = useState('/') 9 | const modulePerms = useSelector((state) => state.userLogin.modulePerms); 10 | 11 | useEffect(() => { 12 | setPre(modulePerms ? '/module1' : '' ) 13 | }, [ ]) 14 | 15 | return ( 16 |
17 | Module 1 18 | {(!modulePerms || modulePerms.m1_feature1 ) &&

Feature : 1

} 19 | {(!modulePerms || modulePerms.m1_feature2 ) &&

Feature : 2

} 20 | {(!modulePerms || modulePerms.m1_feature3 ) &&

Feature : 3

} 21 | {(!modulePerms || modulePerms.m1_feature4 ) &&

Feature : 4

} 22 |
23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /child1/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | child 1 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /child1/src/index.js: -------------------------------------------------------------------------------- 1 | import("./App"); 2 | -------------------------------------------------------------------------------- /child1/src/index.scss: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body { 6 | font-family: Arial, Helvetica, sans-serif; 7 | 8 | } -------------------------------------------------------------------------------- /child1/src/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, compose, applyMiddleware, combineReducers } from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | 4 | const initialState = {}; 5 | const userSigninReducer = ( state = {} , action ) => 6 | { 7 | switch (action.type) { 8 | case 'CHANGE_PERMS': 9 | return { modulePerms : action.payload }; 10 | default: 11 | return state; 12 | } 13 | }; 14 | 15 | const reducer = combineReducers({ 16 | userLogin : userSigninReducer , 17 | }); 18 | const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; 19 | const store = createStore( 20 | reducer, 21 | initialState, 22 | composeEnhancer(applyMiddleware(thunk)) 23 | ); 24 | 25 | export default store; -------------------------------------------------------------------------------- /child1/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: [], 3 | darkMode: false, // or 'media' or 'class' 4 | theme: { 5 | extend: {}, 6 | }, 7 | variants: { 8 | extend: {}, 9 | }, 10 | plugins: [], 11 | } 12 | -------------------------------------------------------------------------------- /child1/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebPackPlugin = require("html-webpack-plugin"); 2 | const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); 3 | 4 | const deps = require("./package.json").dependencies; 5 | module.exports = { 6 | output: { 7 | publicPath: "http://localhost:3002/", 8 | }, 9 | 10 | resolve: { 11 | extensions: [".tsx", ".ts", ".jsx", ".js", ".json"], 12 | }, 13 | 14 | devServer: { 15 | port: 3002, 16 | historyApiFallback: true, 17 | }, 18 | 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.m?js/, 23 | type: "javascript/auto", 24 | resolve: { 25 | fullySpecified: false, 26 | }, 27 | }, 28 | { 29 | test: /\.(css|s[ac]ss)$/i, 30 | use: ["style-loader", "css-loader", "postcss-loader"], 31 | }, 32 | { 33 | test: /\.(ts|tsx|js|jsx)$/, 34 | exclude: /node_modules/, 35 | use: { 36 | loader: "babel-loader", 37 | }, 38 | }, 39 | ], 40 | }, 41 | 42 | plugins: [ 43 | new ModuleFederationPlugin({ 44 | name: "child1", 45 | filename: "remoteEntry.js", 46 | remotes: { 47 | child1: "child1@http://localhost:3002/remoteEntry.js", 48 | }, 49 | exposes: { 50 | "./Child1Content": "./src/Child1Content.jsx", 51 | "./Feature1_M1":"./src/Feature1_M1.jsx", 52 | "./Feature2_M1":"./src/Feature2_M1.jsx", 53 | "./Feature3_M1":"./src/Feature3_M1.jsx", 54 | "./Feature4_M1":"./src/Feature4_M1.jsx" 55 | }, 56 | shared: { 57 | ...deps, 58 | react: { 59 | singleton: true, 60 | requiredVersion: deps.react, 61 | }, 62 | "react-dom": { 63 | singleton: true, 64 | requiredVersion: deps["react-dom"], 65 | }, 66 | }, 67 | }), 68 | new HtmlWebPackPlugin({ 69 | template: "./src/index.html", 70 | }), 71 | ], 72 | }; 73 | -------------------------------------------------------------------------------- /child2/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-react", "@babel/preset-env"], 3 | "plugins": [ 4 | ["@babel/transform-runtime"] 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /child2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "child2", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "build": "webpack --mode production", 6 | "build:dev": "webpack --mode development", 7 | "build:start": "cd dist && PORT=3001 npx serve", 8 | "start": "webpack serve --open --mode development", 9 | "start:live": "webpack serve --open --mode development --live-reload --hot" 10 | }, 11 | "license": "MIT", 12 | "author": { 13 | "name": "Syed Ashar", 14 | "email": "saghirashar@gmail.com" 15 | }, 16 | "devDependencies": { 17 | "@babel/core": "^7.15.8", 18 | "@babel/plugin-transform-runtime": "^7.15.8", 19 | "@babel/preset-env": "^7.15.8", 20 | "@babel/preset-react": "^7.14.5", 21 | "autoprefixer": "^10.1.0", 22 | "babel-loader": "^8.2.2", 23 | "css-loader": "^6.3.0", 24 | "html-webpack-plugin": "^5.3.2", 25 | "postcss": "^8.2.1", 26 | "postcss-loader": "^4.1.0", 27 | "style-loader": "^3.3.0", 28 | "tailwindcss": "^2.0.2", 29 | "webpack": "^5.57.1", 30 | "webpack-cli": "^4.9.0", 31 | "webpack-dev-server": "^4.3.1" 32 | }, 33 | "dependencies": { 34 | "@babel/runtime": "^7.13.10", 35 | "react": "^17.0.2", 36 | "react-dom": "^17.0.2", 37 | "react-redux": "^7.2.8", 38 | "react-router-dom": "^5.3.0", 39 | "redux": "^4.1.2", 40 | "redux-thunk": "^2.4.1", 41 | "remixicon": "^2.5.0", 42 | "windowed-observable": "^1.0.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /child2/postcss.config.js: -------------------------------------------------------------------------------- 1 | const autoprefixer = require("autoprefixer"); 2 | const tailwindcss = require("tailwindcss"); 3 | 4 | module.exports = { 5 | plugins: [tailwindcss, autoprefixer], 6 | }; 7 | -------------------------------------------------------------------------------- /child2/src/App.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | 4 | import "remixicon/fonts/remixicon.css"; 5 | import "./index.scss"; 6 | import Child2Content from 'child2/Child2Content' 7 | import { Provider } from 'react-redux' 8 | import store from './store'; 9 | 10 | ReactDOM.render( 11 | 12 | , document.getElementById("app")); 13 | -------------------------------------------------------------------------------- /child2/src/Child2Content.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { BrowserRouter as Router, Route, Switch , Link } from "react-router-dom"; 3 | import Feature1_M2 from "./Feature1_M2"; 4 | import Feature2_M2 from "./Feature2_M2"; 5 | import Feature3_M2 from "./Feature3_M2"; 6 | import Feature4_M2 from "./Feature4_M2"; 7 | import IndexPage from "./IndexPage"; 8 | import { Observable } from 'windowed-observable'; 9 | import { useDispatch } from 'react-redux' 10 | 11 | const observable = new Observable('child2'); 12 | 13 | export default function Child2Content({ }) { 14 | 15 | const dispatch = useDispatch() 16 | 17 | const handleNewMessage = (newMessage) => { 18 | console.log(newMessage); 19 | dispatch({ type: 'CHANGE_PERMS', payload: newMessage.modulePerms}); 20 | }; 21 | useEffect(() => { 22 | observable.subscribe(handleNewMessage); 23 | return () => { 24 | observable.unsubscribe(handleNewMessage) 25 | } 26 | }, [handleNewMessage]); 27 | 28 | return ( 29 |
30 | 31 | 32 | 33 | } /> 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /child2/src/Feature1_M2.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router-dom' 3 | import { useSelector } from 'react-redux' 4 | 5 | export default function Feature1_M2() { 6 | 7 | const modulePerms = useSelector((state) => state.userLogin.modulePerms); 8 | 9 | return ( 10 |
Module 2 / Feature 1
11 | ) 12 | } -------------------------------------------------------------------------------- /child2/src/Feature2_M2.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router-dom' 3 | import { useSelector } from 'react-redux' 4 | 5 | export default function Feature2_M2() { 6 | 7 | const modulePerms = useSelector((state) => state.userLogin.modulePerms); 8 | 9 | return ( 10 |
Module 2 / Feature 2
11 | ) 12 | } -------------------------------------------------------------------------------- /child2/src/Feature3_M2.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router-dom' 3 | import { useSelector } from 'react-redux' 4 | 5 | export default function Feature3_M2() { 6 | 7 | const modulePerms = useSelector((state) => state.userLogin.modulePerms); 8 | 9 | return ( 10 |
Module 2 / Feature 3
11 | ) 12 | } -------------------------------------------------------------------------------- /child2/src/Feature4_M2.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router-dom' 3 | import { useSelector } from 'react-redux' 4 | 5 | export default function Feature4_M2() { 6 | 7 | const modulePerms = useSelector((state) => state.userLogin.modulePerms); 8 | 9 | return ( 10 |
Module 2 / Feature 4
11 | ) 12 | } -------------------------------------------------------------------------------- /child2/src/IndexPage.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { BrowserRouter as Router, Route, Switch , Link } from "react-router-dom"; 3 | import { useSelector } from 'react-redux' 4 | 5 | export default function IndexPage({}) { 6 | 7 | 8 | const [pre, setPre] = useState('/') 9 | const modulePerms = useSelector((state) => state.userLogin.modulePerms); 10 | 11 | useEffect(() => { 12 | setPre(modulePerms ? '/module2' : '' ) 13 | }, [ ]) 14 | 15 | return ( 16 |
17 | Module 2 18 | {(!modulePerms || modulePerms.m2_feature1 ) &&

Feature : 1

} 19 | {(!modulePerms || modulePerms.m2_feature2 ) &&

Feature : 2

} 20 | {(!modulePerms || modulePerms.m2_feature3 ) &&

Feature : 3

} 21 | {(!modulePerms || modulePerms.m2_feature4 ) &&

Feature : 4

} 22 |
23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /child2/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Child 2 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /child2/src/index.js: -------------------------------------------------------------------------------- 1 | import("./App"); 2 | -------------------------------------------------------------------------------- /child2/src/index.scss: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body { 6 | font-family: Arial, Helvetica, sans-serif; 7 | } -------------------------------------------------------------------------------- /child2/src/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, compose, applyMiddleware, combineReducers } from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | 4 | const initialState = {}; 5 | const userSigninReducer = ( state = {} , action ) => 6 | { 7 | switch (action.type) { 8 | case 'CHANGE_PERMS': 9 | return { modulePerms : action.payload }; 10 | default: 11 | return state; 12 | } 13 | }; 14 | 15 | const reducer = combineReducers({ 16 | userLogin : userSigninReducer , 17 | }); 18 | const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; 19 | const store = createStore( 20 | reducer, 21 | initialState, 22 | composeEnhancer(applyMiddleware(thunk)) 23 | ); 24 | 25 | export default store; -------------------------------------------------------------------------------- /child2/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: [], 3 | darkMode: false, // or 'media' or 'class' 4 | theme: { 5 | extend: {}, 6 | }, 7 | variants: { 8 | extend: {}, 9 | }, 10 | plugins: [], 11 | } 12 | -------------------------------------------------------------------------------- /child2/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebPackPlugin = require("html-webpack-plugin"); 2 | const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); 3 | 4 | const deps = require("./package.json").dependencies; 5 | module.exports = { 6 | output: { 7 | publicPath: "http://localhost:3001/", 8 | }, 9 | 10 | resolve: { 11 | extensions: [".tsx", ".ts", ".jsx", ".js", ".json"], 12 | }, 13 | 14 | devServer: { 15 | port: 3001, 16 | historyApiFallback: true, 17 | }, 18 | 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.m?js/, 23 | type: "javascript/auto", 24 | resolve: { 25 | fullySpecified: false, 26 | }, 27 | }, 28 | { 29 | test: /\.(css|s[ac]ss)$/i, 30 | use: ["style-loader", "css-loader", "postcss-loader"], 31 | }, 32 | { 33 | test: /\.(ts|tsx|js|jsx)$/, 34 | exclude: /node_modules/, 35 | use: { 36 | loader: "babel-loader", 37 | }, 38 | }, 39 | ], 40 | }, 41 | 42 | plugins: [ 43 | new ModuleFederationPlugin({ 44 | name: "child2", 45 | filename: "remoteEntry.js", 46 | remotes: { 47 | child2: "child2@http://localhost:3001/remoteEntry.js", 48 | }, 49 | exposes: { 50 | "./Child2Content": "./src/Child2Content.jsx", 51 | "./Feature1_M2":"./src/Feature1_M2.jsx", 52 | "./Feature2_M2":"./src/Feature2_M2.jsx", 53 | "./Feature3_M2":"./src/Feature3_M2.jsx", 54 | "./Feature4_M2":"./src/Feature4_M2.jsx" 55 | }, 56 | shared: { 57 | ...deps, 58 | react: { 59 | singleton: true, 60 | requiredVersion: deps.react, 61 | }, 62 | "react-dom": { 63 | singleton: true, 64 | requiredVersion: deps["react-dom"], 65 | }, 66 | }, 67 | }), 68 | new HtmlWebPackPlugin({ 69 | template: "./src/index.html", 70 | }), 71 | ], 72 | }; 73 | -------------------------------------------------------------------------------- /container/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-react", "@babel/preset-env"], 3 | "plugins": [ 4 | ["@babel/transform-runtime"] 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /container/cypress.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /container/cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } 6 | -------------------------------------------------------------------------------- /container/cypress/integration/e2e_spec.js: -------------------------------------------------------------------------------- 1 | describe("My First E2E Test", () => { 2 | it("Should add an item to the cart", () => { 3 | cy.visit("http://localhost:3000/"); 4 | cy.get("#showlogin").click(); 5 | cy.get("#loginbtn").click(); 6 | cy.get("#showcart").click(); 7 | cy.get("#clearcart").click(); 8 | cy.get("#addtocart_1").click(); 9 | cy.get("#cart").click(); 10 | cy.get("#grand_total").should("contain", "$5.99"); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /container/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | /// 2 | // *********************************************************** 3 | // This example plugins/index.js can be used to load plugins 4 | // 5 | // You can change the location of this file or turn off loading 6 | // the plugins file with the 'pluginsFile' configuration option. 7 | // 8 | // You can read more here: 9 | // https://on.cypress.io/plugins-guide 10 | // *********************************************************** 11 | 12 | // This function is called when a project is opened or re-opened (e.g. due to 13 | // the project's config changing) 14 | 15 | /** 16 | * @type {Cypress.PluginConfig} 17 | */ 18 | // eslint-disable-next-line no-unused-vars 19 | module.exports = (on, config) => { 20 | // `on` is used to hook into various events Cypress emits 21 | // `config` is the resolved Cypress config 22 | } 23 | -------------------------------------------------------------------------------- /container/cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add('login', (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This will overwrite an existing command -- 25 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /container/cypress/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /container/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: "jsdom", 3 | }; 4 | -------------------------------------------------------------------------------- /container/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "container", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "build": "webpack --mode production", 6 | "build:dev": "webpack --mode development", 7 | "build:start": "cd dist && PORT=3000 npx serve", 8 | "start": "webpack serve --open --mode development", 9 | "start:live": "webpack serve --open --mode development --live-reload --hot" 10 | }, 11 | "license": "MIT", 12 | "author": { 13 | "name": "Syed Ashar", 14 | "email": "saghirashar@gmail.com" 15 | }, 16 | "devDependencies": { 17 | "@babel/core": "^7.15.8", 18 | "@babel/plugin-transform-runtime": "^7.15.8", 19 | "@babel/preset-env": "^7.15.8", 20 | "@babel/preset-react": "^7.14.5", 21 | "@testing-library/react-hooks": "^7.0.2", 22 | "autoprefixer": "^10.1.0", 23 | "babel-loader": "^8.2.2", 24 | "css-loader": "^6.3.0", 25 | "cypress": "^8.7.0", 26 | "html-webpack-plugin": "^5.3.2", 27 | "jest": "^27.3.1", 28 | "postcss": "^8.2.1", 29 | "postcss-loader": "^4.1.0", 30 | "style-loader": "^3.3.0", 31 | "tailwindcss": "^2.0.2", 32 | "webpack": "^5.57.1", 33 | "webpack-cli": "^4.9.0", 34 | "webpack-dev-server": "^4.3.1" 35 | }, 36 | "dependencies": { 37 | "@babel/runtime": "^7.13.10", 38 | "react": "^17.0.2", 39 | "react-dom": "^17.0.2", 40 | "react-redux": "^7.2.7", 41 | "react-router-dom": "^5.3.0", 42 | "redux": "^4.1.2", 43 | "redux-thunk": "^2.4.1", 44 | "remixicon": "^2.5.0", 45 | "windowed-observable": "^1.0.0" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /container/postcss.config.js: -------------------------------------------------------------------------------- 1 | const autoprefixer = require("autoprefixer"); 2 | const tailwindcss = require("tailwindcss"); 3 | 4 | module.exports = { 5 | plugins: [tailwindcss, autoprefixer], 6 | }; 7 | -------------------------------------------------------------------------------- /container/src/App.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | 4 | import "remixicon/fonts/remixicon.css"; 5 | import "./index.scss"; 6 | 7 | import MainLayout from "container/MainLayout"; 8 | import { Provider } from 'react-redux' 9 | import store from './store'; 10 | 11 | ReactDOM.render( 12 | 13 | 14 | , 15 | document.getElementById("app")); 16 | -------------------------------------------------------------------------------- /container/src/MainLayout.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState , useEffect } from "react"; 2 | import { BrowserRouter as Router, Route, Switch , Link } from "react-router-dom"; 3 | import { useDispatch, useSelector } from 'react-redux' 4 | 5 | import "remixicon/fonts/remixicon.css"; 6 | import "./index.scss"; 7 | import NotDeployed from './NotDeployed'; 8 | import { Observable } from 'windowed-observable'; 9 | const observable1 = new Observable('child1'); 10 | const observable2 = new Observable('child2'); 11 | const observable3 = new Observable('child3'); 12 | 13 | let Child1Content , Child2Content , Child3Content , Feature1_M1 , Feature2_M1 , Feature3_M1 , Feature4_M1 ,Feature1_M2 , Feature2_M2 , Feature3_M2 , Feature4_M2 ; 14 | try { Child1Content = require('child1/Child1Content').default } catch (e) {Child1Content = NotDeployed} 15 | try { Child2Content = require('child2/Child2Content').default } catch (e) {Child2Content = NotDeployed} 16 | try { Child3Content = require('child3/Child3Content').default } catch (e) {Child3Content = NotDeployed} 17 | try { Feature1_M1 = require('child1/Feature1_M1').default } catch (e) {Feature1_M1 = NotDeployed} 18 | try { Feature1_M2 = require('child1/Feature2_M1').default } catch (e) {Feature2_M1 = NotDeployed} 19 | try { Feature3_M1 = require('child1/Feature3_M1').default } catch (e) {Feature3_M1 = NotDeployed} 20 | try { Feature4_M1 = require('child1/Feature4_M1').default } catch (e) {Feature4_M1 = NotDeployed} 21 | try { Feature1_M2 = require('child2/Feature1_M2').default } catch (e) {Feature1_M2 = NotDeployed} 22 | try { Feature2_M2 = require('child2/Feature2_M2').default } catch (e) {Feature2_M2 = NotDeployed} 23 | try { Feature3_M2 = require('child2/Feature3_M2').default } catch (e) {Feature3_M2 = NotDeployed} 24 | try { Feature4_M2 = require('child2/Feature4_M2').default } catch (e) {Feature4_M2 = NotDeployed} 25 | 26 | 27 | 28 | export default function MainLayout() { 29 | 30 | const dispatch = useDispatch() 31 | 32 | const modules = useSelector((state) => state.userLogin.modules); 33 | const modulePerms = useSelector((state) => state.userLogin.modulePerms); 34 | 35 | const [path, setPath] = useState(null) 36 | 37 | const login = () => { 38 | dispatch({ type: 'USER_SIGNIN_REQUEST'}); 39 | //wait 40 | dispatch({ type: 'USER_SIGNIN_SUCCESS'}) 41 | 42 | } 43 | 44 | const logout = () => { 45 | dispatch({ type: 'USER_SIGNOUT'}); 46 | window.sessionStorage.clear(); 47 | } 48 | 49 | useEffect(() => { 50 | console.log(modules); 51 | console.log(modulePerms); 52 | if (!modules || !modulePerms ) return ; 53 | window.sessionStorage.setItem('modules',JSON.stringify(modules)) 54 | window.sessionStorage.setItem('modulePerms',JSON.stringify(modulePerms)) 55 | }, [modules,modulePerms]) 56 | 57 | 58 | const changePerm = () => { 59 | 60 | if(!modules) return; 61 | dispatch({ type: 'USER_CHANGE_PERMISSIONS'}); 62 | setPath('/') 63 | } 64 | 65 | const handleRouteChange = (r) => { 66 | 67 | if(r=='/module1') observable1.publish({modulePerms : {m1_feature1 : modulePerms.m1_feature1 , m1_feature2 : modulePerms.m1_feature2 , m1_feature3 : modulePerms.m1_feature3 , m1_feature4 : modulePerms.m1_feature4} }); 68 | if(r=='/module2') observable2.publish({modulePerms : {m2_feature1 : modulePerms.m2_feature1 , m2_feature2 : modulePerms.m2_feature2 , m2_feature3 : modulePerms.m2_feature3 , m2_feature4 : modulePerms.m2_feature4} }); 69 | if(r=='/module3') observable3.publish({modulePerms : {m3_feature1 : modulePerms.m3_feature1 , m3_feature2 : modulePerms.m3_feature2 , m3_feature3 : modulePerms.m3_feature3 , m3_feature4 : modulePerms.m3_feature4} }); 70 | 71 | setPath(r) 72 | } 73 | 74 | 75 | return ( 76 | 77 | 78 |
79 |
80 |
81 | Home 82 |
|
83 | 84 |
|
85 | 86 |
87 |
88 | Change Controls 89 |
90 |
91 |
92 | 93 |
94 |
95 | {(modules && modules.m1) && handleRouteChange('/module1')} >

Module 1

} 96 | {(modules && modules.m2) && handleRouteChange('/module2')} >

Module 2

} 97 | {(modules && modules.m3) && handleRouteChange('/module3')} >

Module 3

} 98 |
99 |
100 |
101 |
102 | {modules && modulePerms ? 103 |
Welcome
} /> 104 | 105 | {modules.m1 && } />} 106 | {modulePerms.m1_feature1 && } />} 107 | {modulePerms.m1_feature2 && } />} 108 | {modulePerms.m1_feature3 && } />} 109 | {modulePerms.m1_feature4 && } />} 110 | 111 | {modules.m2 && } />} 112 | {modulePerms.m2_feature1 && } />} 113 | {modulePerms.m2_feature2 && } />} 114 | {modulePerms.m2_feature3 && } />} 115 | {modulePerms.m2_feature4 && } />} 116 | 117 | {modules.m3 && } />} 118 | 119 |
Page Not Found
} /> 120 | 121 |
122 | : 123 |

Please Log In First

124 | } 125 |
126 |
127 |
128 |
129 | 130 | 131 | 132 |
133 | Footer 134 |
135 |
136 | ); 137 | } 138 | -------------------------------------------------------------------------------- /container/src/NotDeployed.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useHistory } from 'react-router-dom' 3 | 4 | export default function NotDeployed() { 5 | 6 | const history = useHistory() 7 | return ( 8 |
9 |

{history && history.location.pathname.split('/')[1]}

10 |

is Not Deployed. Please Contact Support.

11 |
12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /container/src/Test3.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | // import TrySome from "child3/TrySome"; 3 | 4 | export default function Test3() { 5 | return ( 6 |
7 |
8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /container/src/component/Layout.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router-dom' 3 | 4 | export default function Layout({children}) { 5 | return ( 6 |
7 |
8 |
9 |
10 | Home 11 |
|
12 | 13 | 14 |
15 |
16 | Change Controls 17 |
18 |
19 |
20 | 21 | 22 |
23 | {children} 24 |
25 | 26 |
27 | Footer 28 |
29 | 30 |
31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /container/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | home 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /container/src/index.js: -------------------------------------------------------------------------------- 1 | import("./App"); 2 | -------------------------------------------------------------------------------- /container/src/index.scss: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body { 6 | font-family: Arial, Helvetica, sans-serif; 7 | } -------------------------------------------------------------------------------- /container/src/reducers/userReducer.js: -------------------------------------------------------------------------------- 1 | import {m1 , m2 , m3 , m1_feature1 , m1_feature2 , m2_feature3 , m2_feature1 , m1_feature3 , m2_feature2 , m3_feature1 , m1_feature4 , m2_feature4 } from './utils' 2 | 3 | export const userSigninReducer = ( 4 | 5 | state = { 6 | modules : window.sessionStorage.getItem("modules") ? JSON.parse(window.sessionStorage.getItem("modules")) : null , 7 | modulePerms : window.sessionStorage.getItem("modulePerms") ? JSON.parse(window.sessionStorage.getItem("modulePerms")) : null , 8 | } 9 | , action ) => 10 | { 11 | 12 | console.log(state); 13 | 14 | switch (action.type) { 15 | case 'USER_SIGNIN_REQUEST': 16 | return { loading: true }; 17 | case 'USER_SIGNIN_SUCCESS': 18 | return { 19 | loading: false, 20 | modules: {m1 , m2 , m3 }, 21 | modulePerms:{ 22 | m1_feature1, 23 | m1_feature2, 24 | m1_feature3, 25 | m1_feature4, 26 | m2_feature1, 27 | m2_feature2, 28 | m2_feature3, 29 | m2_feature4, 30 | m3_feature1} 31 | }; 32 | case 'USER_CHANGE_PERMISSIONS': 33 | return { 34 | loading: false, 35 | modules: {m1} , 36 | modulePerms:{ 37 | m1_feature2, 38 | m1_feature3 } 39 | }; 40 | case 'USER_SIGNIN_FAIL': 41 | return { loading: false, error: action.payload }; 42 | case 'USER_SIGNOUT': 43 | return {}; 44 | default: 45 | return state; 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /container/src/reducers/utils.js: -------------------------------------------------------------------------------- 1 | const m1 = true ; 2 | const m2 = true ; 3 | const m3 = true ; 4 | const m1_feature1 = {} ; 5 | const m1_feature2 = {} ; 6 | const m1_feature3 = {} ; 7 | const m1_feature4 = {} ; 8 | const m2_feature1 = {} ; 9 | const m2_feature2 = {} ; 10 | const m2_feature3 = {} ; 11 | const m2_feature4 = {} ; 12 | const m3_feature1 = {} ; 13 | 14 | export {m1 , m2 , m3 , m1_feature1 , m1_feature2 , m2_feature3 , m2_feature1 , m1_feature3 , m2_feature2 , m3_feature1 , m1_feature4 , m2_feature4 } -------------------------------------------------------------------------------- /container/src/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, compose, applyMiddleware, combineReducers } from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | import {userSigninReducer} from './reducers/userReducer'; 4 | 5 | 6 | const initialState = {}; 7 | 8 | const reducer = combineReducers({ 9 | userLogin : userSigninReducer , 10 | }); 11 | const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; 12 | const store = createStore( 13 | reducer, 14 | initialState, 15 | composeEnhancer(applyMiddleware(thunk)) 16 | ); 17 | 18 | export default store; -------------------------------------------------------------------------------- /container/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: [], 3 | darkMode: false, // or 'media' or 'class' 4 | theme: { 5 | extend: {}, 6 | }, 7 | variants: { 8 | extend: {}, 9 | }, 10 | plugins: [], 11 | } 12 | -------------------------------------------------------------------------------- /container/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebPackPlugin = require("html-webpack-plugin"); 2 | const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); 3 | 4 | const deps = require("./package.json").dependencies; 5 | module.exports = { 6 | output: { 7 | publicPath: "http://localhost:3000/", 8 | }, 9 | 10 | resolve: { 11 | extensions: [".tsx", ".ts", ".jsx", ".js", ".json"], 12 | }, 13 | 14 | devServer: { 15 | port: 3000, 16 | historyApiFallback: true, 17 | }, 18 | 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.m?js/, 23 | type: "javascript/auto", 24 | resolve: { 25 | fullySpecified: false, 26 | }, 27 | }, 28 | { 29 | test: /\.(css|s[ac]ss)$/i, 30 | use: ["style-loader", "css-loader", "postcss-loader"], 31 | }, 32 | { 33 | test: /\.(ts|tsx|js|jsx)$/, 34 | exclude: /node_modules/, 35 | use: { 36 | loader: "babel-loader", 37 | }, 38 | }, 39 | ], 40 | }, 41 | 42 | plugins: [ 43 | new ModuleFederationPlugin({ 44 | name: "container", 45 | filename: "remoteEntry.js", 46 | remotes: { 47 | container: "container@http://localhost:3000/remoteEntry.js", 48 | child2: "child2@http://localhost:3001/remoteEntry.js", 49 | child1: "child1@http://localhost:3002/remoteEntry.js", 50 | child3: "child3@http://localhost:3003/remoteEntry.js" 51 | }, 52 | exposes: { 53 | "./MainLayout": "./src/MainLayout.jsx", 54 | }, 55 | shared: { 56 | ...deps, 57 | react: { 58 | singleton: true, 59 | requiredVersion: deps.react, 60 | }, 61 | "react-dom": { 62 | singleton: true, 63 | requiredVersion: deps["react-dom"], 64 | }, 65 | }, 66 | }), 67 | new HtmlWebPackPlugin({ 68 | template: "./src/index.html", 69 | }), 70 | ], 71 | }; 72 | --------------------------------------------------------------------------------