So what we do is we load React 18 a second time, and this time React 18.3.0, and basically we do a hot swap, which is the namesake of this solution, which means we load React 18 again, and we basically override the window.react18 object, and we're now using React 18.3.0, which should be compatible, if Sanver allows, with the previous version, which is 18.1.0. Now, the first problem that we ran into is a problem of sub-dependencies. So some dependencies in our list depend on other dependencies in our list, so for example, MobX React depends on both MobX and React, which both exist in our list, which creates a lot of varying combinations. So let's take two examples, for instance, let's say that one of our micro-frontends has MobX React 8 and uses React 16, and another one has MobX React 8, again the same version of MobX React, and uses React 18. In this case, both micro-frontends are going to use the same global version of MobX React, which is MobX React 8, but the problem is that MobX React under the hood needs a very specific version of React, needs React 18, in order to work properly in runtime and not get a lot of React errors and red screens of death. So what do we do about this? Our fix was very, very simple. We want to make sure that when we create our dependencies, we make sure that we create them with compound names, and that means that every dependency also includes in its global name all of its sub-dependencies. So let's say that we have MobX React, which depends on React. When we add the combination of MobX React 8 and React 16, we're going to do React 16 underscore MobX React 8, and when we have MobX React 8 and React 18, we're going to do React 18 underscore, and so on and so forth. This is a simplified example. We might end up with a situation where we have like three sub-dependencies of differing levels and we might end up with a very long compound name, but that basically ensures that the micro-frontends and their sub-dependencies get the correct reference in production. That also forces us to update the metadata file to reflect this change and include sub-dependencies.
So what do we do about this? Our fix was very, very simple. We want to make sure that when we create our dependencies, we make sure that we create them with compound names, and that means that every dependency also includes in its global name all of its sub-dependencies. So let's say that we have MobX React, which depends on React. When we add the combination of MobX React 8 and React 16, we're going to do React 16 underscore MobX React 8, and when we have MobX React 8 and React 18, we're going to do React 18 underscore, and so on and so forth. This is a simplified example. We might end up with a situation where we have like three sub-dependencies of differing levels and we might end up with a very long compound name, but that basically ensures that the micro-frontends and their sub-dependencies get the correct reference in production. That also forces us to update the metadata file to reflect this change and include sub-dependencies. So this kind of changes our solution a little bit. So instead of building with a simplified name, we use the same list with the same versions we get from Package.json to create the bundle with compound names per dependency. This is the only difference. All of the other steps in the build process and in the runtime process remain the same, and that was it for this issue, but we ran into a different issue.
What we thought would happen when we hot-swap dependencies in production is as follows. Let's say that we load our first micro-frontend that uses React 18.1.0 and it references window.react18, and now let's say that we load a second micro-frontend that uses React 18.3.0. As we mentioned before, React 18.1.0 is not compliant with 18.3.0, which is what our second micro-frontend needs. So we load React again and we replace the window.react18 pointer to point to our new React version, so we loaded React twice. What we expected would happen is that both of our micro-frontends are going to reference window.react18 in the runtime code, and everything would be great, but what actually ended up happening is that our first micro-frontend kept the memory reference to the first instance of React that we loaded on the page. So instead of using the same new instance as our second micro-frontend, it kept using the old instance, which meant that it wasn't garbage collected and we actually loaded React twice to the page, which it kind of misses the point of what we're trying to do here. So this led us to revise our whole solution and basically create a v2 version of our solution, which we called global dependencies. So global dependencies is very similar to the previous solution that we had in both the build process and the runtime process. The only difference being is that during the build process of our micro-frontends, we also update some sort of global metadata file that we have, which we kept on the server. And basically, this global metadata file held references to which micro-frontend in our system had the highest version per major of our dependencies. So in this simplified example, we can see that, for example, if you need to use React18 in your micro-frontend, and it doesn't matter which micro-frontend you are, we know that micro-frontend1 has the highest version of React18 possible on the system. So this might be a situation in which, for example, we have micro-frontend1 is using React18.7.0 and micro-frontend3, for example, uses React18.3.0. When micro-frontend3 is loaded on the page, it's going to use the React18 version that is provided by micro-frontend1 and exists in this URL. This way, we have a list with all of the, including the compound names and all of our dependencies per major version across our system.
Comments