Micro-frontends with React, Vue and Ragu

Maniero
5 min readSep 11, 2020

Micro-frontends is a fascinating idea which was introduced to me at the ThoughtWorks Tech Radar. To bring the microservices ideas to front-end enable teams to have autonomy to develop their products side-to-side ideally without dependencies.

If you do know what micro-frontends are, you probably saw the image bellow which illustrates what I’m talking about:

If you did not recognized this image, I recommend you to follow this link https://micro-frontends.org/ to learn more about micro-frontends.

After some time flirting with some approaches to build micro-frontends, I created the Ragu ecosystem. This article aims to share a practical example of usage and the main ideas behind Ragu.

Straight to the code: An “E-commerce”

You can see the project live in https://ragu-ecommerce.herokuapp.com/. This project is responsible to manage the micro-fronends. (It may take a while to load since it is using the Heroku’s free plan 🤑)

Take a look at the page’s source code. The body’s content is just that:

<header class="ecommerce-header">
<h1>Ragu Pokémon Ecommerce</h1>
<div class="ecommerce-header__cart_count">
<ragu-component
src="https://ragu-cart-vuejs.herokuapp.com/components/cart"
</ragu-component>
</div>
</header>
<ragu-component
id="main-component"
src="https://ragu-catalog-react.herokuapp.com/components/featured-products">
</ragu-component>

<ragu-component /> is a CustomElement. It is designed to fetch a remote component. To understand what is going on under the hood, take a quick look at the response from one of those endpoints.

GET https://ragu-cart-vuejs.herokuapp.com/components/cart
{
"html":"<div data-server-rendered=\"true\" class=\"cart-cart-cart-count-CXgs\"><div class=\"cart-cart-icon-3Tit\"><svg>...</svg></div> <strong>0</strong></div>",
"props":{
},
"dependencies":[
{
"nodeRequire":"vue",
"globalVariable":"Vue",
"dependency":"https://cdn.jsdelivr.net/npm/vue/dist/vue.js"
}
],
"client":"https://ragu-cart-vuejs.herokuapp.com/component-assets/client.735370104395e93efee6.js",
"resolverFunction":"cartcart"
}

The response includes the HTML to be rendered. The other fields are instructions about the hydration process (which means to add behaviour to the generated HTML, much like adding event listeners to buttons).

How does this works?

The schema below illustrates how Ragu works. RaguDOM communicates with RaguServer though the micro-frontend URL. RaguServer in its turn aims to delivery components pre-rendered from the server.

Creating the product catalog with React

You can find the entire example at https://github.com/carlosmaniero/ragu-catalog-react.

Ragu server works with a directory structure such as NextJs. Any component inside the /components directory will be published by the RaguServer.

Example:
The component defined at: https://github.com/carlosmaniero/ragu-catalog-react/blob/master/components/featured-products/view.tsx
Is available at: https://ragu-catalog-react.herokuapp.com/components/featured-products

The propsToState function processes any information required in order to render the React component. This function is always called by the server. You will not see any call to PokéAPI at your Browser network tool.

The renderComponent, as the name suggests, returns the component that must be rendered. In this case the <PokemonList /> which receives the state from propsToState.

That’s cool because the <PokeList /> is just a regular React component. There is nothing special on it. You can use the RaguServer with any react project you want.

Creating the product catalog with React

The cart project can be found at this repository. It is pretty much the same mechanism of the react component, nonetheless, we can learn how to integrate two micro-frontends.

cart: /cart/index.jswindow.addEventListener('add-to-cart', (e) => {
app.addProductToCart(e.detail);
});
catalog: /react-components/pokemon.tsx<Button onClick={(e) => {
e.target.dispatchEvent(
new CustomEvent('add-to-cart', {
detail: pokemon,
bubbles: true
})
)
}}>Add To Cart</Button>

We can use CustomEvents to make this integration. Simple as it. Just using the DOM API.

Tell me more about Ragu

Ok! There are few principles who guided architecture and design of Ragu.

Independent deploys

One of the most common patterns used when organizations want to share code across teams is to create a shared NPM package. Unfortunately, this approach brings a high coupling between applications and requires that all projects that have the shared package as dependency to be re-builded when the shared package changes.

With Ragu, each component can be deployed isolated, eliminating bottlenecks.

Technology agnostic

You are free to build your micro-frontends with any framework/library. There are only two limitations:

It requires components to be rendered as string: The output expected by a RaguComponent is a HTML as string. React, for example can delivery it through the ReactDOMServer.renderToString().

It requires an hydration mechanism: Again with react we can do it though the ReactDOM.hydrate().

It allows global dependency sharing

You might have few micro-frontends developed with the same library, ie: React, and I believe you don’t want to have the React at all bundles of all components you have. To solve this issue you can declare the shared dependencies you have. These dependencies will be discarded from the bundle and automatically fetched when the component is requested.

dependencies: [
{
nodeRequire: 'react',
globalVariable: 'React',
dependency: 'https://react.production.min.js'
},
{
nodeRequire: 'react-dom',
globalVariable: 'ReactDOM',
dependency: 'https://react-dom.production.min.js'
}
]

What are the next steps to have Ragu ready-to-production?

To review the build system: It was possible to use RaguServer with React nearly effortless. However, it was pretty hard to make the same thing with Vue and I think the build system at the way it is now will be hard to integrate with frameworks with a complex build system.

To create a standard setup for mainstream frameworks: All projects I’ve configured I did from scratch. It was not so hard because I know how RaguServer works, but it is not so simple.

To create clients for mainstream frameworks: RaguDOM is CustomElements based, which works pretty well with other frameworks. However, I believe that it could be easier to use if there was framework-specific clients.

To improve RaguDOM: There are few improvements that I want to do at RaguDOM. Mostly about dispatch lifecycle events, such as “component is loading”, “component loaded”, “component load fails”.

And also to add more documentation.

And why 🔪 Ragu?

“Did you die with my cuteness? So clap this article, otherwise accept the consequences”

When I was trying to name the project I was thinking about something that represented “distributed” or “sliced”. After a whole day thinking I was almost giving up. I layed down on the bed and then my cat (named Ragu) came to ask for affection.

Its name suits perfectly! After all, Ragu or Ragout is a sauce made of small chunks of something (am I right, technical cookers?), preferable of mushrooms #GoVegan.

--

--