craigmiller160
craigmiller160

Reputation: 6283

Micro-Frontends, Web Components, and Sharing Libraries

so I'm working on migrating my company's app to a micro-frontend approach. We are following the standard described in https://micro-frontends.org/. While under the hood everything is currently using React, we are wrapping things with Web Components so that we will have the freedom and flexibility to be framework-agnostic in the future. We've got a working architecture up and running and so far it is working beautifully. We even created a fancy compatibility layer on top of the Web Component spec which allows us to pass React-like props to the Web Components, including Objects, Arrays, and even Functions. This allows for much better interaction between them.

The main concern we have right now is duplication of libraries. We're a React shop, so even though we have this framework agnostic approach, everything is using React. While this new approach gives us the ability to individually upgrade pieces of our app to a newer React version (finally), we still don't like the idea of so much duplication of the React library.

To put it in perspective, even Gzipped, React/ReactDOM are over 40kb. That's super tiny individually, but scaled up it starts to take up more and more bandwidth. RAM-wise it's less of an issue, about 130kb for those libraries, and given the RAM capacity of most devices now it's not a huge deal.

But, of course, we want things to be as optimized and streamlined as possible. So I'm hoping someone can suggest a way for the micro-frontend apps (the ones wrapped in a Web Component) can get React and other libraries from the parent app.

You should know that the parent app JavaScript is loaded prior to the micro-frontends. Each micro-frontend is loaded via a <script> tag. Lastly, we are NOT using the Shadow DOM at the moment, a tradeoff we made to benefit how we are migrating our existing code into the new micro-frontend architecture.

Upvotes: 3

Views: 6637

Answers (2)

Michał M
Michał M

Reputation: 41

Possible solution is to prepare library using Import Map but as it does not support IE11+ I recommend you using SystemJS?

https://github.com/systemjs/systemjs

especially this one seems to be close to your case:

https://github.com/systemjs/systemjs-examples/tree/master/loading-code/react-hello-world

At html you do:

  <script type="systemjs-importmap">
    {
      "imports": {
        "react": "https://cdn.jsdelivr.net/npm/react/umd/react.production.min.js",
        "react-dom": "https://cdn.jsdelivr.net/npm/react-dom/umd/react-dom.production.min.js"
      }
    }
  </script>
  <script src="https://cdn.jsdelivr.net/npm/systemjs/dist/system.min.js"></script>

Then you could import ReactJS, as browser knows where to take it from.

Potentially there is a possibility to build some kind of library that looks into your micro front end's (MFE's) package.json, to get know which library needs to be in use and dynamically create imports object similar to example above.

We need to keep in mind that there is a need to cover versioning check. Mentioned library could potentially store some kind of map which connects dependency with version with place where it's accessible. Imagine that in case mentioned we need to deal with different react version on each MFE :).

Then while loading another MFE library could check if required dependencies has been already included, if some missing, download it, otherwise use what has been already fetched.

Another thing is use webpack 5, with module federation, which I not recommended yet, as it is not stable enough for production, but there is a possibility to start playing with it. Hard part will be covering version check, so most probably need another abstraction layer to deal with it.

https://indepth.dev/webpack-5-module-federation-a-game-changer-in-javascript-architecture/

Upvotes: 1

Harshal Patil
Harshal Patil

Reputation: 21030

The core idea is to tell module bundler on how to package your micro-frontends.

Assuming you are using Webpack to bundle your applications, here are the two things that you need to do.

Step 1:

Declare React as an External dependency like in your Webpack config:

externals: {
  'react': 'React',
  'react-dom': 'ReactDOM'
},

Step 2:

Before you load your parent application's JS, ensure that you are loading React and ReactDOM from CDN or other equivalent place:

<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>

Put these script in your main index.html which is responsible for bootstrapping your entire SPA.

Explanation

When you declare certain package/library as external, Webpack does not include it as part of the bundle. It assumes that the outer environment will make that particular version available as a global variable. In case of React, it uses React and ReactDOM as global variables.

By doing this and including it via CDN, you will be left with exactly one copy of React and ReactDOM. When a user visits the application for the First time, it will be slower but once cached, it should not be a problem

Further, you can extend this idea and also declare them as external for your parent app or parent shell container.

Upvotes: 2

Related Questions