@umijs/plugin-qiankun

Umi plugin for qiankun.

NPM version Build Status NPM downloads

启用方式

  1. yarn add @umijs/plugin-qiankun -D
  2. 配置 qiankun 开启。

介绍

Umi 应用一键开启 qiankun 微前端模式。

Examples

导航是主应用,App1/App2 是子应用,App1/App2 也支持单独打开。主应用可以嵌套 APP1 和 APP2,App1 也可以嵌套 App2。

$ yarn
$ yarn build
$ cd packages/plguin-qiankun && yarn start

Features

  • ✔︎ 基于 qiankun
  • ✔︎ 支持主应用和子应用都用 umi
  • ✔︎ 支持通过 <MicroApp /> 组件引入子应用
  • ✔︎ 父子应用通讯
  • ✔︎ 子应用运行时配置自定义 bootstrap()mount()unmount()
  • ✔︎ 主应用、子应用联调
  • ✔︎ 嵌套子应用

Usage

主应用配置

第一步:注册子应用

子应用注册有两种方式,二选一即可

a. 插件构建期配置子应用
export default {
qiankun: {
master: {
// 注册子应用信息
apps: [
{
name: 'app1', // 唯一 id
entry: '//localhost:7001', // html entry
},
{
name: 'app2', // 唯一 id
entry: '//localhost:7002', // html entry
},
],
},
},
};
b. 运行时动态配置子应用(src/app.ts 里开启)
// 从接口中获取子应用配置,export 出的 qiankun 变量是一个 promise
export const qiankun = fetch('/config').then(({ apps }) => ({
// 注册子应用信息
apps,
// 完整生命周期钩子请看 https://qiankun.umijs.org/zh/api/#registermicroapps-apps-lifecycles
lifeCycles: {
afterMount: props => {
console.log(props);
},
},
// 支持更多的其他配置,详细看这里 https://qiankun.umijs.org/zh/api/#start-opts
}));

完整的主应用配置项看这里 masterOptions 配置列表

第二步:装载子应用

子应用的装载有两种方式,二选一即可:

a. 使用路由绑定的方式
建议使用这种方式来引入自带路由的子应用。

假设我们的系统之前有这样的一些路由:

export default {
routes: [
{
path: '/',
component: '../layouts/index.js',
routes: [
{
path: '/app1',
component: './app1/index.js',
routes: [
{
path: '/app1/user',
component: './app1/user/index.js',
},
],
},
{
path: '/',
component: './index.js',
},
],
},
],
}

我们现在想在 /app1/project/app2 这两个路径时分别加载微应用 app1 和 app2,只需要增加这样一些配置即可:

export default {
routes: [
{
path: '/',
component: '../layouts/index.js',
routes: [
{
path: '/app1',
component: './app1/index.js',
routes: [
{
path: '/app1/user',
component: './app1/user/index.js',
},
+ // 配置微应用 app1 关联的路由
+ {
+ path: '/app1/project',
+ microApp: 'app1',
+ },
],
},
+ // 配置 app2 关联的路由
+ {
+ path: '/app2',
+ microApp: 'app2'
+ },
{
path: '/',
component: './index.js',
},
],
},
],
}
b. 使用 <MicroApp /> 组件的方式
建议使用这种方式来引入不带路由的子应用。 否则请自行关注微应用依赖的路由跟当前浏览器 url 是否能正确匹配上,否则很容易出现微应用加载了,但是页面没有渲染出来的情况。

我们可以直接使用 React 标签的方式加载我们已注册过的子应用:

import { MicroApp } from 'umi';
export function MyPage() {
return (
<div>
<div>
+ <MicroApp name="app1" />
</div>
</div>
)
}
loading 动画与组件样式

你可以通过配置 autoSetLoading 的方式,开启微应用的 loading 动画。

import { MicroApp } from 'umi';
export function MyPage() {
return (
<div>
<div>
<MicroApp name="app1" autoSetLoading />
</div>
</div>
)
}

默认情况下,当我们检测到你使用的是 antd 组件库时,loading 动画使用的是 antd Spin 组件。

如果你需要定制自己的 loading 动画,或者修改组件的样式,你可以这样处理:

import { MicroApp } from 'umi';
export function MyPage() {
return (
<div>
<MicroApp
name="app1"
autoSetLoading
// 设置自定义 loading 动画
loader={loading => <div>loading: {loading}</div>}
// 微应用容器 class
className="myContainer"
// wrapper class,仅开启 loading 动画时生效
wrapperClassName="myWrapper"
/>
</div>
)
}

路由模式下,你可以这样设置一些静态配置开启 loading 动画:

{
path: '/user',
microApp: 'user',
microAppProps: {
autoSetLoading: true,
className: 'myContainer',
wrapperClassName: 'myWrapper',
}
}

或者,你可以通过设置 autoSetLoading false 来关闭自动的 loading 动画:

import { MicroApp } from 'umi';
export function MyPage() {
return (
<div>
<div>
<MicroApp
name="app1"
// 关闭 loading 动画
autoSetLoading={false}
/>
</div>
</div>
)
}

子应用配置

第一步:插件注册(config.js)

export default {
qiankun: {
slave: {}
}
}

第二步:配置运行时生命周期钩子(可选)

如果你需要在子应用的生命周期期间加一些自定义逻辑,可以在子应用的 src/app.ts 里导出 qiankun 对象,并实现每一个生命周期钩子,其中钩子函数的入参 props 由主应用自动注入。

export const qiankun = {
// 应用加载之前
async bootstrap(props) {
console.log('app1 bootstrap', props);
},
// 应用 render 之前触发
async mount(props) {
console.log('app1 mount', props);
},
// 应用卸载之后触发
async unmount(props) {
console.log('app1 unmount', props);
},
};

环境变量配置

为了获得更好地本地开发及调试体验,我们建议您提前在子应用中指定应用启动的具体端口号,如通过.env指定

PORT=8081

详细配置参考:https://umijs.org/zh/guide/env-variables.html#port

父子应用通讯

有两种方式可以实现

配合 useModel 使用(推荐)

需确保已安装 @umijs/plugin-model@umijs/preset-react

  1. 主应用使用下面任一方式透传数据:

    1. 如果你用的 MicroApp 组件模式消费微应用,那么数据传递的方式就跟普通的 react 组件通信是一样的,直接通过 props 传递即可:

      function MyPage() {
      const [name, setName] = useState(null);
      return <MicroApp name={name} onNameChange={newName => setName(newName)} />
      }
    2. 如果你用的 路由绑定式 消费微应用,那么你需要在 src/app.ts 里导出一个 useQiankunStateForSlave 函数,函数的返回值将作为 props 传递给微应用,如:

      // src/app.ts
      export function useQiankunStateForSlave() {
      const [globalState, setGlobalState] = useState({});
      return {
      globalState,
      setGlobalState,
      }
      }
  2. 微应用中会自动生成一个全局 model,可以在任意组件中获取主应用透传的 props 的值。

    import { useModel } from 'umi';
    function MyPage() {
    const masterProps = useModel('@@qiankunStateFromMaster');
    return <div>{ JSON.strigify(masterProps) }</div>;
    }

    或者可以通过高阶组件 connectMaster 来获取主应用透传的 props

    import { connectMaster } from 'umi';
    function MyPage(props) {
    return <div>{ JSON.strigify(props) }</div>;
    }
    export default connectMaster(MyPage);
  3. <MicroApp /> 的方式一同使用时,会额外向子应用传递一个 setLoading 的属性,在子应用中合适的时机执行 masterProps.setLoading(false),可以标记微模块的整体 loading 为完成状态。

基于 props 传递

类似 react 中组件间通信的方案

  1. 主应用中配置 apps 时以 props 将数据传递下去(参考主应用运行时配置一节)

    // src/app.js
    export const qiankun = fetch('/config').then(config => {
    return {
    apps: [
    {
    name: 'app1',
    entry: '//localhost:2222',
    props: {
    onClick: event => console.log(event),
    name: 'xx',
    age: 1,
    },
    },
    ],
    };
    });
  2. 子应用在生命周期钩子中获取 props 消费数据(参考子应用运行时配置一节)

嵌套子应用

除了导航应用之外,App1 与 App2 均依赖浏览器 url,为了让 App1 嵌套 App2,两个应用同时存在,我们需要在运行时将 App2 的路由改为 memory 类型。

  1. 在 App1 中加入 master 配置
export default {
qiankun: {
master: {
// 注册子应用信息
apps: [
{
name: 'app2', // 唯一 id
entry: '//localhost:7002', // html entry
},
],
},
},
};
  1. 通过 <MicroAppWithMemoHistory /> 引入 App2
import { MicroAppWithMemoHistory } from 'umi';
export function MyPage() {
return (
<div>
<div>
+ <MicroAppWithMemoHistory name="app2" url="/user" />
</div>
</div>
)
}

API

MasterOptions

配置说明类型是否必填默认值
apps子应用配置App[]
sandbox是否启用沙箱,详细说明booleanfalse
prefetch是否启用 prefetch 特性,详细说明boolean | 'all'true

App

配置说明类型是否必填默认值
name子应用唯一 idstring
entry子应用 html 地址string | { script: string[], styles: [] }
props主应用传递给子应用的数据object{}

升级指南

v2.3.0 完全兼容 v2 之前的版本,但我们还是建议您能升级到最新版本已获得更好的开发体验。

  1. 移除无必要的应用配置

    export default {
    qiankun: {
    master: {
    apps: [
    {
    name: 'microApp',
    entry: '//umi.dev.cnd/entry.html',
    - base: '/microApp',
    - mountElementId: 'root-subapp',
    - history: 'browser',
    }
    ]
    }
    }
    }
  2. 移除无必要的全局配置

    export default {
    qiankun: {
    master: {
    apps: [],
    - defer: true,
    }
    }
    }
  3. 移除不必要的挂载容器

    -export default MyContainer() {
    - return (
    - <div>
    - <div id="root-subapp"></div>
    - </div>
    - )
    -}
  4. 关联微应用

    比如我们之前配置了微应用名为 microApp 的 base 为 /microApp ,mountElementId 为 subapp-container, 那么我们只需要(二选一):

    a. 增加 /microApp 的路由

    export default {
    routes: [
    ...,
    { path: '/microApp', microApp: 'microApp' }
    ]
    }

    使用路由关联模式时,不再需要微应用的 base 配置必须跟主应用中的保持一致了。

    b. 在 /microApp 路由对应的组件里使用 MicroApp

    export default {
    routes: [
    ...,
    { path: '/microApp', component: 'MyPage' }
    ]
    }
    import { MicroApp } from 'umi';
    export default MyPage() {
    return (
    <div>
    <MicroApp name="microApp" />
    </div>
    )
    }
  5. 移除一些无效配置,如 手动添加子应用路由配置

CHANGELOG

与 @umijs/plugin-qiankun 2.3.0 之前版本的变化

  • 主应用注册子应用时不再需要手动配置 base 和 mountElementId。

    这类方式会导致很多关联问题,最典型的是如果我们需要将子应用挂载到某一个具体的子路由下时,常出现由于挂载点还未初始化或已被销毁导致的问题。

    现在只需要在注册完子应用后,在期望的路由下指定需要挂载的子应用的 name 即可。

  • 可以直接通过 <MicroApp /> 组件的方式在任意位置挂载自己的子应用。详见 API 说明

  • 不再支持主应用是 browser 路由模式,子应用是 hash 路由的混合模式。如果有场景需要可以通过自己使用 <MicroApp />组件加载子应用。

  • 移除了 base、mountElementId、defer 等配置,现在有更好的方式来解决这类问题,参见第一条。

  • rename jsSandbox -> sandbox,来自 qiankun2.0 的升级。

  • 完全兼容 1.x 插件。

Roadmap

  • 动态 history type 支持(即将到来 🎉),依赖 umi: ^3.2.7, @umijs/plugin-qiankun: ^2.4.0

    通过运行时设置微应用 props 的方式,修改微应用 history 相关配置,从而解耦微应用配置,如:

    // HistoryOptions 配置见 https://github.com/ReactTraining/history/blob/master/docs/api-reference.md
    type HistoryProp = { type: 'browser' | 'memory' | 'hash' } & HistoryOptions;
    <MicroApp history={{ type: 'browser', basename: '/microApp' }} />
  • 运行时统一,针对多层嵌套微应用场景

  • 微应用自动 mountElementId,避免多个 umi 子应用 mountElementId 冲突

  • 自动 loading

  • 本地集成开发支持

相关