DEV Community

Abhishek V
Abhishek V

Posted on • Updated on

React Micro-Frontends using Vite

Source code for this article is available on GitHub.

In this article


Introduction

Micro Frontends: Enhancing Web Development with Vite and Module Federation

In this article, we'll explore the concept of Micro Frontends—a powerful architectural approach for web applications. Micro Frontends allow you to divide your front-end code into smaller, independently developed and deployable units. These units, known as micro frontends, offer numerous benefits, including increased development speed, scalability, and flexibility. By enabling different teams to work on separate parts of the front end while maintaining integration through an isolation layer, Micro Frontends help manage complexity and promote autonomy in front-end development.

What is a Micro Frontend?

A Micro Frontend is an architectural approach for web applications where the front-end code is divided into smaller, independently developed and deployable units called micro frontends. This approach enhances development speed, scalability, and flexibility by allowing different teams to work on separate parts of the front end while maintaining integration through an isolation layer. It's a way to manage complexity and promote autonomy in front-end development.

Module Federation

Module Federation is a key technology that enables a JavaScript application to load code dynamically from another application while sharing dependencies. When an application consuming a federated module lacks a required dependency, Webpack (the underlying technology) automatically fetches the missing dependency from the federated build source. This allows for efficient sharing and use of common libraries across multiple micro frontends.

Why Vite?

While Module Federation was initially introduced in Webpack, the landscape of JavaScript development has evolved. Vite has emerged as a game-changer by providing lightning-fast build times. Combining Vite and Module Federation can unlock immense capabilities for developing micro frontends quickly and efficiently.

Creating a Micro Frontend using Vite + React

Creating a micro frontend typically involves two main parts:

  1. Host Application: This is the primary application that users interact with. It serves as the container for the micro frontends.

  2. Remote Application: These are the micro frontends themselves, which act as building blocks for the host application.

Now that we have an understanding of the technologies we'll be using, let's dive into the practical implementation.

Setting Up the Host App

To create a host application using Vite and React, run the following command:

# Using npm 6.x
npm create vite@latest host-app --template react

# Using npm 7+, add an extra double-dash:
npm create vite@latest host-app -- --template react
Enter fullscreen mode Exit fullscreen mode

Setting Up the Remote App

For the remote application, execute the following command:

npm create vite@latest todo-components -- --template react
Enter fullscreen mode Exit fullscreen mode

This will create two folders, host-app and todo-components. Next, install the dependencies for both apps:

npm install
Enter fullscreen mode Exit fullscreen mode

Creating Components in the Remote App

In the todo-components app, create the following components:

// components/List.jsx
import React from "react";

const List = (props) => {
  const { items } = props;
  return (
    <ul>
      {items.map((item, index) => (
        <li key={index}>{item}</li>
      ))}
    </ul>
  );
};

export default List;
Enter fullscreen mode Exit fullscreen mode
// components/Input.jsx
import React from "react";

const Input = (props) => {
  const { value, onChange, onSubmit } = props;
  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        onSubmit(e);
      }}
    >
      <div className="flex-row">
        <input
          type="text"
          value={value}
          onChange={(e) => onChange(e.target.value)}
        />
        <button type="submit">Add</button>
      </div>
    </form>
  );
};

export default Input;
Enter fullscreen mode Exit fullscreen mode

Now that the components are ready, make the following changes to the App.jsx file:

// App.jsx
import { useState } from "react";
import reactLogo from "./assets/react.svg";
import viteLogo from "/vite.svg";
import "./App.css";
import Input from "./components/Input";
import List from "./components/List";

function App() {
  const [count, setCount] = useState(0);

  return (
    <>
      <Input value={count} onChange={setCount} onSubmit={console.log} />
      <List items={["Learn React", "Learn Vite", "Make an awesome app"]} />
    </>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Previewing the Remote App

To preview the components, run the following command in the todo-components app:

npm run dev
Enter fullscreen mode Exit fullscreen mode

You should see the following output.

Remote App Preview

Adding Module Federation to the Remote App

Now, let's add Module Federation to the todo-components app. First, install the necessary dependencies:

npm install @originjs/vite-plugin-federation --save-dev
Enter fullscreen mode Exit fullscreen mode

Next, configure Module Federation in the vite.config.js file:

// vite.config.js in todo-components
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import federation from "@originjs/vite-plugin-federation";

export default defineConfig({
  plugins: [
    react(),
    federation({
      name: "todo-components",
      filename: "remoteEntry.js",
      exposes: {
        "./List": "./src/components/List.jsx",
        "./Input": "./src/components/Input.jsx",
      },
      shared: ["react"],
    }),
  ],
  build: {
    modulePreload: false,
    target: "esnext",
    minify: false,
    cssCodeSplit: false,
  },
});
Enter fullscreen mode Exit fullscreen mode

In this configuration:

  • name: Specifies the name of the remote app.
  • filename: Sets the name of the file generated by Module Federation.
  • exposes: Lists the components to expose from the remote app.
  • shared: Declares shared dependencies, such as React, to optimize the bundle size.

Now, build the remote app:

npm run build
Enter fullscreen mode Exit fullscreen mode

This generates a dist folder in the todo-components app containing a remoteEntry.js file.

Serving the Remote App

Serve the remote app locally:

npm run preview
Enter fullscreen mode Exit fullscreen mode

This serves the remote app on port 4173.

Adding Module Federation to the Host App

To use the remote app components in the host app, set up Module Federation in the vite.config.js file of the host app:

// vite.config.js in host-app
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import federation from "@originjs/vite-plugin-federation";

export default defineConfig({
  plugins: [
    react(),
    federation({
      name: "host-app",
      remotes: {
        todo_components: "http://localhost:4173/assets/remoteEntry.js",
      },
      shared: ["react"],
    }),
  ],
  build: {
    modulePreload: false,
    target: "esnext",

    minify: false,
    cssCodeSplit: false,
  },
});
Enter fullscreen mode Exit fullscreen mode

In this configuration:

  • name: Specifies the name of the host app.
  • remotes: Lists the remote apps to be used by the host app. In this case, we have one remote app, "todo_components," and we provide the URL of its remoteEntry.js file.
  • shared: Declares shared dependencies, such as React, to optimize the bundle size.

Using Remote Components in the Host App

Now, you can import and use the remote app components in the host app's App.jsx:

// App.jsx in host-app
import { useState } from "react";
import List from "todo_components/List";
import Input from "todo_components/Input";

function App() {
  const [newTodo, setNewTodo] = useState("");
  const [todos, setTodos] = useState([]);
  const onSubmit = () => {
    setTodos((prev) => [...prev, newTodo]);
    setNewTodo("");
  };

  return (
    <>
      <Input value={newTodo} onChange={setNewTodo} onSubmit={onSubmit} />
      <List items={todos} />
    </>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Here, we import the components from the remote app using the specified remote name, "todo_components."

Serving the Host App

To serve the host app locally, run:

npm run dev
Enter fullscreen mode Exit fullscreen mode

Now, you should see that the components from the remote app are being used seamlessly in the host app.

Conclusion

In this article, we've explored the concept of Micro Frontends and demonstrated how to create a micro frontend architecture using Vite and React, enhanced with Module Federation. By leveraging the power of Vite's fast build times and Module Federation's dynamic code loading capabilities, you can efficiently develop and scale web applications in a modular and maintainable way. This approach empowers multiple teams to work on different parts of the app independently, promoting flexibility and agility in front-end development.

For further exploration and to apply these concepts to your own projects, feel free to adapt and expand upon the examples provided here. Micro Frontends, when used judiciously, can significantly streamline your web development workflow.

Top comments (22)

Collapse
 
webjose profile image
José Pablo Ramírez Vargas

Since Module Federation is a webpack thing, and since Vite does not use webpack, wouldn't the introduction of webpack negate the building speed of Vite? After all, I cannot possibly imagine that this works by compiling with Vite (rollup). I can only imagine that at least part of the building process is done by webpack.

Doesn't this mean that doing Vite with Module Federation is kind of a contradiction? Are we not mixing things that shouldn't be mixed? Aren't we losing rollup's build and going back to slower builds with webpack?

Collapse
 
ebitzu profile image
eBitzu

please check dependencies, there is no webpack involved

Collapse
 
webjose profile image
José Pablo Ramírez Vargas

Isn't Module Federation a webpack feature? I'm confused if not.

Thread Thread
 
ebitzu profile image
eBitzu

it's a common misconception, module federation is just a principle to share code around on runtime.

Thread Thread
 
webjose profile image
José Pablo Ramírez Vargas

Wow, this is an eye-opener. I'll read about it.

Thread Thread
 
zmrfzn profile image
Zameer Fouzan

Module federation is a concept. Yes it was originally added to the webpack first but the original author was working on porting this to different build/pack libraries like vite.
You'll be able to find information under issues for each more vite/roll-up

Collapse
 
dsaga profile image
Dusan Petkovic • Edited

For each of the remote Apps, are the "shared" libraries always loaded from the host app, or how does that work, which app provides the shared libraries?

Also any pointers on which things to implement when making a module federation setup like this production ready?

Thanks! great read btw. clear and simple to understand!

Collapse
 
abhi0498 profile image
Abhishek V

Yes, you are right. The "shared" libraries are always loaded from the host app. Few examples are react, react-dom, react-router-dom. These libraries need to be shared because if we have multiple variants in host and remote app, it will cause errors. If you find any libraries with this issue, you can include them in the "shared" libraries in host and remote apps.

Happy coding!! 🚀

Collapse
 
sreecharanshroff profile image
Sreecharan Shroff

Cors error in host-app, could you please help ?

Collapse
 
abhi0498 profile image
Abhishek V

Can you help me with the exact issue or any screenshots? I have uploaded the entire working on github so please do check that once and see if it's of any help.

Collapse
 
sreecharanshroff profile image
Sreecharan Shroff

CORS policy error is there with your github repo as well, enclosed screenshot, please check
localhost/:1 Access to script at 'localhost:3000/assets/remoteEntry.js' from origin 'localhost:5173' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
remoteEntry.js:1 Failed to load resource: net::ERR_FAILED
localhost/:1 Uncaught TypeError: Failed to fetch dynamically imported module: localhost:3000/assets/remoteEntry.js

Collapse
 
sreecharanshroff profile image
Sreecharan Shroff

Image description

Thread Thread
 
abhi0498 profile image
Abhishek V

Thanks for the update, I have edited the blog to reflect the changes and code is also updated on github.
The issue was how the remote app was being served, instead of npx serve dist, we can use npm run preview. This will serve the app on port 4173 instead of 3000, so you would need to edit the vite.config.js file in host-app to reflect this.
These changes are updated on the blog post, let me know if that helps.

Happy Coding!! 🚀

Collapse
 
serifcolakel profile image
Serif COLAKEL

Great article, but i hate module federation with vite in react hot refresh doesn't support. Module federation with webpack you can see your changes in second your shell / container app.

Collapse
 
webjose profile image
José Pablo Ramírez Vargas • Edited

This is a problem that is most likely resolved if your host app is itself a Vite + React project. However, I haven't tested much. It is the lack of "preamble" that kills HMR, and I think the preamble comes with a host name. So I guess, in a micro-frontend world, that one preamble per React MiFe is needed.

It is a complex matter. I once tried to get the attention of Vite about it, but I was shot down.

Collapse
 
leanh profile image
LeAnh1011

Great article, thank you for sharing but I have concerns when using with typescript. Does this import "import List from "todo_components/List";" work without warning or errors in IDE?

Collapse
 
q118 profile image
Shelby Anne

I am working on this now with typescript and the IDE does throw an error. I am sure there is a way to configure it to work, just need to figure out how....

Collapse
 
q118 profile image
Shelby Anne

So in my case, i just needed to add a declarations.d.ts file to the root of my src with:

declare module 'yourRemotePath' {
const RemoteApp: any;
export default RemoteApp;
}

Collapse
 
allenshamrock profile image
Allen Shamrock

Good read as a beginner I was able to understand micro-frontends after reading this blog but I have encountered a bug on the inputs.You should check on that

Collapse
 
abhi0498 profile image
Abhishek V

Thank you for taking the time to read the blog post and for your feedback! I'm glad to hear that you found the post helpful in understanding micro-frontends.

I'm sorry to hear that you encountered a bug with the inputs. To better assist you and potentially address the issue, could you please provide more details about the bug you encountered? With more information, I'll do my best to assist you in resolving the issue.

Collapse
 
allenshamrock profile image
Allen Shamrock

It doesn’t render the newTodos in the UI rather it prints object.object on the input

Thread Thread
 
abhi0498 profile image
Abhishek V

Thanks for pointing that out, I have modified the article to fix the issue. Happy Coding 🚀

Some comments may only be visible to logged-in visitors. Sign in to view all comments.