umi3 + qiankun 实现一个简单的微前端demo | 🏆 技术专题第四期征文

微前端最近是个很火的概念,社区其实也有很多成熟的方案去实现。做为一个umi的重度使用者,就必须来体验一下阿里基于single-spa开发的qiankun了,而umi跟qiankun的最佳结合方案,无疑是qiankun为umi量身订造的插件@umijg/plugin-qiankun。那咱们就跟着官网的文档开造吧。html

Demo实现

建立项目

  1. npx @umijs/create-umi-app
  2. npm install
  3. npm install @umijs/plugin-qiankun --save
  4. npm start

共建立3个项目,一个做为主应用,2个做为子应用前端

配置主应用

  1. 注册子应用。在主应用的.umirc.ts中,开启qiankun。这是umi3插件的一种开启方式,只要插件的key(这里的plugin-qiankun的key就是qiankun)有对应的配置就会开启qiankun。
export default {
  qiankun: {
    master: {
      // 注册子应用信息
      apps: [
        {
          name: 'app1', // 惟一 id,须要与微应用中的package.json中的name一致
          entry: '//localhost:8001', // html entry
        },
        {
          name: 'app2', // 惟一 id,须要与微应用中的package.json中的name一致
          entry: '//localhost:8002', // html entry
        }
      ],
    },
  },
};
复制代码
  1. 装载子应用。在须要加载子应用的路由上,配置microApp属性为子应用的name,便可与路由绑定
export default {
    routes: [
    {
      path: '/',
      component: '../layouts/index.js',
      routes: [
   		{
           path: '/app1',
           microApp: 'app1',
           // 配置Loading属性
           microAppProps: {
            autoSetLoading: true,
            className: 'myContainer',
            wrapperClassName: 'myWrapper',
          }
        },
        {
          // app2
          // ...
        }
      ],
    },
  ],
}
复制代码

配置子应用

  1. 插件注册。在package.json中配置"name": "app1"。同时也须要配置.umirc.ts文件开启qiankun。
export default {
  qiankun: {
    slave: {}
  }
}
复制代码
  1. 环境变量配置。在.env中:
PORT=8001
HMR=none
复制代码

若是遇到了由于dev scripts端口号等问题产生冲突以至于报错,能够先关闭HMR试试react

  1. 若是子应用须要在生命周期中加一些自定义逻辑,则须要在src/app.ts中导出一个对象qiankun
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);
  },
};
复制代码

如今运行3个项目,就能经过主应用来访问子应用了,例如访问http://localhost:8000/app1显示的是子应用的页面。这里须要注意的是,子应用的端口号是_8001_,那么原来的子应用发出的处于同一域名下的后端请求也是在_8001_端口上,但经过主应用来访问,后端请求就会变为_8000_端口,须要作一个代理转发,能够在.umirc.ts中配置proxynpm

这时候应用之间是基本独立的,咱们还须要加入供应用间通信的全局状态json

应用间通信

这里主要使用@umijs/plugin-modeluseModel来将数据全局化。umi应该已经内置了@umijs/preset-react插件集,其中就包括了@umijs/plugin-modelbootstrap

主应用生产数据

src/app.ts中导出一个函数useQiankunStateForSlave,这个函数返回的内容会注入到props中传递给子应用。这里有个坑,useState返回的setXXX方法不能命名为setGlobalState,否则会在mount阶段被qiankun的另外一个同名函数覆盖。后端

// src/app.ts
export function useQiankunStateForSlave() {
const [globalState, setQiankunGlobalState] = useState({ str: 'aaa'})

  return {
    globalState,
    setQiankunGlobalState
  };
}
复制代码

若是主应用也须要用到这个globalState的话,也能够在组件中借助useModel访问@@qiankunStateForSlave:markdown

const { globalState } = useModel('@@qiankunStateForSlave');app

子应用消费数据

子应用经过useModel访问@@qiankunStateFromMaster这个model以获取主应用传过来的propsasync

export default () => {
  const masterProps = useModel('@@qiankunStateFromMaster');

  useEffect(() => {
    masterProps.setQiankunGlobalState({ str: 'bbb' })
  }, [])

  return (
    <p>{JSON.stringify(masterProps.globalState)}</p>
  );
}
复制代码

这样,主应用负责管理globalState,子应用也能取到globalState并调用masterProps上的方法来通知主应用修改globalState

Demo 演示

子应用app2将主应用的globalState从aaa改为bbb 微前端Demo

一些思考

如何管理应用权限

每一个中后台系统,基本都是须要获取用户权限来限制某些页面的访问权。那么在微前端这种使用场景,最好是由主应用来统一管理每一个子应用的用户权限等信息。最好还能不改变原来单独访问子应用时的逻辑。

假设token放在localStorage中,这里想到了2个方案:

  1. 【统一处理】一旦进入任一子应用,主应用取出token发出一个checkLogin请求,后端检查2个子应用的登陆态,只要任一没登陆就触发login逻辑,若是都登陆了就返回2个子应用的userInfo,主应用再分别传递userInfo给子应用。(须要后端加接口支持)
  2. 【分开处理】一旦进入任一子应用,主应用取出2个token发出2个checkLogin请求,经转发至子应用的域名后,子应用的2个后端各自检查登陆态,有则返回userInfo。主应用只要没有拿到2份userInfo,就触发login逻辑,再分别传递userInfo给子应用。

子应用经过当前域名识别出是否位于主应用,是则不走本身的登陆校验逻辑。

能支持Umi2吗?

本人也尝试接入umi2版本的项目,即按照对应版本的qiankun插件

npm install @umijs/plugin-qiankun@umi2 --save

也能运行起来

可是这个版本的qiankun插件,子应用并不能使用@@qiankunStateForSlave来获取masterProps,这个版本是使用const masterProps = useRootExports()来获取masterProps。显然这样的话主应用还得额外处理供umi2子应用使用的全局state,感受不是很方便。

参考资料

umi官网 @umijs/plugin-qiankun

🏆 技术专题第四期 | 聊聊微前端的那些事......