├── .gitignore ├── auth ├── src │ ├── index.tsx │ ├── components │ │ ├── styled.tsx │ │ ├── Input.tsx │ │ ├── Register.tsx │ │ └── Login.tsx │ ├── bootstrap.tsx │ └── App.tsx ├── tsconfig.json ├── webpack.common.js ├── public │ └── index.html ├── webpack.prod.js ├── webpack.dev.js └── package.json ├── header ├── src │ ├── index.tsx │ ├── bootstrap.tsx │ └── Header.tsx ├── tsconfig.json ├── webpack.common.js ├── public │ └── index.html ├── webpack.prod.js ├── webpack.dev.js └── package.json ├── landing ├── src │ ├── index.tsx │ ├── bootstrap.tsx │ └── App.tsx ├── tsconfig.json ├── webpack.common.js ├── public │ └── index.html ├── webpack.prod.js ├── webpack.dev.js └── package.json ├── container ├── src │ ├── index.tsx │ ├── typings │ │ ├── dashboard.d.ts │ │ ├── auth.d.ts │ │ ├── header.d.ts │ │ └── landing.d.ts │ ├── modules │ │ ├── Dashboard.tsx │ │ ├── Landing.tsx │ │ ├── Auth.tsx │ │ └── Header.tsx │ ├── bootstrap.tsx │ ├── hooks │ │ ├── useRouter.ts │ │ └── useAuth.ts │ └── App.tsx ├── tsconfig.json ├── public │ └── index.html ├── webpack.common.js ├── webpack.prod.js ├── webpack.dev.js └── package.json ├── dashboard ├── src │ ├── index.tsx │ ├── App.tsx │ └── bootstrap.tsx ├── tsconfig.json ├── public │ └── index.html ├── webpack.common.js ├── webpack.prod.js ├── package.json └── webpack.dev.js ├── test-iterations ├── iter1 │ ├── cart │ │ ├── src │ │ │ ├── index.js │ │ │ └── bootstrap.js │ │ ├── public │ │ │ └── index.html │ │ ├── package.json │ │ └── webpack.config.js │ ├── container │ │ ├── src │ │ │ ├── index.js │ │ │ └── bootstrap.js │ │ ├── public │ │ │ └── index.html │ │ ├── package.json │ │ └── webpack.config.js │ └── products │ │ ├── src │ │ ├── index.js │ │ └── bootstrap.js │ │ ├── public │ │ └── index.html │ │ ├── package.json │ │ └── webpack.config.js ├── iter2 │ ├── .gitignore │ ├── auth │ │ ├── src │ │ │ ├── index.js │ │ │ ├── App.js │ │ │ ├── bootstrap.js │ │ │ └── components │ │ │ │ ├── Signin.js │ │ │ │ └── Signup.js │ │ ├── public │ │ │ └── index.html │ │ ├── config │ │ │ ├── webpack.common.js │ │ │ ├── webpack.prod.js │ │ │ └── webpack.dev.js │ │ └── package.json │ ├── container │ │ ├── src │ │ │ ├── index.js │ │ │ ├── bootstrap.js │ │ │ ├── components │ │ │ │ ├── DashboardApp.js │ │ │ │ ├── Progress.js │ │ │ │ ├── MarketingApp.js │ │ │ │ ├── AuthApp.js │ │ │ │ └── Header.js │ │ │ └── App.js │ │ ├── public │ │ │ └── index.html │ │ ├── config │ │ │ ├── webpack.common.js │ │ │ ├── webpack.prod.js │ │ │ └── webpack.dev.js │ │ └── package.json │ ├── dashboard │ │ ├── src │ │ │ ├── index.js │ │ │ ├── bootstrap.js │ │ │ └── components │ │ │ │ └── Dashboard.vue │ │ ├── public │ │ │ └── index.html │ │ ├── config │ │ │ ├── webpack.prod.js │ │ │ ├── webpack.common.js │ │ │ └── webpack.dev.js │ │ └── package.json │ ├── marketing │ │ ├── src │ │ │ ├── index.js │ │ │ ├── App.js │ │ │ ├── bootstrap.js │ │ │ └── components │ │ │ │ ├── Landing.js │ │ │ │ └── Pricing.js │ │ ├── marketing-components.zip │ │ ├── public │ │ │ └── index.html │ │ ├── config │ │ │ ├── webpack.common.js │ │ │ ├── webpack.prod.js │ │ │ └── webpack.dev.js │ │ └── package.json │ └── .github │ │ └── workflows │ │ ├── auth.yml │ │ ├── dashboard.yml │ │ ├── marketing.yml │ │ └── container.yml ├── iter3 │ ├── app1 │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── app2.d.ts │ │ │ ├── bootstrap.tsx │ │ │ └── App.tsx │ │ ├── public │ │ │ └── index.html │ │ ├── tsconfig.json │ │ ├── package.json │ │ └── webpack.config.js │ └── app2 │ │ ├── public │ │ └── index.html │ │ ├── src │ │ ├── index.tsx │ │ ├── Button.tsx │ │ ├── bootstrap.tsx │ │ └── App.tsx │ │ ├── tsconfig.json │ │ ├── package.json │ │ └── webpack.config.js └── README.md ├── deploy ├── .npmignore ├── jest.config.js ├── .gitignore ├── bin │ └── deploy.ts ├── cdk.json ├── tsconfig.json ├── test │ └── deploy.test.ts ├── README.md ├── package.json └── lib │ └── deploy-stack.ts ├── scripts ├── app ├── ts-config ├── bootstrap ├── index-html ├── webpack-common ├── webpack-prod ├── webpack-dev └── mfegen.ts ├── package.json ├── README.md ├── .github └── workflows │ └── cdk-deploy.yml └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | dist 4 | -------------------------------------------------------------------------------- /auth/src/index.tsx: -------------------------------------------------------------------------------- 1 | import("./bootstrap"); 2 | -------------------------------------------------------------------------------- /header/src/index.tsx: -------------------------------------------------------------------------------- 1 | import("./bootstrap") 2 | -------------------------------------------------------------------------------- /landing/src/index.tsx: -------------------------------------------------------------------------------- 1 | import("./bootstrap"); 2 | -------------------------------------------------------------------------------- /container/src/index.tsx: -------------------------------------------------------------------------------- 1 | import("./bootstrap"); 2 | -------------------------------------------------------------------------------- /dashboard/src/index.tsx: -------------------------------------------------------------------------------- 1 | import("./bootstrap"); 2 | -------------------------------------------------------------------------------- /test-iterations/iter1/cart/src/index.js: -------------------------------------------------------------------------------- 1 | import("./bootstrap"); 2 | -------------------------------------------------------------------------------- /test-iterations/iter2/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | dist 4 | -------------------------------------------------------------------------------- /test-iterations/iter2/auth/src/index.js: -------------------------------------------------------------------------------- 1 | import("./bootstrap"); 2 | -------------------------------------------------------------------------------- /test-iterations/iter1/container/src/index.js: -------------------------------------------------------------------------------- 1 | import("./bootstrap"); 2 | -------------------------------------------------------------------------------- /test-iterations/iter1/products/src/index.js: -------------------------------------------------------------------------------- 1 | import("./bootstrap"); 2 | -------------------------------------------------------------------------------- /test-iterations/iter2/container/src/index.js: -------------------------------------------------------------------------------- 1 | import("./bootstrap"); 2 | -------------------------------------------------------------------------------- /test-iterations/iter2/dashboard/src/index.js: -------------------------------------------------------------------------------- 1 | import("./bootstrap"); 2 | -------------------------------------------------------------------------------- /test-iterations/iter2/marketing/src/index.js: -------------------------------------------------------------------------------- 1 | import("./bootstrap"); 2 | -------------------------------------------------------------------------------- /deploy/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | -------------------------------------------------------------------------------- /test-iterations/iter3/app1/src/index.tsx: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import bootstrap from "./bootstrap"; 3 | bootstrap(() => {}); 4 | -------------------------------------------------------------------------------- /test-iterations/iter3/app2/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | -------------------------------------------------------------------------------- /test-iterations/iter3/app2/src/index.tsx: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import bootstrap from "./bootstrap"; 3 | bootstrap(() => {}); 4 | -------------------------------------------------------------------------------- /test-iterations/iter3/app1/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | -------------------------------------------------------------------------------- /scripts/app: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const App = () => { 4 | return
__MODULE_NAME_CAPITALIZED__
; 5 | }; 6 | 7 | export default App; 8 | -------------------------------------------------------------------------------- /test-iterations/iter3/app2/src/Button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | const Button = () => ; 4 | 5 | export default Button; 6 | -------------------------------------------------------------------------------- /container/src/typings/dashboard.d.ts: -------------------------------------------------------------------------------- 1 | declare module "dashboard/DashboardModule" { 2 | const mount: (el: HTMLDivElement | null) => null; 3 | 4 | export { mount }; 5 | } 6 | -------------------------------------------------------------------------------- /deploy/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['/test'], 3 | testMatch: ['**/*.test.ts'], 4 | transform: { 5 | '^.+\\.tsx?$': 'ts-jest' 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /test-iterations/iter2/marketing/marketing-components.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashwanth1109/react-micro-frontends/HEAD/test-iterations/iter2/marketing/marketing-components.zip -------------------------------------------------------------------------------- /test-iterations/iter3/app1/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": true, 4 | "strict": true, 5 | "jsx": "react" 6 | }, 7 | "include": ["src/**/*"] 8 | } 9 | -------------------------------------------------------------------------------- /test-iterations/iter3/app2/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": true, 4 | "strict": true, 5 | "jsx": "react" 6 | }, 7 | "include": ["src/**/*"] 8 | } 9 | -------------------------------------------------------------------------------- /test-iterations/iter3/app1/src/app2.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module "app2/Button" { 4 | const Button: React.ComponentType; 5 | 6 | export default Button; 7 | } 8 | -------------------------------------------------------------------------------- /deploy/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | !jest.config.js 3 | *.d.ts 4 | node_modules 5 | 6 | # CDK asset staging directory 7 | .cdk.staging 8 | cdk.out 9 | 10 | # Parcel default cache directory 11 | .parcel-cache 12 | -------------------------------------------------------------------------------- /deploy/bin/deploy.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import * as cdk from '@aws-cdk/core'; 3 | import { DeployStack } from '../lib/deploy-stack'; 4 | 5 | const app = new cdk.App(); 6 | new DeployStack(app, 'DeployStack'); 7 | -------------------------------------------------------------------------------- /test-iterations/iter2/container/src/bootstrap.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import App from "./App"; 4 | 5 | ReactDOM.render(, document.querySelector("#root")); 6 | -------------------------------------------------------------------------------- /test-iterations/iter1/cart/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /test-iterations/iter1/products/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /test-iterations/iter3/app1/src/bootstrap.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as ReactDOM from "react-dom"; 3 | 4 | import App from "./App"; 5 | 6 | ReactDOM.render(, document.getElementById("root")); 7 | -------------------------------------------------------------------------------- /test-iterations/iter3/app2/src/bootstrap.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as ReactDOM from "react-dom"; 3 | 4 | import App from "./App"; 5 | 6 | ReactDOM.render(, document.getElementById("root")); 7 | -------------------------------------------------------------------------------- /test-iterations/iter2/auth/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Auth 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /test-iterations/iter2/container/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Container 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /test-iterations/iter2/dashboard/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dashboard 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /test-iterations/iter2/marketing/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Marketing 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /scripts/ts-config: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": true, 4 | "strict": true, 5 | "jsx": "react", 6 | "esModuleInterop": true, 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["src/**/*"] 10 | } 11 | -------------------------------------------------------------------------------- /test-iterations/README.md: -------------------------------------------------------------------------------- 1 | The code inside this directory is primarily to iterate on the best approach. These are mostly intermediate setups which have been abandoned for a better setup. 2 | 3 | You should ignore the code here since its for reference only. 4 | -------------------------------------------------------------------------------- /container/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": true, 4 | "strict": true, 5 | "jsx": "react", 6 | "esModuleInterop": true, 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["src/**/*"] 10 | } 11 | -------------------------------------------------------------------------------- /dashboard/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": true, 4 | "strict": true, 5 | "jsx": "react", 6 | "esModuleInterop": true, 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["src/**/*"] 10 | } 11 | -------------------------------------------------------------------------------- /test-iterations/iter1/container/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /dashboard/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "@emotion/styled"; 3 | 4 | const Title = styled.h1` 5 | padding: 16px; 6 | `; 7 | 8 | const App = () => { 9 | return Dashboard Microfrontend; 10 | }; 11 | 12 | export default App; 13 | -------------------------------------------------------------------------------- /test-iterations/iter1/container/src/bootstrap.js: -------------------------------------------------------------------------------- 1 | import { mount as productsMount } from "products/ProductsIndex"; 2 | import { mount as cartsMount } from "cart/CartShow"; 3 | 4 | productsMount(document.querySelector("#test-products")); 5 | cartsMount(document.querySelector("#test-cart")); 6 | -------------------------------------------------------------------------------- /test-iterations/iter3/app2/src/App.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import LocalButton from "./Button"; 4 | 5 | const App = () => ( 6 |
7 |

Typescript

8 |

App 2

9 | 10 |
11 | ); 12 | 13 | export default App; 14 | -------------------------------------------------------------------------------- /container/src/typings/auth.d.ts: -------------------------------------------------------------------------------- 1 | interface AuthMountOptions { 2 | login: VoidFunction; 3 | history: import("history").History; 4 | } 5 | 6 | declare module "auth/AuthModule" { 7 | const mount: (el: HTMLDivElement | null, options: AuthMountOptions) => null; 8 | 9 | export { mount }; 10 | } 11 | -------------------------------------------------------------------------------- /deploy/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node bin/deploy.ts", 3 | "context": { 4 | "@aws-cdk/core:enableStackNameDuplicates": "true", 5 | "aws-cdk:enableDiffNoFail": "true", 6 | "@aws-cdk/core:stackRelativeExports": "true", 7 | "@aws-cdk/aws-ecr-assets:dockerIgnoreSupport": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /auth/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": true, 4 | "strict": true, 5 | "jsx": "react", 6 | "esModuleInterop": true, 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": [ 10 | "src/**/*", 11 | "../container/src/typings/auth.d.ts" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /header/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": true, 4 | "strict": true, 5 | "jsx": "react", 6 | "esModuleInterop": true, 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": [ 10 | "src/**/*", 11 | "../container/src/typings/header.d.ts" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /landing/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": true, 4 | "strict": true, 5 | "jsx": "react", 6 | "esModuleInterop": true, 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": [ 10 | "src/**/*", 11 | "../container/src/typings/landing.d.ts" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /auth/src/components/styled.tsx: -------------------------------------------------------------------------------- 1 | import styled from "@emotion/styled"; 2 | 3 | export const TabTitle = styled.h2` 4 | margin-bottom: 24px; 5 | `; 6 | 7 | export const CardFooter = styled.div` 8 | display: flex; 9 | justify-content: flex-end; 10 | padding: 16px; 11 | 12 | > Button { 13 | margin-left: 16px; 14 | } 15 | `; 16 | -------------------------------------------------------------------------------- /container/src/modules/Dashboard.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef } from "react"; 2 | import { mount } from "dashboard/DashboardModule"; 3 | 4 | export default () => { 5 | const ref = useRef(null); 6 | 7 | useEffect(() => { 8 | mount(ref.current); 9 | }, []); 10 | 11 | return
; 12 | }; 13 | -------------------------------------------------------------------------------- /test-iterations/iter2/container/src/components/DashboardApp.js: -------------------------------------------------------------------------------- 1 | import React, { useRef, useEffect } from "react"; 2 | import { mount } from "dashboard/DashboardApp"; 3 | 4 | export default () => { 5 | const ref = useRef(null); 6 | 7 | useEffect(() => { 8 | mount(ref.current); 9 | }, []); 10 | 11 | return
; 12 | }; 13 | -------------------------------------------------------------------------------- /container/src/typings/header.d.ts: -------------------------------------------------------------------------------- 1 | interface HeaderMountOptions { 2 | navigate: NavigateFunction; 3 | isSignedIn$: import("rxjs").Observable; 4 | logout: VoidFunction; 5 | } 6 | 7 | declare module "header/HeaderComponent" { 8 | const mount: (el: HTMLDivElement | null, options: HeaderMountOptions) => null; 9 | 10 | export { mount }; 11 | } 12 | -------------------------------------------------------------------------------- /container/src/typings/landing.d.ts: -------------------------------------------------------------------------------- 1 | type NavigateFunction = (route: string) => void 2 | 3 | interface LandingMountOptions { 4 | navigate: NavigateFunction; 5 | } 6 | 7 | declare module "landing/LandingModule" { 8 | const mount: ( 9 | el: HTMLDivElement | null, 10 | options: LandingMountOptions 11 | ) => null; 12 | 13 | export { mount }; 14 | } 15 | -------------------------------------------------------------------------------- /container/src/bootstrap.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import App from "./App"; 4 | import { BrowserRouter } from "react-router-dom"; 5 | 6 | const AppWithRouter = () => { 7 | return ( 8 | 9 | 10 | 11 | ); 12 | }; 13 | 14 | ReactDOM.render(, document.querySelector("#root")); 15 | -------------------------------------------------------------------------------- /test-iterations/iter3/app1/src/App.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | const RemoteButton = React.lazy(() => import("app2/Button")); 4 | 5 | const App = () => ( 6 |
7 |

Typescript

8 |

App 1

9 | 10 | 11 | 12 |
13 | ); 14 | 15 | export default App; 16 | -------------------------------------------------------------------------------- /scripts/bootstrap: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import App from "./App"; 4 | 5 | const mount = (el: Element) => { 6 | ReactDOM.render(, el); 7 | }; 8 | 9 | if (process.env.NODE_ENV === "development") { 10 | const rootNode = document.querySelector("#__MODULE_NAME__-module-root"); 11 | 12 | if (rootNode) { 13 | mount(rootNode); 14 | } 15 | } 16 | 17 | export { mount }; 18 | -------------------------------------------------------------------------------- /test-iterations/iter1/cart/src/bootstrap.js: -------------------------------------------------------------------------------- 1 | import faker from "faker"; 2 | 3 | const mount = (el) => { 4 | const cartText = `
You have ${faker.random.number()} items in your cart
`; 5 | 6 | el.innerHTML = cartText; 7 | }; 8 | 9 | if (process.env.NODE_ENV === "development") { 10 | const el = document.querySelector("#dev-cart"); 11 | 12 | if (el) { 13 | mount(el); 14 | } 15 | } 16 | 17 | export { mount }; 18 | -------------------------------------------------------------------------------- /container/src/modules/Landing.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef } from "react"; 2 | import { mount } from "landing/LandingModule"; 3 | import useRouter from "../hooks/useRouter"; 4 | 5 | export default () => { 6 | const ref = useRef(null); 7 | const { navigate } = useRouter(); 8 | 9 | useEffect(() => { 10 | mount(ref.current, { navigate }); 11 | }, []); 12 | 13 | return
; 14 | }; 15 | -------------------------------------------------------------------------------- /dashboard/src/bootstrap.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import App from "./App"; 4 | 5 | const mount = (el: Element) => { 6 | ReactDOM.render(, el); 7 | }; 8 | 9 | if (process.env.NODE_ENV === "development") { 10 | const rootNode = document.querySelector("#dashboard-module-root"); 11 | 12 | if (rootNode) { 13 | mount(rootNode); 14 | } 15 | } 16 | 17 | export { mount }; 18 | -------------------------------------------------------------------------------- /test-iterations/iter2/dashboard/src/bootstrap.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue"; 2 | import Dashboard from "./components/Dashboard"; 3 | 4 | const mount = (el) => { 5 | const app = createApp(Dashboard); 6 | app.mount(el); 7 | }; 8 | 9 | if (process.env.NODE_ENV === "development") { 10 | const devRoot = document.querySelector("#_dashboard-dev-root"); 11 | if (devRoot) { 12 | mount(devRoot); 13 | } 14 | } 15 | 16 | export { mount }; 17 | -------------------------------------------------------------------------------- /test-iterations/iter2/auth/config/webpack.common.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | module: { 3 | rules: [ 4 | { 5 | test: /\.m?js$/, 6 | exclude: /node_modules/, 7 | use: { 8 | loader: "babel-loader", 9 | options: { 10 | presets: ["@babel/preset-react", "@babel/preset-env"], 11 | plugins: ["@babel/plugin-transform-runtime"], 12 | }, 13 | }, 14 | }, 15 | ], 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /test-iterations/iter2/marketing/config/webpack.common.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | module: { 3 | rules: [ 4 | { 5 | test: /\.m?js$/, 6 | exclude: /node_modules/, 7 | use: { 8 | loader: "babel-loader", 9 | options: { 10 | presets: ["@babel/preset-react", "@babel/preset-env"], 11 | plugins: ["@babel/plugin-transform-runtime"], 12 | }, 13 | }, 14 | }, 15 | ], 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /test-iterations/iter1/cart/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cart", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack serve" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "faker": "^5.1.0", 14 | "html-webpack-plugin": "^4.5.0", 15 | "webpack": "^5.4.0", 16 | "webpack-cli": "^4.3.0", 17 | "webpack-dev-server": "^3.11.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test-iterations/iter1/products/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "products", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack serve" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "faker": "^5.1.0", 14 | "html-webpack-plugin": "^4.5.0", 15 | "webpack": "^5.4.0", 16 | "webpack-cli": "^4.3.0", 17 | "webpack-dev-server": "^3.11.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test-iterations/iter1/container/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "container", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack serve" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "html-webpack-plugin": "^4.5.0", 14 | "nodemon": "^2.0.6", 15 | "webpack": "^5.3.2", 16 | "webpack-cli": "^4.3.0", 17 | "webpack-dev-server": "^3.11.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /auth/src/components/Input.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { TextField } from "@material-ui/core"; 3 | 4 | const Input = ({ label }: { label: string }) => { 5 | const [val, setVal] = useState(""); 6 | 7 | return ( 8 | setVal(e.target.value)} 13 | value={val} 14 | /> 15 | ); 16 | }; 17 | 18 | export default Input; 19 | -------------------------------------------------------------------------------- /container/src/modules/Auth.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef } from "react"; 2 | import { mount } from "auth/AuthModule"; 3 | import { History } from "history"; 4 | 5 | interface AuthProps { 6 | login: VoidFunction; 7 | history: History; 8 | } 9 | 10 | export default ({ login, history }: AuthProps) => { 11 | const ref = useRef(null); 12 | 13 | useEffect(() => { 14 | mount(ref.current, { login, history }); 15 | }, []); 16 | 17 | return
; 18 | }; 19 | -------------------------------------------------------------------------------- /landing/src/bootstrap.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import App from "./App"; 4 | 5 | const mount = (el: Element, { navigate }: LandingMountOptions) => { 6 | ReactDOM.render(, el); 7 | }; 8 | 9 | if (process.env.NODE_ENV === "development") { 10 | const rootNode = document.querySelector("#landing-module-root"); 11 | 12 | if (rootNode) { 13 | mount(rootNode, { navigate: () => {} }); 14 | } 15 | } 16 | 17 | export { mount }; 18 | -------------------------------------------------------------------------------- /test-iterations/iter1/products/src/bootstrap.js: -------------------------------------------------------------------------------- 1 | import faker from "faker"; 2 | 3 | const mount = (el) => { 4 | let products = ""; 5 | 6 | for (let i = 0; i < 5; i++) { 7 | const name = faker.commerce.productName(); 8 | products += `
${name}
`; 9 | } 10 | 11 | el.innerHTML = products; 12 | }; 13 | 14 | if (process.env.NODE_ENV === "development") { 15 | const el = document.querySelector("#dev-products"); 16 | if (el) { 17 | mount(el); 18 | } 19 | } 20 | 21 | export { mount }; 22 | -------------------------------------------------------------------------------- /container/src/hooks/useRouter.ts: -------------------------------------------------------------------------------- 1 | import { useHistory } from "react-router-dom"; 2 | import { useCallback } from "react"; 3 | 4 | interface UseRouterFunctions { 5 | navigate: NavigateFunction; 6 | } 7 | 8 | const useRouter = (): UseRouterFunctions => { 9 | const history = useHistory(); 10 | 11 | const navigate = useCallback((route: string) => { 12 | if (route !== history.location.pathname) { 13 | history.push(route); 14 | } 15 | }, []); 16 | 17 | return { navigate }; 18 | }; 19 | 20 | export default useRouter; 21 | -------------------------------------------------------------------------------- /auth/webpack.common.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | module.exports = { 4 | entry: "./src/index", 5 | resolve: { 6 | extensions: [".ts", ".tsx", ".js", ".jsx"], 7 | modules: [path.resolve(__dirname, "src"), "node_modules"], 8 | }, 9 | module: { 10 | rules: [ 11 | { 12 | test: /\.tsx?$/, 13 | loader: "babel-loader", 14 | exclude: /node_modules/, 15 | options: { 16 | presets: ["@babel/preset-react", "@babel/preset-typescript"], 17 | }, 18 | }, 19 | ], 20 | }, 21 | plugins: [], 22 | }; 23 | -------------------------------------------------------------------------------- /header/webpack.common.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | module.exports = { 4 | entry: "./src/index", 5 | resolve: { 6 | extensions: [".ts", ".tsx", ".js", ".jsx"], 7 | modules: [path.resolve(__dirname, "src"), "node_modules"], 8 | }, 9 | module: { 10 | rules: [ 11 | { 12 | test: /\.tsx?$/, 13 | loader: "babel-loader", 14 | exclude: /node_modules/, 15 | options: { 16 | presets: ["@babel/preset-react", "@babel/preset-typescript"], 17 | }, 18 | }, 19 | ], 20 | }, 21 | plugins: [], 22 | }; 23 | -------------------------------------------------------------------------------- /landing/webpack.common.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | module.exports = { 4 | entry: "./src/index", 5 | resolve: { 6 | extensions: [".ts", ".tsx", ".js", ".jsx"], 7 | modules: [path.resolve(__dirname, "src"), "node_modules"], 8 | }, 9 | module: { 10 | rules: [ 11 | { 12 | test: /\.tsx?$/, 13 | loader: "babel-loader", 14 | exclude: /node_modules/, 15 | options: { 16 | presets: ["@babel/preset-react", "@babel/preset-typescript"], 17 | }, 18 | }, 19 | ], 20 | }, 21 | plugins: [], 22 | }; 23 | -------------------------------------------------------------------------------- /auth/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Auth Page 5 | 6 | 10 | 18 | 19 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /container/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Container Page 5 | 6 | 10 | 18 | 19 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /header/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Header 5 | 6 | 10 | 18 | 19 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /container/src/modules/Header.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef } from "react"; 2 | import { mount } from "header/HeaderComponent"; 3 | import useRouter from "../hooks/useRouter"; 4 | import { Observable } from "rxjs"; 5 | 6 | export default ({ 7 | isSignedIn$, 8 | logout, 9 | }: { 10 | isSignedIn$: Observable; 11 | logout: VoidFunction; 12 | }) => { 13 | const ref = useRef(null); 14 | const { navigate } = useRouter(); 15 | 16 | useEffect(() => { 17 | mount(ref.current, { navigate, isSignedIn$, logout }); 18 | }, []); 19 | 20 | return
; 21 | }; 22 | -------------------------------------------------------------------------------- /landing/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Landing Page 5 | 6 | 10 | 18 | 19 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /dashboard/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dashboard Page 5 | 6 | 10 | 18 | 19 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /scripts/index-html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | __MODULE_NAME_CAPITALIZED__ Page 5 | 6 | 10 | 18 | 19 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /test-iterations/iter1/cart/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); 3 | 4 | module.exports = { 5 | mode: "development", 6 | devServer: { 7 | port: 8082, 8 | }, 9 | plugins: [ 10 | new ModuleFederationPlugin({ 11 | name: "cart", 12 | filename: "remoteEntry.js", 13 | exposes: { 14 | "./CartShow": "./src/bootstrap", 15 | }, 16 | shared: ["faker"], 17 | }), 18 | new HtmlWebpackPlugin({ 19 | template: "./public/index.html", 20 | }), 21 | ], 22 | }; 23 | -------------------------------------------------------------------------------- /test-iterations/iter2/container/src/components/Progress.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { makeStyles, createStyles } from "@material-ui/core/styles"; 3 | import LinearProgress from "@material-ui/core/LinearProgress"; 4 | 5 | const useStyles = makeStyles((theme) => { 6 | return createStyles({ 7 | bar: { 8 | width: "100%", 9 | "& > * + *": { 10 | marginTop: theme.spacing(2), 11 | }, 12 | }, 13 | }); 14 | }); 15 | 16 | export default () => { 17 | const classes = useStyles(); 18 | 19 | return ( 20 |
21 | 22 |
23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /test-iterations/iter1/products/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); 3 | 4 | module.exports = { 5 | mode: "development", 6 | devServer: { 7 | port: 8081, 8 | }, 9 | plugins: [ 10 | new ModuleFederationPlugin({ 11 | name: "products", 12 | filename: "remoteEntry.js", 13 | exposes: { 14 | "./ProductsIndex": "./src/bootstrap.js", 15 | }, 16 | shared: ["faker"], 17 | }), 18 | new HtmlWebpackPlugin({ 19 | template: "./public/index.html", 20 | }), 21 | ], 22 | }; 23 | -------------------------------------------------------------------------------- /test-iterations/iter2/container/config/webpack.common.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | 3 | module.exports = { 4 | module: { 5 | rules: [ 6 | { 7 | test: /\.m?js$/, 8 | exclude: /node_modules/, 9 | use: { 10 | loader: "babel-loader", 11 | options: { 12 | presets: ["@babel/preset-react", "@babel/preset-env"], 13 | plugins: ["@babel/plugin-transform-runtime"], 14 | }, 15 | }, 16 | }, 17 | ], 18 | }, 19 | plugins: [ 20 | new HtmlWebpackPlugin({ 21 | template: "./public/index.html", 22 | }), 23 | ], 24 | }; 25 | -------------------------------------------------------------------------------- /test-iterations/iter1/container/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); 3 | 4 | module.exports = { 5 | mode: "development", 6 | devServer: { 7 | port: 8080, 8 | }, 9 | plugins: [ 10 | new ModuleFederationPlugin({ 11 | name: "container", 12 | remotes: { 13 | products: "products@http://localhost:8081/remoteEntry.js", 14 | cart: "cart@http://localhost:8082/remoteEntry.js", 15 | }, 16 | }), 17 | new HtmlWebpackPlugin({ 18 | template: "./public/index.html", 19 | }), 20 | ], 21 | }; 22 | -------------------------------------------------------------------------------- /deploy/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target":"ES2018", 4 | "module": "commonjs", 5 | "lib": ["es2018"], 6 | "declaration": true, 7 | "strict": true, 8 | "noImplicitAny": true, 9 | "strictNullChecks": true, 10 | "noImplicitThis": true, 11 | "alwaysStrict": true, 12 | "noUnusedLocals": false, 13 | "noUnusedParameters": false, 14 | "noImplicitReturns": true, 15 | "noFallthroughCasesInSwitch": false, 16 | "inlineSourceMap": true, 17 | "inlineSources": true, 18 | "experimentalDecorators": true, 19 | "strictPropertyInitialization":false, 20 | "typeRoots": ["./node_modules/@types"] 21 | }, 22 | "exclude": ["cdk.out"] 23 | } 24 | -------------------------------------------------------------------------------- /deploy/test/deploy.test.ts: -------------------------------------------------------------------------------- 1 | import { expect as expectCDK, haveResource } from '@aws-cdk/assert'; 2 | import * as cdk from '@aws-cdk/core'; 3 | import * as Deploy from '../lib/deploy-stack'; 4 | 5 | test('SQS Queue Created', () => { 6 | const app = new cdk.App(); 7 | // WHEN 8 | const stack = new Deploy.DeployStack(app, 'MyTestStack'); 9 | // THEN 10 | expectCDK(stack).to(haveResource("AWS::SQS::Queue",{ 11 | VisibilityTimeout: 300 12 | })); 13 | }); 14 | 15 | test('SNS Topic Created', () => { 16 | const app = new cdk.App(); 17 | // WHEN 18 | const stack = new Deploy.DeployStack(app, 'MyTestStack'); 19 | // THEN 20 | expectCDK(stack).to(haveResource("AWS::SNS::Topic")); 21 | }); 22 | -------------------------------------------------------------------------------- /scripts/webpack-common: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 3 | 4 | module.exports = { 5 | entry: "./src/index", 6 | resolve: { 7 | extensions: [".ts", ".tsx", ".js", ".jsx"], 8 | modules: [path.resolve(__dirname, "src"), "node_modules"], 9 | }, 10 | module: { 11 | rules: [ 12 | { 13 | test: /\.tsx?$/, 14 | loader: "babel-loader", 15 | exclude: /node_modules/, 16 | options: { 17 | presets: ["@babel/preset-react", "@babel/preset-typescript"], 18 | }, 19 | }, 20 | ], 21 | }, 22 | plugins: [ 23 | new HtmlWebpackPlugin({ 24 | template: "./public/index.html", 25 | }), 26 | ], 27 | }; 28 | -------------------------------------------------------------------------------- /auth/src/bootstrap.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { Router } from "react-router-dom"; 4 | import { createBrowserHistory } from "history"; 5 | 6 | import App from "./App"; 7 | 8 | const mount = (el: Element, { history, login }: AuthMountOptions) => { 9 | ReactDOM.render( 10 | 11 | 12 | , 13 | el 14 | ); 15 | }; 16 | 17 | if (process.env.NODE_ENV === "development") { 18 | const rootNode = document.querySelector("#auth-module-root"); 19 | 20 | if (rootNode) { 21 | mount(rootNode, { 22 | history: createBrowserHistory(), 23 | login: () => {}, 24 | }); 25 | } 26 | } 27 | 28 | export { mount }; 29 | -------------------------------------------------------------------------------- /container/webpack.common.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 3 | 4 | module.exports = { 5 | entry: "./src/index", 6 | resolve: { 7 | extensions: [".ts", ".tsx", ".js", ".jsx"], 8 | modules: [path.resolve(__dirname, "src"), "node_modules"], 9 | }, 10 | module: { 11 | rules: [ 12 | { 13 | test: /\.tsx?$/, 14 | loader: "babel-loader", 15 | exclude: /node_modules/, 16 | options: { 17 | presets: ["@babel/preset-react", "@babel/preset-typescript"], 18 | }, 19 | }, 20 | ], 21 | }, 22 | plugins: [ 23 | new HtmlWebpackPlugin({ 24 | template: "./public/index.html", 25 | }), 26 | ], 27 | }; 28 | -------------------------------------------------------------------------------- /dashboard/webpack.common.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 3 | 4 | module.exports = { 5 | entry: "./src/index", 6 | resolve: { 7 | extensions: [".ts", ".tsx", ".js", ".jsx"], 8 | modules: [path.resolve(__dirname, "src"), "node_modules"], 9 | }, 10 | module: { 11 | rules: [ 12 | { 13 | test: /\.tsx?$/, 14 | loader: "babel-loader", 15 | exclude: /node_modules/, 16 | options: { 17 | presets: ["@babel/preset-react", "@babel/preset-typescript"], 18 | }, 19 | }, 20 | ], 21 | }, 22 | plugins: [ 23 | new HtmlWebpackPlugin({ 24 | template: "./public/index.html", 25 | }), 26 | ], 27 | }; 28 | -------------------------------------------------------------------------------- /deploy/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to your CDK TypeScript project! 2 | 3 | You should explore the contents of this project. It demonstrates a CDK app with an instance of a stack (`DeployStack`) 4 | which contains an Amazon SQS queue that is subscribed to an Amazon SNS topic. 5 | 6 | The `cdk.json` file tells the CDK Toolkit how to execute your app. 7 | 8 | ## Useful commands 9 | 10 | * `npm run build` compile typescript to js 11 | * `npm run watch` watch for changes and compile 12 | * `npm run test` perform the jest unit tests 13 | * `cdk deploy` deploy this stack to your default AWS account/region 14 | * `cdk diff` compare deployed stack with current state 15 | * `cdk synth` emits the synthesized CloudFormation template 16 | -------------------------------------------------------------------------------- /test-iterations/iter2/auth/config/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const { merge } = require("webpack-merge"); 2 | const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); 3 | const commonConfig = require("./webpack.common"); 4 | const packageJson = require("../package.json"); 5 | 6 | const prodConfig = { 7 | mode: "production", 8 | output: { 9 | filename: "[name].[contenthash].js", 10 | publicPath: "/auth/latest/", 11 | }, 12 | plugins: [ 13 | new ModuleFederationPlugin({ 14 | name: "auth", 15 | filename: "remoteEntry.js", 16 | exposes: { 17 | "./AuthApp": "./src/bootstrap", 18 | }, 19 | shared: packageJson.dependencies, 20 | }), 21 | ], 22 | }; 23 | 24 | module.exports = merge(commonConfig, prodConfig); 25 | -------------------------------------------------------------------------------- /header/src/bootstrap.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import Header from "./Header"; 4 | import { Observable } from "rxjs"; 5 | 6 | const mount = (el: Element, options: HeaderMountOptions) => { 7 | ReactDOM.render(
, el); 8 | }; 9 | 10 | if (process.env.NODE_ENV === "development") { 11 | const rootNode = document.querySelector("#header-component-root"); 12 | 13 | if (rootNode) { 14 | const mockOptions: HeaderMountOptions = { 15 | navigate: (route: string) => { 16 | console.log(`Navigate to route: ${route}`); 17 | }, 18 | isSignedIn$: new Observable(), 19 | logout: () => {}, 20 | }; 21 | 22 | mount(rootNode, mockOptions); 23 | } 24 | } 25 | 26 | export { mount }; 27 | -------------------------------------------------------------------------------- /test-iterations/iter2/container/src/components/MarketingApp.js: -------------------------------------------------------------------------------- 1 | import { mount } from "marketing/MarketingApp"; 2 | import React, { useRef, useEffect } from "react"; 3 | import { useHistory } from "react-router-dom"; 4 | 5 | export default () => { 6 | const ref = useRef(null); 7 | const history = useHistory(); 8 | 9 | useEffect(() => { 10 | const { onParentNavigate } = mount(ref.current, { 11 | initialPath: history.location.pathname, 12 | onNavigate: ({ pathname: nextPathname }) => { 13 | const { pathname } = history.location; 14 | if (pathname !== nextPathname) { 15 | history.push(nextPathname); 16 | } 17 | }, 18 | }); 19 | 20 | history.listen(onParentNavigate); 21 | }, []); 22 | 23 | return
; 24 | }; 25 | -------------------------------------------------------------------------------- /test-iterations/iter2/dashboard/config/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const { merge } = require("webpack-merge"); 2 | const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); 3 | const commonConfig = require("./webpack.common"); 4 | const packageJson = require("../package.json"); 5 | 6 | const prodConfig = { 7 | mode: "production", 8 | output: { 9 | filename: "[name].[contenthash].js", 10 | publicPath: "/dashboard/latest/", 11 | }, 12 | plugins: [ 13 | new ModuleFederationPlugin({ 14 | name: "dashboard", 15 | filename: "remoteEntry.js", 16 | exposes: { 17 | "./DashboardApp": "./src/bootstrap", 18 | }, 19 | shared: packageJson.dependencies, 20 | }), 21 | ], 22 | }; 23 | 24 | module.exports = merge(commonConfig, prodConfig); 25 | -------------------------------------------------------------------------------- /test-iterations/iter2/marketing/config/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const { merge } = require("webpack-merge"); 2 | const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); 3 | const commonConfig = require("./webpack.common"); 4 | const packageJson = require("../package.json"); 5 | 6 | const prodConfig = { 7 | mode: "production", 8 | output: { 9 | filename: "[name].[contenthash].js", 10 | publicPath: "/marketing/latest/", 11 | }, 12 | plugins: [ 13 | new ModuleFederationPlugin({ 14 | name: "marketing", 15 | filename: "remoteEntry.js", 16 | exposes: { 17 | "./MarketingApp": "./src/bootstrap", 18 | }, 19 | shared: packageJson.dependencies, 20 | }), 21 | ], 22 | }; 23 | 24 | module.exports = merge(commonConfig, prodConfig); 25 | -------------------------------------------------------------------------------- /deploy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "deploy", 3 | "version": "0.1.0", 4 | "bin": { 5 | "deploy": "bin/deploy.js" 6 | }, 7 | "scripts": { 8 | "build": "tsc", 9 | "watch": "tsc -w", 10 | "test": "jest", 11 | "cdk": "cdk", 12 | "deploy": "cdk deploy --require-approval never" 13 | }, 14 | "devDependencies": { 15 | "@aws-cdk/assert": "1.74.0", 16 | "@types/jest": "26.0.10", 17 | "@types/node": "10.17.27", 18 | "aws-cdk": "1.74.0", 19 | "jest": "26.4.2", 20 | "ts-jest": "26.2.0", 21 | "ts-node": "8.1.0", 22 | "typescript": "3.9.7" 23 | }, 24 | "dependencies": { 25 | "@aws-cdk/core": "1.74.0", 26 | "@aws-cdk/aws-cloudfront": "1.74.0", 27 | "@aws-cdk/aws-s3": "1.74.0", 28 | "@aws-cdk/aws-s3-deployment": "1.74.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /auth/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const { merge } = require("webpack-merge"); 2 | const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); 3 | const commonConfig = require("./webpack.common"); 4 | const packageJson = require("./package.json"); 5 | 6 | module.exports = () => { 7 | const prodConfig = { 8 | mode: "production", 9 | output: { 10 | publicPath: "/auth/", 11 | filename: "[name].[contenthash].js", 12 | }, 13 | plugins: [ 14 | new ModuleFederationPlugin({ 15 | name: "auth", 16 | filename: "remoteEntry.js", 17 | exposes: { 18 | "./AuthModule": "./src/bootstrap", 19 | }, 20 | shared: packageJson.dependencies, 21 | }), 22 | ], 23 | }; 24 | 25 | return merge(commonConfig, prodConfig); 26 | }; 27 | -------------------------------------------------------------------------------- /test-iterations/iter2/container/src/components/AuthApp.js: -------------------------------------------------------------------------------- 1 | import { mount } from "auth/AuthApp"; 2 | import React, { useRef, useEffect } from "react"; 3 | import { useHistory } from "react-router-dom"; 4 | 5 | export default ({ onSignIn }) => { 6 | const ref = useRef(null); 7 | const history = useHistory(); 8 | 9 | useEffect(() => { 10 | const { onParentNavigate } = mount(ref.current, { 11 | initialPath: history.location.pathname, 12 | onNavigate: ({ pathname: nextPathname }) => { 13 | const { pathname } = history.location; 14 | if (pathname !== nextPathname) { 15 | history.push(nextPathname); 16 | } 17 | }, 18 | onSignIn, 19 | }); 20 | 21 | history.listen(onParentNavigate); 22 | }, []); 23 | 24 | return
; 25 | }; 26 | -------------------------------------------------------------------------------- /header/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const { merge } = require("webpack-merge"); 2 | const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); 3 | const commonConfig = require("./webpack.common"); 4 | const packageJson = require("./package.json"); 5 | 6 | module.exports = () => { 7 | const prodConfig = { 8 | mode: "production", 9 | output: { 10 | publicPath: "/header/", 11 | filename: "[name].[contenthash].js", 12 | }, 13 | plugins: [ 14 | new ModuleFederationPlugin({ 15 | name: "header", 16 | filename: "remoteEntry.js", 17 | exposes: { 18 | "./HeaderComponent": "./src/bootstrap", 19 | }, 20 | shared: packageJson.dependencies, 21 | }), 22 | ], 23 | }; 24 | 25 | return merge(commonConfig, prodConfig); 26 | }; 27 | -------------------------------------------------------------------------------- /landing/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const { merge } = require("webpack-merge"); 2 | const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); 3 | const commonConfig = require("./webpack.common"); 4 | const packageJson = require("./package.json"); 5 | 6 | module.exports = () => { 7 | const prodConfig = { 8 | mode: "production", 9 | output: { 10 | publicPath: "/landing/", 11 | filename: "[name].[contenthash].js", 12 | }, 13 | plugins: [ 14 | new ModuleFederationPlugin({ 15 | name: "landing", 16 | filename: "remoteEntry.js", 17 | exposes: { 18 | "./LandingModule": "./src/bootstrap", 19 | }, 20 | shared: packageJson.dependencies, 21 | }), 22 | ], 23 | }; 24 | 25 | return merge(commonConfig, prodConfig); 26 | }; 27 | -------------------------------------------------------------------------------- /dashboard/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const { merge } = require("webpack-merge"); 2 | const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); 3 | const commonConfig = require("./webpack.common"); 4 | const packageJson = require("./package.json"); 5 | 6 | module.exports = () => { 7 | const prodConfig = { 8 | mode: "production", 9 | output: { 10 | publicPath: "/dashboard/", 11 | filename: "[name].[contenthash].js", 12 | }, 13 | plugins: [ 14 | new ModuleFederationPlugin({ 15 | name: "dashboard", 16 | filename: "remoteEntry.js", 17 | exposes: { 18 | "./DashboardModule": "./src/bootstrap", 19 | }, 20 | shared: packageJson.dependencies, 21 | }), 22 | ], 23 | }; 24 | 25 | return merge(commonConfig, prodConfig); 26 | }; 27 | -------------------------------------------------------------------------------- /scripts/webpack-prod: -------------------------------------------------------------------------------- 1 | const { merge } = require("webpack-merge"); 2 | const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); 3 | const commonConfig = require("./webpack.common"); 4 | const packageJson = require("./package.json"); 5 | 6 | module.exports = () => { 7 | const prodConfig = { 8 | mode: "production", 9 | output: { 10 | publicPath: "/__MODULE_NAME__/", 11 | filename: "[name].[contenthash].js", 12 | }, 13 | plugins: [ 14 | new ModuleFederationPlugin({ 15 | name: "__MODULE_NAME__", 16 | filename: "remoteEntry.js", 17 | exposes: { 18 | "./__MODULE_NAME_CAPITALIZED__Module": "./src/bootstrap", 19 | }, 20 | shared: packageJson.dependencies, 21 | }), 22 | ], 23 | }; 24 | 25 | return merge(commonConfig, prodConfig); 26 | }; 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-micro-frontends", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "mfegen": "ts-node scripts/mfegen.ts" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/ashwanth1109/react-micro-frontends.git" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "bugs": { 17 | "url": "https://github.com/ashwanth1109/react-micro-frontends/issues" 18 | }, 19 | "homepage": "https://github.com/ashwanth1109/react-micro-frontends#readme", 20 | "devDependencies": { 21 | "@types/fs-extra": "9.0.6", 22 | "@types/yargs": "15.0.12", 23 | "fs-extra": "9.0.1", 24 | "npm-add-script": "^1.1.0", 25 | "ts-node": "9.1.1", 26 | "typescript": "4.1.3", 27 | "yargs": "16.2.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test-iterations/iter2/marketing/src/App.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Switch, Route, Router } from "react-router-dom"; 3 | import { 4 | StylesProvider, 5 | createGenerateClassName, 6 | } from "@material-ui/core/styles"; 7 | 8 | import Landing from "./components/Landing"; 9 | import Pricing from "./components/Pricing"; 10 | 11 | const generateClassName = createGenerateClassName({ 12 | productionPrefix: "ma", 13 | }); 14 | 15 | export default ({ history }) => { 16 | return ( 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /test-iterations/iter3/app1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@typescript/app1", 3 | "version": "0.0.0", 4 | "private": true, 5 | "devDependencies": { 6 | "@babel/core": "7.12.3", 7 | "@babel/preset-react": "7.12.1", 8 | "@babel/preset-typescript": "7.12.1", 9 | "@types/react": "16.14.1", 10 | "@types/react-dom": "16.9.8", 11 | "babel-loader": "8.1.0", 12 | "bundle-loader": "0.5.6", 13 | "html-webpack-plugin": "4.5.0", 14 | "serve": "11.3.2", 15 | "typescript": "4.0.3", 16 | "webpack": "5.6.0", 17 | "webpack-cli": "4.3.0", 18 | "webpack-dev-server": "3.11.0" 19 | }, 20 | "scripts": { 21 | "start": "webpack-cli serve", 22 | "build": "webpack --mode production", 23 | "serve": "serve dist -p 3001", 24 | "clean": "rm -rf dist" 25 | }, 26 | "dependencies": { 27 | "react": "^16.13.0", 28 | "react-dom": "^16.13.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test-iterations/iter3/app2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@typescript/app2", 3 | "version": "0.0.0", 4 | "private": true, 5 | "devDependencies": { 6 | "@babel/core": "7.12.3", 7 | "@babel/preset-react": "7.12.1", 8 | "@babel/preset-typescript": "7.12.1", 9 | "@types/react": "16.14.1", 10 | "@types/react-dom": "16.9.8", 11 | "babel-loader": "8.1.0", 12 | "bundle-loader": "0.5.6", 13 | "html-webpack-plugin": "4.5.0", 14 | "serve": "11.3.2", 15 | "typescript": "4.0.3", 16 | "webpack": "5.6.0", 17 | "webpack-cli": "4.3.0", 18 | "webpack-dev-server": "3.11.0" 19 | }, 20 | "scripts": { 21 | "start": "webpack-cli serve", 22 | "build": "webpack --mode production", 23 | "serve": "serve dist -p 3002", 24 | "clean": "rm -rf dist" 25 | }, 26 | "dependencies": { 27 | "react": "^16.13.0", 28 | "react-dom": "^16.13.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test-iterations/iter2/auth/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "auth", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "start": "webpack serve --config config/webpack.dev.js", 6 | "build": "webpack --config config/webpack.prod.js" 7 | }, 8 | "dependencies": { 9 | "@material-ui/core": "^4.11.0", 10 | "@material-ui/icons": "^4.9.1", 11 | "react": "^17.0.1", 12 | "react-dom": "^17.0.1", 13 | "react-router-dom": "^5.2.0" 14 | }, 15 | "devDependencies": { 16 | "@babel/core": "^7.12.3", 17 | "@babel/plugin-transform-runtime": "^7.12.1", 18 | "@babel/preset-env": "^7.12.1", 19 | "@babel/preset-react": "^7.12.1", 20 | "babel-loader": "^8.1.0", 21 | "clean-webpack-plugin": "^3.0.0", 22 | "css-loader": "^5.0.0", 23 | "html-webpack-plugin": "^4.5.0", 24 | "style-loader": "^2.0.0", 25 | "webpack": "^5.4.0", 26 | "webpack-cli": "^4.1.0", 27 | "webpack-dev-server": "^3.11.0", 28 | "webpack-merge": "^5.2.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test-iterations/iter2/container/config/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const { merge } = require("webpack-merge"); 2 | const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); 3 | const commonConfig = require("./webpack.common"); 4 | const packageJson = require("../package.json"); 5 | 6 | const domain = process.env.PRODUCTION_DOMAIN; 7 | 8 | const prodConfig = { 9 | mode: "production", 10 | output: { 11 | filename: "[name].[contenthash].js", 12 | publicPath: "/container/latest/", 13 | }, 14 | plugins: [ 15 | new ModuleFederationPlugin({ 16 | name: "container", 17 | remotes: { 18 | marketing: `marketing@${domain}/marketing/latest/remoteEntry.js`, 19 | auth: `auth@${domain}/auth/latest/remoteEntry.js`, 20 | dashboard: `dashboard@${domain}/dashboard/latest/remoteEntry.js`, 21 | }, 22 | shared: packageJson.dependencies, 23 | }), 24 | ], 25 | }; 26 | 27 | module.exports = merge(commonConfig, prodConfig); 28 | -------------------------------------------------------------------------------- /test-iterations/iter2/container/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "container", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "start": "webpack serve --config config/webpack.dev.js", 6 | "build": "webpack --config config/webpack.prod.js" 7 | }, 8 | "dependencies": { 9 | "@material-ui/core": "^4.11.0", 10 | "@material-ui/icons": "^4.9.1", 11 | "react": "^17.0.1", 12 | "react-dom": "^17.0.1", 13 | "react-router-dom": "^5.2.0" 14 | }, 15 | "devDependencies": { 16 | "@babel/core": "^7.12.3", 17 | "@babel/plugin-transform-runtime": "^7.12.1", 18 | "@babel/preset-env": "^7.12.1", 19 | "@babel/preset-react": "^7.12.1", 20 | "babel-loader": "^8.1.0", 21 | "clean-webpack-plugin": "^3.0.0", 22 | "css-loader": "^5.0.0", 23 | "html-webpack-plugin": "^4.5.0", 24 | "style-loader": "^2.0.0", 25 | "webpack": "^5.4.0", 26 | "webpack-cli": "^4.1.0", 27 | "webpack-dev-server": "^3.11.0", 28 | "webpack-merge": "^5.2.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test-iterations/iter2/marketing/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "marketing", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "start": "webpack serve --config config/webpack.dev.js", 6 | "build": "webpack --config config/webpack.prod.js" 7 | }, 8 | "dependencies": { 9 | "@material-ui/core": "^4.11.0", 10 | "@material-ui/icons": "^4.9.1", 11 | "react": "^17.0.1", 12 | "react-dom": "^17.0.1", 13 | "react-router-dom": "^5.2.0" 14 | }, 15 | "devDependencies": { 16 | "@babel/core": "^7.12.3", 17 | "@babel/plugin-transform-runtime": "^7.12.1", 18 | "@babel/preset-env": "^7.12.1", 19 | "@babel/preset-react": "^7.12.1", 20 | "babel-loader": "^8.1.0", 21 | "clean-webpack-plugin": "^3.0.0", 22 | "css-loader": "^5.0.0", 23 | "html-webpack-plugin": "^4.5.0", 24 | "style-loader": "^2.0.0", 25 | "webpack": "^5.4.0", 26 | "webpack-cli": "^4.1.0", 27 | "webpack-dev-server": "^3.11.0", 28 | "webpack-merge": "^5.2.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test-iterations/iter2/container/config/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const { merge } = require("webpack-merge"); 2 | const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); 3 | const commonConfig = require("./webpack.common"); 4 | const packageJson = require("../package.json"); 5 | 6 | const devConfig = { 7 | mode: "development", 8 | output: { 9 | publicPath: "http://localhost:8080/", 10 | }, 11 | devServer: { 12 | port: 8080, 13 | historyApiFallback: { 14 | index: "index.html", 15 | }, 16 | }, 17 | plugins: [ 18 | new ModuleFederationPlugin({ 19 | name: "container", 20 | remotes: { 21 | marketing: "marketing@http://localhost:8081/remoteEntry.js", 22 | auth: "auth@http://localhost:8082/remoteEntry.js", 23 | dashboard: "dashboard@http://localhost:8083/remoteEntry.js", 24 | }, 25 | shared: packageJson.dependencies, 26 | }), 27 | ], 28 | }; 29 | 30 | module.exports = merge(commonConfig, devConfig); 31 | -------------------------------------------------------------------------------- /test-iterations/iter2/auth/src/App.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Switch, Route, Router } from "react-router-dom"; 3 | import { 4 | StylesProvider, 5 | createGenerateClassName, 6 | } from "@material-ui/core/styles"; 7 | import Signin from "./components/Signin"; 8 | import Signup from "./components/Signup"; 9 | 10 | const generateClassName = createGenerateClassName({ 11 | productionPrefix: "au", 12 | }); 13 | 14 | export default ({ history, onSignIn }) => { 15 | console.log("Auth loaded"); 16 | return ( 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | ); 32 | }; 33 | -------------------------------------------------------------------------------- /test-iterations/iter2/auth/config/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const { merge } = require("webpack-merge"); 2 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 3 | const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); 4 | const commonConfig = require("./webpack.common"); 5 | const packageJson = require("../package.json"); 6 | 7 | const devConfig = { 8 | mode: "development", 9 | output: { 10 | publicPath: "http://localhost:8082/", 11 | }, 12 | devServer: { 13 | port: 8082, 14 | historyApiFallback: { 15 | index: "index.html", 16 | }, 17 | }, 18 | plugins: [ 19 | new ModuleFederationPlugin({ 20 | name: "auth", 21 | filename: "remoteEntry.js", 22 | exposes: { 23 | "./AuthApp": "./src/bootstrap", 24 | }, 25 | shared: packageJson.dependencies, 26 | }), 27 | new HtmlWebpackPlugin({ 28 | template: "./public/index.html", 29 | }), 30 | ], 31 | }; 32 | 33 | module.exports = merge(commonConfig, devConfig); 34 | -------------------------------------------------------------------------------- /test-iterations/iter2/marketing/config/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const { merge } = require("webpack-merge"); 2 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 3 | const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); 4 | const commonConfig = require("./webpack.common"); 5 | const packageJson = require("../package.json"); 6 | 7 | const devConfig = { 8 | mode: "development", 9 | output: { 10 | publicPath: "http://localhost:8081/", 11 | }, 12 | devServer: { 13 | port: 8081, 14 | historyApiFallback: { 15 | index: "index.html", 16 | }, 17 | }, 18 | plugins: [ 19 | new ModuleFederationPlugin({ 20 | name: "marketing", 21 | filename: "remoteEntry.js", 22 | exposes: { 23 | "./MarketingApp": "./src/bootstrap", 24 | }, 25 | shared: packageJson.dependencies, 26 | }), 27 | new HtmlWebpackPlugin({ 28 | template: "./public/index.html", 29 | }), 30 | ], 31 | }; 32 | 33 | module.exports = merge(commonConfig, devConfig); 34 | -------------------------------------------------------------------------------- /container/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const { merge } = require("webpack-merge"); 2 | const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); 3 | const commonConfig = require("./webpack.common"); 4 | const packageJson = require("./package.json"); 5 | 6 | const domain = process.env.PRODUCTION_DOMAIN; 7 | 8 | module.exports = () => { 9 | const prodConfig = { 10 | mode: "production", 11 | output: { 12 | publicPath: "/", 13 | filename: "[name].[contenthash].js", 14 | }, 15 | plugins: [ 16 | new ModuleFederationPlugin({ 17 | name: "container", 18 | remotes: { 19 | landing: `landing@${domain}/landing/remoteEntry.js`, 20 | auth: `auth@${domain}/auth/remoteEntry.js`, 21 | header: `header@${domain}/header/remoteEntry.js`, 22 | dashboard: `dashboard@${domain}/dashboard/remoteEntry.js`, 23 | }, 24 | shared: packageJson.dependencies, 25 | }), 26 | ], 27 | }; 28 | 29 | return merge(commonConfig, prodConfig); 30 | }; 31 | -------------------------------------------------------------------------------- /test-iterations/iter2/marketing/src/bootstrap.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import App from "./App"; 4 | import { createMemoryHistory, createBrowserHistory } from "history"; 5 | 6 | const mount = (el, { onNavigate, defaultHistory, initialPath }) => { 7 | const history = 8 | defaultHistory || 9 | createMemoryHistory({ 10 | initialEntries: [initialPath], 11 | }); 12 | 13 | if (onNavigate) { 14 | history.listen(onNavigate); 15 | } 16 | 17 | ReactDOM.render(, el); 18 | 19 | return { 20 | onParentNavigate({ pathname: nextPathname }) { 21 | const { pathname } = history.location; 22 | if (pathname !== nextPathname) { 23 | history.push(nextPathname); 24 | } 25 | }, 26 | }; 27 | }; 28 | 29 | if (process.env.NODE_ENV === "development") { 30 | const devRoot = document.querySelector("#_marketing-dev-root"); 31 | if (devRoot) { 32 | mount(devRoot, { defaultHistory: createBrowserHistory() }); 33 | } 34 | } 35 | 36 | export { mount }; 37 | -------------------------------------------------------------------------------- /dashboard/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dashboard", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack serve --config webpack.dev.js", 8 | "build": "webpack --config webpack.prod.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "@babel/core": "7.12.10", 15 | "@babel/preset-react": "7.12.10", 16 | "@babel/preset-typescript": "7.12.7", 17 | "@types/node": "14.14.20", 18 | "@types/react": "17.0.0", 19 | "@types/react-dom": "17.0.0", 20 | "babel-loader": "8.2.2", 21 | "html-webpack-plugin": "4.5.1", 22 | "ts-node": "9.1.1", 23 | "typescript": "4.1.3", 24 | "webpack": "5.11.1", 25 | "webpack-cli": "4.3.1", 26 | "webpack-dev-server": "3.11.1", 27 | "webpack-merge": "5.7.3" 28 | }, 29 | "dependencies": { 30 | "@emotion/react": "11.1.4", 31 | "@emotion/styled": "11.0.0", 32 | "@material-ui/core": "4.11.2", 33 | "react": "17.0.1", 34 | "react-dom": "17.0.1" 35 | } 36 | } -------------------------------------------------------------------------------- /auth/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Card } from "@material-ui/core"; 3 | import styled from "@emotion/styled"; 4 | import { Switch, Route } from "react-router-dom"; 5 | 6 | import Login from "./components/Login"; 7 | import Register from "./components/Register"; 8 | 9 | const CardContainer = styled.div` 10 | max-width: 600px; 11 | margin: 80px auto 0 auto; 12 | `; 13 | 14 | const CardTitle = styled.h1` 15 | text-align: center; 16 | margin: 24px 0; 17 | `; 18 | 19 | interface AppProps { 20 | login: VoidFunction; 21 | } 22 | 23 | const App = ({ login }: AppProps) => { 24 | return ( 25 | 26 | 27 | Auth microfrontend (2 routes) 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | ); 39 | }; 40 | 41 | export default App; 42 | -------------------------------------------------------------------------------- /test-iterations/iter2/auth/src/bootstrap.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import App from "./App"; 4 | import { createMemoryHistory, createBrowserHistory } from "history"; 5 | 6 | const mount = (el, { onNavigate, defaultHistory, initialPath, onSignIn }) => { 7 | const history = 8 | defaultHistory || 9 | createMemoryHistory({ 10 | initialEntries: [initialPath], 11 | }); 12 | 13 | if (onNavigate) { 14 | history.listen(onNavigate); 15 | } 16 | 17 | ReactDOM.render(, el); 18 | 19 | return { 20 | onParentNavigate({ pathname: nextPathname }) { 21 | const { pathname } = history.location; 22 | if (pathname !== nextPathname) { 23 | history.push(nextPathname); 24 | } 25 | }, 26 | }; 27 | }; 28 | 29 | if (process.env.NODE_ENV === "development") { 30 | const devRoot = document.querySelector("#_auth-dev-root"); 31 | if (devRoot) { 32 | mount(devRoot, { defaultHistory: createBrowserHistory() }); 33 | } 34 | } 35 | 36 | export { mount }; 37 | -------------------------------------------------------------------------------- /test-iterations/iter2/dashboard/config/webpack.common.js: -------------------------------------------------------------------------------- 1 | const { VueLoaderPlugin } = require("vue-loader"); 2 | 3 | module.exports = { 4 | entry: "./src/index.js", 5 | output: { 6 | filename: "[name].[contenthash].js", 7 | }, 8 | resolve: { 9 | extensions: [".js", ".vue"], 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.(png|jpe?g|gif|woff|svg|eot|ttf)$/i, 15 | use: [{ loader: "file-loader" }], 16 | }, 17 | { 18 | test: /\.vue$/, 19 | use: "vue-loader", 20 | }, 21 | { 22 | test: /\.scss|\.css$/, 23 | use: ["vue-style-loader", "style-loader", "css-loader", "sass-loader"], 24 | }, 25 | { 26 | test: /\.m?js$/, 27 | exclude: /node_modules/, 28 | use: { 29 | loader: "babel-loader", 30 | options: { 31 | presets: ["@babel/preset-env"], 32 | plugins: ["@babel/plugin-transform-runtime"], 33 | }, 34 | }, 35 | }, 36 | ], 37 | }, 38 | plugins: [new VueLoaderPlugin()], 39 | }; 40 | -------------------------------------------------------------------------------- /test-iterations/iter2/dashboard/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dashboard", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "start": "webpack serve --config config/webpack.dev.js", 6 | "build": "webpack --config config/webpack.prod.js" 7 | }, 8 | "dependencies": { 9 | "chart.js": "^2.9.4", 10 | "primeflex": "^2.0.0", 11 | "primeicons": "^4.0.0", 12 | "primevue": "^3.0.1", 13 | "vue": "^3.0.0" 14 | }, 15 | "devDependencies": { 16 | "@babel/core": "^7.12.3", 17 | "@babel/plugin-transform-runtime": "^7.12.1", 18 | "@babel/preset-env": "^7.12.1", 19 | "@vue/compiler-sfc": "^3.0.2", 20 | "babel-loader": "^8.1.0", 21 | "css-loader": "^5.0.0", 22 | "file-loader": "^6.2.0", 23 | "html-webpack-plugin": "^4.5.0", 24 | "node-sass": "^4.14.1", 25 | "sass-loader": "^10.0.4", 26 | "style-loader": "^2.0.0", 27 | "vue-loader": "^16.0.0-beta.9", 28 | "vue-style-loader": "^4.1.2", 29 | "webpack": "^5.4.0", 30 | "webpack-cli": "^4.1.0", 31 | "webpack-dev-server": "^3.11.0", 32 | "webpack-merge": "^5.2.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /auth/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const { merge } = require("webpack-merge"); 2 | const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); 3 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 4 | const commonConfig = require("./webpack.common"); 5 | const packageJson = require("./package.json"); 6 | 7 | module.exports = () => { 8 | const devConfig = { 9 | mode: "development", 10 | output: { 11 | publicPath: "http://localhost:8082/", 12 | filename: "[name].[contenthash].js", 13 | }, 14 | devServer: { 15 | port: 8082, 16 | historyApiFallback: { 17 | index: "/", 18 | }, 19 | }, 20 | plugins: [ 21 | new ModuleFederationPlugin({ 22 | name: "auth", 23 | filename: "remoteEntry.js", 24 | exposes: { 25 | "./AuthModule": "./src/bootstrap", 26 | }, 27 | shared: packageJson.dependencies, 28 | }), 29 | new HtmlWebpackPlugin({ 30 | template: "./public/index.html", 31 | }), 32 | ], 33 | }; 34 | 35 | return merge(commonConfig, devConfig); 36 | }; 37 | -------------------------------------------------------------------------------- /test-iterations/iter2/dashboard/config/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const { merge } = require("webpack-merge"); 2 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 3 | const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); 4 | const commonConfig = require("./webpack.common"); 5 | const packageJson = require("../package.json"); 6 | 7 | const devConfig = { 8 | mode: "development", 9 | output: { 10 | publicPath: "http://localhost:8083/", 11 | }, 12 | devServer: { 13 | port: 8083, 14 | historyApiFallback: { 15 | index: "index.html", 16 | }, 17 | headers: { 18 | "Access-Control-Allow-Origin": "*", 19 | }, 20 | }, 21 | plugins: [ 22 | new ModuleFederationPlugin({ 23 | name: "dashboard", 24 | filename: "remoteEntry.js", 25 | exposes: { 26 | "./DashboardApp": "./src/bootstrap", 27 | }, 28 | shared: packageJson.dependencies, 29 | }), 30 | new HtmlWebpackPlugin({ 31 | template: "./public/index.html", 32 | }), 33 | ], 34 | }; 35 | 36 | module.exports = merge(commonConfig, devConfig); 37 | -------------------------------------------------------------------------------- /header/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const { merge } = require("webpack-merge"); 2 | const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); 3 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 4 | const commonConfig = require("./webpack.common"); 5 | const packageJson = require("./package.json"); 6 | 7 | module.exports = () => { 8 | const devConfig = { 9 | mode: "development", 10 | output: { 11 | publicPath: "http://localhost:8083/", 12 | filename: "[name].[contenthash].js", 13 | }, 14 | devServer: { 15 | port: 8083, 16 | historyApiFallback: { 17 | index: "/", 18 | }, 19 | }, 20 | plugins: [ 21 | new ModuleFederationPlugin({ 22 | name: "header", 23 | filename: "remoteEntry.js", 24 | exposes: { 25 | "./HeaderComponent": "./src/bootstrap", 26 | }, 27 | shared: packageJson.dependencies, 28 | }), 29 | new HtmlWebpackPlugin({ 30 | template: "./public/index.html", 31 | }), 32 | ], 33 | }; 34 | 35 | return merge(commonConfig, devConfig); 36 | }; 37 | -------------------------------------------------------------------------------- /auth/src/components/Register.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from "react"; 2 | import { Button, CardContent } from "@material-ui/core"; 3 | import Input from "./Input"; 4 | import { CardFooter, TabTitle } from "./styled"; 5 | import { Link } from "react-router-dom"; 6 | 7 | const Register = () => { 8 | const onRegister = useCallback(() => { 9 | console.log("register"); 10 | }, []); 11 | 12 | return ( 13 | <> 14 | 15 | Register page (route) 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 32 | 33 | 34 | ); 35 | }; 36 | 37 | export default Register; 38 | -------------------------------------------------------------------------------- /dashboard/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const { merge } = require("webpack-merge"); 2 | const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); 3 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 4 | const commonConfig = require("./webpack.common"); 5 | const packageJson = require("./package.json"); 6 | 7 | module.exports = () => { 8 | const devConfig = { 9 | mode: "development", 10 | output: { 11 | publicPath: "http://localhost:8084/", 12 | filename: "[name].[contenthash].js", 13 | }, 14 | devServer: { 15 | port: 8084, 16 | historyApiFallback: { 17 | index: "/", 18 | }, 19 | }, 20 | plugins: [ 21 | new ModuleFederationPlugin({ 22 | name: "dashboard", 23 | filename: "remoteEntry.js", 24 | exposes: { 25 | "./DashboardModule": "./src/bootstrap", 26 | }, 27 | shared: packageJson.dependencies, 28 | }), 29 | new HtmlWebpackPlugin({ 30 | template: "./public/index.html", 31 | }), 32 | ], 33 | }; 34 | 35 | return merge(commonConfig, devConfig); 36 | }; 37 | -------------------------------------------------------------------------------- /landing/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const { merge } = require("webpack-merge"); 2 | const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); 3 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 4 | const commonConfig = require("./webpack.common"); 5 | const packageJson = require("./package.json"); 6 | 7 | module.exports = () => { 8 | const devConfig = { 9 | mode: "development", 10 | output: { 11 | publicPath: "http://localhost:8081/", 12 | filename: "[name].[contenthash].js", 13 | }, 14 | devServer: { 15 | port: 8081, 16 | historyApiFallback: { 17 | index: "index.html", 18 | }, 19 | }, 20 | plugins: [ 21 | new ModuleFederationPlugin({ 22 | name: "landing", 23 | filename: "remoteEntry.js", 24 | exposes: { 25 | "./LandingModule": "./src/bootstrap", 26 | }, 27 | shared: packageJson.dependencies, 28 | }), 29 | new HtmlWebpackPlugin({ 30 | template: "./public/index.html", 31 | }), 32 | ], 33 | }; 34 | 35 | return merge(commonConfig, devConfig); 36 | }; 37 | -------------------------------------------------------------------------------- /auth/src/components/Login.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from "react"; 2 | import { Button, CardContent } from "@material-ui/core"; 3 | import { Link } from "react-router-dom"; 4 | import Input from "./Input"; 5 | import { CardFooter, TabTitle } from "./styled"; 6 | 7 | const Login = ({ login }: { login: VoidFunction }) => { 8 | const onLogin = useCallback(() => { 9 | // verify details are correct (not implemented) and login 10 | login(); 11 | }, []); 12 | 13 | return ( 14 | <> 15 | 16 | Login page (route) 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 32 | 33 | 34 | ); 35 | }; 36 | 37 | export default Login; 38 | -------------------------------------------------------------------------------- /test-iterations/iter2/.github/workflows/auth.yml: -------------------------------------------------------------------------------- 1 | name: deploy-auth 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - "auth/**" 9 | 10 | defaults: 11 | run: 12 | working-directory: auth 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - run: npm install 21 | - run: npm run build 22 | 23 | - name: ACTIONS_ALLOW_UNSECURE_COMMANDS 24 | run: echo 'ACTIONS_ALLOW_UNSECURE_COMMANDS=true' >> $GITHUB_ENV 25 | 26 | - uses: chrislennon/action-aws-cli@v1.1 27 | - run: aws s3 sync dist s3://${{ secrets.AWS_S3_BUCKET_NAME }}/auth/latest 28 | env: 29 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 30 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 31 | 32 | - run: aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_DISTRIBUTION_ID }} --paths "/auth/latest/remoteEntry.js" 33 | env: 34 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 35 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 36 | -------------------------------------------------------------------------------- /container/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); 2 | const { merge } = require("webpack-merge"); 3 | const commonConfig = require("./webpack.common"); 4 | const packageJson = require("./package.json"); 5 | 6 | module.exports = () => { 7 | const devConfig = { 8 | mode: "development", 9 | output: { 10 | publicPath: "http://localhost:8080/", 11 | filename: "[name].[contenthash].js", 12 | }, 13 | devServer: { 14 | port: 8080, 15 | historyApiFallback: { 16 | index: "/", 17 | }, 18 | }, 19 | plugins: [ 20 | new ModuleFederationPlugin({ 21 | name: "container", 22 | remotes: { 23 | landing: "landing@http://localhost:8081/remoteEntry.js", 24 | auth: "auth@http://localhost:8082/remoteEntry.js", 25 | header: "header@http://localhost:8083/remoteEntry.js", 26 | dashboard: "dashboard@http://localhost:8084/remoteEntry.js", 27 | }, 28 | shared: packageJson.dependencies, 29 | }), 30 | ], 31 | }; 32 | 33 | return merge(commonConfig, devConfig); 34 | }; 35 | -------------------------------------------------------------------------------- /scripts/webpack-dev: -------------------------------------------------------------------------------- 1 | const { merge } = require("webpack-merge"); 2 | const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); 3 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 4 | const commonConfig = require("./webpack.common"); 5 | const packageJson = require("./package.json"); 6 | 7 | module.exports = () => { 8 | const devConfig = { 9 | mode: "development", 10 | output: { 11 | publicPath: "http://localhost:__PORT_CONFIG__/", 12 | filename: "[name].[contenthash].js", 13 | }, 14 | devServer: { 15 | port: __PORT_CONFIG__, 16 | historyApiFallback: { 17 | index: "/", 18 | }, 19 | }, 20 | plugins: [ 21 | new ModuleFederationPlugin({ 22 | name: "__MODULE_NAME__", 23 | filename: "remoteEntry.js", 24 | exposes: { 25 | "./__MODULE_NAME_CAPITALIZED__Module": "./src/bootstrap", 26 | }, 27 | shared: packageJson.dependencies, 28 | }), 29 | new HtmlWebpackPlugin({ 30 | template: "./public/index.html", 31 | }), 32 | ], 33 | }; 34 | 35 | return merge(commonConfig, devConfig); 36 | }; 37 | -------------------------------------------------------------------------------- /test-iterations/iter2/.github/workflows/dashboard.yml: -------------------------------------------------------------------------------- 1 | name: deploy-dashboard 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - "dashboard/**" 9 | 10 | defaults: 11 | run: 12 | working-directory: dashboard 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - run: npm install 21 | - run: npm run build 22 | 23 | - name: ACTIONS_ALLOW_UNSECURE_COMMANDS 24 | run: echo 'ACTIONS_ALLOW_UNSECURE_COMMANDS=true' >> $GITHUB_ENV 25 | 26 | - uses: chrislennon/action-aws-cli@v1.1 27 | - run: aws s3 sync dist s3://${{ secrets.AWS_S3_BUCKET_NAME }}/dashboard/latest 28 | env: 29 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 30 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 31 | 32 | - run: aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_DISTRIBUTION_ID }} --paths "/dashboard/latest/remoteEntry.js" 33 | env: 34 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 35 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 36 | -------------------------------------------------------------------------------- /test-iterations/iter2/.github/workflows/marketing.yml: -------------------------------------------------------------------------------- 1 | name: deploy-marketing 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - "marketing/**" 9 | 10 | defaults: 11 | run: 12 | working-directory: marketing 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - run: npm install 21 | - run: npm run build 22 | 23 | - name: ACTIONS_ALLOW_UNSECURE_COMMANDS 24 | run: echo 'ACTIONS_ALLOW_UNSECURE_COMMANDS=true' >> $GITHUB_ENV 25 | 26 | - uses: chrislennon/action-aws-cli@v1.1 27 | - run: aws s3 sync dist s3://${{ secrets.AWS_S3_BUCKET_NAME }}/marketing/latest 28 | env: 29 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 30 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 31 | 32 | - run: aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_DISTRIBUTION_ID }} --paths "/marketing/latest/remoteEntry.js" 33 | env: 34 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 35 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 36 | -------------------------------------------------------------------------------- /auth/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "auth", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack serve --config webpack.dev.js", 8 | "build": "webpack --config webpack.prod.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "@babel/core": "7.12.10", 15 | "@babel/preset-react": "7.12.10", 16 | "@babel/preset-typescript": "7.12.7", 17 | "@types/node": "14.14.20", 18 | "@types/react": "17.0.0", 19 | "@types/react-dom": "17.0.0", 20 | "@types/react-router-dom": "^5.1.7", 21 | "babel-loader": "8.2.2", 22 | "html-webpack-plugin": "4.5.1", 23 | "ts-node": "9.1.1", 24 | "typescript": "4.1.3", 25 | "webpack": "5.11.1", 26 | "webpack-cli": "4.3.1", 27 | "webpack-dev-server": "3.11.1", 28 | "webpack-merge": "5.7.3" 29 | }, 30 | "dependencies": { 31 | "@emotion/react": "11.1.4", 32 | "@emotion/styled": "11.0.0", 33 | "@material-ui/core": "4.11.2", 34 | "react": "17.0.1", 35 | "react-dom": "17.0.1", 36 | "react-router-dom": "5.2.0", 37 | "rxjs": "6.6.3" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /header/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "header", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack serve --config webpack.dev.js", 8 | "build": "webpack --config webpack.prod.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "@babel/core": "7.12.10", 15 | "@babel/preset-react": "7.12.10", 16 | "@babel/preset-typescript": "7.12.7", 17 | "@types/node": "14.14.20", 18 | "@types/react": "17.0.0", 19 | "@types/react-dom": "17.0.0", 20 | "@types/react-router-dom": "5.1.7", 21 | "babel-loader": "8.2.2", 22 | "html-webpack-plugin": "4.5.1", 23 | "ts-node": "9.1.1", 24 | "typescript": "4.1.3", 25 | "webpack": "5.11.1", 26 | "webpack-cli": "4.3.1", 27 | "webpack-dev-server": "3.11.1", 28 | "webpack-merge": "5.7.3" 29 | }, 30 | "dependencies": { 31 | "@emotion/react": "11.1.4", 32 | "@emotion/styled": "11.0.0", 33 | "@material-ui/core": "4.11.2", 34 | "react": "17.0.1", 35 | "react-dom": "17.0.1", 36 | "react-router-dom": "5.2.0", 37 | "rxjs": "6.6.3" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /landing/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "landing", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack serve --config webpack.dev.js", 8 | "build": "webpack --config webpack.prod.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "@babel/core": "7.12.10", 15 | "@babel/preset-react": "7.12.10", 16 | "@babel/preset-typescript": "7.12.7", 17 | "@types/node": "14.14.20", 18 | "@types/react": "17.0.0", 19 | "@types/react-dom": "17.0.0", 20 | "@types/react-router-dom": "5.1.7", 21 | "babel-loader": "8.2.2", 22 | "html-webpack-plugin": "4.5.1", 23 | "ts-node": "9.1.1", 24 | "typescript": "4.1.3", 25 | "webpack": "5.11.1", 26 | "webpack-cli": "4.3.1", 27 | "webpack-dev-server": "3.11.1", 28 | "webpack-merge": "5.7.3" 29 | }, 30 | "dependencies": { 31 | "@emotion/react": "11.1.4", 32 | "@emotion/styled": "11.0.0", 33 | "@material-ui/core": "4.11.2", 34 | "react": "17.0.1", 35 | "react-dom": "17.0.1", 36 | "react-router-dom": "5.2.0", 37 | "rxjs": "^6.6.3" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /container/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "container", 3 | "version": "1.0.1", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack serve --config webpack.dev.js", 8 | "build": "webpack --config webpack.prod.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "@babel/core": "7.12.10", 15 | "@babel/preset-react": "7.12.10", 16 | "@babel/preset-typescript": "7.12.7", 17 | "@types/node": "14.14.20", 18 | "@types/react": "17.0.0", 19 | "@types/react-dom": "17.0.0", 20 | "@types/react-router-dom": "5.1.7", 21 | "babel-loader": "8.2.2", 22 | "html-webpack-plugin": "4.5.1", 23 | "ts-node": "9.1.1", 24 | "typescript": "4.1.3", 25 | "webpack": "5.11.1", 26 | "webpack-cli": "4.3.1", 27 | "webpack-dev-server": "3.11.1", 28 | "webpack-merge": "5.7.3" 29 | }, 30 | "dependencies": { 31 | "@emotion/react": "11.1.4", 32 | "@emotion/styled": "11.0.0", 33 | "@material-ui/core": "4.11.2", 34 | "react": "17.0.1", 35 | "react-dom": "17.0.1", 36 | "react-router-dom": "5.2.0", 37 | "rxjs": "6.6.3" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /container/src/hooks/useAuth.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect } from "react"; 2 | import { useHistory } from "react-router-dom"; 3 | import { History } from "history"; 4 | import { BehaviorSubject, Observable } from "rxjs"; 5 | 6 | interface UseAuthFunctions { 7 | login: VoidFunction; 8 | logout: VoidFunction; 9 | history: History; 10 | isSignedIn$: Observable; 11 | } 12 | 13 | const isSignedIn$ = new BehaviorSubject(false); 14 | 15 | const useAuth = (): UseAuthFunctions => { 16 | const history = useHistory(); 17 | 18 | useEffect(() => { 19 | const subscription = isSignedIn$.subscribe((val) => { 20 | if (val) { 21 | history.push("/dashboard"); 22 | } else if (history.location.pathname === "/dashboard") { 23 | history.push("/"); 24 | } 25 | }); 26 | 27 | return () => { 28 | subscription.unsubscribe(); 29 | }; 30 | }, []); 31 | 32 | const login = useCallback(() => isSignedIn$.next(true), []); 33 | const logout = useCallback(() => isSignedIn$.next(false), []); 34 | 35 | return { login, logout, history, isSignedIn$: isSignedIn$.asObservable() }; 36 | }; 37 | 38 | export default useAuth; 39 | -------------------------------------------------------------------------------- /landing/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from "react"; 2 | import styled from "@emotion/styled"; 3 | import { Button } from "@material-ui/core"; 4 | 5 | const Header = styled.div` 6 | background-color: #282c34; 7 | color: white; 8 | width: 100%; 9 | display: flex; 10 | align-items: center; 11 | justify-content: center; 12 | flex-direction: column; 13 | padding-top: 60px; 14 | padding-bottom: 70px; 15 | `; 16 | 17 | const Title = styled.h1` 18 | margin-bottom: 24px; 19 | color: #1db954; 20 | `; 21 | 22 | const Description = styled.h2` 23 | margin-bottom: 16px; 24 | color: white; 25 | `; 26 | 27 | const App = ({ navigate }: { navigate: NavigateFunction }) => { 28 | const navigateToAuth = useCallback(() => { 29 | navigate("/auth/login"); 30 | }, [navigate]); 31 | 32 | return ( 33 |
34 |
35 | Landing Microfrontend (route) 36 | Some random text goes here 37 | 40 |
41 |
42 | ); 43 | }; 44 | 45 | export default App; 46 | -------------------------------------------------------------------------------- /test-iterations/iter2/.github/workflows/container.yml: -------------------------------------------------------------------------------- 1 | name: deploy-container 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - "container/**" 9 | 10 | defaults: 11 | run: 12 | working-directory: container 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - run: npm install 21 | - run: npm run build 22 | env: 23 | PRODUCTION_DOMAIN: ${{ secrets.PRODUCTION_DOMAIN }} 24 | 25 | - name: ACTIONS_ALLOW_UNSECURE_COMMANDS 26 | run: echo 'ACTIONS_ALLOW_UNSECURE_COMMANDS=true' >> $GITHUB_ENV 27 | 28 | - uses: chrislennon/action-aws-cli@v1.1 29 | - run: aws s3 sync dist s3://${{ secrets.AWS_S3_BUCKET_NAME }}/container/latest 30 | env: 31 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 32 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 33 | 34 | - run: aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_DISTRIBUTION_ID }} --paths "/container/latest/index.html" 35 | env: 36 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 37 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 38 | -------------------------------------------------------------------------------- /test-iterations/iter3/app1/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const ModuleFederationPlugin = require("webpack").container 3 | .ModuleFederationPlugin; 4 | const path = require("path"); 5 | 6 | module.exports = { 7 | entry: "./src/index", 8 | mode: "development", 9 | devServer: { 10 | contentBase: path.join(__dirname, "dist"), 11 | port: 3001, 12 | }, 13 | output: { 14 | publicPath: "auto", 15 | }, 16 | resolve: { 17 | extensions: [".ts", ".tsx", ".js"], 18 | }, 19 | module: { 20 | rules: [ 21 | { 22 | test: /bootstrap\.tsx$/, 23 | loader: "bundle-loader", 24 | options: { 25 | lazy: true, 26 | }, 27 | }, 28 | { 29 | test: /\.tsx?$/, 30 | loader: "babel-loader", 31 | exclude: /node_modules/, 32 | options: { 33 | presets: ["@babel/preset-react", "@babel/preset-typescript"], 34 | }, 35 | }, 36 | ], 37 | }, 38 | plugins: [ 39 | new ModuleFederationPlugin({ 40 | name: "app1", 41 | remotes: { 42 | app2: "app2@http://localhost:3002/remoteEntry.js", 43 | }, 44 | shared: ["react", "react-dom"], 45 | }), 46 | new HtmlWebpackPlugin({ 47 | template: "./public/index.html", 48 | }), 49 | ], 50 | }; 51 | -------------------------------------------------------------------------------- /test-iterations/iter3/app2/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); 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: "auto", 14 | }, 15 | resolve: { 16 | extensions: [".ts", ".tsx", ".js"], 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /bootstrap\.tsx$/, 22 | loader: "bundle-loader", 23 | options: { 24 | lazy: true, 25 | }, 26 | }, 27 | { 28 | test: /\.tsx?$/, 29 | loader: "babel-loader", 30 | exclude: /node_modules/, 31 | options: { 32 | presets: ["@babel/preset-react", "@babel/preset-typescript"], 33 | }, 34 | }, 35 | ], 36 | }, 37 | plugins: [ 38 | new ModuleFederationPlugin({ 39 | name: "app2", 40 | filename: "remoteEntry.js", 41 | exposes: { 42 | "./Button": "./src/Button", 43 | }, 44 | shared: ["react", "react-dom"], 45 | }), 46 | new HtmlWebpackPlugin({ 47 | template: "./public/index.html", 48 | }), 49 | ], 50 | }; 51 | -------------------------------------------------------------------------------- /container/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { lazy, Suspense } from "react"; 2 | import { Switch, Route } from "react-router-dom"; 3 | import useAuth from "./hooks/useAuth"; 4 | import Header from "./modules/Header"; 5 | import styled from "@emotion/styled"; 6 | 7 | const LandingLazy = lazy(() => import("./modules/Landing")); 8 | const AuthLazy = lazy(() => import("./modules/Auth")); 9 | const DashboardLazy = lazy(() => import("./modules/Dashboard")); 10 | 11 | const HeaderContainer = styled.div` 12 | height: 60px; 13 | background-color: #282c34; 14 | `; 15 | 16 | const App = () => { 17 | const { login, history, isSignedIn$, logout } = useAuth(); 18 | 19 | return ( 20 |
21 | 22 |
23 | 24 | 25 |
26 | Loading . . .
}> 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 |
41 | ); 42 | }; 43 | 44 | export default App; 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Micro Frontends in React 2 | 3 | Live Demo: https://dnsiwzptduezx.cloudfront.net/ 4 | 5 | Repo used during talk: https://github.com/ashwanth1109/mfe-talk 6 | 7 | ## How to add a new MFE to repo? 8 | 9 | - Run `npm run mfegen dashboard 8084` 10 | - Add new entry `webpack.dev.js` in `container` 11 | 12 | ``` 13 | dashboard: "dashboard@http://localhost:8084/remoteEntry.js" 14 | ``` 15 | 16 | - Add new entry in `webpack.prod.js` in `container` 17 | 18 | ``` 19 | dashboard: `dashboard@${domain}/dashboard/remoteEntry.js` 20 | ``` 21 | 22 | - Add your entry to `.github/workflows/cdk-deploy.yml` 23 | 24 | ``` 25 | aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_DISTRIBUTION_ID }} --paths "..." "/dashboard/remoteEntry.js" 26 | ``` 27 | 28 | - Before step to build `deploy`, add build step for your module: 29 | 30 | ``` 31 | - name: Install dashboard dependencies 32 | run: npm install 33 | working-directory: dashboard 34 | 35 | - name: Build dashboard dist 36 | run: npm run build 37 | working-directory: dashboard 38 | ``` 39 | 40 | - Add deployment of your assets to cdk code: 41 | 42 | ``` 43 | new BucketDeployment(this, "DeployDashboardAssets", { 44 | destinationBucket, 45 | sources: [Source.asset("../dashboard/dist")], 46 | destinationKeyPrefix: "dashboard/", 47 | prune: false, 48 | }); 49 | ``` 50 | -------------------------------------------------------------------------------- /header/src/Header.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useEffect, useState } from "react"; 2 | import styled from "@emotion/styled"; 3 | import Button from "@material-ui/core/Button"; 4 | 5 | const Container = styled.div` 6 | width: 100%; 7 | background-color: #20232a; 8 | padding: 16px; 9 | height: 60px; 10 | position: fixed; 11 | top: 0; 12 | left: 0; 13 | color: white; 14 | display: flex; 15 | align-items: center; 16 | justify-content: space-between; 17 | `; 18 | 19 | const Header = ({ navigate, isSignedIn$, logout }: HeaderMountOptions) => { 20 | const [isSignedIn, setIsSignedIn] = useState(false); 21 | 22 | const navigateToLanding = useCallback(() => { 23 | navigate("/"); 24 | }, []); 25 | 26 | useEffect(() => { 27 | const subscription = isSignedIn$.subscribe((val) => { 28 | setIsSignedIn(val); 29 | }); 30 | 31 | return () => { 32 | subscription.unsubscribe(); 33 | }; 34 | }, []); 35 | 36 | return ( 37 | 38 |

39 | Header Microfrontend 40 |

41 | 42 | 49 |
50 | ); 51 | }; 52 | 53 | export default Header; 54 | -------------------------------------------------------------------------------- /test-iterations/iter2/container/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { lazy, Suspense, useState, useEffect } from "react"; 2 | import Header from "./components/Header"; 3 | import { Router, Switch, Route, Redirect } from "react-router-dom"; 4 | import { createBrowserHistory } from "history"; 5 | import { 6 | StylesProvider, 7 | createGenerateClassName, 8 | } from "@material-ui/core/styles"; 9 | import Progress from "./components/Progress"; 10 | 11 | const MarketingLazy = lazy(() => import("./components/MarketingApp")); 12 | const AuthLazy = lazy(() => import("./components/AuthApp")); 13 | const DashboardLazy = lazy(() => import("./components/DashboardApp")); 14 | 15 | const generateClassName = createGenerateClassName({ 16 | productionPrefix: "co", 17 | }); 18 | 19 | const history = createBrowserHistory(); 20 | 21 | export default () => { 22 | const [isSignedIn, setIsSignedIn] = useState(false); 23 | 24 | useEffect(() => { 25 | if (isSignedIn) { 26 | history.push("/dashboard"); 27 | } 28 | }, [isSignedIn]); 29 | 30 | return ( 31 | 32 | 33 |
34 |
setIsSignedIn(false)} 37 | /> 38 | }> 39 | 40 | 41 | setIsSignedIn(true)} /> 42 | 43 | 44 | {!isSignedIn && } 45 | 46 | 47 | 48 | 49 | 50 |
51 |
52 |
53 | ); 54 | }; 55 | -------------------------------------------------------------------------------- /deploy/lib/deploy-stack.ts: -------------------------------------------------------------------------------- 1 | import { 2 | App, 3 | CfnOutput, 4 | RemovalPolicy, 5 | Stack, 6 | StackProps, 7 | } from "@aws-cdk/core"; 8 | import { Bucket } from "@aws-cdk/aws-s3"; 9 | import { BucketDeployment, Source } from "@aws-cdk/aws-s3-deployment"; 10 | import { 11 | CloudFrontWebDistribution, 12 | ViewerProtocolPolicy, 13 | } from "@aws-cdk/aws-cloudfront"; 14 | 15 | export class DeployStack extends Stack { 16 | constructor(scope: App, id: string, props?: StackProps) { 17 | super(scope, id, props); 18 | 19 | const bucketName = "react-mfe-w4544"; 20 | const destinationBucket = new Bucket(this, bucketName, { 21 | bucketName, 22 | publicReadAccess: true, 23 | websiteIndexDocument: "index.html", 24 | websiteErrorDocument: "index.html", 25 | removalPolicy: RemovalPolicy.RETAIN, 26 | }); 27 | 28 | new BucketDeployment(this, "DeployLandingAssets", { 29 | destinationBucket, 30 | sources: [Source.asset("../landing/dist")], 31 | destinationKeyPrefix: "landing/", 32 | prune: false, 33 | }); 34 | 35 | new BucketDeployment(this, "DeployAuthAssets", { 36 | destinationBucket, 37 | sources: [Source.asset("../auth/dist")], 38 | destinationKeyPrefix: "auth/", 39 | prune: false, 40 | }); 41 | 42 | new BucketDeployment(this, "DeployHeaderAssets", { 43 | destinationBucket, 44 | sources: [Source.asset("../header/dist")], 45 | destinationKeyPrefix: "header/", 46 | prune: false, 47 | }); 48 | 49 | new BucketDeployment(this, "DeployDashboardAssets", { 50 | destinationBucket, 51 | sources: [Source.asset("../dashboard/dist")], 52 | destinationKeyPrefix: "dashboard/", 53 | prune: false, 54 | }); 55 | 56 | new BucketDeployment(this, "DeployContainerAssets", { 57 | destinationBucket, 58 | sources: [Source.asset("../container/dist")], 59 | prune: false, 60 | }); 61 | 62 | const distribution = new CloudFrontWebDistribution(this, "React-MFE-CDN", { 63 | originConfigs: [ 64 | { 65 | s3OriginSource: { 66 | s3BucketSource: destinationBucket, 67 | }, 68 | behaviors: [{ isDefaultBehavior: true }], 69 | }, 70 | ], 71 | defaultRootObject: "/index.html", 72 | viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS, 73 | errorConfigurations: [ 74 | { 75 | errorCode: 403, 76 | responseCode: 200, 77 | responsePagePath: "/index.html", 78 | errorCachingMinTtl: 10, 79 | }, 80 | ], 81 | }); 82 | 83 | new CfnOutput(this, "CdnUrl", { 84 | value: distribution.distributionDomainName, 85 | }); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /.github/workflows/cdk-deploy.yml: -------------------------------------------------------------------------------- 1 | name: cdk-deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - "deploy/**" 9 | - "container/**" 10 | - "landing/**" 11 | - "header/**" 12 | - "dashboard/**" 13 | - ".github/workflows/cdk-deploy.yml" 14 | 15 | defaults: 16 | run: 17 | working-directory: deploy 18 | 19 | jobs: 20 | deploy: 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - name: Checkout 25 | uses: actions/checkout@v2 26 | 27 | - name: Configure AWS Credentials 28 | uses: aws-actions/configure-aws-credentials@v1 29 | with: 30 | aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 31 | aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 32 | aws-region: us-east-1 33 | 34 | - name: Install container dependencies 35 | run: npm install 36 | working-directory: container 37 | 38 | - name: Build container dist 39 | run: npm run build 40 | working-directory: container 41 | env: 42 | PRODUCTION_DOMAIN: ${{ secrets.PRODUCTION_DOMAIN }} 43 | 44 | - name: Install landing dependencies 45 | run: npm install 46 | working-directory: landing 47 | 48 | - name: Build landing dist 49 | run: npm run build 50 | working-directory: landing 51 | 52 | 53 | - name: Install auth dependencies 54 | run: npm install 55 | working-directory: auth 56 | 57 | - name: Build auth dist 58 | run: npm run build 59 | working-directory: auth 60 | 61 | - name: Install header dependencies 62 | run: npm install 63 | working-directory: header 64 | 65 | - name: Build header dist 66 | run: npm run build 67 | working-directory: header 68 | 69 | - name: Install dashboard dependencies 70 | run: npm install 71 | working-directory: dashboard 72 | 73 | - name: Build dashboard dist 74 | run: npm run build 75 | working-directory: dashboard 76 | 77 | - name: Install deploy dependencies 78 | run: npm install 79 | 80 | - name: Run cdk deployment 81 | run: npm run deploy 82 | 83 | - name: ACTIONS_ALLOW_UNSECURE_COMMANDS 84 | run: echo 'ACTIONS_ALLOW_UNSECURE_COMMANDS=true' >> $GITHUB_ENV 85 | 86 | - name: Run cache invalidation 87 | run: aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_DISTRIBUTION_ID }} --paths "/auth/remoteEntry.js" "/landing/remoteEntry.js" "/index.html" "/header/remoteEntry.js" "/dashboard/remoteEntry.js" 88 | env: 89 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 90 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 91 | 92 | -------------------------------------------------------------------------------- /test-iterations/iter2/container/src/components/Header.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import AppBar from '@material-ui/core/AppBar'; 3 | import Button from '@material-ui/core/Button'; 4 | import Toolbar from '@material-ui/core/Toolbar'; 5 | import Typography from '@material-ui/core/Typography'; 6 | import { makeStyles } from '@material-ui/core/styles'; 7 | import { Link as RouterLink } from 'react-router-dom'; 8 | 9 | const useStyles = makeStyles((theme) => ({ 10 | '@global': { 11 | ul: { 12 | margin: 0, 13 | padding: 0, 14 | listStyle: 'none', 15 | }, 16 | a: { 17 | textDecoration: 'none', 18 | }, 19 | }, 20 | appBar: { 21 | borderBottom: `1px solid ${theme.palette.divider}`, 22 | }, 23 | toolbar: { 24 | flexWrap: 'wrap', 25 | justifyContent: 'space-between', 26 | }, 27 | link: { 28 | margin: theme.spacing(1, 1.5), 29 | }, 30 | heroContent: { 31 | padding: theme.spacing(8, 0, 6), 32 | }, 33 | cardHeader: { 34 | backgroundColor: 35 | theme.palette.type === 'light' 36 | ? theme.palette.grey[200] 37 | : theme.palette.grey[700], 38 | }, 39 | cardPricing: { 40 | display: 'flex', 41 | justifyContent: 'center', 42 | alignItems: 'baseline', 43 | marginBottom: theme.spacing(2), 44 | }, 45 | footer: { 46 | borderTop: `1px solid ${theme.palette.divider}`, 47 | marginTop: theme.spacing(8), 48 | paddingTop: theme.spacing(3), 49 | paddingBottom: theme.spacing(3), 50 | [theme.breakpoints.up('sm')]: { 51 | paddingTop: theme.spacing(6), 52 | paddingBottom: theme.spacing(6), 53 | }, 54 | }, 55 | })); 56 | 57 | export default function Header({ isSignedIn, onSignOut }) { 58 | const classes = useStyles(); 59 | 60 | const onClick = () => { 61 | if (isSignedIn && onSignOut) { 62 | onSignOut(); 63 | } 64 | }; 65 | 66 | return ( 67 | 68 | 74 | 75 | 82 | App 83 | 84 | 94 | 95 | 96 | 97 | ); 98 | } 99 | -------------------------------------------------------------------------------- /test-iterations/iter2/auth/src/components/Signin.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Avatar from '@material-ui/core/Avatar'; 3 | import Button from '@material-ui/core/Button'; 4 | import TextField from '@material-ui/core/TextField'; 5 | import FormControlLabel from '@material-ui/core/FormControlLabel'; 6 | import Checkbox from '@material-ui/core/Checkbox'; 7 | import Grid from '@material-ui/core/Grid'; 8 | import Box from '@material-ui/core/Box'; 9 | import LockOutlinedIcon from '@material-ui/icons/LockOutlined'; 10 | import Typography from '@material-ui/core/Typography'; 11 | import { makeStyles } from '@material-ui/core/styles'; 12 | import Container from '@material-ui/core/Container'; 13 | import { Link } from 'react-router-dom'; 14 | 15 | function Copyright() { 16 | return ( 17 | 18 | {'Copyright © '} 19 | 20 | Your Website 21 | {' '} 22 | {new Date().getFullYear()} 23 | {'.'} 24 | 25 | ); 26 | } 27 | 28 | const useStyles = makeStyles((theme) => ({ 29 | '@global': { 30 | a: { 31 | textDecoration: 'none', 32 | }, 33 | }, 34 | paper: { 35 | marginTop: theme.spacing(8), 36 | display: 'flex', 37 | flexDirection: 'column', 38 | alignItems: 'center', 39 | }, 40 | avatar: { 41 | margin: theme.spacing(1), 42 | backgroundColor: theme.palette.secondary.main, 43 | }, 44 | form: { 45 | width: '100%', 46 | marginTop: theme.spacing(1), 47 | }, 48 | submit: { 49 | margin: theme.spacing(3, 0, 2), 50 | }, 51 | })); 52 | 53 | export default function SignIn({ onSignIn }) { 54 | const classes = useStyles(); 55 | 56 | return ( 57 | 58 |
59 | 60 | 61 | 62 | 63 | Sign in 64 | 65 |
e.preventDefault()} 67 | className={classes.form} 68 | noValidate 69 | > 70 | 81 | 92 | } 94 | label="Remember me" 95 | /> 96 | 106 | 107 | 108 | {"Don't have an account? Sign Up"} 109 | 110 | 111 | 112 |
113 | 114 | 115 | 116 |
117 | ); 118 | } 119 | -------------------------------------------------------------------------------- /scripts/mfegen.ts: -------------------------------------------------------------------------------- 1 | import * as yargs from "yargs"; 2 | import * as fs from "fs-extra"; 3 | import * as nodeFs from "fs"; 4 | import * as cp from "child_process"; 5 | import * as path from "path"; 6 | 7 | const { writeFile, readFile } = nodeFs.promises; 8 | 9 | const capitalizeFirstLetter = (str: string) => 10 | str.charAt(0).toUpperCase() + str.slice(1); 11 | 12 | (async () => { 13 | try { 14 | // Usage: npm run mfegen module port 15 | // Example: npm run mfegen dashboard 8084 16 | const moduleName = `${yargs.argv._[0]}`; 17 | const modulePath = `/${moduleName}`; 18 | const port = `${yargs.argv._[1]}`; 19 | console.log(`Generating micro-frontend at path: ${modulePath}`); 20 | const cwd = path.resolve(__dirname, `..${modulePath}`); 21 | 22 | const dirExists = fs.existsSync(`.${modulePath}`); 23 | 24 | if (dirExists) { 25 | throw new Error("Package already seems to exist!"); 26 | } 27 | 28 | fs.mkdirSync(cwd); 29 | 30 | const devDeps = [ 31 | "@babel/core@7.12.10", 32 | "@babel/preset-react@7.12.10", 33 | "@babel/preset-typescript@7.12.7", 34 | "@types/node@14.14.20", 35 | "@types/react@17.0.0", 36 | "@types/react-dom@17.0.0", 37 | "babel-loader@8.2.2", 38 | "html-webpack-plugin@4.5.1", 39 | "ts-node@9.1.1", 40 | "typescript@4.1.3", 41 | "webpack@5.11.1", 42 | "webpack-cli@4.3.1", 43 | "webpack-dev-server@3.11.1", 44 | "webpack-merge@5.7.3", 45 | ]; 46 | 47 | // `react-router-dom` and `rxjs` need to be manually installed 48 | const deps = [ 49 | "react@17.0.1", 50 | "react-dom@17.0.1", 51 | "@emotion/react@11.1.4", 52 | "@emotion/styled@11.0.0", 53 | "@material-ui/core@4.11.2", 54 | ]; 55 | 56 | cp.spawnSync( 57 | `npm init -y && npm i -D ${devDeps.join(" ")} && npm i ${deps.join(" ")}`, 58 | { 59 | cwd, 60 | shell: true, 61 | stdio: "inherit", 62 | } 63 | ); 64 | 65 | const data = fs.readFileSync(`.${modulePath}/package.json`); 66 | const dataAsString = data.toString().replace(/\^/g, ""); 67 | const packageJson = JSON.parse(dataAsString); 68 | delete packageJson.scripts.test; 69 | packageJson.scripts.start = "webpack serve --config webpack.dev.js"; 70 | packageJson.scripts.build = "webpack --config webpack.prod.js"; 71 | 72 | const readPromises: Promise[] = [ 73 | readFile("scripts/webpack-common"), 74 | readFile("scripts/webpack-dev"), 75 | readFile("scripts/webpack-prod"), 76 | readFile("scripts/ts-config"), 77 | readFile("scripts/index-html"), 78 | readFile("scripts/bootstrap"), 79 | readFile("scripts/app"), 80 | ]; 81 | 82 | const files = await Promise.all(readPromises); 83 | const outputFiles = []; 84 | 85 | for (const file of files) { 86 | let content = file.toString(); 87 | content = content.replace(/__PORT_CONFIG__/g, port); 88 | content = content.replace(/__MODULE_NAME__/g, moduleName); 89 | content = content.replace( 90 | /__MODULE_NAME_CAPITALIZED__/g, 91 | capitalizeFirstLetter(moduleName) 92 | ); 93 | outputFiles.push(content); 94 | } 95 | 96 | fs.ensureDirSync(`.${modulePath}/public`); 97 | fs.ensureDirSync(`.${modulePath}/src`); 98 | 99 | const writePromises: Promise[] = [ 100 | writeFile( 101 | `.${modulePath}/package.json`, 102 | JSON.stringify(packageJson, null, 2) 103 | ), 104 | writeFile(`.${modulePath}/webpack.common.js`, outputFiles[0]), 105 | writeFile(`.${modulePath}/webpack.dev.js`, outputFiles[1]), 106 | writeFile(`.${modulePath}/webpack.prod.js`, outputFiles[2]), 107 | writeFile(`.${modulePath}/tsconfig.json`, outputFiles[3]), 108 | writeFile(`.${modulePath}/public/index.html`, outputFiles[4]), 109 | writeFile(`.${modulePath}/src/index.tsx`, 'import("./bootstrap");\n'), 110 | writeFile(`.${modulePath}/src/bootstrap.tsx`, outputFiles[5]), 111 | writeFile(`.${modulePath}/src/App.tsx`, outputFiles[6]), 112 | ]; 113 | 114 | await Promise.all(writePromises); 115 | } catch (e) { 116 | console.error(e); 117 | } 118 | })(); 119 | -------------------------------------------------------------------------------- /test-iterations/iter2/auth/src/components/Signup.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Avatar from '@material-ui/core/Avatar'; 3 | import Button from '@material-ui/core/Button'; 4 | import TextField from '@material-ui/core/TextField'; 5 | import FormControlLabel from '@material-ui/core/FormControlLabel'; 6 | import Checkbox from '@material-ui/core/Checkbox'; 7 | import Grid from '@material-ui/core/Grid'; 8 | import Box from '@material-ui/core/Box'; 9 | import LockOutlinedIcon from '@material-ui/icons/LockOutlined'; 10 | import Typography from '@material-ui/core/Typography'; 11 | import { makeStyles } from '@material-ui/core/styles'; 12 | import Container from '@material-ui/core/Container'; 13 | import { Link } from 'react-router-dom'; 14 | 15 | function Copyright() { 16 | return ( 17 | 18 | {'Copyright © '} 19 | Your Website {new Date().getFullYear()} 20 | {'.'} 21 | 22 | ); 23 | } 24 | 25 | const useStyles = makeStyles((theme) => ({ 26 | '@global': { 27 | a: { 28 | textDecoration: 'none', 29 | }, 30 | }, 31 | paper: { 32 | marginTop: theme.spacing(8), 33 | display: 'flex', 34 | flexDirection: 'column', 35 | alignItems: 'center', 36 | }, 37 | avatar: { 38 | margin: theme.spacing(1), 39 | backgroundColor: theme.palette.secondary.main, 40 | }, 41 | form: { 42 | width: '100%', 43 | marginTop: theme.spacing(3), 44 | }, 45 | submit: { 46 | margin: theme.spacing(3, 0, 2), 47 | }, 48 | })); 49 | 50 | export default function SignUp({ onSignIn }) { 51 | const classes = useStyles(); 52 | 53 | return ( 54 | 55 |
56 | 57 | 58 | 59 | 60 | Sign up 61 | 62 |
e.preventDefault()} 64 | className={classes.form} 65 | noValidate 66 | > 67 | 68 | 69 | 79 | 80 | 81 | 90 | 91 | 92 | 101 | 102 | 103 | 113 | 114 | 115 | } 117 | label="I want to receive inspiration, marketing promotions and updates via email." 118 | /> 119 | 120 | 121 | 131 | 132 | 133 | Already have an account? Sign in 134 | 135 | 136 |
137 |
138 | 139 | 140 | 141 |
142 | ); 143 | } 144 | -------------------------------------------------------------------------------- /test-iterations/iter2/marketing/src/components/Landing.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Button from '@material-ui/core/Button'; 3 | import Card from '@material-ui/core/Card'; 4 | import CardActions from '@material-ui/core/CardActions'; 5 | import CardContent from '@material-ui/core/CardContent'; 6 | import CardMedia from '@material-ui/core/CardMedia'; 7 | import Grid from '@material-ui/core/Grid'; 8 | import Typography from '@material-ui/core/Typography'; 9 | import { makeStyles } from '@material-ui/core/styles'; 10 | import Container from '@material-ui/core/Container'; 11 | import MaterialLink from '@material-ui/core/Link'; 12 | import { Link } from 'react-router-dom'; 13 | 14 | function Copyright() { 15 | return ( 16 | 17 | {'Copyright © '} 18 | 19 | Your Website 20 | {' '} 21 | {new Date().getFullYear()} 22 | {'.'} 23 | 24 | ); 25 | } 26 | 27 | const useStyles = makeStyles((theme) => ({ 28 | '@global': { 29 | a: { 30 | textDecoration: 'none', 31 | }, 32 | }, 33 | icon: { 34 | marginRight: theme.spacing(2), 35 | }, 36 | heroContent: { 37 | backgroundColor: theme.palette.background.paper, 38 | padding: theme.spacing(8, 0, 6), 39 | }, 40 | heroButtons: { 41 | marginTop: theme.spacing(4), 42 | }, 43 | cardGrid: { 44 | paddingTop: theme.spacing(8), 45 | paddingBottom: theme.spacing(8), 46 | }, 47 | card: { 48 | height: '100%', 49 | display: 'flex', 50 | flexDirection: 'column', 51 | }, 52 | cardMedia: { 53 | paddingTop: '56.25%', // 16:9 54 | }, 55 | cardContent: { 56 | flexGrow: 1, 57 | }, 58 | footer: { 59 | backgroundColor: theme.palette.background.paper, 60 | padding: theme.spacing(6), 61 | }, 62 | })); 63 | 64 | const cards = [1, 2, 3, 4, 5, 6, 7, 8, 9]; 65 | 66 | export default function Album() { 67 | const classes = useStyles(); 68 | 69 | return ( 70 | 71 |
72 | {/* Hero unit */} 73 |
74 | 75 | 82 | Home Page 83 | 84 | 90 | Something short and leading about the collection below—its 91 | contents, the creator, etc. Make it short and sweet, but not too 92 | short so folks don't simply skip over it entirely. 93 | 94 |
95 | 96 | 97 | 98 | 101 | 102 | 103 | 104 | 105 | 108 | 109 | 110 | 111 |
112 |
113 |
114 | 115 | {/* End hero unit */} 116 | 117 | {cards.map((card) => ( 118 | 119 | 120 | 125 | 126 | 127 | Heading 128 | 129 | 130 | This is a media card. You can use this section to describe 131 | the content. 132 | 133 | 134 | 135 | 138 | 141 | 142 | 143 | 144 | ))} 145 | 146 | 147 |
148 | {/* Footer */} 149 |
150 | 151 | Footer 152 | 153 | 159 | Something here to give the footer a purpose! 160 | 161 | 162 |
163 | {/* End footer */} 164 |
165 | ); 166 | } 167 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 8 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 9 | // "lib": [], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 13 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 15 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | // "outDir": "./", /* Redirect output structure to the directory. */ 18 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 19 | // "composite": true, /* Enable project compilation */ 20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | // "noEmit": true, /* Do not emit outputs. */ 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 26 | 27 | /* Strict Type-Checking Options */ 28 | "strict": true, /* Enable all strict type-checking options. */ 29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 30 | // "strictNullChecks": true, /* Enable strict null checks. */ 31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 36 | 37 | /* Additional Checks */ 38 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | 43 | /* Module Resolution Options */ 44 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 45 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 46 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 47 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 48 | // "typeRoots": [], /* List of folders to include type definitions from. */ 49 | // "types": [], /* Type declaration files to be included in compilation. */ 50 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 51 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 52 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 53 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 54 | 55 | /* Source Map Options */ 56 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 58 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 59 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 60 | 61 | /* Experimental Options */ 62 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 63 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 64 | 65 | /* Advanced Options */ 66 | "skipLibCheck": true, /* Skip type checking of declaration files. */ 67 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /test-iterations/iter2/marketing/src/components/Pricing.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Button from '@material-ui/core/Button'; 3 | import Card from '@material-ui/core/Card'; 4 | import CardActions from '@material-ui/core/CardActions'; 5 | import CardContent from '@material-ui/core/CardContent'; 6 | import CardHeader from '@material-ui/core/CardHeader'; 7 | import Grid from '@material-ui/core/Grid'; 8 | import StarIcon from '@material-ui/icons/StarBorder'; 9 | import Typography from '@material-ui/core/Typography'; 10 | import Link from '@material-ui/core/Link'; 11 | import { makeStyles } from '@material-ui/core/styles'; 12 | import Container from '@material-ui/core/Container'; 13 | import Box from '@material-ui/core/Box'; 14 | import { Link as RouterLink } from 'react-router-dom'; 15 | 16 | function Copyright() { 17 | return ( 18 | 19 | {'Copyright © '} 20 | 21 | Your Website 22 | {' '} 23 | {new Date().getFullYear()} 24 | {'.'} 25 | 26 | ); 27 | } 28 | 29 | const useStyles = makeStyles((theme) => ({ 30 | '@global': { 31 | ul: { 32 | margin: 0, 33 | padding: 0, 34 | listStyle: 'none', 35 | }, 36 | }, 37 | toolbar: { 38 | flexWrap: 'wrap', 39 | }, 40 | toolbarTitle: { 41 | flexGrow: 1, 42 | }, 43 | link: { 44 | margin: theme.spacing(1, 1.5), 45 | }, 46 | heroContent: { 47 | padding: theme.spacing(8, 0, 6), 48 | }, 49 | cardHeader: { 50 | backgroundColor: 51 | theme.palette.type === 'light' 52 | ? theme.palette.grey[200] 53 | : theme.palette.grey[700], 54 | }, 55 | cardPricing: { 56 | display: 'flex', 57 | justifyContent: 'center', 58 | alignItems: 'baseline', 59 | marginBottom: theme.spacing(2), 60 | }, 61 | footer: { 62 | borderTop: `1px solid ${theme.palette.divider}`, 63 | marginTop: theme.spacing(8), 64 | paddingTop: theme.spacing(3), 65 | paddingBottom: theme.spacing(3), 66 | [theme.breakpoints.up('sm')]: { 67 | paddingTop: theme.spacing(6), 68 | paddingBottom: theme.spacing(6), 69 | }, 70 | }, 71 | })); 72 | 73 | const tiers = [ 74 | { 75 | title: 'Free', 76 | price: '0', 77 | description: [ 78 | '10 users included', 79 | '2 GB of storage', 80 | 'Help center access', 81 | 'Email support', 82 | ], 83 | buttonText: 'Sign up for free', 84 | buttonVariant: 'outlined', 85 | }, 86 | { 87 | title: 'Pro', 88 | subheader: 'Most popular', 89 | price: '15', 90 | description: [ 91 | '20 users included', 92 | '10 GB of storage', 93 | 'Help center access', 94 | 'Priority email support', 95 | ], 96 | buttonText: 'Get started', 97 | buttonVariant: 'contained', 98 | }, 99 | { 100 | title: 'Enterprise', 101 | price: '30', 102 | description: [ 103 | '50 users included', 104 | '30 GB of storage', 105 | 'Help center access', 106 | 'Phone & email support', 107 | ], 108 | buttonText: 'Contact us', 109 | buttonVariant: 'outlined', 110 | }, 111 | ]; 112 | const footers = [ 113 | { 114 | title: 'Company', 115 | description: ['Team', 'History', 'Contact us', 'Locations'], 116 | }, 117 | { 118 | title: 'Features', 119 | description: [ 120 | 'Cool stuff', 121 | 'Random feature', 122 | 'Team feature', 123 | 'Developer stuff', 124 | 'Another one', 125 | ], 126 | }, 127 | { 128 | title: 'Resources', 129 | description: [ 130 | 'Resource', 131 | 'Resource name', 132 | 'Another resource', 133 | 'Final resource', 134 | ], 135 | }, 136 | { 137 | title: 'Legal', 138 | description: ['Privacy policy', 'Terms of use'], 139 | }, 140 | ]; 141 | 142 | export default function Pricing() { 143 | const classes = useStyles(); 144 | 145 | return ( 146 | 147 | {/* Hero unit */} 148 | 149 | 156 | Pricing 157 | 158 | 164 | Quickly build an effective pricing table for your potential customers 165 | with this layout. It's built with default Material-UI components 166 | with little customization. 167 | 168 | 169 | {/* End hero unit */} 170 | 171 | 172 | {tiers.map((tier) => ( 173 | // Enterprise card is full width at sm breakpoint 174 | 181 | 182 | : null} 188 | className={classes.cardHeader} 189 | /> 190 | 191 |
192 | 193 | ${tier.price} 194 | 195 | 196 | /mo 197 | 198 |
199 |
    200 | {tier.description.map((line) => ( 201 | 207 | {line} 208 | 209 | ))} 210 |
211 |
212 | 213 | 221 | 222 |
223 |
224 | ))} 225 |
226 |
227 | {/* Footer */} 228 | 229 | 230 | {footers.map((footer) => ( 231 | 232 | 233 | {footer.title} 234 | 235 |
    236 | {footer.description.map((item) => ( 237 |
  • 238 | 239 | {item} 240 | 241 |
  • 242 | ))} 243 |
244 |
245 | ))} 246 |
247 | 248 | 249 | 250 |
251 | {/* End footer */} 252 |
253 | ); 254 | } 255 | -------------------------------------------------------------------------------- /test-iterations/iter2/dashboard/src/components/Dashboard.vue: -------------------------------------------------------------------------------- 1 | 254 | 255 | 345 | 346 | 753 | --------------------------------------------------------------------------------