Module Federation using ViteJS
For backend developers, the microservice architecture represents a software development approach composed of small, independent services communicating over well-defined APIs. A similar concept for frontend applications is Micro-Frontends. This architectural pattern shares similarities and differences, akin to Microservices, specifically designed for front-end development. One crucial implementation tool enabling this architecture is Module Federation.
What is Module Federation?
Module federation is a concept where different builds come together to make one application. These separate builds act like containers and can expose and consume code between builds, creating a single, unified application. They are also termed as remote modules.
Local modules are standard modules within the current build, while remote modules, not part of the current build, are loaded at runtime from a remote container. Loading remote modules operates as an asynchronous operation.
Module Federation is unidirectional, involving one host while allowing an application to consume multiple remote builds. Each build acts as a container and can consume other builds as containers. This way, each build can access any other exposed module by loading it from its container.
Module Federation was introduced by Zack Jackson, a Webpack maintainer, and became a flagship feature of Webpack 5 in 2020. It serves as a plugin offering flexibility in project development. While Webpack was a popular bundling tool previously used in the Create React App, CRA was deprecated in early 2023, and Vite.js gained popularity as the Next Generation Frontend Tooling. Astro is built on top of Vite.
Today, we'll delve into Module Federation using the Vite Plugin, an excellent tool created by Originjs to develop micro-frontend applications with Vite.
Host and Remote Applications
When using Module Federation, two scopes come into play:
Host: This application typically serves as the primary or container application responsible for integrating and orchestrating multiple micro-frontends or modules. It's the central hub that imports and consumes functionalities or components from "remote" applications.
Remote: These applications are standalone modules or micro-frontends exposing certain functionalities, components, or features. They are self-contained entities developed, deployed, and maintained independently. They provide functionalities that can be consumed by the "host" application or other remotes via Module Federation.
Let's understand these terminologies with the help of an example:
Suppose we have a remote application exposing the Header.tsx
file. We've also developed two different host applications โ a video listing application and a blog page. Both these applications require a common header component.
Remote Component - Header.tsx
To understand creating and exposing the Header component as a remote application, let's utilize Vite's react + typescript template:
# npm 7+, extra double-dash is needed:
npm create vite@latest remote-header -- --template react-ts
# yarn
yarn create vite remote-header --template react-ts
The above command creates a new react application. Now, let's create the Header component:
// ./src/components/Header.tsx
import React from 'react';
import './Header.css';
interface HeaderProps {
title: string;
logo?: string;
}
const Header: React.FC<HeaderProps> = ({ logo, title }) => {
return (
<header className="navbar">
<div className="logo-container">{logo?<img src={logo} className="logo react" alt="React logo" />:<small className='logo-text'>Logo</small>}</div>
<h1 className="title">{title}</h1>
<div className="login-btn-container">
<button className="login-btn">Login</button>
</div>
</header>
);
};
export default Header;
The header component accepts the title
and logo
as props, granting consuming applications the liberty to provide custom title and logo options.
To make this header component truly remote, we expose them using the @originjs/vite-plugin-federation
plugin with the Vite config file.
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import federation from "@originjs/vite-plugin-federation";
export default defineConfig({
plugins: [react(), federation({
name:"header_app", // any name of your choice
filename: "remoteHeader.js",
exposes:{
"./Header":"./src/components/Header"
// expose other components if required
},
shared: ["react","react-dom"]
})],
build: {
target: "esnext",
cssCodeSplit: false,
},
})
Ensure to remember the filename
value within this configuration. It represents the manifest for all exposed modules. This value is further referenced within the host application. In this example, only the header component is exposed, although additional components can be added. Share any libraries the module depends on. In this case, the Header component shares React and React-DOM that is included in the shared
group.
Finally, a few tweaks are made to the configuration. The code transpiles to ESNext, enabling the use of modern JavaScript features. Additionally, CSS file code splitting is disabled, consolidating all styles within the main bundle.
At this point, let us test how the header component looks.
// Testing the rendering of header component - App.tsx
function App() {
return (
<>
<Header logo={reactLogo} title='Header Component'/> {/* Header with logo */}
<div className='spacing'/>
<Header title='Header Component'/> {/* Header without logo */}
</>
)
}
// package.json
"scripts":{
"dev": "vite --port 5001 --strictPort",
"build": "tsc && vite build",
"preview": "vite preview --port 5001 --strictPort"
}
Running yarn dev
presents the output. We have a default logo shown if the user doesn't pass any icon.
To create a local build for this component, make sure to run a build first and then preview the component. Observe that we have restricted this application to be deployed on port 5001.
yarn build && yarn preview
Upon execution, the main bundle, containing shared libraries and the exposed header component, becomes accessible at http://localhost:5001/assets/remoteHeader.js
. This file is not generated when using the yarn dev
command.
assets
directory. This remoteHeader
file and all of the bundles it references are just JavaScript files. Hence, they should be treated and deployed like assets.Host Application with Videos.tsx Component
Now that our remote application is ready, let's create the host application and consume the Header
component. We'll develop a simple application displaying a list of video cards with title and author details.
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import federation from "@originjs/vite-plugin-federation";
export default defineConfig({
plugins: [react(), federation({
name:"video_app",
remotes:{
headerApp: "http://localhost:5001/assets/remoteHeader.js",
// consume other remote components
},
shared:["react","react-dom"]
})],
build: {
target: "esnext",
cssCodeSplit: false,
},
})
In the Vite configuration, use the @originjs/vite-plugin-federation
plugin to define headerApp
as the remote points to the served location. Also, specify every shared library the host shares with the remote, ensuring uniformity across applications.
We can now extract and render the Header
component as follows:
// App.tsx
import React from 'react';
import VideoList from './components/VideoList';
import { dataVideos } from './utils/sampleData';
import customLogo from './assets/react.svg'
import Header from "headerApp/Header";
const App: React.FC = () => {
return (
<div>
<Header logo={customLogo} title="My Videos"/>
<VideoList videos={dataVideos} />
</div>
);
};
export default App;
The Header component is imported from headerApp/Header
, accepting a custom logo and title string. The video list is rendered within the <VideoList>
component.
Consuming Remote in a Webpack Application
Now that we have learned how to use module federation in Vite applications, let's understand how this remote component can be consumed by a host application created using Webpack.
Host Component - Blogs.tsx (Webpack Application)
// webpack.config.js
// ...rest of the config details
config.target: "es2020",
experiments: {
outputModule: true,
},
config.plugins: [
new ModuleFederationPlugin({
name: "host_wp_blogs",
library: { type: "module" },
filename: "remoteHeader.js",
remotes: {
headerModule: "http://localhost:5001/assets/remoteHeader.js",
},
exposes: {},
shared: {
...deps,
react: {
singleton: true,
requiredVersion: deps.react,
},
"react-dom": {
singleton: true,
requiredVersion: deps["react-dom"],
},
},
}),
new HtmlWebPackPlugin({
template: "./index.ejs",
inject: false,
}),
],
The remote component remoteHeader.js
has been consumed within the Webpack's ModuleFederationPlugin. Note that we have named this remote as headerModule
. Considering that the remote application is built with ViteJS, include library: { type: "module" }
in the module federation settings as Vite works on ECMAScript modules. Additionally, set up config.target
to support modules. These changes ensure the proper linkage between the remote application built with ViteJS and the Webpack setup.
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import BlogList from "./components/BlogList";
import { dataBlogs } from "./utils/sampleData";
import logo from "./assets/react.svg";
import HeaderModule from "headerModule/Header";
const Header = HeaderModule.default;
const App = () => (
<div className="container">
<Header logo={logo} title="My Blogs" />
<BlogList blogs={dataBlogs} />
</div>
);
ReactDOM.render(<App />, document.getElementById("app"));
In this scenario, the Header is imported as a module, accessing the default export assigned to a variable named Header.
Further options for extending the use of Module Federation
What an enriching learning experience! We've successfully created a remote module and integrated it into host applications. This example serves as a foundation for various extensions and adaptations.
In this scenario, the Video listing application (host) consumes two distinct remote components - Header and Subscribe Button. Interestingly, the same button component is utilized by yet another application.
Going a step further:
The header component serves a dual role as both a remote and a host application. It functions as a remote component for the Videos and Blogs application, while also serving as a host to consume the remote Login component.
Numerous possibilities exist for expanding the utilization of module federation in real-world scenarios. I would be interested in knowing the specifics of your implementation. Please share them in the comments section.
Conclusion
In this exploration of Micro-frontend Architecture through Module Federation using ViteJS, we've delved into a groundbreaking approach. Understanding the intricate dynamics between host and remote applications, we've unlocked the potential for seamless integration and autonomy.
This journey has shown how breaking down tasks into separate, cohesive parts can create strong, unified applications. As we wrap up, it's clear that the potential for innovative and scalable front-end development in Micro-frontend Architecture knows no bounds.
Thank you for joining me on this exploration today. Remember, continuous learning and adaptation are key in the ever-evolving landscape of software development.
Feel free to refer to my Github repo and show your support by starring it.
I hope you were able to take away something new and useful from this article! If you did, please drop a like on this post. ๐
You can connect with me on Twitter, LinkedIn, and GitHub โจ
Peace โ๏ธ
References
Subscribe to my newsletter
Read articles from Supriya M directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Supriya M
Supriya M
I am a Web Developer who works on MERN stack technology. I write articles about JavaScript, React, web development, and open source. I have recently started expanding my knowledge of React Native and application development!