Epic Federation

Zack Jackson
Zack Jackson

Scaling frontend applications often leads to the frustrating reality of agonizingly slow build times, nightmarish dependency management, and significant bottlenecks when multiple teams try to release features simultaneously. The perceived safety of sticking with monolithic builds can quickly become a drag on productivity and innovation, leaving developers to endure painful coordination and slow rollouts.

There's a powerful alternative: a world where teams ship features independently, where shared components update instantly across all applications without the "dependency hell" of chained NPM installs, and where your app still operates as a single, cohesive unit for the user.

This talk unveils Module Federation, a robust solution forged in the fires of massive-scale development at companies like ByteDance. You'll discover its core concepts – how it enables you to decompose your frontend into independently buildable and deployable "remotes" that are consumed by a "host" application at runtime. It offers sophisticated version control for shared dependencies and crucial singleton support for libraries like React, ensuring stability and consistency.

Zack also explores Module Federation's evolution, clarifies why it's often superior to import maps for complex, dynamic scenarios, and culminates in a live demonstration of its integration with Epic Stacks. Witness firsthand how components and even entire login pages can be federated, transforming your approach to building and delivering large-scale applications, boosting engineering output, and accelerating feature delivery.

Share this talk

Transcript

So, yeah, thanks for having me here. It's good to see a lot of familiar faces floating around in the crowd. So first, who knows what module federation is? Okay. Perfect. So I actually put those first ten slides in for a reason. Okay. Great.

So there's a little bit of a interesting thing when, Kent called me to see if I wanted to come and do this, do this talk. He was like, hey. You know, one of my favorite tweets from Kent was, he he was talking about,

I don't know, somewhere in Twitter it come up where he said, oh, Kent's very non confrontational. And, and so somebody had messaged him and they were like, Okay. And started asking, like, confrontational type questions. You know, something like that. And so somebody asked, Well, Ken, what do you think of module federation? And I'm like, Okay. Yeah.

Let's hear it. And then Ken's reply which I absolutely love was just it solves a problem I hope I never have Very non confrontational, but anyway so I was really surprised when he when he, reached out and he's like hey do you want to come and, do a talk on Federation here?

And I'm like yeah that sounds great what do you want me to do it on? And he's like yeah well you know everybody's gonna know Epic Stacks it'd be great if it was with Epic Stacks and I'm like cool awesome I can do that I just need to write a Rust plug in and

you know actually show up with code that runs so then I embarked on a frantic mission to essentially wrap up our experimental ESM support in RSPack and get it to a point where Federation would run-in there then write an RS build plugin for it and so on and so forth we go.

So, we actually ended up releasing a special version of RS pack for this talk because I think 1.3 only comes out at the end of the week and I was like okay well I want people to be able to install it when they give the talk so we ended up shipping a version of RS pack specially for today to make this talk happen

and, today is also the also maybe twenty minutes ago I actually officially announced that the, React Router ARES build plug in is on NPM and available at version 0.02. So, anyway, let's get into it. Oh, why is this, okay. Whoops. Alright. The resolution looks a little cut off. Let me try just exiting and jumping back in.

There we go. Okay. So essentially, federation is, used by, you know, a whole bunch of big brands, big companies.

Originally it was kind of built to solve problems of scale this is from back in the webpack days where everybody who's had a JavaScript based build you probably experienced the slow build times that kind of begin to exist and this was a really big problem at any company that had a large code base lots of people working on it and usually most company organizations, you know, somebody owns certain parts of the site but when it comes to building it you're back to this monolithic structure.

And you're looking at maybe a forty minute build to get anything out the door. So this is primarily what Federation was built to solve as a way to distribute and share code between independent builds, at runtime. And that there's essentially two kind of things that we modeled it off of.

The first, it kind of follows the package exports convention. So you have, like, things you can export out, which we call expose so that'd be like a feature or something like that and then you would have, things that you would share which would be kind of like your dependencies list and package JSON

when you share code we needed to be able to control stuff like semantic versioning so simver a bunch of apps using different parts they might need different versions of things you need to be able to pin versions but in cases like React you also need to be able to state something as like a singleton so that two apps who use React don't try and pull in two different instances of

it so anyway, that's kind of like why we had built it so I'm an infrastructure architect over at ByteDance and they ended up actually being one of the largest users of federation that exist so some of the scale that we have here which is really great because federation's great for like bigger scale things or at least that's what I had built it for and ByteDance was probably the largest scale that I've I've seen, or heard of.

So some of the kind of infra tools that we do have already the list has grown now. I need to actually make an entire slide for it. But, you know, as of last year, this was the this was the graphic from then, where, you know, essentially we have, like, our compiler tools.

So RSPRESS, RSBuild, Speedy, which is what powers links. If you know you might not know links, but that's something new that was released, which is essentially like R react native.

We have rslib which is like for packages it kind of takes a lot of those things from what you would you know get from like a roll up or ES build where you want that unbundled NPM package that's really good at tree shaking bring in tools like that for diagnostics so on and so forth we have PIA which is other web native more like web native style than real native links and then we have things like Garfish and VMock which are micro front end kind of systems

as you can see, like our deployments and size of apps we have about a thousand unique apps everywhere so that's a lot to manage and often a lot of these things use a unified architecture and infrastructure so, teams or groups can own certain components so they can be used somewhere else.

And we have, you know, again, about 15,000 front end repos. And there's 10,000 or probably closer to, like, 15,000 or 20,000 deployments a week across the board. So that's obviously,

very very slow considering some of these apps are like hundreds of megabytes so this was one of the reasons that we'd actually ended up building RS Pack was we needed the Rust speed and we still needed a really powerful compiler that could essentially run a thousand unique products with, you know, really complicated build requirements.

So anyway, this is the kind of scale that, that, that we usually, run at. So in federation specifically

not switching there we go okay so in federation specifically there's like a couple of terminologies just so you're familiar with it you have the concept of a host or a consumer this is essentially you you import stuff I usually refer to this as like the top level app if you have like an app shell and it's gonna import things the app shell's the the host you would have, remote which is, something that will essentially provide or export features and components out that can be built independently and deployed independently and that's consumed by the first one.

And then you have the concept of, like, code sharing or share scopes technically which allows you to define, you know, certain scopes like in some cases, you could say, oh, well, I need maybe two copies of React.

We could have a scope that says, well, we have the legacy and the modern version, and then you could say, well, there's two singleton ones, and each app can kind of pick and choose which scope they're gonna use and how they're gonna negotiate between each other. So why did we really build federation? Mainly increasing complexity in software.

Repos are massive, and, product velocity was a really big problem or a growing problem across the board.

Mostly due to like things like release bottlenecks trying to coordinate internationally and across multiple groups where teams are very very large it was really really difficult to just keep the output high so the requirements we're looking for is you know efficient code delivery we want decentralized ability to build these apps but when they run they still need to run as a single normal app not like a iframe or mounting a bunch of little apps on DOMs we still wanted it to work like a normal app and all you have to do is import your code and then it magically works even if that code is from a foreign location

so the kind of evolution that we went on we started out with v1 which is what I had worked with Webpack on and this came out maybe 2020 I think the whole idea was share dependencies, consume exposed code we had limited dynamic control and it had pretty much no disaster recovery in v2 which is essentially when I joined ByteDance they had forked the work out of Webpack and had been building something called VMock and then when I got there we essentially like took VMock and, re architected it back into Federation v2 released that and then kind of replaced a large portion of the VMock runtime with what's v2 so the idea behind that was things like HMR pretty much other dev DevX things that you would want at a bigger scale so big ones were HMR the ability to control this from a back end server in real time

extensions, so on and so forth and then because we were also working on RS Pack we knew we needed to make this bundler agnostic and with the rise of things like Vite and RollDown and so on and so forth we thought it would be a good idea not to couple ourselves to a bundler again like like I had originally done with webpack

so some people always ask is like okay well and I think generally like the the the React router crowd is a lot more pro vanilla ESM or at least the the authors are I know they're not huge fans of bundlers so I often get the question of well why not import maps

so the reason that we didn't end up doing it is mostly

the it it works with usually straightforward requirements so I I usually compare import maps very close to like a v1 federation style so they can do almost the same things with what we had originally put in Webpack but I do notice some challenges around dynamic code loading you usually have to use a shim and the challenge that I always run into with the shim is it's pretty much transpiling all the code in the browser and injecting it as a blob again so there's a lot of like delays and CSP issues with, certain vendors I also think the how you share code in import maps is a little quirky because it's kind of like path based so you would have to know who's importing it and what the file structure of the code coming in is to say oh well when so and so imports React I have to know who so and so is like down the tree which is kind of hard to know what the file structures of you know, the sum of a hundred parts might be.

But the perks of it are, you know, it's standardized. You don't really need a bundler to use it. It works very well when you're just dealing with ESM. Not sure about CSS or like other payloads.

And, you know, for basic code sharing it's lightweight and gives you what you would want so anyway, kind of getting into a bit about what what, what federation does or what we've done in v2 I'm gonna skip over this kind of quick but essentially we have runtime APIs examples of doing that if you say didn't want to use a bundler and you wanted to use this straight, like as the underlying library that we've built you know this is kind of what it would look like to set up the parts and maybe load something in this case like apt2util so that's how you could do it vanilla without anything if you wanted to share something through the codebase without using the compiler plugins this is kind of what the the sharing would look like.

Bit of a delay on the screen.

Come on now. Okay so there we are. So this is kind of what it looks like to share it essentially you set your versions the scopes that it's going into.

In this case, I'm just importing React so I can attach it to, like, our system and say, hey, when you need React, it's synchronously available and just use this, and then you can kind of define what it is.

So the most interesting come on now so then the most interesting part we really added was the concept of runtime plugins so my idea was like well what I thought I wanted was the ability to share code between different builds but it turns out what I actually need is something closer to like express but for require or import so we introduced this kind of life cycle where essentially before the system initializes you could change certain aspects before it's loading something when it's resolving the module that it's going to load and I think all in all we have something like maybe 25 life cycle hooks that can control various aspects of the of the loading sequence throughout the the kind of life cycle of loading dependencies at runtime

Okay, so then just some basic examples because it usually helps articulate this. So let's say you have, like, an app and you want to change the version of it.

So in this scenario I can go in and, like, change out Lodash and hit save and then all this is is a runtime plugin that reads, like, browser storage where I'm setting the values of this little React app and then every time you go in here and change something you can change and influence how the application is using these dependencies.

And it's getting the dependencies by saying, well what do all the apps have in total? And then I just kind of loop over it and say, okay, put it all in the list and now you can dictate you're gonna use this from here and that from there.

In kind of V1 this was automatic and you didn't really have any control over this. So this kind of gives you an example of controlling where like, who's vending what and how. Another big issue we obviously had was, like, offline remotes.

So if, say, you are importing code from somewhere else and it's not available, this would usually like crash the applications in in v1. So, a really nice thing we've introduced is the offline capability.

So essentially if something isn't available you could build a little runtime plugin for when it errors and you could import backup modules or fallback modules from your own bundle in there. Or in this case, I just returned like a basic error message.

So when it, falls over, you know, it would essentially just return an error message with whatever the error is that it got. But, you know, you could replace that bottom line with a button or whatever you wanted.

So kind of how we use this at, how VMock kind of works is something more similar to this where we have a kind of a run time plug in where the server feeds us a module snapshot and, essentially we can like, this is essentially our deployments.

So when you wanna redeploy something, it's a change at a SQL table. It's not zip files moving around the infrastructure. All of that was already done at some point in the past. And what controls what the app is is, whatever the payload sends back from the server.

And then essentially you can go in here and we can remap what the slots are in the application. So yeah. These are just kind of like random examples of like how it would work. In this case, let's say you had like a green blue, deploy.

Clicking it pulls it from a different origin and we're essentially switching between deploys on click. Without like refreshing the page or anything like that. So you can also do things like dynamic maps. So big challenges but what if you don't know the URLs you're actually trying to load these resources from?

So a very rudimentary example you know you have some local hosts or local ones non existent URLs But then in the runtime plug in you can check what the environment is, or in this case I just pull it off of like an object. And then you can remap them on the fly.

So now based on the environment you're in you don't have to rebuild the app, but you can tell it, hey, use the stage resource now. Or, whatever you want. So, one other really cool thing is we have so this was a big request before, but we have, the ability to sync federated remote types.

So, when you change it in another repo, or somewhere else in the code base, or, you know, on somebody else's machine, and they deploy that up, your machine will immediately sync the types from the remote origin. So if an API changes, you'll see it go red the moment that you, make the change.

And it and when you're developing, it actually hot reloads the environments for you. We did just release a CLI for this as well. So if you do use the vanilla runtime or something like that, we now have a standalone CLI that will sync types to give it a little bit more flexibility there. Okay.

So we also have things like runtimes and lock files. This is just a similar kind of thing where if you were to say, hey, we had a back end. We introduced a manifest protocol. So it allows us to then perform a lot of the lookups we do in the browser on the server.

And then you could save that as a lock file.

Which means that the system obeys whatever this kind of payload is returning and a little run time plug in to essentially read it and then this, makes it stop doing its non deterministic figure out what's needed just in time and it switches to essentially this, which is as good as an immutable deployment. Okay.

So anyway, the kind of ROI that we see from, from these things.

There we go. Okay. So the ROI that we see from these things.

So one of the big challenge or like the main reason that we ended up building it and because we do a lot of deployments, we're able to see you know what do we actually end up getting out of it so the major things and this was kind of done back with our with webpack style so when we introduced RS pack speeds are about 10 times faster 11 times faster but in general these were the results that we got because Federation had come first before RS Packet ByteDance but I would say probably the biggest one that really captured the value was anybody using Federation the team saw about a 300% increase in engineering output and overall 80% reduction in code rollout time since all we have to do is change drop down hit save and then it's anywhere we want at any time there's no waiting there's none of that QA ing and things like that it's also really effective you don't have to get an environment and wait for it but your QA can open a Chrome extension and say I want to try this and then it appears right there so it just reduces a lot of overhead in like the logistics of, of moving things around the codebase so another interesting thing we actually did this research for RS pack but I found it quite relevant, to, federation as well because they're both kinda interrelated but, essentially what we had seen is how much latency happens based on, like, CI time or moving things through the infrastructure what's the delay that you end up encountering and so what we had kind of seen is the sweet spot you want to get into is like five to ten minutes in there.

And if you can do five or if you can do within five minutes, essentially, I think the time to merge and, like, get it into the product is usually about twenty minutes.

But if it's say if your CI time takes thirty five minutes you're looking at about a sixteen hour latency on every merge that you try to do. Just because, people forget things or it takes too long you go off it just slows down the whole flow of getting code out the door.

So you can see this exponential growth in actually getting things into the product based on how much latency you're introducing. So anyway, this is, this is just kind of some of the, this background behind it.

Inside the company, we did have, we have VMock obviously but outside of the company for anybody who does micro front ends we did end up releasing essentially the same thing which is Zephyr Cloud which is like a public version that users could use it's not run by us but we both share the same base fork and so if you are looking at micro front ends and you want to manage it I would say definitely check out Zephyr it's a great team there who's, put a lot of work into making micro front ends sane and manageable okay so I don't know what my next slide is but I'm going to switch over and let's get out of the boring stuff here and let's actually show you this with, with EPIC stack so bear in mind, this is still a little experimental because it was quite, a close call to get to build everything, get it all shipped, and then get this working.

But in general, it does, it does how it work how it would expect, but there's a little polish left. So what I've essentially done here is I have two applications running. I have one on port 3,001 and another one on port 3,000. And I never do live demos, so you know, we'll see what happens.

So this is, I believe, essentially 3,001. It looks the same as what we would see on port 3,000. So what we're gonna see in the actual source code here, just to give you a general idea of, like, how the how the app is structured. I have the remote and I have the host for this example.

But what I've kind of done in here is I've just said, okay, well let me just make two copies of the app and split them up to make it easy enough to kind of follow along so what I did in the main app is I deleted all the components that come with EPIC stack so any component from there is actually coming from a separate independently built app which you could deploy separately on it doesn't have to be on this machine It could be anywhere, released anytime essentially.

And how I kind of show you that, you know, what's from here and what's from, like, the remote origin was I just put a red border around everything in the remote app so it was easy to tell.

The other thing that I had gone and done is I experimented with, well what if we took Kent's login screen, and we made that federated page as well?

So there's none of that code, or the action code, or any of the server stuff sitting in, the main application, but it's actually all rendered, or it's all from a independently deployed location, but it's rendered on my box inside of the host.

So example of like how I switched out the the login page, you know, we have the login and the server actions for it, and all of that is just, you know, one line of code that's coming from this, remote origin.

So to go and actually use the app, you can see you know, we have everything with a border on it. The search, the search button, the login button all of those are outlined in red so they're not part of this application.

If I go and hit login you can see as well again every piece of it is from a, from the federated location. And if we hit the login it, you know, it'll take us to our login screen. We hop in here you know, again, you can see all the bits and pieces.

As well as the entire login page doesn't actually exist in 3,000. It's pulled in at runtime. And this also works server side and client side. So we're server side rendering it, and CSR obviously, as you can see. And, you know, just kind of looking, if you go and create notes, you can see, okay, all the little fragments.

This is obviously really useful in case of when teams own certain portions of it being able to say okay well maybe it's not going to be this granular but if somebody owned notes the notes team could ship you this update as you need it and whenever they change it it's magically updated in your app.

And you never have to do like a bump install or, you know any of the normal painful things. I think a good analogy of like where it's useful is in cases of, like okay what happens when it's a dependency and a dependency and a dependency.

So in order for you to update this thing you have to install it there turn around install it into the next one release turn around install it into the next one release and then install it into your app and restart the app and that gets really really laborious So in cases like that this is a really useful solution to it.

Obviously I would say, don't use it unless you have a reason to need it because that's usually the flaw of everything is you know if you do need it it does work if you don't I would say just stick with what you've got but it's a good solution for problems where there are few solutions to it as Kent said it's a solution to a problem I hope I never have So anyway, if you do want to check out any of the docs I would say go over to module federation IO you can see everything that we work with all the bundlers we're working with if you're not a big fan of the webpack era stuff we do have, Vite's roll down we'll support it as a first class feature so it'll be built into the Rust side of Vite's roll down.

I've already seen it and looked over the code it does look pretty awesome. So I'm very excited for that to come along.

And if you do wanna check out the React Router RS Build plugin, it's just, we just pushed it up there, but RS Pack and Trib, RS Build React Router, and it uses all the same conventions as you would get in Vite. You know, the the configs are the same.

RS build is very similar to the Vite style of lightweight configuration so if you do want to try it out it should be as familiar as I could make it between the two Alright well thank you for your time

Related Talks