├── .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 = () => App 2 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 | Login
24 |
25 |
30 | Register
31 |
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 | Register
24 |
25 |
30 | Login
31 |
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 |
38 | Get Started
39 |
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 | (isSignedIn ? logout() : navigate("/auth/login"))}
46 | >
47 | {isSignedIn ? "Logout" : "Login"}
48 |
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 |
92 | {isSignedIn ? 'Logout' : 'Login'}
93 |
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 |
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 |
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 |
99 | Pricing
100 |
101 |
102 |
103 |
104 |
105 |
106 | Pricing
107 |
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 |
136 | View
137 |
138 |
139 | Edit
140 |
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 |
219 | {tier.buttonText}
220 |
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 |
2 |
3 |
4 |
5 | Users
6 | Number of visitors
7 | 12
8 |
9 |
10 |
11 |
12 | Sales
13 | Number of purchases
14 | 534
15 |
16 |
17 |
18 |
19 | Revenue
20 | Income for today
21 | $3,200
22 |
23 |
24 |
25 |
26 |
27 |
28 | TV
29 |
30 |
31 |
32 | Total Queries
33 | 523
34 |
35 |
36 |
37 |
38 |
39 |
40 | TI
41 |
42 |
43 |
44 | Total Issues
45 | 81
46 |
47 |
48 |
49 |
50 |
51 |
52 | OI
53 |
54 |
55 |
56 | Open Issues
57 | 21
58 |
59 |
60 |
61 |
62 |
63 |
64 | CI
65 |
66 |
67 |
68 | Closed Issues
69 | 60
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | Sales Reports
80 |
81 |
82 |
83 |
84 | Pay Invoices
85 |
86 |
87 |
88 |
89 | Dinner with Tony
90 |
91 |
92 |
93 |
94 | Client Meeting
95 |
96 |
97 |
98 |
99 | New Theme
100 |
101 |
102 |
103 |
104 | Flight Ticket
105 |
106 |
107 |
108 |
109 |
110 |
111 |
137 |
138 |
168 |
169 |
170 |
171 |
182 |
183 |
184 |
185 |
186 |
Income
187 |
$900
188 |
189 |
190 |
191 |
192 |
193 |
Tax
194 |
$250
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
Recent Sales
205 |
212 |
213 | Logo
214 |
215 |
220 |
221 |
222 |
223 |
224 |
225 |
226 | {{ formatCurrency(slotProps.data.price) }}
227 |
228 |
229 |
230 | View
231 |
232 |
237 |
242 |
243 |
244 |
245 |
246 |
247 |
252 |
253 |
254 |
255 |
345 |
346 |
753 |
--------------------------------------------------------------------------------