Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletter Hub
Free Learning
Arrow right icon
timer SALE ENDS IN
0 Days
:
00 Hours
:
00 Minutes
:
00 Seconds
Arrow up icon
GO TO TOP
The Art of Micro Frontends

You're reading from   The Art of Micro Frontends Build highly scalable, distributed web applications with multiple teams

Arrow left icon
Product type Paperback
Published in Oct 2024
Publisher Packt
ISBN-13 9781835460351
Length 356 pages
Edition 2nd Edition
Arrow right icon
Author (1):
Arrow left icon
Florian Rappl Florian Rappl
Author Profile Icon Florian Rappl
Florian Rappl
Arrow right icon
View More author details
Toc

Table of Contents (27) Chapters Close

Preface 1. Part 1:The Hive – Introducing Frontend Modularization FREE CHAPTER
2. Chapter 1: Why Micro Frontends? 3. Chapter 2: Common Challenges and Pitfalls 4. Chapter 3: Deployment Scenarios 5. Chapter 4: Domain Decomposition 6. Part 2: Dry Honey – Implementing Micro Frontend Architectures
7. Chapter 5: Types of Micro Frontend Architectures 8. Chapter 6: The Web Approach 9. Chapter 7: Server-Side Composition 10. Chapter 8: Edge-Side Composition 11. Chapter 9: Client-Side Composition 12. Chapter 10: SPA Composition 13. Chapter 11: Siteless UIs 14. Part 3: Bee Brood – Implementation Details
15. Chapter 12: Sharing Dependencies with Module Federation 16. Chapter 13: Isolating CSS 17. Chapter 14: Securing the Application 18. Chapter 15: Decoupling Using a Discovery Service 19. Part 4: Busy Bees – Scaling Organizations
20. Chapter 16: Preparing Teams and Stakeholders 21. Chapter 17: Dependency Management, Governance, and Security 22. Chapter 18: Impact of Micro Frontends on UX and Screen Design 23. Chapter 19: Building a Great Developer Experience 24. Chapter 20: Case Studies 25. Index 26. Other Books You May Enjoy

Emerging web standards

There are a couple of problems that make developing micro frontends that run on the client problematic. Among other considerations, we need to be able to isolate code as much as possible to avoid conflicts. However, in contrast to backend microservices, we may also want to share resources such as a runtime framework; otherwise, exhausting the available resources on a user’s computer is possible.

While this sounds exotic at first, performance is one of the bigger challenges for micro frontends. On the backend, we can give every service a suitable amount of hardware. On the frontend, we need to live with the browser running code on a machine that the user chooses. This could be a larger desktop machine, but it could also be a Raspberry Pi or a smartphone.

One area where recent web standards help quite a bit is with style isolation. Without style isolation, the Document Object Model (DOM) would treat every style as global, resulting in accidental style leaks or overwrites due to conflicts.

Isolation via web components

Styles can be isolated using the technique of a shadow DOM, which is part of the web components specification. A shadow DOM enables us to write components that are projected into the parent DOM without being part of the parent DOM. Instead, only a container element (referred to as the host) is directly mounted. Not only do style rules from the parent not leak into the shadow DOM; but style definitions in general are not applied either.

Consequently, a shadow DOM requires style sheets from the parent to be loaded again – if we want to use these styles. This isolation thus only makes sense if we want to be completely autonomous regarding styling in the shadow DOM.

The same isolation does not work for the JavaScript parts. Through global variables, we are still able to access the exports of the parent’s scripts. Here, another drawback of the web components’ standard shines through. Usually, the way to transport shadow DOM definitions is by using custom elements. However, custom elements require a unique name that cannot be redefined.

A way around many of the JavaScript drawbacks is to fall back to the mentioned <iframe> element. An iframe comes with style and script isolation, but how can we communicate between the parent and the content living in the frame? Let’s find out.

Frame communication

Originally introduced in HTML5, the window.postMessage function has proven to be quite useful when it comes to micro frontends. This was already introduced quite early in reusable frontend pieces. Today, we can find reusable frontend pieces that rely on frame communication – for instance, in most chatbot services or cookie consent services.

While sending a message is one part of the story, the other part is to receive messages. For this, a message event was introduced on the window object. The difference between this event and many other events is that this one also gives us an origin property, which allows us to track the URL of the sender.

The URL of a particular frame to send a message to is also necessary when sending a message. Let’s see an example of such a process.

Here is the HTML code of the parent document:

<!doctype html>
<iframe src="iframe.html" id="iframe"></iframe>
<script>
setTimeout(() => {
iframe.contentWindow.postMessage('Hello!', '*');
}, 1000);
</script>

After a second, we’ll send a message containing the Hello! string to the document loaded from iframe.html. For the URL, we use the * wildcard string.

The iframe can be defined like so:

<!doctype html>
<script>
window.addEventListener('message', event => {
  const text = document.body.appendChild(
    document.createElement('div'));
  text.textContent = ` Received "${event.data}" from
    ${event.origin}`;
});
</script>

This will display the posted message in the frame.

Important note

The access and manipulation of frames are only forbidden when using a cross-origin. The definition of a cross-domain origin is one of the security fundamentals that the web is currently built upon. An origin consists of the protocol (for example, HTTPS), the domain, and the port. Different subdomains also correspond to different origins. Many HTTP requests can only be performed if the cross-origin rules are positively evaluated by the browser. Technically, this is referred to as Cross-Origin Resource Sharing (CORS). See the following MDN Web Docs page for more on this: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS.

While these communication patterns are quite fruitful, they do not allow you to share any resources. Remember that only strings can be transported via the postMessage function. While these strings may be complex objects serialized as JSON, they can never be true objects with nested cyclic references or functions inside.

An alternative is to give up on direct isolation and instead work on indirect isolation. A wonderful possibility is to leverage web workers to do this.

Web workers and proxies

A web worker represents an easy way to break out of the single-threaded model of the JavaScript engine, without all the usual hassle of multithreading.

As with iframes, the only method of communication between the main thread and the worker thread is to post messages. A key difference, however, is that the worker runs in another global context that is different from the current window object. While some APIs still exist, many parts are either different or not available at all.

One example is in the way of loading additional scripts. While standard code may just append another <script> element, the web worker needs to use the importScripts function. This one is synchronous and allows not only one URL to be specified but also multiple ones. The given URLs are loaded and evaluated in order.

So far so good, but how can we use the web worker if it comes with a different global context? Any frontend-related code that tries to do DOM manipulation will fail here. This is where proxies come to the rescue.

A proxy can be used to capture desired object accesses and function calls. This allows us to forward certain behaviors from the web worker to the host. The only drawback is that the postMessage interface is asynchronous in nature, which can be a challenge when synchronous APIs should be mimicked.

One of the simplest proxies is actually the I can handle everything proxy. This small amount of code, as shown in the following snippet, provides a solid basis for almost all stubs:

const generalProxy = new Proxy(() => generalProxy, {
  get(target, name) {
    if (name === Symbol.toPrimitive) {
      return () => ({}).toString();
    } else {
      return generalProxy();
    }
  },
});

The trick here is that this allows it to be used like generalProxy.foo.bar().qxz, without any problems and with no undefined access or invalid functions.

Using a proxy, we can mock necessary DOM APIs, which are then forwarded to the parent document. Of course, we would only define and forward API calls that could be safely used. Ultimately, the trick is to filter against a safe list of acceptable APIs.

When facing the problem of transporting object references such as callbacks from the web worker to the parent, we may fall back to wrapping these. A custom marker may be used to allow the web worker to be called to run the callback at a later point in time.

We will go into the details of such a solution later – for now, it’s enough to know that the web offers a fair share of security and performance measures that can be utilized to allow strong modularization.

For the final section, let’s close out the original question this chapter asks and answers, why micro frontends? with a look at the business reasons for choosing them.

lock icon The rest of the chapter is locked
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime