Using ES Modules Based Micro-Frontends to Enable Distributed Development


A look at the open source tooling created by the UI Engineering group at JP Morgan to streamline the developer workflow of building and deploying apps in isolation while still delivering a single unifying micro-frontend based portal to the end user, leveraging the use of native support for ES module imports in the browser.



Good morning everyone. With that amount of videos about JPMorgan, no pressure doing this today. So, as I said, my name is Steve and I'm here to talk a little bit about how we're using modern web technologies, JP. Things like ES modules where we can use them to solve kind of age-old problems. Problems like how do we run multiple applications that have all been built and deployed independently of each other in single container portal-like applications. So, a little bit about me. I've been working in and around the FinTech space for about 20 years now. And I've had the privilege to see UI technologies grow during that time. And now that I'm with JPMorgan, I'm actually no longer a core developer. But now I get to work with developers to put together our open source providing for we have two things I'm going to talk about today. One is the UITK, which is a React component library that we use to standardize the way that our applications look and feel. And Modular, which is an opinionated monorepo management tool. And they're actually both on our public GitHub if you want to check them out. And we have we appreciate pull requests. So, the kind of places I've worked at before, I said it's always in FinTech. Some of those organizations are really small. And when I came to JPMorgan last year, I had no real idea of a sense of scale that I was going to be facing. We have across the bank as a whole is 50,000 developers, I think. 18,000 of which are in our division. And they're deploying about 5,800 applications into one of our container types, which is a desktop container. 2,000 of those are in production. They service about 200,000 daily users, and there's hundreds of deployments going on every day. So, obviously, we have this really distributed development problem. Or joy, depending on how you look at it. Because distributed development means we've got loads of development teams. They all work independently. They're empowered to make their own choices about how they're going to build their applications, how they're going to deploy them, and more recently, how often do they deploy? Why are they actually sending code out? But looking at it from an organizational standpoint, what is the developer experience of that distributed development model? Well, every development team has to solve the same problems. They're all going to need to have some form of continuous integration. They're all going to need continuous deployment. And they're all going to have the same need to apply quality assurance. So, they're going to need test tooling. They're going to need to choose the same products for doing their testing. And then because we're all under the same brand umbrella, we're going to need to have the same design language. So, how do we add that consistency to the applications that we're deploying, bearing in mind there's thousands of them being pushed out? So, when the applications go out as well, they're going to need to solve the same kind of problems. We need to know how they're going to authenticate the users, how they authorize them to use the apps. And because we want to have the same rich user experiences, we're thinking how are you going to be able to share the layouts you've created? How are you going to share access to your applications? Which all kind of implies that you've got the same kind of understanding of having a preferences system, how you're going to store and persist the user data. So, what was our solution to that? So, our solution is in UI engineering. We have what we call the digital platform, which is a bit like a Jamstack provider inside the bank. So, I've already mentioned we have a desktop container, but we have ways that we distribute our mobile and web apps as well. We call it Omni Channel. But we also provide more of a DevOps providing. So, we have things like continuous integration. Actually, it's based on Jenkins. We have continuous deployment, which is based on Amazon S3. And we also provide like a centralized application registry. And that's where we have our developers push their versions of their code so we can determine which are going to be made available in production. And more importantly, when we need to roll things back, it simplifies our processes. And then probably more relevant to today and what I'm going to be talking about later is we have a design system. So, that is actually it's an accessibility-first design system that has a companion React-based component library. We have been embracing web components and allowed for the use of Angular. There was a decision made a couple of years ago that we were going to be React-focused. We actually have a new version of the React library being developed at the moment. It will be released fairly soon. Feel free to go and have a look at it on our GitHub. And then finally, the modular, which is our opinionated monorepo management tool. It has a few features in it that I will be showing today. It's similar to create React app and then an opinionation on developer tooling that goes in with building the applications themselves. So, we have a desktop container. The developers, when they create applications, push it through to our continuous deployment infrastructure and tell the desktop container, which is an electron-based app, where to fetch the URLs. When we come to deliver a web portal, what is the user experience expectation? It's almost the same as on desktop. Users still want to have a single way of launching applications. They want to have a consistent look and feel on the apps that they're loading. And they want to be able to use their apps in all modern browsers. So, historically, that could have meant using iframes to load applications together. Which would come with the usual drawbacks of you've got many, potentially many instances of frameworks being loaded into one application, into one web browser window. The use of memory, things get janky. You can't share or readily share CSS and theming between the various apps that you've loaded. So, what was our solution? I mentioned that we're using modern technologies, so now we are focusing on having micro front-ends. The micro front-ends that we're looking at are relying on ES modules for splitting the applications and loading them dynamically, and the React library we've created uses CSS variables to share theming across the various apps that you might be loading. It comes with many benefits, things like if you're using ES modules, the way that we will be transpiling our applications is we take the source code, usually in TypeScript, rewrite imports that are in your own application to bundle your application code separately, and then rewrite all of your external dependencies onto a CDN. We use an open-source CDN for ES modules, ESM.SH. Have a look. It's great. You may also be using Skypack. We have internal versions of these that are able to access and transpile our existing Commons JS node modules in our internal registry into ES modules. In case you're not familiar, ES modules are a modern standard of creating JavaScript in its simplest form. You're probably already using them in TypeScript, and it's a convenient way of simply having your imports and exports in your code after it's been transpiled to JavaScript. The other thing is that we have a web application container. We dropped support for Internet Explorer in the last year or so. We don't really mourn its loss because it means we're able to use evergreen browsers now, all of which are able to support dynamic loading of imports in JavaScript without any need for any plugins. Okay, so, building a micro front-end. It's live demo time which never goes wrong, so please bear with me. So, I've mentioned, oh, good, I've mentioned modular. It does have a way of scaffolding applications. It's based very similar on create React app. However, what we're looking at here, I'm going to use a minimal monorepo. It has a couple of packages in so far. One package is simply an express server that is mocking out the application registry that I talked about before. It's going to serve up our built content so you can see the demo. We also have an example of how you would create a host app. Now, create React app would be able to serve these components, so it looks very similar to normal, using webpack, so webpack and webpack-dev-server. We switched to webpack 5 and added support for ES build. With ES build, instead of generating a commons.js bundle, we're now creating an ES module bundle. All I'm going to do in here is run a quick command to start up the express server. It's also going to start webpack-dev-server that builds this host application. It should. There we go. Good. It's going to very slowly load on local host. My application. We will just assume that's the case. I will come back to it. Thereafter, other things that modular has in it. I mentioned this delivering of development at scale. At scale, we often have the need to create new applications, and those new applications are created from templates. Modular has a feature that can either use templates based on that come directly from npm or from within your repository. I'm going to use them coming from my repository, so I'm going to add a new module into my monorepo. I can't type with people watching me. So, bring modular commands to add a new package into my template. I've created some templates already, but just so you can see how they get added in. Why am I running through this? The templates that we add are actually, they can all be run as independent applications. So, in the code view, I've now got a new application. It doesn't do an awful lot. Fortunately, it doesn't use React. Oh, it does. Is that an effective one? Maybe. Pretend you haven't seen my use effects. I did say I don't do code for a living any more. Now that that's created a new package, I can start this for myself. I'm going to start using a new port. Okay. So, let's say that I'm going to just run this application. What this is proxying is if a developer is creating an app for themselves, then... did the old one. There we go. These do load. Oh, well. If it doesn't load, I'm just going to talk for a little bit longer and then show you the video, because live demos do that! Good. Anyway, assume that you can render those. Never mind. 3000. 3001. You know, I've never seen create React app just stop. Good, I knew I shouldn't have tried to do a live demo. I will carry on for now, and then I will show you the video, and it will be amazing. I'm going to add another one. Okay. So, trying again. The next I'm going to have to talk about is the new package. Does it start? Good. Good. Who would want it to just work straight out of the box, eh? Never mind. Okay. So, what am I going to talk about? The idea is we have this card view. They actually get rendered as independent applications that you can start with a webpack dev server. The next up is having created a couple of packages. I can show them running together. Next, I would need to build them. Now, the difference in the build process is that, by running through ES build, we are running through ES build, we create an ES module which is a single file that can be pushed on to a CDN. In the example I have here, it runs as a local server. Let's just see if it's magic to its way through the internet. Well, it turns out I'm not online. That's a shame, because I've styled it since I created this video. Disappointing. You get to see what I was doing yesterday. Assume this is running live. I'm going to see why my modules aren't fetching from the internet. If you come find me, I can show you a real running version of this. We have a stand out the front. I will be there later with a working version. I'm going to show you the mocked-up version instead. Here we've got the same code window where I did a yarn serve. This is going to start my Node server which is like the proxy for our app registry, and sets up a create React app. There's nothing on the screen so far. What I need to do is add one of those components. This is showing you how I built one of those, run through ES build, and by running through ES build, generate my ES module, that gets stuck on the CDN. You can see here, if we have fetched it using our portal, this is a JavaScript file that gets loaded dynamically. How did we do that? In the host package, I created a remote view. The remote view is a very simple component that just fetches a module from a URL. That module, it shares its state through a context to ensure we don't try and load these modules multiple times, but here, you can see how we are actually loading the remote view. That loading is using all JavaScript that runs natively in the browser. We don't need any plugins to do this. We simply have fetch from our manifest. We use package JSON to define the content that describes the module. We use that to describe how the module has been built. You can see including some packages, that's our design system, but also where is the output of the JavaScript and the CSS? Those are then going to be used by this remote view to say when we fetch that package JSON, then we know if we've got a style sheet, we can insert it to the HTML. That may not be the best way of doing it, but it worked. If we have a manifest, this is where we've put the JavaScript, so see that dynamic import that is natively supported in all browsers. If you're running this through Webpack, you will need a little escape hatch because Webpack will try and evaluate that import statement for you. Knowing that we've done a dynamic import in the browser, that actually gives us the evaluated default content of the package itself. We know those are going to be views for us, so we use that view as a component constructor and can include it in the TypeScript TSX. I'm presuming I still haven't gone online. The next bit is I can add more. I can add more components into this independently building them from each other, and then we can use the portal simply because it does dynamic loading, and the portal is deployed independently of the applications that it contains, and then they can actually, in my real working demo, I was so proud, I have the grid and the card talking to each other. There we go. There is an example that I can share that shows this running. It is a very simple thing to get up and running. I think that that was me approaching time. So there was left for me to say that we have by loading the modules into the same React tree, we can benefit from things like sharing of modules, sharing of dependencies, especially if you've got stateful dependencies, they actually share the same instance, so we do have a system of recommending that our app developers pin at least React to the same version which simply just uses the resolutions object in a package JSON that we then use the same React version across all of our applications, and, yes, so our builds are faster because we only have to build and transpile our own application code. They reduce the file size that we are storing on a CDN. They load faster in the browser because they share dependencies between them, and, yes, I'm really excited that we get to use modern technology to solve problems that have been around for a long time. So apologies that the demo didn't work quite well, but honestly, come see me later, and it does actually work. Cool. Thank you. Thank you. So, good job. We have a couple of questions in the Slido already. Remember,, and the code is 2124. Let's go to the live. How do you manage deployments of these distributed apps? Do you just deploy ESM bundles? What happens when you need to synchronise deployments of multiple apps? Fun. So it kind of depends on the team. We have got, as a mentioning of scale, not all of those development teams know each other. Sometimes, where we have things that are interlinked, we actually use a variation on a theme of Scrum of Scrums, so we can plan together. There are large programmes of work. Those programmes of work usually involve like the tech leads from the applications that are going to be deployed, their product managers, so we can shape actually how they're going to be deployed. The deployments themselves, we use a range of technologies. I was talking about the shiniest and the newest. Some of them are containerised applications. Some of them, they are totally independent of other apps. The CDN that I was mentioning is fairly new. That one, actually, we use, we treat each version as being immutable, so even in our continuous deployment infrastructure, each build that comes off CI gets pushed into the CDN, and is therefore available for use as a production application. So, yes, we've got loads of apps that all depend on each other. It's down to human contact. We have to talk to each other to say when we can deploy them, really. What a concept! I have a question about the opinions of modular. I remember, so I was there when modular got created some time ago, and so I don't know what it looks like now, but I remember that the concept was it was going to be very opinionated. You mentioned that. What part of having a very opinionated app creator has worked for you, has worked for you at scale, and may or may not work for someone with not quite so many apps? It has worked in some ways. When it was used to start with, it really was about bootstrapping new applications, so, in the ever-changing face of UI development, it was convenient to have a consistent way of either picking tooling or setting up configuration for your tooling. The UI development space moves very quickly, so what was confusing and difficult then isn't necessarily confusing and difficult now. So, while modular exists as a way of standardising how we interact with applications, it is becoming ever less opinionated, so there are more and more escape patches to come with it, and, actually, it's giving us a really unique way of helping, like scale-up development, so we now don't just have, like, we aren't there at the origin of applications. We are now being included into large-scale applications that are wanting to add new features, so things like the new releases of, I think we called it ghost testing, similar to running tests on only the things that have changed in your pull request, or things that depend on you. Soon, we are going to have the incremental build of building selectively. It gets added in. How do you manage to keep it snappy between the micro front-ends? One of the things we rely on is by using ES modules and a consistent CDN. It's not quite as elegant and seamless as I made it look. We do recommend a specific CDN, so it's always we all use the same CDN. We pin versions for things that we know are stateful. The CDN that we use, we have an implementation of it internally so we can share our proprietary code with internal users, but the CDN itself is heavily cached, so the modules themselves do load really quickly. We also rely on HTTP2 and newer standards, so it doesn't matter having lots of connections needed, needing to fetch lots of dependencies, that was old school, we would think that was a problem, but modern tech, it isn't really. So, yes, we try and rely on modern tech. There's a couple of questions here that I'm going to bundle which is people asking why not this other thing, right? Which is kind of always comes up. How is this different from Webpack's module federation or Nexus approach? So module federation, the version of using micro front-ends in this way means you don't actually build everything together, so you don't need to know what modules are going to be loaded in order to build your host application. I know there are a few of why have you chosen coming up. Because, I think, to be opinionated, you have to make a choice. Those choices have come around over time, and we actually, there's often people push back on why did you do a thing and now we take it case by case and we have to evolve. And then in the similar vein, how do you do specific things such as user authentication across multiple modules or communication between one micro front-end in the deployed app and the other if it exists? So, in the version that does work, please come and see me later. In the speaker room Q&A down by the lobby. I showed the grid talking to that card view, and they talk to each other. The answer that we've got internally is part of that digital platform is we provide platform services, so things like the authorisation authentication that's done on launching the host application, there's an arcane system for authorisation. But it is handled by the host application. So as an app is loaded, it can define which credentials the user must have, and then the host is responsible for ensuring that those have been fetched. That's great. We have a ton more questions, but unfortunately not a ton more time. So let's thank Steve, and you can find him later at his speaker Q&A room. Thank you for your time. Thank you. Thank you.
28 min
21 Oct, 2022

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

Workshops on related topic