Photo by Markus Spiske on Unsplash
Web developers are beginning to take a strong interest in the idea of Micro-Frontends. Remix and Next.js are two examples of React meta-frameworks that have a lot of useful capabilities, but they don't always give complete support for creating micro-frontends. By providing built-in support for micro-frontends, Modern.js fills this gap and enables developers to design modular, scalable, and flexible apps.
ByteDance created Modern.js, a progressive web framework built on React. Giving developers an outstanding development experience and empowering applications to provide better user experiences simplifies the process of creating contemporary web applications. In addition to having built-in state management, data gathering, and routing capabilities, Modern.js offers all required setups and tools for React applications.
Key features of Modern.js include:
Rspack is a high-performance JavaScript bundler written in Rust, designed to be compatible with the webpack ecosystem while providing significantly faster build speeds.
Why Rspack?
Rspack was created to solve performance issues at ByteDance, where large app projects had excessively long build and startup times. Rspack addresses these problems with:
This guide will walk us through setting up a Modern.js project, enabling micro-frontend capabilities, and configuring multiple applications to work together seamlessly. We will use pnpm as our package manager and Rspack as the bundler. The shell app will act as the main application that dynamically loads micro-frontends.
1. Initialize the Repository
Start by initializing a new pnpm project in the root directory.
pnpm init
This will create a package.json file to manage dependencies and scripts for your project.
2. Create the Shell Application
Next, create the shell application using Modern.js.
npx @modern-js/create@latest shell-app
( Note: npx @modern-js/create@latest will also commit new changes. So you might have to manually remove .git folder and keep it only on the top level: rm -rf .git )
Select the following options:
Package manager: pnpm
In the latest versions, Rspack is used as the default bundler.
3. Run the Shell Application
Navigate into the shell application directory and start the development server.
cd shell-app pnpm start
Running the application ensures everything is set up correctly. You should see the default Modern.js application running in your browser.
4. Enable Micro-frontend Features
Enable micro-frontend features for the shell application.
pnpm run new
Select the following options:
Enabling micro-frontend features configures the project to support dynamic loading and micro-frontend integration, allowing us to build modular and scalable applications.
5. Update modern.config.ts
Modify the configuration file to include necessary plugins and runtime settings.
// shell-app/modern.config.ts import { appTools, defineConfig } from '@modern-js/app-tools'; import { garfishPlugin } from '@modern-js/plugin-garfish'; export default defineConfig({ runtime: { router: true, state: true, masterApp: {}, }, plugins: [appTools(), garfishPlugin()], });
This configuration enables the router and sets up the shell app as a main application using the garfishPlugin.
(Garfish, developed by the ByteDance team, is a micro-frontend framework similar to single-spa. Modern.js handles the rest under the hood, so we don't need to focus much on it.)
6. Create Custom Entry and Update App.tsx
Remove the default routes folder and create a new App.tsx file in the src directory.
// shell-app/src/App.tsx import { Suspense } from 'react'; import { defineConfig } from '@modern-js/runtime'; import { BrowserRouter as Router, Routes, Route, } from '@modern-js/runtime/router'; import styled from '@modern-js/runtime/styled'; const AppContainer = styled.div` text-align: center; padding: 20px; background-color: #f5f5f5; `; const Title = styled.h1` color: #333; `; const Loading = styled.div` font-size: 1.2em; color: #666; `; const App: React.FC = () => { return (); }; export default defineConfig(App, { masterApp: { manifest: { getAppList: async () => { // here we will add our mfe return []; }, }, }, }); Loading...}> Welcome to the Shell Application} />
Here, we set up the main component for the shell application, configuring it as the master application that will manage micro-frontends.
7. Create the First Micro-frontend Application
Navigate back to the root directory and create the first micro-frontend.
cd .. npx @modern-js/create@latest mfe1
Select the following options:
8. Enable Micro-frontend Feature for mfe1
Enable micro-frontend features for the first micro-frontend application.
cd mfe1 pnpm run new
Select the following options:
9. Update modern.config.ts for mfe1
Update the configuration file for the micro-frontend.
// mfe1/modern.config.ts import { appTools, defineConfig } from '@modern-js/app-tools'; import { garfishPlugin } from '@modern-js/plugin-garfish'; export default defineConfig({ server: { port: 8081, }, deploy: { microFrontend: true, }, plugins: [appTools(), garfishPlugin()], });
This configuration sets the port and enables micro-frontend features.
10. Create Custom Entry and Update App.tsx for mfe1
Remove the default routes folder and create a new App.tsx file in the src directory.
// mfe1/src/App.tsx import React from 'react'; import { BrowserRouter as Router, Routes, Route, Link, } from '@modern-js/runtime/router'; import styled from '@modern-js/runtime/styled'; const Container = styled.div` font-size: 1.2em; color: #333; `; const AppContainer = styled.div` text-align: center; padding: 20px; background-color: #f5f5f5; `; const NavBar = styled.nav` margin-bottom: 20px; a { margin: 0 10px; color: #007acc; text-decoration: none; } `; const Contact: React.FC = () => { returnContact Page ; }; const Home: React.FC = () => { returnHome Page ; }; const About: React.FC = () => { returnAbout Page ; }; const App: React.FC = () => { return (); }; export default App; Home About Contact } /> } /> } />
Here, we set up the micro-frontend application with its routes and navigation.
11. Update the Root package.json
Add a start script in the root package.json to run both the shell app and the micro-frontend simultaneously.
{ "name": "micro-frontends-with-modern.js-and-rspack", "version": "1.0.0", "description": "", "scripts": { "start": "pnpm --filter shell-app --filter mfe1 --parallel start" }, "keywords": [], "author": "", "license": "ISC" }
12. Update App.tsx in shell-app to Load mfe1
Modify the App.tsx file in the shell application to dynamically load and integrate the micro-frontend.
// shell-app/src/App.tsx import React, { Suspense } from 'react'; import { useModuleApps } from '@modern-js/plugin-garfish/runtime'; import { defineConfig } from '@modern-js/runtime'; import { BrowserRouter as Router, Routes, Route, Link, } from '@modern-js/runtime/router'; import styled from '@modern-js/runtime/styled'; const AppContainer = styled.div` text-align: center; padding: 20px; background-color: #f5f5f5; `; const Title = styled.h1` color: #333; `; const Loading = styled.div` font-size: 1.2em; color: #666; `; const App: React.FC = () => { const { MApp } = useModuleApps(); return (); }; export default defineConfig(App, { masterApp: { manifest: { getAppList: async () => { return [ { name: 'mfe1', entry: 'http://localhost:8081/index.js', activeWhen: path => path.includes('mfe1'), }, ]; }, }, }, }); Welcome to the Shell Application Loading...}> MFE1> } />
The useModuleApps hook returns sub-app components and gives us full control of routing.
13. Run Both Applications
Navigate back to the root directory and run the start script to launch both applications.
pnpm start
This command will start both the shell-app and mfe1 concurrently, allowing you to access mfe1 inside the shell-app and navigate its subroutes.
We looked at the vertical split of micro-frontends in the previous section. The horizontal divide with module federation will be discussed in the following section. What do these splits indicate, though?
A horizontal split is a pattern in which various application components—manufactured by separate teams—are merged into a single view or path. Every team is in charge of particular features or portions of the page, which are then smoothly integrated. Because teams may operate independently without impacting the program as a whole, this enables parallel development and speedier deployment.
On the other hand, a vertical split separates the application into discrete micro-frontends, each in charge of a separate feature or area of the system as a whole. These micro-frontends operate as distinct, feature-rich programs that can be accessed via several URLs. Because each micro-frontend can be designed, deployed, and updated individually, this approach improves modularity and scalability.
This section will create mfe2 using Module Federation, focusing on a horizontal split. This means mfe2 will handle a specific part of the application's UI, such as a shared component or feature, that can be dynamically loaded and shared across different parts of the shell application.
1. Create the Second Microfrontend Application
Navigate back to the root directory and create the second micro-frontend ( since it's module-federation we can use not only Modern.js ):
cd .. npx @modern-js/create@latest mfe2
Select the following options:
2. Update modern.config.ts for mfe2
Update the configuration file to include module federation settings.
We added some additional configurations to make module federation work with Modern.js. You can use a regular React app with Rspack instead of Modern.js as well. Additional examples you can find here module-fedration examples.
// mfe2/modern.config.ts import { appTools, defineConfig } from '@modern-js/app-tools'; export default defineConfig({ server: { port: 8082, }, source: { enableAsyncEntry: true, }, plugins: [appTools({ bundler: 'rspack' })], tools: { rspack: (config, { rspack, appendPlugins }) => { // just to remove noise form ts if (config.output) { config.output.publicPath = 'auto'; config.output.uniqueName = 'mfe2'; } else { config.output = { publicPath: 'auto', uniqueName: 'mfe2', }; } if (config.optimization) { delete config.optimization.splitChunks; delete config.optimization.runtimeChunk; } appendPlugins([ new rspack.container.ModuleFederationPlugin({ name: 'mfe2', library: { type: 'var', name: 'mfe2' }, filename: 'static/js/remoteEntry.js', exposes: { './MFE2Component': './src/MFE2Component.tsx', }, shared: { react: { singleton: true }, 'react-dom': { singleton: true }, }, }), ]); }, }, });
3. Create MFE2Component
Create a new component that will be exposed via module federation.
// src/MFE2Component.tsx import React from 'react'; import styled from '@modern-js/runtime/styled'; const Container = styled.div` font-size: 1.2em; color: #007acc; `; const MFE2Component: React.FC = () => { returnThis is MFE2 Component! ; }; export default MFE2Component;
4. Update the Shell App modern.config.ts
We need to add module fedration configuration in the Shell App to consume what mfe2 produce.
import { appTools, defineConfig } from '@modern-js/app-tools'; import { garfishPlugin } from '@modern-js/plugin-garfish'; export default defineConfig({ runtime: { router: true, state: true, masterApp: {}, }, source: { // automatically generated asynchronous boundary via Dynamic Import, allowing the page code to consume remote modules generated by the module federation. enableAsyncEntry: true, }, output: { disableTsChecker: true, }, tools: { rspack: (config, { rspack, appendPlugins }) => { appendPlugins([ new rspack.container.ModuleFederationPlugin({ name: 'host', remotes: { mfe2: 'mfe2@http://localhost:8082/static/js/remoteEntry.js', }, shared: { react: { singleton: true }, 'react-dom': { singleton: true }, }, }), ]); }, }, plugins: [ appTools({ bundler: 'rspack', }), garfishPlugin(), ], });
5. Update the Shell App to Load mfe2
Update the App.tsx in the shell-app to dynamically load mfe2.
// shell-app/src/App.tsx ... import MFE2Component from 'mfe2/MFE2Component'; ...Welcome to the Shell Application
In this updated App.tsx, we've added a link to mfe2 and included it in the manifest configuration.
6. Run Both Applications
Navigate back to the root directory, and update the script.
// package.json ... "scripts": { "start": "pnpm --filter shell-app --filter mfe1 --filter mfe2 --parallel start" }, ...
Run the start script to launch all applications.
pnpm start
This command will start both the shell-app, mfe1, and mfe2 concurrently, allowing you to access mfe2 inside the shell-app and navigate its subroutes.
We have barely scratched the surface of Modern.js by concentrating on just a couple of its features: integrated micro-frontend capabilities and module federation using Rspack. This is merely a simple illustration to help you get ideas for using Modern.js and creating micro-frontends for your applications.
? Find the code for this article on GitHub.
Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.
Copyright© 2022 湘ICP备2022001581号-3