Develop Main App
In the previous Experience micro frontend, an example was used to demonstrate how to create and configure micro frontend sub-applications. Through this chapter, you can further understand how to develop the main application, and its common configuration.
After creating an Modern.js project through the @modern-js/create
command, you can execute pnpm run new
in the project (the modern new
command is actually executed). After selecting the 「micro frontend」 mode, the micro frontend will be installed. Dependencies, just register the plugin manually.
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: true,
},
plugins: [appTools(), garfishPlugin()],
});
Register Sub-app
When the information is provided on the masterApp
configuration, the application will be considered the main application. At present, there are two configuration methods for sub-app information, and these two methods are applied to different scenarios.
Register sub-app info directly
You can register the sub-application information directly through the configuration:
TIP
It can be configured at runtime via the API defineConfig.
When the parameter is a function, it cannot be serialized to the output code, so please configure it through defineConfig when it comes to configuration such as functions
src/App.tsx
import { defineConfig } from '@modern-js/runtime';
defineConfig(App, {
masterApp: {
apps: [
{
name: 'DashBoard',
entry: 'http://127.0.0.1:8081/',
},
{
name: 'TableList',
entry: 'http://localhost:8082',
},
],
},
});
Custom remote app list
This function allows you to pull a list of remote child applications and register them with the runtime framework:
App.tsx
defineConfig(App, {
masterApp: {
manifest: {
getAppList: async () => {
// get from remote api
return [
{
name: 'Table',
entry: 'http://localhost:8081',
// activeWhen: '/table'
},
{
name: 'Dashboard',
entry: 'http://localhost:8082',
// activeWhen: '/dashboard'
},
];
},
},
},
});
Renderer sub-app
There are two ways to load sub-app in micro frontend:
- **Sub-app component ** Get the components of each sub-app, and then you can render the sub-app of micro frontend just like using ordinary'React 'components.
- Centralized routing Through centralized routing configuration, the corresponding sub-app of rendering is automatically activated according to the current page
pathname
.
Sub-app component
Developers can use the useModuleApps
method to obtain the components of each child application.
Through the combined use of the router
component, developers can autonomously render different sub-applications according to different routes.
Suppose our subapp list is configured as follows:
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: true,
},
plugins: [appTools(), garfishPlugin()],
});
App.tsx
as follow:
App.tsx
import { useModuleApps } from '@modern-js/plugin-garfish/runtime';
import {
RouterProvider,
Route,
createBrowserRouter,
createRoutesFromElements,
BrowserRouter,
Link,
Outlet,
} from '@modern-js/runtime/router';
const AppLayout = () => (
<>
<div>
<Link to={'/table'}>load file-based sub-app</Link>
</div>
<div>
<Link to={'/dashboard'}>load self-controlled sub-app</Link>
</div>
<div>
<Link to={'/'}>unmount sub-app</Link>
</div>
<Outlet />
</>
);
export default () => {
const { apps, MApp } = useModuleApps();
// Instead of using the MApp component, you need to use createBrowserRouter to create the route
const router = createBrowserRouter(
createRoutesFromElements(
<Route path="/" element={<AppLayout />}>
{apps.map(app => {
const { Component } = app;
// Fuzzy match, path needs to be written in a pattern similar to abc/*
return (
<Route
key={app.name}
path={`${app.name.toLowerCase()}/*`}
element={
<Component
loadable={{
loading: ({ pastDelay, error }: any) => {
if (error) {
return <div>error: {error?.message}</div>;
} else if (pastDelay) {
return <div>loading</div>;
} else {
return null;
}
},
}}
/>
}
/>
);
})}
</Route>,
),
);
return (
// Use MApp to automatically load sub-applications according to the configured activeWhen parameters (this project is configured in modern.config.ts)
// <BrowserRouter>
// <MApp />
// </BrowserRouter>
// Manually write the Route component to load the sub-application, which is convenient for scenarios that require pre-operation such as authentication
<>
<RouterProvider router={router} />
</>
);
};
Here, the activation route of Table is customized as /table through the Route
component, and the activation route of Dashboard is /dashboard.
Centralized routing
Centralized Routing is a way to centrally configure active routes for subapps. We enable Centralized Routing by adding an activeWhen
field to the subapp list information.
src/App.tsx
import { defineConfig } from '@modern-js/runtime';
defineConfig(App, {
masterApp: {
apps: [
{
name: 'DashBoard',
entry: 'http://127.0.0.1:8081/',
},
{
name: 'TableList',
entry: 'http://localhost:8082',
},
],
},
});
Use useModuleApp
api in Main App, get MApp
component and then render it
Main App: App.tsx
import { useModuleApp } from '@modern-js/plugin-garfish/runtime';
function App() {
const { MApp } = useModuleApps();
return (
<div>
<MApp />
</div>
);
}
After starting the application in this way, accessing the '/table' route will render the'Table 'sub-application, and accessing the'/dashboard 'route will render the'Dashboard' sub-application.
Mix Mode
Of course, sub-application components and centralized routing can be mixed.
- One molecular application is activated as a sub-application component, and the other part is activated as a centralized routing.
- A molecular application can be activated either as a centralized routing or as a sub-application component.
Loading
By configuring the loadable
configuration, loading components can be added for 「centralized routing」 and 「sub-applicati」, and errors, timeouts, flashes, etc. can be considered, so as to provide users with a better user experience. The design and implementation of this function refer to react-loadable, and the basic functions are similar.
function Loading() {
return <div>Loading...</div>;
}
function App(){
return <>
<Table
loadable={{
loading: Loading,
}}
/>
<>
}
Add Error Status
When the micro-frontend sub-application fails to load or render, the loading component
will receive the error
parameter (if there is no error, the error is null).
function Loading({ error }) {
if (error) {
return <div>Error msg {error?.message}</div>;
} else {
return <div>Loading...</div>;
}
}
Avoid Loading Flash Back
Sometimes the display time of the loading component may be less than 200ms, and the loading component will flash back at this time. Many user studies have proved that the loading flash back situation will cause the user to perceive that the loading content takes longer than the actual time.
When loading is less than 200ms, if the content is not displayed, the user will think it is faster.
Therefore, the loading component also provides the pastDelay
parameter, which will only be true when it exceeds the set delay display. You can set the delay duration through the delay
parameter.
function Loading({ error, pastDelay }) {
if (error) {
return <div>Error! {error?.message}</div>;
} else if (pastDelay) {
return <div>Loading...</div>;
} else {
return null;
}
}
The default value of delay
is 200ms
, you can set the delay display time through delay
in loadable
.
function App(){
return <>
<Table
loadable={{
loading: Loading,
delay: 300 // 0.3 seconds
}}
/>
<>
}
Add Timeout State
Sometimes because of the network, the micro-front-end sub-application fails to load, resulting in the loading state being displayed all the time, which is very bad for users, because they don't know the right response to get a specific response, whether they need to refresh the page, by Increasing the timeout state can solve this problem well.
The loading component will get the timeOut
parameter when timeout, when the micro frontend application loads timeout, it will get the timeOut
property value of true.
function Loading({ error, timeOut, pastDelay }) {
if (error) {
return <div>Error! {error?.message}</div>;
} else if (timeOut) {
return <div>Loading timed out, please refresh the page... </div>;
} else if (pastDelay) {
return <div>Loading...</div>;
} else {
return null;
}
}
The timeout state is off and can be enabled by setting the timeout
parameter in loadable
:
function App(){
return <>
<Table
loadable={{
loading: Loading,
timeOut: 10000 // 10s
}}
/>
<>
}