A new plug-in is out for Vite and Rollup applications that allows them
to support Module Federation. Which is super cool
because it allows them to share code dynamically
between two deployed applications. So what are we talking about here? Well, there's two ways to share code
between applications, and mostly I'm talking about
maybe shared stories or shared components. Imagine that you have two applications
like the home page and the product detail page, and they want to be able to share a header
between those two applications. Now, with build time sharing,
which is what we're kind of all familiar with, you have a library
where you've got the header in it and then both of those applications
import that library and in order to update that header, then you would need
to deploy both of those applications. That's build time sharing. Now, runtime sharing
allows for a different model. The home page, for example,
can have a Header in it. It could then expose that header
via Module Federation and then the product detail page
can now consume that header dynamically at runtime
and that means that the home page, when it publishes
and puts out a new version of the Header, that product detail page will get it
dynamically without any redeployment. And this is a really big architectural win in the kind of Fortune 500 space. You've got those big companies
that have multiple teams managing multiple applications, and you want to be able to have
a consistent user experience, but you want to be able
to have shared components. So in this case, when you want the header
to be updated, it's really great if all of those teams can get the header
simultaneously and runtime sharing allows for that. So what I'm gonna do this video is upgrade
your skills to the architecture level by showing you how to do Module Federation
between two Vite applications. Then I’m gonna show you how to do shared stores
between two applications. Pretty cool stuff. Then we're going to bring in Webpack
and see if it's compatible because Webpack is where all this module
federation started and you want to make sure that your older applications
that have Model Federation are now compatible
with your new vite or rollup applications that have module federation. And finally, I'll show you how to do this
and a monorepo so that you can get a sense
of the advantages and disadvantages of a build timesharing system
versus a runtime sharing system. This is very cool stuff
and I can't wait to bring it to you. Let's get right into it. So let's start off with our plug-in
federation. This is a plug in for Vite. It uses the classic micro front end image here to show you the advantages
of a micro frontend system. And then it talks about how to install it
and how to configure it. So really good documentation here. It also has some really great examples. If you scroll down here
into the example projects that show you kind of all of the different
configurations and combinations of using different frameworks, using
framework, different frameworks together. I mean, potentially you can use
using this system, you can bring in dynamically a Vue component
into a React component or vice versa. It's really cool stuff. So what we're going to do in
this one is mainly show off, React, just React talking
to other React applications, but you got a whole bunch of different
examples that you can play with here. All right. I'm going to go into the terminal
and set up my project. I'll create a project called Vite Mod Fed and then into there, and then I'll bring up VS code. So to start off with,
we need two applications because we want to share code
between those two applications. One of those applications
can be named remote because the remote app
is the one that shares the component out. And we're going to have
another application called Host, and that's going to be
the one that consumes that component. Now, an interesting little side note here
is that in module federation, an application can be both a host
and a remote. We're not going to do that here, but
that is one architectural option for you. All right. Let's start off
with our remote application. So I'm going to use pnpm create vite. Name it remote and use the React template. All right, now in remote. I'm going to go
install the plug in for Federation. I'll do that. And development mode. Now, let's
go take a look at what we've got here. Got a basic app, src/app, all that good stuff. We've got a package.json. Really nothing exciting in here. It's got all the regular React stuff,
but it does have this new plugin federation
that new just added. Awesome. Now, one of the important aspects of module federation
is you're going to be using a URL to point to where the code is
in the remote application and you want that
URL to be fixed effectively. So in this case,
every time we launch this application, we're going to get a different port
number, maybe 5176 or 5177 or 5178. That's not great. What we really want to do
is fix the port number down so that when the host application
references that remote application, it has a consistent port number. So we're going to go make some changes to the package
scripts here to allow for that. So all we're doing here is we're saying
that the port number is 5001, and if you don't get 5001,
then then don't do anything at all. So there you go. That's what strict port does. So now we need a component to share with
our host application, once we create that. Now I'm just going to use a custom button
here because this is more of a tech demo. I just want to show
how these connections are made. I'm not trying to actually show off
like a real world scenario. That's up to you. Okay, so let's go create our button now into this button that jsx while
I'm going to paste in an implementation, all of this code is available to you
for free on GitHub and a link in the description
right down below. So you don't really have to type
it in as we go. And all this button is doing is it
just has a little state on it is a counter goes up by one. Nothing too exciting. It's important that it does
have a hook in it though, because you can get into a scenario
with module federation where it looks like it's working,
but when you bring in a hook, it blows up. So it's important to have in your test
some local state. Any kind of hook will do. Just to make sure that the React
is configured properly. What's going to happen
if it isn't configured properly, is that you'll get a error in the console about how
there are multiple react instances loaded. And that's because
both of the applications are competing for which react instances
is loaded at runtime. All right. We save that out. Okay. And just to check out what we've got,
let's go over to app that app.jsx and bring in our button. Now, clean this up a little bit. Oh, instantiate our button. But for convenience sake,
I do want to be able to identify which application
we're looking at in the browser. So I'm going to put in remote application
up here and let's give it a try. So we got the counter that is part of the application itself
that's implemented here inside of App. So that's the local app counter. And then this is the button counter says Click me
and then it's got that 12 in it. Now what we want to do is we want to create a host application
and share this button component with it. So let's go build that. So I’m going to create another terminal
and then into there I'm going to do the same pnpm create,
but this time I'm going to call it host. Then I go into host and again
I'll go and add our plugin in. So you need this plugin on both sides
because of remote needs to use that plug in to expose that button,
which we haven't done yet. And then the host application needs the plugin to go
and then reference that remote. So it's going to add it here. So the only thing I'm going to do in host
at the moment is I'm going to go and make some changes to the app.jsx,
just to kind of trim it down the way that we did before. Okay, Looks pretty good. Now I'm going to go run that. Now, let's notice that host in this case is on port 5176, not a problem. It doesn't matter where host is. All that matters is where that remote is
because remote is going to have the manifest
of all of the components that are sharing. And it has to be at a specific URL. All right. Let's see how this looks cool. We've got our host application sitting
side by side with our remote application. So now we want to go and share the button
out of the remote application. So let's go
configure the remote for sharing. All right. First thing I need to do
is bring in our plug-in, the federation plug in, and then we need to configure
that federation plug in. So let's start at the top here. We've got our name in this case,
that's remote app. You can name whatever you want. Then we have the file
name for the manifest of all of the modules
that are exposed by this application. And the name of that file
is going to be remoteEntry. That's pretty standard
in the Module Federation world. And then we're going to say,
what are we exposing? And in this case,
we're going to be exposing button and we need to give it the path
to that file. So in this case, src/button, then we need to specify
which libraries going to share. And now you need to expose any libraries
that your modules depend on. In this case, button depends on react
and react-dom. Therefore,
we're sharing, react and react-dom. And then finally, I'm gonna make some
small tweaks to the build configuration. So now we need to go over
into our remote terminal and this is where things start to diverge
a little bit from a Webpack style demo. To get this to work with. Vite, you actually need to build
the application and then serve it. So let's do that. So we do yarn build and then have yarn build a successful
we do a yarn serve, looks like yarn build is successful
and looks like yarn serve is successful. Now one last trick that we should do is take a look
to see if we have a remote entry. So go and create a new tab and I’m going
to look for where that is supposed to go. And that's supposed to go
in assets/remoteEntry.js. Now remoteEntry is a manifest. It tells the consuming application
in this case. Host Here's what I've got
and in this case has got the libraries react and react-dom as shared libraries
and then it's also got our button that has our button implementation in it. So if you don't see this file, then
nothing is going to work at this point. And if you just run pnpm dev,
it won't generate this file. So that's why it's important
to do that build, and then that serve because this build
that creates the remoteEntry file. And I also want to emphasize
the location of this file. This is going into the assets directory
and that's because this remoteEntry file and all of the bundles that are
references are just JavaScript files and they should be treated
and deployed like assets. One of the questions
I get most often about module federation is what happens to the sharing
between two applications if the application
that's sharing that component that I rely on goes down. Well, nothing, because what you should do is you should go
and deploy your assets, in this case, all your CSS, your JavaScript,
your images, all of it should be deployed to a static asset store,
and that includes all the these modules. So yes, your application could go down,
but that's just the server side of your application. The static assets should never go down
because those should always be deployed on something like S3,
which is never going to go down. And if S3 does go down, well, it's a holiday for everybody,
so don't worry about that. All right.
Sorry about the little excursion there. I just get asked that question
so often that I kind of want to address it right here. Okay, let's go and configure host
so we can consume this remoteEntry. Then I'll go into host Vite config,
I'll bring in that federation plugin and then I'll define federation. So in this case,
this is going to be our app and we are going to have a remote
and that remote is remoteApp and then we give it
that remoteEntry as well. Now this remoteApp key
is what you use in your import. So when you want to import button
you say remoteApp slash button. You can use any name for that you want,
but it's often good to keep it with what it is. So in this case, it's remote app. And then of course any libraries
that we want to share with our remote. So in this case
we want to share, react and react-dom to make sure that everybody's using the same react and react-dom
And we don't end up downloading multiple copies
because that would just be bad for both performance
and also for the stability of the app. So this allows for module federation
to arbitrate between the host and the remote and say,
Hey, I've got React. Great, you've got React great. Cool. There it is. Okay, cool. And then finally, some build tweaks and looks
like we're good on the configuration side. Let's go now
and try and import that button. So important, we're going to use
a standard import statement. We're going to say that we have a button on a remote app slash button
and then we're just going to use it. Okay, let's try this again. So I'm going to hit Ctrl-C. And now in host, I'm going to build,
but I'm going to do preview instead of serve,
because if we look at the package.json, we can see that
we have just the build and preview. I haven't actually
made any changes to this because we didn't
need to go make that port strict. So let's go and do a build
and then a preview. All right,
that's putting me now on port 4173. Let's go take a look. Here we go. Port 4173. We have our host application
and we have exactly the same component
share between those two things. And this is actually really important
to understand as well. Notice that I'm over here on 5001. My Click Me is now at seven. Let's say my account is at four,
or it's entirely different from the host application where Click Me is now at 17
and count is it eight. This is another common question
I get about module federation, which is why am I seeing different state
between two deployed applications when you're using the same component
between those applications? And the answer is because
it's sharing the code and not the state you wanted to share the state,
you'd have to use some kind of state sharing approach like, say,
Firebase or something like that. There's no magic here. All we're doing is just sharing code. So let's actually see the code change
between these two applications dynamically. So what we want to do
is we want to be able to redeploy remote and see the changes in the host
without redeploying the host application. So let's see if that actually works. So to do that, I'm going to change the CSS on button,
so I'll go back over to our remote and into src. I'll create a new file
called Button dot CSS us and I'll put it in our CSS. Now we've got a shared button class. Just so happens
that the class name here is Shared Button. Who would have thunk it? So now I'm going to bring in our CSS. So we just defined and then rebuild and redeploy just the remote. All right, let's take a look. So if I refresh 5001, then we get this kind of dark
black click me looks pretty good. And we should see if I refresh
the host application over here. We should see that same dark black button. And we do. Cool. How cool is that? I did not go and redeploy 4173. Right. It automatically got that change between
those two applications because host application is looking dynamically
at the code from remote application. Not state
just code, but still wicked cool. So what module federation is
most often used for is micro front end or micro-fes. And one of the things you want to do with a micro front end is share
a state between the host application and the micro frontend, for example,
the user ID. Now the way that
I strongly recommend doing that is with an atomic state manager,
either Recoil or Jotai. Both of those work well. I prefer Jotai,
so we're going to use Jotai. So what are we going to do? We're going to have a shared state
store here that's going to hold the value for click me
and then we're going to attach to that in the application itself
so that when I click here, count up to eight
and there'll be shared between those two. It's pretty rudimentary,
but it will show sharing between the host application, in this case
remote and its own button. And then over here
we'll do the same thing in host. So we'll see how to share state,
and subscribe to shared state. So first we need to create some
shared state and we need to install Jotai. Let's go do that. Then over in remote I'm going to go and add Jotai and then I'm going to create our store. I'll call it store.js. and I'll paste in our implementation. We'll take a look at it. So we are going to define
an atom called count, and then we will have a custom hook called
use count. We'll return a use atom that's going
to give us back a value for it as well as a setter in a tuple
pretty much like useState. So that will be the default output of this store, will be this useCount
and that we can use for our count. All right, let's go bring this in a button and then all we need to do is just replace
that useState with useCount and that all about do it now. Let's go over here to our app
and also bring that in and replace this useState
with that useCount. And so theoretically,
when I click on either of those buttons, because the state is shared between
those two, it will update in both places. So let's go to try now, of course, to see any changes. I want to do that, build and serve and if I click, yeah, there it is, I click in either place
now that button goes up. But here
I don't get that same interaction. It doesn't fail, which is good,
but I don't see that same interaction. So what do I need to do? I need to share that store
between the remote application and the host application. So let's go do that. So we'll go into my Vite config again. Now I'm going to go and expose that store by just changing button to store here and now I need specify
that we want to share Jotai because Jotai is essential
to the implementation of store. All right, let's build in the play again. Now let's go over in the host
and try and use that store. Same kind of deal here. We're going to bring in useStore from the store and use it. Let's see. All right. Again,
I want to do that, build and preview. Okay. Looks happy. Let's go back over here or hit refresh. Perfect! So imagine what you can do with this. You can have your user ID,
you can have your cart, you can have all this
all on shared pieces of atomic state. Now, why do I recommend using something
like Jotai shared atoms for sharing state between a host
application and its micro frontends as opposed to something is, say,
more conventional like a Redux or Zustand? Well, what are we trying to accomplish
with shared state? We're trying to accomplish a coupling between the host application
and the microphone ends. The more state that you share between those two things,
the tighter that coupling becomes. If you have something just like the user
ID and then the contents of the cart, that's a pretty small surface
area to exchange between those two. The great thing about atomic state management like this
is it to extend the surface area that you want to share between those two applications,
you need to go and add new atoms. Whereas with Redux or Zustand,
you can just extend the size of the store. So there's no friction to adding new data
that's sharing between the application and the micro-frontend. And that's not great because the more data that you share,
the tighter the coupling becomes. Now, Module Federation came out with Webpack five,
I guess almost two years ago now. So are Webpack
five applications compatible with the module federation
being done here with Rollup and Vite? Let's see! To test that, I'm going to go
create a new Webpack five application that imports this same button. We'll see how it works. Let's create yet another terminal and into there I'm going to do my own npx create-mf-app,
MF meaning Module Federation, and we'll call this wp-host
for Webpack host We'll say that as an application,
we'll put it on 8080 and we'll use React and we use JavaScript. Keep the standard CSS and we'll go into that directory. Now let's go take a look at we got we've got a source directory
that has an app.jsx file. Cool. And we've got our package JSON. Now we do need to make a few upgrades here
because this one has a react of 17.0 and the applications have 18.2. Definitely want to upgrade that. And the Webpack CLI also needs an upgrade
to version 4.10. Now over in our app.jsx
file, we're using the old school reactDom renderer, so it's going upgrade
that to the create root as well as get the react
dom now from react dom client. Cool and by default
this is a light mode application and since everybody is telling me
don't do light mode, we'll go make this a dark mode application by just
bringing in the CSS from the Vite side. Okay. I think we're good. Let's give it a try. So now I'll do my pnpm install and I'll start and here we go. Localhost 8080. Well that's beautiful. Okay, so now let's go
see if we can bring in that button. So over and over and config we are going
to go and specify that we have our remote. This is in the model generation plug in. We're going to say that we have a remote. It's going to be the same exact remote
as we had before. Localhost 5001. Cool. And let's go see
if we can bring it into our app.jsx, bring in our button and let's just do a console.log now
to see what we actually have. So I'm going to go and do a console.log
and then I'll restart and that's not good. So what I think is happening here
is that Vite is working on Ecmascript modules,
so we need to specify a different way of building out our application
here in Webpack using Ecmascript modules. So let's bring up the Webpack config and we'll specify here that we are using a module. Let's see if that helps. Oh, okay. So now we're getting library
type modules only allowed when we have experiment output
module enabled. Okay, let's go do that. So let's say that
we want to target EcmaScript 2020 and then also specify our experiment
module that it complained about. Let's give it a try. Okay, so these didn't blow up,
but it's still not working. So to complete the migration of modules, we need to have an index
that has a script of type module in it. So let's go and create a new index. Now we're going to bring in all of our plug in files via the type module. Cool. So now let's go back over to our Webpack
config. We'll change this to the index.ejs and then we'll say that we don't want to inject anything and let's give it a try. Okay? It looks like it's not blowing up. That's a good sign.
Let's bring up our console. And now that we see that, Oh,
we got a button. Awesome. We have a module button there. Cool. So now let's go and try that out. Let's go over to our app.jsx. So this is a little different. We're going to bring in a button module and then from that we are going to dereference button by saying that we want default
from button. So that should give us button. Let's see how we do. All right. It looks like this time we actually have our component function,
so let's give it a try. So we’ll go back down in our app, instantiate button, and there we go. Now we have our button all the way
from our meet roll up applications all the way into our Webpack
five application. So they are compatible
between the Module Federation from Vite and Rollup and Webpack. Now the last thing I want to do
is show you a monorepo version of this. I'm going to bring the monorepo code
into our project so when they get have repo
linked in the description right down below you'll see a new folder called
monorepo-version. This is a turborepo monorepo that has two applications in it, app
one and app two. I didn't name them host and remote
because those concepts do not make sense in this case.
You just have two applications. You're not actually doing that
kind of runtime sharing. There's no remote, there is no host. They're both sharing the components
that are in UI. So in this case button,
the same button that we had before. They're also sharing
a shared store project. Now each one of these is a conventional
NPM project. In the case of this store package,
the name is Store, and then over here in app
one in the package JSON, we reference store by saying that
we want the store out of the workspace. That's how a monorepo works. We also get the UI from the workspace and then down here in the tsx file
we bring in use count from our store, we bring in button from our UI,
and it's as simple as that. It is the buildtime sharing that we've
all known and loved for a long time. The reason that I'm including it in
this project is so that you can have a look
at the differences between buildtime sharing and runtime sharing and think about the pros
and the cons of each for buildtime sharing the pros are that TypeScript works really
easily, unit testing works really easily, and when you build and deploy,
the application is 100% coherent. It is a single package. There are no external runtime
dependencies. The big con is that you don't get that runtime sharing
that you do on the runtime side. On the runtime sharing side,
the pro is that you have runtime sharing. You deploy one application once
and then anything that relies on remote from that application
get updated seamlessly. The cons of runtime
sharing are pretty significant. TypeScript sharing becomes
more problematic because when you go from TypeScript to JavaScript,
you lose the typing. Unit testing can be a problem
because those modules aren’t available at the time of the unit test. And then there's the big one,
which is the runtime risk. Another application that gives you
a component that you rely on at runtime could make changes to that component
that break your application, and you wouldn't know about it
until you got a runtime issue as opposed to a build time issue
with build time sharing. Well I hope this video helps
you understand more about how module penetration works
and what it allows you to do now with Vite and Rollup applications as well,
which is super cool, but I hope it helps you understand
also the pros and the cons of each one of these architectures
that you can make a choice for your application,
which you're going to use. And in the meantime, of course, if you
like this video, hit that like button. And if you really like the video, hit
the subscribe button and click on that bell and be notified
the next time a new blue collar coder comes out.