In today’s fast-paced development environment, building scalable and maintainable applications requires a modular approach. Micro Frontends (MFEs) are a design pattern that allow teams a way to break down monolithic applications into smaller independent modules, improving development speed, autonomy, and maintainability.
In this two-part article, we’ll explore the design and implementation of an MFE architecture that leverages Lit-Element for creating custom web components. We’ll also delve into various best practices and technologies that enhance the architecture’s performance and scalability.
Key Benefits of Micro Frontends over Monoliths
Micro Front-End Architectures offer several advantages over monolithic approaches:
- Independent Deployment: Teams can work on and deploy individual MFEs independently, reducing bottlenecks and allowing for faster feature delivery.
- Autonomy: Each MFE can have its own technology stack, enabling teams to choose the best tools for their specific needs. This approach also facilitates gradual updates of individual MFEs to more advanced technology stacks at a later point, eliminating the necessity to rewrite the entire application from scratch.
- Reduced Risks: With smaller codebases, you only need to test the MFE that has changed, reducing the scope of testing and the risk of regressions.
The Modular Frontend Architecture
Choosing the Right Technologies
One of the first decisions when building a Micro Frontend architecture is selecting the technologies to be used. To avoid MFE anarchy, where competing technologies are mixed, teams should agree upon the adopted technologies allowing easier collaboration across teams.
Native JavaScript vs. Frameworks
While JavaScript frameworks like Angular or React are powerful, they might be overkill for building individual MFEs. Native JavaScript provides several benefits:
- Lightweight: MFEs are components, not full applications. Native JavaScript keeps your codebase lightweight and reduces unnecessary complexity.
- Performance: Native JavaScript often outperforms frameworks because it doesn’t introduce the overhead of a framework’s virtual DOM or reconciliation algorithms.
Lit-Element: Speed, Reactivity, and Declarative Approach
Lit-Element is a modern library for building fast, lightweight web components. Its key features make it an ideal choice for Micro Frontend development:
- Speed: Weighing in at around 5 KB (minified and compressed), Lit helps keep your bundle size small and your loading time short. And rendering is exceptionally fast, because Lit touches only the dynamic parts of your UI when updating — no need to rebuild a virtual tree and diff it with the DOM.
- Reactive Properties: Lit-Element provides a convenient way to define reactive properties within your components. These properties automatically trigger updates when their values change, reducing the need for manual DOM manipulation code.
- Declarative Approach: Lit-Element encourages a declarative approach to building UI components. Developers define the component’s template using HTML and JavaScript template literals, making it easy to read and maintain.
Benefits of Shadow DOM
When building MFEs, encapsulation is crucial to prevent style and functionality conflicts between components. Lit-Element leverages Shadow DOM to achieve this encapsulation:
- Isolation: Shadow DOM creates a scoped boundary for styles and encapsulates the component’s internal structure. This isolation ensures that styles from one component don’t affect others, reducing the chances of CSS clashes.
- Encapsulation: Shadow DOM hides the internal implementation of a component, exposing only the public API. This abstraction allows developers to focus on component usage without worrying about its internal workings.
Rollup for Smaller Bundled Scripts
To optimize the loading performance of Micro Frontends, using a module bundler like Rollup is essential:
- Tree Shaking: Rollup can eliminate unused code (tree shaking) during the bundling process, resulting in smaller bundle sizes. This is especially important for MFEs to reduce initial loading times.
- Code Splitting: Rollup supports code splitting, allowing you to create separate bundles for different parts of your application. This enables lazy loading, which improves the initial load time by only loading what’s necessary.
GitLab for Versioned CDN Deployments
To ensure efficient caching consider using GitLab to deploy versioned URLs to a Content Delivery Network (CDN). By tagging releases and deploying them to a CDN, you allow for greater cacheability. If one Micro Frontend is updated, all the others can remain cached, speeding up the customer’s journey. If the goal is to be able to deploy often, we don’t want the customer to have to download the entire application after every change (as in Monolithic applications)
Utilizing versioned URLs for our web component JavaScript files proves to be a superior deployment strategy for Micro Frontends compared to relying on npm packages. The latter necessitates the Micro Frontends installation within the shell application and its deployment alongside any changes, while the former grants greater autonomy.
Lightweight Shell Application
In our architecture, the shell application serves as the container for MFEs. It includes a lightweight router for page navigation and loading MFEs dynamically when needed. Lit-Element can be used to build the shell application, providing a consistent and modular UI.
Additionally, it contains a settingsService that enables the dynamic retrieval of deployed MFE versions, eliminating the necessity for frequent updates to the shell application whenever an MFE undergoes changes.
Communication
Using native events for communication between MFEs promotes loose coupling and independence. This approach allows each MFE to function autonomously and reduces the risk of compatibility issues.
Custom Element Versioning
For custom Elements used across multiple applications, it may be worth considering versioning. If multiple Micro Frontends use different versions of the same custom element, the first to register takes precedence. This can lead to broken code if multiple versions of the same custom element are used.
More details can be found in this article.
Testing Strategy
For testing, we recommend using Jest for testing the shell application and its services. For individual MFEs, Open-WC is a great choice. This testing strategy ensures that both the shell and MFEs are thoroughly tested.
Differential Loading
To support a wide range of browsers, MFEs can be built with differential loading, generating both .js and .legacy.js files for newer and older browsers, respectively.
In Part B of this article, we will demonstrate code examples of building MFEs using Lit-Element and implementing the discussed best practices.