Introducing the Series: Unleashing the Power of Module Federation

ido mor
Yotpo Engineering
Published in
5 min readAug 3, 2023

Welcome to the first blog post in our exciting new series, “Unleashing the Power of Module Federation.” In this series, we will dive deep into the journey we embarked upon to overcome two critical challenges in modern front-end development: sharing dependencies efficiently and automatically consuming changes made to “remote” components. Whether you’re a front-end developer, architect, or part of a development team seeking to enhance collaboration and accelerate development velocity, this series is designed to provide valuable insights and solutions. So, fasten your seatbelts and get ready to explore the requirements we identified, the innovative architecture we devised, and the obstacles we encountered along the way. Prepare to be amazed by the incredible possibilities of module federation!

A Challenging Path to Modern Front-End Development

As a member of the Front-End Infrastructure team at Yotpo, our mission is to deliver comprehensive front-end solutions to support the entire R&D organization. With a primary focus on empowering developers and enhancing development velocity, we strive to provide self-service front-end capabilities.

Yotpo’s journey in front-end development has been a fascinating one. We started with a single AngularJS / Angular monolith and gradually expanded into a multi-app, multi-framework company, adding React to our tech stack. This evolution highlighted the need for a robust cross-framework infrastructure to ensure seamless collaboration and increased development velocity.

To meet these challenges, we recognized the importance of creating tools, frameworks, and architectural patterns that enable our teams to develop and deploy applications with greater efficiency and agility. Accelerating the development process and enabling faster iterations became paramount, allowing our teams to iterate faster and deliver high-quality experiences to our users.

The Introduction of Cross-Framework Features Shared Dependencies and Web Components

To facilitate the development of cross-framework features, we made a significant advancement by introducing an innovative web-components-based design system. Web components are a standardized set of web platform APIs that allow developers to create reusable components encapsulating their functionality and styling. These components can then be used across different frameworks, providing a seamless and consistent experience.

By adopting web components, we unlocked a new level of flexibility in our front-end development. Developers no longer need to be constrained by the limitations of a single framework but could leverage the power of web components to build features that seamlessly integrate into applications regardless of the underlying framework. This approach promotes code reuse, reduces duplication, and streamlines development efforts when working with multiple frameworks within a single organization.

However, embracing new changes introduced to our design system posed significant challenges, especially in a multi-app architecture. Traditionally, each new version of an npm package, including our design system, required a time-consuming development cycle for each application using the library. This process involved opening pull requests, code reviews, quality assurance, and deployment through the continuous integration (CI) pipeline. With multiple applications relying on the design system, even a minor change in a shared component could result in a substantial development effort across the entire ecosystem.

This manual and time-intensive process hindered our ability to iterate quickly and delayed the deployment of critical updates. It became evident that we needed a more efficient approach to automatically consume changes made to components in our design system, reducing the burden on development teams and accelerating the delivery of updates.

When converting an Angular (or any other framework) component into a web component, it becomes bundled alongside its dependencies, including peer dependencies. This bundling strategy, while initially promising, can lead to unforeseen complications. Web components are registered in the browser as soon as they are imported, allowing them to be used across different frameworks. However, conflicts arise when multiple components attempt to register under the same name, resulting in the “DOMException: Failed to execute ‘define’ on ‘CustomElementRegistry’” error.

Consider a component from our design system, such as a “button.” This button component is used in both an Angular component and a React application. During the rendering process of the converted Angular component, an error known as “DOMException: Failed to execute ‘define’ on ‘CustomElementRegistry’” occurs. This error occurs when the Angular component attempts to import the button component file from its own bundle.

To provide a clearer picture, here’s a code snippet demonstrating the typical registration of a web component:

// Register the "button" web component with a custom tag name
customElements.define('button', class ButtonComponent extends HTMLElement {
constructor() {
super();
// Component logic and behavior implementation
}
});

In this example, we register the “button” web component using the customElements.define method. We assign the custom tag name 'button' to this component, which can be used in our HTML markup. The class definition for the web component includes only a constructor for simplicity.

Conflicts can arise when web components with the same name, such as the “button” component, are imported and registered in different parts of an application. This can result in the “DOMException: Failed to execute ‘define’ on ‘CustomElementRegistry’” error, highlighting the challenges of double registration in the web component ecosystem.

Seeking a Real and Permanent Solution: The Birth of Module Federation

Recognizing the urgency of finding a comprehensive solution, we established two key requirements:

  1. Shared Dependencies: We needed a mechanism to share dependencies among components to avoid the double registration issue. This would not only resolve the immediate problem but also improve performance by leveraging shared resources.
  2. Automatic Consumption of Component Changes: We desired a streamlined process that would automatically propagate any changes made to components built using our design system. Manual updates and npm package management across multiple projects were cumbersome and error-prone. We sought a more efficient and automated approach.

Enter Module Federation, an Intriguing Webpack Feature

Our quest for a real and permanent solution led us to module federation, an intriguing feature of Webpack. Module federation offered the perfect answer to our challenges: efficient dependency sharing and automatic consumption of component changes. We were particularly captivated by two capabilities: the ability to share dependencies and dynamically load modules that exist outside the bundle. However, we were fully aware that module federation came with its own set of challenges, requiring a deep understanding and significant effort for developers to master.

In the upcoming articles, we have an exciting lineup of topics that dive deeper into our module federation journey. One crucial aspect we’ll explore is ensuring seamless integration between the host and remote modules by tackling the challenges of sharing Angular’s injection tokens in the module federation ecosystem. We’ll discuss the adjustments and strategies required to ensure smooth compatibility and effective communication between the different modules. Additionally, we’ll dive into the world of RxJS within the module federation environment and uncover various challenges that arise when utilizing RxJS in this context. From managing observables across boundaries to handling asynchronous operations and communication between federated modules, we’ll provide valuable insights and practical solutions. Get ready to unravel the secrets of quality assurance and discover how we tackle these complex problems in our module federation adventure. Stay tuned for an enlightening exploration of these topics in the upcoming articles!

--

--