React inside EmberJS: Extend your legacy Front End using React-based Web Components

A couple of months ago my company decided to get rid of their legacy Ember frontend in favor of React.

With limited team capacity, we wouldn't have been able to keep on implementing new customer features in Ember and in parallel rebuild the whole frontend in React. That’s why we decided to take a gradual approach: We’re implementing new features in React, before actually replacing our whole Ember app. By exporting these React widgets as Web Components, we can easily integrate and use them in our existing Ember frontend.

This approach allows our team to already gain experience in our future stack (React) and produce code that later can be used in the new codebase. We’re in a comfortable position to be able to act on new customer demands without rewriting the whole app to start our React journey.

But enough of my team’s situation for now.

In this article, I’m going to show you how to create and use React-based Web Component inside Ember. We will in particular discuss:

Feel free to have a look at the example codebase/demo:

Github Repo: https://github.com/jhinter/react-inside-ember
Working Demo: https://jhinter.github.io/react-inside-ember/

Check out the working demo: React inside Ember

1. Preparation

First, you have to decide where to put your new React project’s files. In my case, I decided to set up a mono repo using yarn workspaces. The repo consists of a root package.json and two child packages, namely @example/ember and @example/react.

project-root/
├── packages/
└── ember
└── ...
└── package.json (@example/ember)
└── react
└── ...
└── package.json (@example/react)
├── ...
├── package.json (root)

I set up this working example from scratch using Ember and Direflow CLI. To keep things simple, especially when it comes to the build process, I took the mono repo approach. If you want to extend an existing large Ember codebase, you could of cause also consider other approaches and e.g. split both codebases into multiple repos.

2. Set up Web Component with Direflow CLI

We’re going to use Direflow for setting up our React-based web component. This tool creates a new React project for us including tools for exporting the whole project as a native web component.

About Direflow, a tool for generating React-based web components

First, you have to install Direflow’s command-line tool:

yarn global add direflow-cli

Now, we’re going to create a scaffold for our project. Direflow sets up the folder structure and creates all the necessary files to get started.

direflow create

That’s how I created the React part of this project. I used it as a starting point and added some functionality. Please check out the code here.

If you want to learn more about Direflow, please refer to the official documentation.

You can try out your fresh web component by running:

yarn workspace @example/react start

Excurse: Web Components
In short, a web component encapsulates HTML, CSS, and JavaScript-based logic. Instead of relying on a specific JS framework like React, web components leverages technologies natively provided by modern browsers. I highly suggest reading this article for getting more information on web components and their inner workings.

Now, add the following scripts to your package.json of your React/ Direflow package. This will make the bundled output (direflowBundle.js) available to your Ember app in the next step of this tutorial.

/packages/react → package.json

"build": "react-app-rewired build",
"postbuild": "cp ./build/direflowBundle.js ../ember/vendor/direflowBundle.js",

You can now try and check if the whole web component build works and direflowBundle.js gets copied to the correct directory inside your Ember project:

yarn workspace @example/react build

3. Embed web component into Ember

At first, you have to decide on which page or in which component you want to use your new web component. In my case, I’m adding it inside a new controllers template file (.hbs):

<example-react-app></example-react-app>

Right now, you won’t see the web component. This makes sense because we haven’t included the web component’s bundle yet.

You have to simply add the build output to your ember-cli-build.js. This will make sure, your web component will be available in your Ember app.

module.exports = function(defaults) {
let app = new EmberApp(defaults, {});
app.import('./vendor/direflowBundle.js')
return app.toTree();
};

4. Establish IO (Ember / React)

Now, we have to enable communication between both our wrapping Ember app and the React context.

The web component will emit global events. These events will be caught by the Ember app. Then, the Ember app can react to these events, do some calculations and trigger a state change in the React context by passing new values to the web component via input props.

Here’s how it works:

Add a did-insert and will-destroy action to your template file. The two actions will be used to (un-)register the listeners for the web component’s example-event-from-react event.

<example-react-app
{{did-insert this.registerListener}}
{{will-destroy this.unregisterListener}}
></example-react-app>

If you haven’t done already, please install render-modifiers to be able to use these template helpers:

yarn workspace @example/ember add @ember/render-modifiers

For those of you working with older Ember versions: You can also use the Non-Octane lifecycle hooks for (un-)registering the event lister. Just do it the way you’re already used to in your project.

Now, we have to set up the actions from the last paragraph. Add them to your controller’s Component class:

We finally connected both worlds using events and input props! The event triggered inside the web component is just a simple JS event. Please check out the complete code example here.

5. Add web component to Ember build

The following prebuild and prestart scripts make sure, that a fresh build of your web component is used always. Both scripts will automatically be run once the Ember start or build script is triggered.

/packages/ember → package.json

"build:webcomponent": "yarn workspace @example/react build",
"prebuild": "yarn build:webcomponent",
"prestart": "yarn build:webcomponent",

6. Try it!

yarn workspace @example/ember start

Open your browser and visit http://localhost:4200. Click on the button and realize how the data is flowing between both contexts:

  1. Click onto React button triggers event emit.
  2. The event is caught by the listener in the Ember context.
  3. Ember controller generates a new background-color.
  4. Ember passes new color to the web component.
  5. React web component displays the color value.

Final thoughts

The described approach of gradually switching to another frontend library/ framework with the help of small web components really helps my team to get things started and stay flexible. We are able to still use a lot of stuff from our legacy codebase. This allows us to stay effective in implementing new customer requirements, even as a really small team.

However, splitting your code into multiple small apps and web components has some disadvantages. One of them is loading speed.

Your users might have to load dependencies twice, e.g. if a dependency is both used in the Ember and the React context. And if you have multiple React-based web components, it can also be the case that you will load their dependencies multiple times. But Direflow already comes with solutions for these problems out-of-the-box. But you should always keep this in mind and check your performance before going live!

Now it’s your turn! Did you like this article? Do you think you will try this out in your legacy Ember projects?

Let me know in the comment section!

Frontend Engineer

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store