Building Microfrontends Part IV — Using CDNs + Tech Radar for consensus
If you look at the Network tab of the homepage we built so far you will be able to see this:
This is because they all have React and ReactDOM inside, which weights a bit, and we want to remove this duplication.
Why not building apps together
There is a tool that is already very good at compiling stuff together, remove duplication and minifying everything, Webpack! Why not using that? We could publish the header to npm and install it on our homepage right?
Yeah, that would do wonders for deduplication, but I believe it would not be a good idea because:
- What if you want to publish a change to the header, an already have fifty pages using that? You would have to ask every page to upgrade it's version of header, meanwhile, your users would get inconsistent headers across the website
- You are now compiling another app as part of yours, what if it throws something unexpected, will your app break too?
- You are forced to have the same technologies on both sides, what if header the header uses clojurescript and your page uses elm? Poor webpack, it now has to understand it all when compiling.
That's why I believe you should share components, not apps.
Since we chose to keep our apps separated, we still need a way for them not to load the same libraries. Webpack has an option for that, it is called externals, you can read more about it on their docs.
The problem of extracting libraries out is the synchronization between the page and the apps, you don't want to load any more or any less libraries that your apps need, so let's just focus on the main, heavier ones, such as React for now.
On the homepage, before loading the apps, add a script tag on the HTML for loading React and ReactDOM from a CDN (don't forget to add the integrity checks!):
Now we have to tell the apps to use the global libraries instead of loading them. Unfortunately, since this will require us to edit our webpack config, you will have to eject from create-react-app:
npm run eject
TADA! You just uncovered all the magic create-react-app was doing. Now, just adding the
externals option to webpack would work, but there is a problem with
externals: if they are not available globally, you will have an error. We know that they will be available in prod because homepage includes them, but they aren't loaded when running the app locally and separated. And what if homepage forgets to load React, will our app break?
For solving that, I've just created a library called safe-externals-loader. This libraries makes webpack emit separate bundles for the libraries that should be available globally, if they are available, the global variable is used, if not, they load from the local bundle, so there is no danger!
After ejecting, install it on header:
npm install --save-dev safe-externals-loader
Then, add it to both
config/webpack.config.prod.js, before all the other loaders:
So this is telling every JS file to use those externals dependencies if they are available. You can test it works right now, run
npm start and look at the Network tab, you should see this:
The first bundle is your main one,
102KB unminified, it loads the other two,
158KB each, the first is React and the latter ReactDOM.
Now add the same script tags above to the HTML of this app (just for testing, we don't want to commit that), then look at the Network tab again:
See? It doesn't load the other bundles, because it is already loading React and ReactDOM from the CDN, neat!
Also, if you
npm run build:
You can see that your main bundle size reduced by A LOT, because those libs went to other chunks. There is a little overhead so it is not a good idea to just go chunking everything.
Now publish the new version of the header with
heroku container:push web and do the same with products-list and cart.
The source codes for everything:
If everything went right, the Network tab of our homepage will be like this:
400KB less from our bundles with just this simple optimization (it is still larger than what we saw on
npm run build because heroku is not configured to gzip things yet).
If you had any problems, here are the sources:
Okay, that is considering that all the apps use React, what if a team wants to user Angular, and another one Ember?
Well, we could, this architecture allows that, it is one of its goals really, but on front-end, things are different from the back-end, on the back-end you could have a service in Ruby that talks to another one in Rust, grabs some data from Clojure and Haskell and everything is fine (woot my favorite languages!), but on the front-end, it is your user that suffers when you have too much things being loaded.
Using a Tech Radar for Consensus
Have you heard of the Tech Radar from ThoughtWorks? It is a great way to organize technologies, including Techniques, Tools, Platforms and Languages & Frameworks.
Check it out: https://www.thoughtworks.com/radar
The great thing about the radar is that it is not Yes or No, neither use it or do not use it, instead, the blips may be nearer or further, like a real radar, depending on weather teams were successful using it or not. At the same time, it doesn't mean that a technology that usually doesn't work won't work for you, it might, and vice-versa.
I believe having this would be great for Microfrontends, while having 5 different framework at the same time is not good, maybe the teams can agree on the 2 they want to maintain, and keep reviewing those choices on the next Tech Radars.
I really recommend for you to read this blogpost on how to build your own technology radar and/or listen to the podcast at the end:
Apps Dependencies as a Service
Again, just like the URLs, we created coupling between the apps and the homepage (although reduced by using safe-externals-loader). We can apply the Dependency Inversion Principle here again and just extract this to a service. Making our architecture like this:
This service could provide an API where apps would register the dependencies they need, and the pages would ask "hey, I need to load apps x and y, what dependencies do I need"?
But then again, this is just an idea, don't implement this until necessary, for this blogpost for example, we won't.
What if we need two versions of the same library?
For example, what if header uses React 15 but the cart is still on React 0.14?
Sorry, I don't know, I don't have an answer to that, even when compiling things together, if you need two version you will still be loading a duplicated library, so the problem persists.
My best suggestion would be to ask the teams to upgrade their apps.
So far the apps we created are isolated from each others, like independent islands on the page sea. On Part V we will see how they can communicate with each other.