Ember app
- Let's create our Ember app inside the apps folder using the Ember CLI.
cd apps
ember new web-app --skip-npm --skip-bower --no-welcome --skip-git
cd ../
pnpm install
npm pkg set scripts.app="pnpm --filter web-app"
- To start the ember web app in the dev server run
pnpm app start
.
Routes setup
Update the ember router.js to create 2 new routes, unlike React Ember which comes with a router by default. one normal route where the logic is present in ember and another admin route where we are going to mount our admin MFE.
Router.map(function () {
this.route('dashboard')
this.route('admin', function () {
this.route('item', { path: '*' })
})
})
We don't have to simulate the exact routing structure present in the React MFE. Instead, we can load the app in the admin route and react will take care of transitioning to the child routes and updating the browser URL.
But the problem is when a user enters the URL '/admin/groups' the host app will throw an error as it is not a valid route according to that. To avoid this, we created a dummy child route called item
inside admin with a glob pattern.
Navbar setup
- Let's create a navigation component so that we can navigate between host and MFE routes from the nav bar and validate the SPA.
touch apps/web-app/app/components/app-nav.hbs
touch apps/web-app/app/components/app-nav.js
app-nav.hbs
<nav>
<LinkTo @route='dashboard'>Dashboard</LinkTo>
<button {{on 'click' this.handleAdminClick}}>Admin</button>
</nav>
app-nav.js
import Component from '@glimmer/component'
import { action } from '@ember/object'
import { service } from '@ember/service'
export default class AppNavComponent extends Component {
@service router
@action handleAdminClick() {
if (this.router.currentURL.startsWith('/admin')) {
window.postMessage({ type: 'navigateToAdminMfeHome' }, window.location.origin)
}
this.router.transitionTo('/admin')
}
}
Note:
Ember only knows the admin route, if the user has navigated to the admin/groups
page in React, Ember will not be aware of it even though the browser URL is updated by React.
If the user clicks on the admin nav link then it won't do anything because according to Ember the user is still on the same admin page, to avoid this we need to send an event to React forcing it to transition to the root route if the user clicks on the Admin
nav link when they are inside any admin route.
If they are on some other router then normal transitioning itself will work.
- Update the
application.hbs
file to add the AppNav component.
{{page-title 'WebApp'}}
<AppNav />
{{outlet}}
Listener for messages from react
- Just like how the host app is sending events to react to handle a few cases, react also will send events to capture that we need to have a listener at the app level so let's create one.
touch apps/web-app/app/routes/application.js
import Route from '@ember/routing/route'
import { service } from '@ember/service'
export default class ApplicationRoute extends Route {
@service router
setupController() {
super.setupController(...arguments)
const { router } = this
window.addEventListener('message', function (event) {
console.log('inside host app listener', event.data)
const { data } = event
if (!data.type) {
return
}
if (data.type === 'navigateToHost') {
router.transitionTo(data.payload.route)
}
})
}
}
If you want to navigate to an ember route from react then we need to send a post message with type as navigateToHost
with the route info so that ember can do the required transition.
Route templates
- Create the route templates for the 2 routes as planned and update the content as below,
touch apps/web-app/app/templates/dashboard.hbs
touch apps/web-app/app/templates/admin.hbs
dashboard.hbs
Dashboard page present in ember <LinkTo @route="admin.item" @model="groups">Groups</LinkTo>
We can create navigation links from ember to react routes using this admin.item
route and passing the model prop.
admin.hbs
<div {{admin-mfe-mount}}></div>
{{outlet}}
This div present in this file is going to act as a root div where we are going to mount the react web component app for all the admin and its child routes.
Admin MFE mount modifier
We are going to make use of the ember modifier package to attach the react app to a div present in ember.
so let's install the ember modifier package.
pnpm app add -D ember-modifier
Create the admin-mfe-mount modifier,
mkdir apps/web-app/app/modifiers
touch apps/web-app/app/modifiers/admin-mfe-mount.js
import Modifier from 'ember-modifier'
export default class AdminMFEMountModifier extends Modifier {
modify(element) {
import('http://localhost:4173/admin-mfe.js').then((module) => {
module.mountApp({
element,
options: {
basename: '/',
baseURL: 'http://localhost:4173/',
},
})
})
}
}
Now run the ember server and validate the admin MFE is successfully rendered in the given element.
Demo:
Pros & Cons
Having used React MFE inside an Ember app, let's discuss the pros and cons of using the above-mentioned approach to integrate React into Ember.js applications.
Pros
- Incrementally migrate an Ember codebase to React
- Easy to split the app into multiple MFEs and have a dedicated team for each Micro-frontend.
- Provides an opportunity to get away from an unstable legacy code and slowly convert the old code to the latest technologies.
- Since it is rendered as a web component, the cookies and session will be extended from the host app so no need to have a dedicated authentication mechanism in the MFE while still achieving the encapsulation provided by the iFrame approach.
- The whole release and deployment cycle for the MFE can be decoupled from the host app process.
Cons
- Framework footprint of an extra 40 KB (React runtime) in your production bundles
Sample repo
The code for this series is hosted in Github here.
Please take a look at the Github repo and let me know your feedback, and queries in the comments section. If you feel there are more pros and cons to be added here, please do let us know also.
Top comments (1)
Great Article...
If you are using latest Vite version (>=Vite 5),
manifest.json
is placed inside the/dist/.vite/
subdirectory, not directly in thedist/
folder. Adjust yourapps/admin-app/public/admin-mfe.js
fetch() path tofetch('${baseURL}.vite/manifest.json')
accordinglyvitejs.dev/guide/migration#manifes...