DEV Community

Vinodh Kumar
Vinodh Kumar

Posted on • Edited on

React micro-frontend in ember app - Part 2

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"
Enter fullscreen mode Exit fullscreen mode
  • 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: '*' })
  })
})
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

app-nav.hbs

<nav>
  <LinkTo @route='dashboard'>Dashboard</LinkTo>
  <button {{on 'click' this.handleAdminClick}}>Admin</button>
</nav>
Enter fullscreen mode Exit fullscreen mode

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')
  }
}
Enter fullscreen mode Exit fullscreen mode

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}}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
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)
      }
    })
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

dashboard.hbs

Dashboard page present in ember <LinkTo @route="admin.item" @model="groups">Groups</LinkTo>
Enter fullscreen mode Exit fullscreen mode

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}}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Create the admin-mfe-mount modifier,

mkdir apps/web-app/app/modifiers
touch apps/web-app/app/modifiers/admin-mfe-mount.js
Enter fullscreen mode Exit fullscreen mode
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/',
        },
      })
    })
  }
}
Enter fullscreen mode Exit fullscreen mode

Now run the ember server and validate the admin MFE is successfully rendered in the given element.

Demo:

Image Description

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.

References

Top comments (1)

Collapse
 
kbtganesh profile image
Ganesh babu

Great Article...

If you are using latest Vite version (>=Vite 5), manifest.json is placed inside the /dist/.vite/ subdirectory, not directly in the dist/ folder. Adjust your apps/admin-app/public/admin-mfe.js fetch() path to fetch('${baseURL}.vite/manifest.json') accordingly

vitejs.dev/guide/migration#manifes...