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.
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.
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/
Related
In a bigger project, I have a fairly high amount of used third-party libraries, including firebase, redux, etc and some more specific ones (but only used on several pages) like konvaJS, jimp, ....
I just migrated recently to nextJS to speed up the website & maybe allow SSR. However, after migrating, the Lighthouse Speedtest dropped compared to the Pure React Version. The main problem seems to be the shared JS first Load bundles.
After some optimizing, including lazy loading bigger Components with dynamic() & modules with await import(), I managed to compress the shared first load JS bundles by half, but they are still around 400KB which is way too heavy. I guess heavy modules like firebase are included there as well, because it is needed basically everywhere in the App.
I also tried to analyze the dependencies with #next/bundle-analyzer. But the Visualization is not really easy to interpret. Is it true that it also lists modules that are lazy loaded? And in addition, I have some dependency packed multiple times in different bundles. Last but not least, the bundles visualized by the analyzer do not match the names of the build output.
Any help to reduce or understand the process better is well appreciated. I am using current React & Next.js versions.
Edit: This is the composition of the shared JS bundles after build:
Edit2: I am still confused about the output of bundle-analyzer. The module jspdf for instance, is only used in one page / component and lazy loaded. Is it correct that it is still visible in the analyzer? It does not have any impact on the shared JS bundle size.
Edit3: I managed to lazy load my firebase modules (which are crucial for my App), so I saved over 200KB shared JS size. Still hoping to cut down the remaining. Btw, the bundle analyzers output did not really change.
Are you using FontAwesome?
We were able to reduce our "First Load JS shared by all" from
504kb down to 276kb
by removing FontAwesome dependency and downloading the individual .svg icons
directly.
Before
After
When, we use create-react-app while creating a new react application. It creates multiple files. Out of which index.html is the rendered html application, where multiple jsx elements are placed depending upon the react application App.jsx
I was curious on what is the best way to import google fonts, bootstrap, jquery and other different external plugins?
As I have researched there are two ways of importing external modules. For eg. if we consider bootstrap that is to be imported from cdn: https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js
Then, one way to import is place it in public/index.html:
# Rest of the index.html code
<div id="root"></div>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
# Rest of the index.html code
Other way is to install bootstrap using npm i bootstrap#4.3.1 --save, and then put it as import in src/index.js .
The above thing can be applied to multiple places. Say, we have to import popper.min.js or jquery.js or import css fonts. Which is better? Placing it in index.html or inside index.js?
Also, what is the major difference between placing it at the two different places?
Both approaches will have the similar effect of making that library available to your application.
The HTML approach causes your dependencies to be fetched and executed at run-time by the browser. Much like you'd include imgs on a page, you can include scripts on a page with the source of the script either inline or from another URI. So, let's say you create an HTML page, put a number of script tags around it, and then hit that page in your browser. Your browser will scan the HTML page, identify all script tags and start downloading them. It will also remember in which order the script tags were found, and it will parse and execute them in that order (unless you use async and defer attributes on them).
Ok, so, browser sees HTML page consisting of various elements, including some script tags, download them (probably in parallel), and execute them (sequentially). Now, these scripts may or may not know anything about each other on the page.
Now, let's go to the realm of React and other rich Javascript apps, which depend on various Javascript libraries.
When you do Javascript Module Bundling i.e. import { something } from "package", this will get picked up at compile time by your Javascript compiler, e.g. React via create-react-app when you do npm run build, or Angular's equivalent script, or other compilers such as webpack, etc. These compilers will scan not just a single file, but rather your entire application. They typically start from an entry point, e.g. index.jsx, and then discover the graph of dependencies and recursively go through each file or module that they identify and discover those dependencies and so on and so forth. Once the compiler is done discovering, resolving, building, and bundling your app, you'll typically end up with a single Javascript file (e.g. main.[some hash].js) which is your application AND all dependencies (all those other modules you imported) bundled together in a single file.
So, you see the big distinction is:
HTML scripts are independent resources that are downloaded and processed by the browser at run time.
Javascript modules are discovered at compile time and end up being bundled together with your application code in a single file.
I'm leaving aside concepts such as package splitting and dynamic references, etc. in order to illustrate the larger distinction; you'll end up reading about variations in each approach as you dig further into them.
HTML Script advantages:
They are fetched independently, perhaps from a CDN, may even already be cached in your browser if another website needed them and downloaded them previously. So things like jQuery, loDash, etc. are common packages that may have already been downloaded, and your browser will benefit from its internal cache.
They can be downloaded in parallel; although you can rely on the browser to ensure that they get executed sequentially. So for example if you had your own script that relied on jQuery having already been loaded, all you need to do is to <script src="jquery.min.js" /> first and then <script src="myscript.js"> and that sequence will be respected.
Javascript Module Advantages
Your code may not need the entire jQuery, lodash, or whatever other library that you're referencing. By importing whatever function of your dependencies that you need in your source code, a smart compiler might be able to artfully scalpel only those functions out of the larger library (tree-shake the library), and you'll end up with a smaller overall download payload.
Your entire bundle can be minified, yielding an optimized package.
Your entire bundle will be (/can be) in a single file. One download, and your entire app is loaded and ready to use. No need to worry about downloading various resources from various URLs.
Hybrid Approach is OK
Feel free to use a hybrid solution! If you inspect your compiled React code, you'll see that the create-react-app compiler will inject a <script src="static/js/main.js" /> element at the end of your HTML document. This means that your app can rely on other`s included higher up in your HTML document. So, feel free to load up some libraries in HTML and reference other ones through JS modules. In fact, there are cases that you'd want to do this; for example, including the Google Maps script can be easily done via an HTML script directive, and your React app can still use the GMaps library.
Let's say I have a website with multiple tabs, built with react.js, and each tab contains a large amount of data. Normally webpack bundles react apps into a single bundle, but this would be a waste of resources if you never visit one of the tabs. Is it possible to break it into separate bundles without having to re-bundle react core & react DOM, and then call these extra static modules upon request?
I'm open to different suggestions - webpack, systemjs etc.
First, check your architecture and make sure you actually need this. Considering the typical scenario in which only the "infrastructure" is part of the bundle and all data is downloaded via Ajax as they are needed, it is kind of unlikely (not impossible) that you will end up with a particularly large bundle when you properly take care of optimization.
Back to your question...
Bear in mind that this might not be a walk in the park. From my experience, when you're trying to do something a little off the dotted line, problems start to pop up down the road. You might have problems with server-rendering, redux or whatever.
Anyway, you got 2 options:
1) Using a multi-page app configuration
Webpack will output multiple "chunks" with you configure it to have multiple entry points like this:
module.exports = {
entry: {
p1: "./page1",
p2: "./page2",
p3: "./page3"
},
output: {
filename: "[name].entry.chunk.js"
}
}
You can even have "common chunks". Take a look here.
2) Using code splitting
There's a Webpack loader called bundle-loader that offers a lazy option that allows modules to be actually downloaded as they are needed. I came across this loader when I was reading the React-Router 4 documentation. They even provide an example which doesn't require the router at all, check it here.
I am going to import styles with CSS Module and make it work with server-side rendering. I tried the following methods but each one has its own caveat. What is the best possible way to require('.style.scss') if any side effects?
Using the css-modules-require-hook:
Advantage: Easy to configure. You just need to call the hook at the beginning of server code. You don't need to modify components.
Caveat: It modifies the require.extensions global object, which is deprecated.
Using the isomorphic-style-loader:
Advantage: No more hooks to require.extensions.
Caveat: Wrapping components with HOCs that uses the React Context, which is an experimental API and likely to break in future releases of React.
Using the webpack-isomorphic-tools:
Advantage: No dependency on require.extensions or Context (AFAIK).
Caveat: Wrapping server inside webpack-isomorphic-tools instance. And can we please get rid of webpack-assets.json?
Bundling server with Webpack:
Advantage: No more hooks or injections.
Caveat: In development, it is very cumbersome to bundle everything whenever the code changes and even makes it harder to debug in a large bundled .js file. Not sure - you may need to pass a bundled .js to test runner.
Disclaimer:
The advantages and caveats below are just my two cents, and actually I love all the libraries, plugins and approaches they took to solve the problem and really appreciate their efforts.
I am not a native English speaker, please correct me if I misrepresent myself.
In the end, I decided to hook the require.extensions in development. Probably it is not the best way, showing warning messages on console, like checksum mismatch, but in development mode, I can ignore it.
Background: My company's web-app is in fact several completely separated apps that have minimal effect on one another. That's why it doesn't make any sense to keep them under a single project - so I'm using separate projects. However, since I started migrating and writing new apps in ReactJS (using Webpack for bundling) I find many opportunities for reuse, in order to avoid code duplication and to avoid downloading common resources again after another app already fetched them:
I'm using specific versions of 3rd party npm modules - so no reason to bundle them over and over again - and no reason to upload them to the server more than once (for all the projects).
How do you share package.json fixed versions across projects?
Do you bundle all/groups of npm modules together or each separately? I want to get everything from the server for cases of on-premise deployment.
What measures to you take to make sure you don't forget to re-bundle the node modules in case you decided to update one of them?
Similar question goes for font files (keeping the font on the server and another font for icons)
common style sheets
common JS code - like utils & common
common web.config configurations
common images (currently some of them are inline in different bundles)
and last - common components - how do you share them? Do you use a separate project and publish to npm? symlink?
Or maybe, after all, I should go with a monorepo? And how?
You could create a separate project, my_modules, which is just a manifest of the shared, common packages. You'd have a package.json with all the common modules, and a single index.js which does something like:
import React, { Component, PropTypes } from 'react';
import moment from 'moment';
export { React, Component, PropTypes, moment };
Then, you'd publish this new my_modules project to github, and install that in your other projects.
import { React, Component, PropTypes } from 'my-modules';
Every time you change a package, you'd increment the version, and then update that version in your projects (which is still a bit of a pain, but you could use something like greenkeeper.io to ensure they don't get stale).
Similarly, you can do the same thing for just about everything else you've mentioned, although you don't want to go overboard with this; you should only share standard assets that doesn't change very often, and keep project-specific assets in their own repo.
EDIT: wanted to add, for React components, this is actually a really good practice, because it forces you to write generalized, independent components. You can use something like React Storybook to create these components in isolation. You can publish one master package with all the packages, individual components as packages, or somewhere in between (eg. all Form components in one package). Either way, though, they can still share a repo for convenience.