开发主应用
在上一章 体验微前端 通过一个示例演示了如何创建和配置微前端子应用,通过本章你可以进一步了解如何开发主应用,以及它的常见配置。
在通过 @modern-js/create
命令创建 Modern.js 工程后,可以在项目中执行 pnpm run new
(实际执行了 modern new
命令),在选择了「微前端」模式后,会安装微前端依赖依赖,只需手动注册插件即可。
modern.config.ts
import { appTools, defineConfig } from '@modern-js/app-tools';
import { garfishPlugin } from '@modern-js/plugin-garfish';
export default defineConfig({
runtime: {
router: true,
masterApp: {
apps: [{
name: 'Table',
entry: 'http://localhost:8081',
// activeWhen: '/table'
}, {
name: 'Dashboard',
entry: 'http://localhost:8082'
// activeWhen: '/dashboard'
}]
},
},
plugins: [appTools(), garfishPlugin()],
});
注册子应用信息
当在 masterApp
配置上提供了信息后,将会认为该应用为主应用,目前存在两种子应用信息的配置方式,这两种方式分别应用于不同的场景。
直接注册子应用信息
可以直接通过配置注册子应用信息:
提示
可以通过 API defineConfig 在运行时进行配置。
当参数为函数时无法被序列化到产物代码,所以在涉及到函数之类的配置时请通过 defineConfig 来进行配置
src/App.tsx
import { defineConfig } from '@modern-js/runtime';
defineConfig(App, {
masterApp: {
apps: [{
name: 'Table',
entry: 'http://localhost:8081',
// activeWhen: '/table'
}, {
name: 'Dashboard',
entry: 'http://localhost:8082'
// activeWhen: '/dashboard'
}]
},
});
自定义远程应用列表
通过该函数可以拉取远程的子应用列表,并将其注册至运行时框架:
App.tsx
defineConfig(App, {
masterApp: {
manifest: {
getAppList: async () => {
// 可以从其他远程接口获取
return [
{
name: 'Table',
entry: 'http://localhost:8081',
// activeWhen: '/table'
},
{
name: 'Dashboard',
entry: 'http://localhost:8082',
// activeWhen: '/dashboard'
},
];
},
},
},
});
渲染子应用
在微前端中分为两种加载子应用的方式:
- 子应用组件 获取到每个子应用的组件,之后就可以像使用普通的
React
组件一样渲染微前端的子应用。
- 集中式路由 通过集中式的路由配置,自动根据当前页面
pathname
激活渲染对应的子应用。
子应用组件
开发者使用 useModuleApps
方法可以获取到各个子应用的组件。
再通过 router
组件的结合运用,开发者可以自控式的根据不同的路由渲染不同的子应用。
假设我们的子应用列表配置如下:
modern.config.ts
import { appTools, defineConfig } from '@modern-js/app-tools';
import { garfishPlugin } from '@modern-js/plugin-garfish';
export default defineConfig({
runtime: {
router: true,
masterApp: {
apps: [{
name: 'Table',
entry: 'http://localhost:8081',
// activeWhen: '/table'
}, {
name: 'Dashboard',
entry: 'http://localhost:8082'
// activeWhen: '/dashboard'
}]
},
},
plugins: [appTools(), garfishPlugin()],
});
编辑主应用 App.tsx
文件如下:
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'}>加载约定式路由子应用</Link>
</div>
<div>
<Link to={'/dashboard'}>加载自控式路由子应用</Link>
</div>
<div>
<Link to={'/'}>卸载子应用</Link>
</div>
<Outlet />
</>
);
export default () => {
const { apps, MApp } = useModuleApps();
// 使用的不是 MApp 组件,需要使用 createBrowserRouter 来创建路由
const router = createBrowserRouter(
createRoutesFromElements(
<Route path="/" element={<AppLayout />}>
{apps.map(app => {
const { Component } = app;
// 模糊匹配,path 需要写成类似 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 (
// 方法一:使用 MApp 自动根据配置的 activeWhen 参数加载子应用(本项目配置在 modern.config.ts 中)
// <BrowserRouter>
// <MApp />
// </BrowserRouter>
// 方法二:手动写 Route 组件方式加载子应用,方便于需要鉴权等需要前置操作的场景
<>
<RouterProvider router={router} />
</>
);
};
这里通过 Route
组件自定义了 Table 的激活路由为 /table, Dashboard 的激活路由为 /dashboard。
集中式路由
集中式路由 是将子应用的激活路由集中配置的方式。我们给子应用列表信息添加 activeWhen
字段来启用 集中式路由。
src/App.tsx
import { defineConfig } from '@modern-js/runtime';
defineConfig(App, {
masterApp: {
apps: [{
name: 'Table',
entry: 'http://localhost:8081',
// activeWhen: '/table'
}, {
name: 'Dashboard',
entry: 'http://localhost:8082'
// activeWhen: '/dashboard'
}]
},
});
然后在主应用中使用 useModuleApp
方法获取 MApp
组件, 并在主应用渲染 MApp
。
主应用:App.tsx
import { useModuleApp } from '@modern-js/plugin-garfish/runtime';
function App() {
const { MApp } = useModuleApps();
return (
<div>
<MApp />
</div>
);
}
这样启动应用后,访问 /table
路由,会渲染 Table
子应用,访问 /dashboard
路由,会渲染 Dashboard
子应用。
两种模式混用
当然 子应用组件 和 集中式路由 是可以混合使用的。
- 一部分子应用作为 子应用组件 激活,另外一部分作为 集中式路由 激活。
- 一部分子应用既可以作为 集中式路由 激活,也可以作为 子应用组件 激活。
添加 loading
通过配置 loadable
配置,可以为「集中式路由」、「子应用」添加 loading 组件,并可以考虑错误、超时、闪烁等情况的出现,从而为用户提供更好的用户体验。该功能的设计与实现参考至 react-loadable,基本功能较为相似。
function Loading() {
return <div>Loading...</div>;
}
function App(){
return <>
<Table
loadable={{
loading: Loading,
}}
/>
<>
}
增加错误状态
当微前端子应用加载失败或渲染失败时,loading component
将会接收 error
参数(若没有错误时 error 是 null)
function Loading({ error }) {
if (error) {
return <div>Error msg {error?.message}</div>;
} else {
return <div>Loading...</div>;
}
}
避免 loading 闪退
有时 loading 组件的显示时间可能小于 200ms,这个时候会出现 loading 组件闪退的情况。许多用户研究证明,loading 闪退的情况会导致用户感知加载内容的耗时比实际耗时更长,在 loading 小于 200ms 时,不展示内容,用户会认为它更快。
所以 loading 组件还提供了 pastDelay
参数,超过设置的延迟展示时才会为 true,可以通过 delay
参数设置延迟的时长
function Loading({ error, pastDelay }) {
if (error) {
return <div>Error! {error?.message}</div>;
} else if (pastDelay) {
return <div>Loading...</div>;
} else {
return null;
}
}
delay
的默认值为 200ms
,可以通过 loadable
中的 delay
来设置延迟展示的时间
function App(){
return <>
<Table
loadable={{
loading: Loading,
delay: 300 // 0.3 seconds
}}
/>
<>
}
增加超时状态
有时因为网络的原因,从而导致微前端子应用加载失败,从而导致一直展示 loading 的状态,这对于用户而言非常糟糕,因为他们不知道合适才会获得具体的响应,他们是否需要刷新页面,通过增加超时状态可以很好的解决该问题。
loading 组件在超时时会获得 timeOut
参数,当微前端应用加载超时时将会获得 timeOut
属性值为 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;
}
}
超时状态是关闭的,可以通过在 loadable
中设置 timeout
参数开启
function App(){
return <>
<Table
loadable={{
loading: Loading,
timeOut: 10000 // 10s
}}
/>
<>
}