体验微前端

通过本章你可以了解到:

  • 如何创建微前端项目的主应用、子应用。
  • 微前端项目开发的基本流程。

创建应用

目前项目的路由模式分为以下三种

  • 约定式路由 (设置 router: true 并使用文件路由)
  • 自控式路由 (设置 router: true 并自己创建 BrowserRouter 等)
  • 其他 (设置 router: false 项目内自己安装和使用 react-router-dom)

在本教程中我们会为主应用创建两个子应用 Table 和 Dashboard (Table 为约定式路由,Dashboard 为自控式路由)

创建约定式路由主应用

通过命令行工具初始化项目:

mkdir masterApp && cd masterApp
npx @modern-js/create@latest
? 请选择开发语言:TS
? 请选择包管理工具:pnpm

完成项目创建后我们可以通过 pnpm run 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()],
});

然后我们在 routes 文件夹下新建两个目录

  • table (用于加载约定式路由子应用)
  • dashboard (用于加载自控式路由子应用)

在这两个目录下我们需要新建一个 $.tsx 文件作为主应用约定式路由的入口($ 代表模糊匹配,即 /table/table/test 都会匹配到这个 $.tsx 作为该路由的入口文件,这会保证在微前端场景下正确加载子应用路由)

加载约定式路由子应用

src/routes/table/$.tsx
import { useModuleApps } from '@modern-js/plugin-garfish/runtime';

const Index = () => {
  const { Table } = useModuleApps();

  return (
    <div>
      <Table />
    </div>
  );
};

export default Index;

加载自控式路由子应用

src/routes/dashboard/$.tsx
import { useModuleApps } from '@modern-js/plugin-garfish/runtime';

const Index = () => {
  const { Dashboard } = useModuleApps();

  return (
    <div>
      <Dashboard />
    </div>
  );
};

export default Index;

路由跳转

此时主应用配置已经完成,通过路由即可加载子应用,修改主应用的 layout.tsx 来跳转路由

src/route/layout.tsx
import { Outlet, Link } from '@modern-js/runtime/router';

const Layout = () => (
  <div>
    <div>
      <Link to={'/table'}>加载约定式路由子应用</Link>
    </div>
    <div>
      <Link to={'/dashboard'}>加载自控式路由子应用</Link>
    </div>
    <div>
      <Link to={'/'}>卸载子应用</Link>
    </div>
    <Outlet />
  </div>
);

export default Layout;

创建自控式路由主应用

通过命令行工具初始化项目:

mkdir masterApp && cd masterApp
npx @modern-js/create@latest
? 请选择开发语言:TS
? 请选择包管理工具:pnpm

完成项目创建后我们可以通过 pnpm run 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()],
});

由于是自控式路由,我们删除掉 routes 文件夹并在 src 目录下增加 App.tsx 文件,此处如果使用的 非 MApp 组件,需要通过 React Router v6createBrowserRouter API 来创建路由

加载子应用

src/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, Table, Dashboard } = useModuleApps();

  // 使用的不是 MApp 组件,需要使用 createBrowserRouter 来创建路由
  const router = createBrowserRouter(
    createRoutesFromElements(
      <Route path="/" element={<AppLayout />}>
        <Route key={'table'} path={'table/*'} element={<Table />} />
        <Route key={'dashboard'} path={'dashboard/*'} element={<Dashboard />} />
      </Route>
    )
  );

  return (
    // 方法一:使用 MApp 自动根据配置的 activeWhen 参数加载子应用(本项目配置在 modern.config.ts 中)
    // <BrowserRouter>
    //   <MApp />
    // </BrowserRouter>

    // 方法二:手动写 Route 组件方式加载子应用,方便于需要鉴权等需要前置操作的场景
    <>
      <RouterProvider router={router} />
    </>
  );
};

创建其他主应用

项目内自己安装和使用 react-router-dom , 与自控式路由唯一的区别是, 设置 router: false 后 plugin-garfish 无法获得 useLocation useHref 等 hook 来辅助计算 basename 和主子应用路由同步

react-router-dom@6
react-router-dom@5
import { useLocation, useHref } from 'react-router-dom';
const App = () => {
  const basename = useHref('/table');
  const { Table } = useModuleApps();
  return <Table useLocation={useLocation} basename={basename} />;
};

创建约定式路由子应用

通过命令行工具初始化项目:

mkdir table && cd table
npx @modern-js/create@latest

按照如下选择,生成项目:

? 请选择开发语言:TS
? 请选择包管理工具:pnpm

我们执行 pnpm run new 来开启 微前端 功能:

? 请选择你想要的操作 启用可选功能
? 请选择功能名称 启用「微前端」模式

接下来,让我们注册微前端插件并修改 modern.config.ts,添加微前端子应用的配置 deploy.microFrontend

modern.config.ts
import { appTools, defineConfig } from '@modern-js/app-tools';
import { garfishPlugin } from '@modern-js/plugin-garfish';

export default defineConfig({
  dev: {
    port: 8081,
  },
  runtime: {
    router: true,
    state: true,
  },
  deploy: {
    microFrontend: true,
  },
  plugins: [appTools(), garfishPlugin()],
});

添加 src/routes/page.tsx 代码

src/routes/page.tsx
const Index = () => {
  return (
    <div className="container-box">subApp: 约定式路由的子应用的根路由</div>
  );
};

export default Index;

创建自控式路由子应用

通过命令行工具初始化项目:

mkdir dashboard && cd dashboard
npx @modern-js/create@latest

按照如下选择,生成项目:

? 请选择开发语言:TS
? 请选择包管理工具:pnpm

我们执行 pnpm run new 来开启 微前端

? 请选择你想要的操作 启用可选功能
? 请选择功能名称 启用「微前端」模式

接下来,让我们注册微前端插件并修改 modern.config.ts,添加微前端子应用的配置 deploy.microFrontend

modern.config.ts
import { appTools, defineConfig } from '@modern-js/app-tools';
import { garfishPlugin } from '@modern-js/plugin-garfish';

export default defineConfig({
  dev: {
    port: 8082,
  },
  runtime: {
    router: true,
    state: true,
  },
  deploy: {
    microFrontend: true,
  },
  plugins: [appTools(), garfishPlugin()],
});

自控式路由需要删除掉 routes 文件夹并在 src 目录下新建 App.tsx

并在 src/App.tsx 添加代码,注意需要从 props 中解析并传递 basenameBrowserRouter

src/App.tsx
import { BrowserRouter, Route, Routes } from '@modern-js/runtime/router';

export default (props: { basename: string }) => {
  const { basename } = props;

  return (
    <BrowserRouter basename={basename}>
      <Routes>
        <Route index element={<div>自控式路由子应用根路由</div>} />
        <Route path={'path'} element={<div>自控式路由子应用子路由</div>} />
      </Routes>
    </BrowserRouter>
  );
};

调试

按顺序在目录执行 pnpm run dev 命令启动应用:

  • masterApp 主应用 http://localhost:8080
  • table 子应用(约定式路由) http://localhost:8081
  • dashboard 子应用(自控式路由) http://localhost:8082

访问主应用地址 http://localhost:8080

在完成了微前端整体开发流程的体验后,你可以进一步了解如何 开发主应用

Modern.js 微前端和直接使用 Garfish 的区别

使用纯 Garfish API 开发微前端应用时

  • 主应用:
    • 安装 Garfish 依赖并使用 Garfish.run 注册子应用 参考
    • 提供一个常驻的 DOM 节点供子应用挂载 参考
  • 子应用:

区别于直接使用 Garfish 运行时 API 开发微前端项目,Modern.js 的微前端方案更加开箱即用。 使用 pnpm new 启用微前端模式后会自动在 Modern.js 应用中集成 Garfish 插件,在 Garfish 插件的加持下,你只需要

  • 主应用:
    • 配置 runtime.masterApp.apps 参数注册子应用
    • 使用 useModuleApps API 获取子应用实例并在组件中完成渲染
  • 子应用:
    • 配置 deploy.microFrontend

所以插件中为你做了如下事情

  • 帮助你通过 Garfish 运行时 API 自动注册子应用(主应用)
  • useModulesApps 函数的返回值提供了一个常驻的 DOM 节点供子应用挂载(主应用)
  • 帮助你正确导出了 provider(子应用)
  • 帮助你正确设置了 basenameModern.js 运行时提供 Router 实例,如果是自控式路由手动引入的 react-router-dom 那么需要从 App.tsxprops 中获取 basename 手动传递给引入的 Router 实例(子应用)

常见问题

自查手册: https://www.garfishjs.org/issues/