Skip to content

Commit

Permalink
feat: change readme
Browse files Browse the repository at this point in the history
  • Loading branch information
thiendang committed Mar 13, 2024
1 parent d880499 commit 8004858
Show file tree
Hide file tree
Showing 7 changed files with 40 additions and 79 deletions.
107 changes: 34 additions & 73 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
# X Microfrontends: Twitter Clone with Microfrontends
# Admin Microfrontends: Admin Dashboard with Microfrontends

x-microfrontends is an open-source project that demonstrates the concept of microfrontends by building a Twitter clone with seven (7) separate React projects that seamlessly work together to create a complete web application.
This project showcases microfrontends by building a modular Admin Dashboard. Three independent React applications integrate seamlessly to form a cohesive web app

I built this as a demo for my ["Scaling teams with Microfrontends - Every CTO's dream"](https://www.papercall.io/talks/254933/children/254934) talk at SysConf 2023.

See [Demo](https://mykeels.github.io/x-microfrontends).

See [Slides](https://docs.google.com/presentation/d/10VkUfwX7NUJMl8jdT0q3qtk2C1RxG9AO4q-8DXjaFss/edit?usp=sharing).
Demo here [Demo](https://thiendang.github.io/admin-microfrontends).

## Table of Contents

Expand All @@ -15,50 +11,50 @@ See [Slides](https://docs.google.com/presentation/d/10VkUfwX7NUJMl8jdT0q3qtk2C1R
- [Getting Started](#getting-started)
- [How it works](#how-it-works)
- [Cool tricks with Microfrontends](#cool-tricks-with-microfrontends)
- [Contributing](#contributing)
- [License](#license)
- [Credits](#credits)

## Overview

Microfrontends are an architectural approach. They help you to break down your frontend monolith into smaller, independently developed, deployable applications.
Microfrontends are an architectural style that promotes building complex UIs from independent, deployable React applications.

This project serves as a practical example of how microfrontends can be used to build a complex web application with different teams working on different parts of the application.
This project demonstrates how this approach enables multiple teams to work on separate sections of an Admin Dashboard application, fostering modular development and faster delivery.

## Project Structure

The [shell](./shell/README.md) project composes 6 web apps, [communities](./mf-communities/README.md), [explore](./mf-explore/README.md), [messages](./mf-messages/README.md), [notifications](./mf-notifications/README.md), [timeline (home)](./mf-timeline/README.md), [verified](./mf-verified/README.md) into a cohesive web experience.
The [shell](./shell/README.md) project acts as the central hub, orchestrating three independent web applications, [cards](./mf-cards/README.md), [tables](./mf-tables/README.md), [charts](./mf-charts/README.md) to deliver a seamless user experience for the Admin Dashboard.

Each microfrontend is responsible for a specific part of the X clone.
The Admin Dashboard is built with modular microfrontends, each responsible for a specific area

[![X Microfrontends Demo Image](https://github.com/mykeels/rest-api-ioc-demo/assets/11996508/d835d338-2283-466e-b393-a06247a75bd2)](https://mykeels.github.io/x-microfrontends)
[![Admin Microfrontends Demo Image](./docs/images/main-page.png)](https://thiendang.github.io/admin-microfrontends)

The outline colors in the image above show which projects are responsible for which parts of the page.
In the image above, the colored outlines visually represent which project is responsible for rendering each section of the page.

## Getting Started

To get started with x-microfrontends and run the Twitter clone locally, run:
To launch the Admin Dashboard locally and experience microfrontends in action, run the following command:

```sh
git clone https://github.com/mykeels/x-microfrontends.git
git clone https://github.com/thiendang/admin-microfrontends.git
pnpm i
pnpm run -r copy:env
pnpm run --parallel -r start
```

and open https://localhost:4000 in the browser.

This starts the 7 Frontend projects described above in different ports, along with the [api](./api/README.md) and begins building the [microfrontends library](./microfrontends/README.md).
This command initiates the development servers for the three previously mentioned frontend projects (cards, tables, charts) on separate ports.

Additionally, it starts the [API](./api/README.md) and commences the process of building the [microfrontends library](./microfrontends/README.md).

## How it works

Each project uses [Webpack's Module Federation](https://webpack.js.org/concepts/module-federation/) to expose files.

See [example in mf-timeline](./mf-timeline/config-overrides.js#L16-L45), where we
See [example in mf-charts](./mf-charts/config-overrides.js), where we

- expose [3 apps](./mf-timeline/config-overrides.js#L19-L23),
- ensure there is [a remoteEntry.js file](./mf-timeline/config-overrides.js#L18) which tells webpack how to find these files.
- and [give them all a unique name identifier](./mf-timeline/config-overrides.js#L17), which we get from the package.json.
- expose [3 apps](./mf-charts/config-overrides.js),
- ensure there is [a remoteEntry.js file](./mf-charts/config-overrides.js) which tells webpack how to find these files.
- and [give them all a unique name identifier](./mf-charts/config-overrides.js), which we get from the package.json.

The above actions form 3 concepts called `module`, `entry`, and `scope` respectively, where a microfrontend can expose one or more `module` apps, under the same `scope`, with an `entry` that tells webpack how to find them. These concepts are helpful for understanding what comes next.

Expand All @@ -72,17 +68,17 @@ When running locally, these values are obtained from the microfrontend manifests

To aid in dynamic remote resolution, each microfrontend publishes a `microfrontend-manifest.json` file that contains information about how to load itself.

See [example in mf-timeline](./mf-timeline/public/microfrontend-manifest.json#L2-L5), where we have a
See [example in mf-charts](./mf-charts/public/microfrontend-manifest.json), where we have a

- scope: `timeline`
- scope: `charts`
- module: `./unused-root-module.js`
- entry: `http://localhost:4001/remoteEntry.js`
- entry: `http://localhost:4003/remoteEntry.js`

Notice that the `module: ./unused-root-module.js` is incorrect compared to the [3 exposed apps](./mf-timeline/config-overrides.js#L19-L23) in its webpack module federation config. This is because the manifest can have multiple [slots](#what-are-slots) modules, so it helps to reserve the root module for route slots.
Notice that the `module: ./unused-root-module.js` is incorrect compared to the [3 exposed apps](./mf-charts/config-overrides.js) in its webpack module federation config. This is because the manifest can have multiple [slots](#what-are-slots) modules, so it helps to reserve the root module for route slots.

#### What are Slots?

[Slots](./mf-timeline/public/microfrontend-manifest.json#L6-L27) are how we are able to express within our `microfrontend-manifest.json` file, that we have exposed more than one app in our module federation config.
[Slots](./mf-charts/public/microfrontend-manifest.json) are how we are able to express within our `microfrontend-manifest.json` file, that we have exposed more than one app in our module federation config.

Each slot represents an exposed interface, that:

Expand All @@ -95,11 +91,11 @@ Slots can be rendered either:
- directly on the route with [MicrofrontendScreen](./microfrontends/src/components/MicrofrontendScreen/README.md), making them a route-level slot.
- To specify routes, you would use the `slots.routes` property of the microfrontend-manifest.json file, which works:
- just the same as other slots,
- except it requires a `"route": "/explore/*"` property, specifying its route.
- except it requires a `"route": "/dashboard/*"` property, specifying its route.
- You can get away with having one route slot that resides in the root, especially if your exposed app includes its own BrowserRouter and handles its own navigation, relying on the `navigate` [mount prop](#what-are-mount-props) when it needs to navigate to a route controlled by its parent.
- Or you can choose to expose an app per route.
- just the same as other slots,
- and lets you add a `"route": "/explore/*"` property, specifying its route.
- and lets you add a `"route": "/dashboard/*"` property, specifying its route.
- directly within an HTML Element with [MicrofrontendSlot](./microfrontends/src/components/MicrofrontendSlot/README.md), making them a non route-level slot.

#### Loading Microfrontends
Expand All @@ -125,32 +121,32 @@ export default {
};
```

such as [in mf-timeline](./mf-timeline/src/main.tsx#L818-L840).
such as [in mf-charts](./mf-charts/src/main.tsx).

#### What are mount props?

the [mount function's props parameter](./microfrontends/src/components/Microfrontend/Microfrontend.types.ts#L56-L65) contain data and functions we pass down to every microfrontend.
the [mount function's props parameter](./microfrontends/src/components/Microfrontend/Microfrontend.types.ts) contain data and functions we pass down to every microfrontend.

Having such a simple interface instead of exporting say a React component directly is a powerful concept because it lets us abstract away the moving parts of different frameworks. Technically, this can be used to load Angular, Vue, Solid, HTMX, etc, because all we need is a way to mount HTML with behaviours to an element, and unmount it when done.

### Communication among microfrontends

One of the properties of the microfrontend-manifest.json I did not talk about is the `events`, which may expose a mapping of event names to a [JSON schema](https://json-schema.org/) object that describes its data.

Using the Microfrontend Context, we can [pass an eventBus](./shell/src/components/AppRouter.tsx#L77) to our microfrontends, which they can use to communicate across the various apps.
Using the Microfrontend Context, we can [pass an eventBus](./shell/src/components/AppRouter.tsx) to our microfrontends, which they can use to communicate across the various apps.

## Cool tricks with Microfrontends

All the above sounds like a lot if there is no clear advantage. Besides the team and management advantages of independence and being able to iterate simultaneously on multiple parts of the product, here are some technical advantages to using this architectural pattern.

### 1. Show off your work to a colleague

Imagine you're working on the Timeline project, and you've deployed your work to staging, so it lives at `https://staging.x.com/mfs/timeline/` while production is at `https://x.com`.
Imagine you're working on the charts project, and you've deployed your work to staging, so it lives at `https://staging.admin-dashboard.com/mfs/charts/` while production is at `https://admin-dashboard.com`.

You could ensure your page takes an `?override_manifest` query string, so you send a URL like:

```txt
https://x.com?override_manifest=https://staging.x.com/mfs/timeline/microfrontend-manifest.json
https://admin-dashboard.com?override_manifest=https://staging.admin-dashboard/mfs/charts/microfrontend-manifest.json
```

to your colleague, and they would see the new funky thing you have in staging, in the production environment, because it would load the remoteEntry.js from staging.
Expand All @@ -160,56 +156,21 @@ to your colleague, and they would see the new funky thing you have in staging, i
A tangent of the above is that when you want to test a new feature in production, or debug a problem, you don't need to wait until your code gets to production, because you can load the following URL:

```txt
https://x.com?override_manifest=http://localhost:4001/microfrontend-manifest.json
https://admin-dashboard.com?override_manifest=http://localhost:4003/microfrontend-manifest.json
```

and instantly have your localhost-served copy of the Timeline project, running in the production web page, with webpack hot-reload.
and instantly have your localhost-served copy of the charts project, running in the production web page, with webpack hot-reload.

Be sure to limit the domains that can be used to override your manifests to `localhost` and other domains you control.

### 3. Instant Rollbacks

Because we are dealing with SPAs, we can easily maintain the say last 50 deployed versions of each microfrontend, because storage and CDN are relatively cheap.

This means that if something happens with the latest 1.2.3 version of the Timeline such as `https://x.com/mfs/timeline/1.2.3/remoteEntry.js`, then we can quickly change the remoteEntry.js location to one we know that works e.g. `https://x.com/mfs/timeline/1.2.0/remoteEntry.js`, and all your users need to do is refresh to get the latest.
This means that if something happens with the latest 1.2.3 version of the charts such as `https://admin-dashboard.com/mfs/charts/1.2.3/remoteEntry.js`, then we can quickly change the remoteEntry.js location to one we know that works e.g. `https://admin-dashboard.com/mfs/charts/1.2.0/remoteEntry.js`, and all your users need to do is refresh to get the latest.

You could even push a notification to the web page to prompt them to refresh when this happens.

## Contributing

Contributions to x-microfrontends are welcome!

Feel free to open issues, or create pull requests.

Potential contribution areas are:

### 1. Sharing Tanstack Query Client

I've been toying with the idea of passing a tanstack query client, as part of the `mount` function's props parameter, which will enable sharing the query cache, a very powerful tool if it works.

### 2. Demonstrate communicating with the Event Bus

A good example of this is the [aside-search app](./mf-explore/src/aside-search.tsx#L12-L29),

- emitting a `search` event that contains `{ "query": "Blah" }`,
- having the shell receive the event data and log to the console.

### 3. Demonstrate route-level slots

To enhance the above, the aside-search app could

- navigate to to `/explore?query=Blah`, - passing the `query` as a route search param..

### 4. Clean up this README

Oh man, I wrote this in a few sleep-deprived hours, so this could use some work. Send help!

## License

This project is licensed under the MIT License. See the [LICENSE](./LICENSE) file for details.

Happy microfrontending, and enjoy [exploring this X clone built with microfrontends](https://mykeels.github.io/x-microfrontends)!

## Credits

The original html+css code used for this demo is by [@shubhstwt on X](https://twitter.com/shubhstwt/status/1692968496010015203). Without his work on the open source [xclone](https://github.com/shubhsharma19/xclone), it would be much harder to put this demo together than it currently is.
The original html+css code used for this demo is by [TailAdmin](https://react-demo.tailadmin.com/).
Binary file added docs/images/main-page.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion mf-cards/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# Tables

This craco project [exposes entry points](./config-overrides.js#L19-L21) to functions that mount and unmount React components for Tables.
This craco project [exposes entry points](./config-overrides.js) to functions that mount and unmount React components for Tables.
4 changes: 2 additions & 2 deletions mf-charts/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# verified
# Charts

This craco project [exposes entry points](./config-overrides.js#L19-L21) to functions that mount and unmount React components for verified.
This craco project [exposes entry points](./config-overrides.js) to functions that mount and unmount React components for verified.
2 changes: 1 addition & 1 deletion mf-tables/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# Tables

This craco project [exposes entry points](./config-overrides.js#L19-L21) to functions that mount and unmount React components for Tables.
This craco project [exposes entry points](./config-overrides.js) to functions that mount and unmount React components for Tables.
2 changes: 1 addition & 1 deletion microfrontends/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ You need the [Microfrontend](./src/components/Microfrontend/index.ts) component
import { Microfrontend } from "microfrontends";

<Microfrontend
entry="http://localhost:3002/remoteEntry.js"
entry="http://localhost:4002/remoteEntry.js"
scope="ProjectScope"
module="./src/bootstrap.js"
Fallback={<div>Fallback</div>}
Expand Down
2 changes: 1 addition & 1 deletion shell/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"scripts": {
"preinstall": "npx only-allow pnpm",
"typings": "bash ../scripts/generate-package-typings.sh",
"build": "GENERATE_SOURCEMAP=false craco build",
"build": "craco build",
"build:css": "npx tailwindcss -o ./public/output.css --minify",
"copy:env": "cpx .env.sample .env",
"copy:manifests": "node ./build/copy-manifests.script.js",
Expand Down

0 comments on commit 8004858

Please sign in to comment.