微前端在vue二、vue3中的实战开发

为了业务在原有系统中,集成新项目,咱们常常会使用qiankun这套微前端解决方案,来解决集成诉求。javascript

vue2,vue3在qiankun中的使用实际差异不大,主要路由处理上有些区别,也会在文中指出,会以【vue3】做为标识。html

经过这边文章,能够了解到,如何在vue(2/3)技术栈里使用微前端集成新老项目,主要内容包括:前端

  1. 不一样的路由模式(history, hash),主、子应用分别在开发、生产环境的代码改造、部署;
  2. 在进入子应用前,如何处理鉴权逻辑;
  3. 主、子应用样式隔离的处理;
  4. 主、子应用间的通讯;
  5. 在应用某个路由下加载微应用;
  6. QA,开发过程当中遇到的问题汇总。

经常使用API

下面是经常使用的两个qiankun API,已给出连接,能够在官网查看具体用法:vue

registerMicroApps: 主应用注册微前端应用java

start: 启动微前端应用webpack

一、 不一样的路由模式,主、子应用分别在开发、生产环境的代码改造、部署;

a. 主、子应用路由都为hash模式的改造

开发环境,主应用改造:

import { registerMicroApps, start } from 'qiankun';
registerMicroApps(
  [
    {
      // 微前端应用名
      name: 'app1',
      
      // 微前端启动地址
      entry: '//localhost:8100/',
      
      // 微前端挂载dom
      container: '#app',
      
      // 微前端触发路由
      activeRule: '#/app1',
      
      // 主应用向子应用传递的静态值
      props: {
        name: 'yuxiaoyu',
      },
    },
  ],
);

start();
复制代码

开发环境,子应用改造:

// 入口文件main.js
// 子应用并不用引入qiankun,只要暴露响应的声明周期钩子给主应用使用就ok
// 挂载实例
function render(props: any = {}) {
  const { container } = props;
  app = createApp(App);
  app.use(router);
  app.use(store);
  router.isReady().then(() => {
    app.mount(container ? container.querySelector('#container') : '#container');
  });
}

// 微应用在主应用运行时,主应用会在微应用中挂载window.__POWERED_BY_QIANKUN__,能够用于判断环境
// 官方提供了下面这个webpack注入publicPath的方法, 开发环境咱们这么使用,生产改到vue.config.js中,后面再介绍。
if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

// 若是是独立运行 window.__POWERED_BY_QIANKUN__=undefined 直接render
if (!window.__POWERED_BY_QIANKUN__) {
  render();
}

// 最后暴露的三个方法是固定的,加载渲染以及销毁
export async function bootstrap() { }

export async function mount(props: any) {
  render(props);
}
export async function unmount() {
  app.unmount();
  app._container.innerHTML = "";
  app = null;
  
  // 这里reload的缘由: 由于在项目中,微应用和主应用没有共用导航等信息
  // 至关于两个独立的页面,因此就共用了<div id="app">
  // 在卸载微应用后,为了再把主应用渲染出来,就从新reload了一遍。
  location.reload();
}
复制代码
// router改造
const router = createRouter({
  history: createWebHashHistory(),
  routes,
});

// 由于主应用在激活子应用时,有一个activeRule前缀,因此在hash模式下,咱们须要给每一个一级路由都添加activeRule的前缀,主应用为'#/app1',那么子应用前咱们就加'/app1'就能够了。
export default [
  {
    path: '/app1/fujidaohang',
    redirect: '/app1/fujidaohang/zijidaohang',
    component: BothLayout,
    name: 'fujidaohang',
    meta: {
      title: '父级导航',
      navPosition: 'top',
    },
    children: [
      {
        path: 'zijidaohang',
        component: () => import('@/apps/fujidaohang/views/zijidaohang.vue'),
        name: 'zijidaohang',
        meta: {
          title: '子级导航',
          navPosition: '',
        },
      },
    ],
  },
  {
    path: '/app1/course',
    component: Course,
    name: 'course',
    children: [],
  }
];
复制代码
// vue.config.js改造
const packageName = require('./package.json').name;

module.exports = {
  ...
  
  // 用于主应用识别子应用,固定写法
  configureWebpack: {
    output: {
      library: 'app1',
      libraryTarget: 'umd',
      jsonpFunction: `webpackJsonp_${packageName}`,
    },
  }
}
复制代码

生产构建部署:

构建部署能够选择两种方式:web

同域名、不一样域名部署。vue-router

同域名部署:

1.能够考虑将子应用打包放在static路径下,做为主应用的静态资源引入。这种处理方式的适用场景包含:子应用不须要独立访问、子应用不须要独立部署。这种方式的好处在于,不用运维配合去Nginx配置,本地打包,并使用原有的部署方式。固然,它同时也失去了微前端,主、子独立部署的优势。vue-cli

做为静态资源引入.png

2.还有一种方式,能够将打包后的静态资源在同一台服务器进行部署,而后针对于项目新增部署和Nginx相关配置。在主应用访问的时候能够经过域名加载,也能够经过相对路径进行加载(由于咱们同台机器部署)。这个相对于上面的方案,增长了运维成本,但保留了主子独立发布的特性。json

不用域名部署

这里的方案和上面的2差很少,区别在于资源可能放在不一样的服务器上,因此只能经过域名进行资源的加载(也就是registerMicroApps中配置的entry须要是域名,不能是相对路径了,和开发环境差很少),记得处理跨域。

固然也能够根据须要选用相应的部署方式,官方都有给出详细的介绍: 如何部署

生产构建,主应用改造:

// main.js
// 注册微应用
registerMicroApps(
  [
    {
      name: 'app1',
      
      // 路径改成部署后,微应用要存放在主应用的目录,其他不变
      entry: '/static/index.html',
      container: '#app',
      activeRule: '#/app1',
      props: {
        name: 'kuitos',
      },
    },
  ],
);
复制代码

生产构建,微应用改造:

// main.js
// 去掉qiankun的__webpack_public_path__注入
// if (window.__POWERED_BY_QIANKUN__) {
// __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
// }

// vue.config.js
module.exports = {
  ...
  
  // 这里新增打包资源存放路径,与上面主应用相对应
  publicPath: '/static'
}
复制代码

b. 主、子应用路由都为history模式的改造

开发模式:

这里咱们和hash-dev模式进行对比,只列举差别的部分。

// 主应用
// main.js, 注册微应用有变化
registerMicroApps(
  [
    {
      name: 'app1',
      entry: '//localhost:8101',
      container: '#app',
      
      // 改变点:激活路由由hash模式变为history模式
      activeRule: '/app1',
      props: {
        name: 'yuxiaoyu',
      },
    },
  ],
);

// router.js 

const router = new VueRouter({

  // 由hash改成history模式
  mode: 'history',
  base: process.env.BASE_URL,
  routes,
});
复制代码
// 子应用
// router/index.js
// 【vue3】由于子应用为vue3版本,这里是vue2和vue3自己路由处理上的区别,咱们只是使用了vue3的写法加上了须要的前缀app1
const router = createRouter({
  
  // 改成history模式,而且加activeRule前缀
  history: createWebHistory('/app1'),
  routes,
});

// router.js

// 去掉这里的app1前缀
export default [
  {
    path: '/fujidaohang',
    redirect: '/fujidaohang/zijidaohang',
    component: BothLayout,
    name: 'fujidaohang',
    meta: {
      title: '父级导航',
      navPosition: 'top',
    },
    children: [
      {
        path: 'zijidaohang',
        component: () => import('@/apps/fujidaohang/views/zijidaohang.vue'),
        name: 'zijidaohang',
        meta: {
          title: '子级导航',
          navPosition: '',
        },
      },
    ],
  },
  {
    path: '/course',
    component: Course,
    name: 'course',
    children: [],
  }
];
复制代码

生产构建部署:

这里与hash模式改动点是一致的,只描述不一样点。

主应用改造:

// main.js
// 注册微应用
registerMicroApps(
  [
    {
      // 路径改成部署后,微应用要存放在主应用的目录,其他不变
      entry: '/static/index.html',
    },
  ],
);
复制代码

微应用改造:

// main.js
// 去掉qiankun的__webpack_public_path__注入
// if (window.__POWERED_BY_QIANKUN__) {
// __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
// }

// vue.config.js
module.exports = {
  ...
  
  // 这里新增打包资源存放路径,与上面主应用相对应
  publicPath: '/static'
}
复制代码

二、在进入子应用前,如何处理鉴权逻辑

当进入微应用时,咱们鉴定微应用是否登陆时,咱们能够考虑在微应用作鉴权,也能够在主应用作鉴权。

这里介绍一种在主应用内鉴权的方案。

qiankun里在注册微应用时,registerMicroApps提供了第二个参数,lifeCycles- 可选,全局的微应用生命周期钩子

registerMicroApps(
    [{...}], {
    
    // 这里采用在进入微前端以前进行鉴权,确保进入微前端时,已经登陆。
    beforeLoad: [
      () => {
        if (!auth.check()) {
            vm.$router.push(loginPath);
        }
      },
    ],
  },);
复制代码

三、主、子应用样式隔离的处理

a. 主、子应用都使用antd

这里官方提供了修改antd前缀的方法,如将ant改成dida-ant, 如何确保主应用跟微应用之间的样式隔离

这里ant-design-vue文档里虽然没有提供prefixCls的参数,可是可使用的,源码里相应的处理。

b. 主应用自定义样式与子应用冲突

这里咱们能够采用官方提供的start(options?),将微应用放入浏览器所支持的shadow dom中。

start({
  sandbox: {
    // 主应用 & 子应用样式隔离
    strictStyleIsolation: true, // 放入shadow dom中
  }
});
复制代码

image.png

这里咱们能够看到微前端被放入了shadow-root里,对于shadow dom能够经过这里进行了解。

这样隔离后,在咱们使用ant design这种外部库时会有一些问题,例如popup组件,本来实现是挂在document.body中的,咱们将子应用放到了shadow dom中,那就须要将popup也挂进去。ant design官方提供了方法,搜索getPopupContainer;

四、 主、子应用间的通讯

a. 静态值传递

咱们能够经过注册微应用时,经过props参数进行数据的传递,在mountedrender生命周期中,能够拿到props数据。

registerMicroApps(
  [
    {
      name: 'app1',
      entry: '//localhost:8100',
      container: '#app',
      activeRule: '/app1',
      props: {
        id: 1,
      },
    },
  ],
);
复制代码

b. 通讯机制

qiankun官方提供了建立主、子应用通讯的定义方法。initGlobalState(state)文档连接,初始化后会返回三个方法,onGlobalStateChangesetGlobalStateoffGlobalStateChange,分别是监听,设置和移除。

// 主应用
// 初始化 state
// 调用initGlobalState后,会挂在props中进行传递
const initialState = {
  userInfo: {}, // 用户信息
};
const actions = initGlobalState(initialState);
actions.onGlobalStateChange((newState, oldState) => {

  // newState: 变动后的状态; oldState 变动前的状态
  console.log('mainapp: global state changed', newState, oldState);
});
actions.setGlobalState({
  userInfo: {
    name: 'Zhangsan',
  },
});
actions.offGlobalStateChange();
复制代码
export async function mount(props: any) {
  render(props);
  
  // props 会注入onGlobalStateChange、setGlobalState方法。
  console.log('props :>> ', props);

  app.config.globalProperties.$onGlobalStateChange =props.onGlobalStateChange;
  app.config.globalProperties.$setGlobalState = props.setGlobalState;
  props.onGlobalStateChange((newState: any, oldState: any) => {

    // newState: 变动后的状态; oldState 变动前的状态
    console.log('microapp: global state changed', newState, oldState);
  });
  
  window.document.addEventListener('click', () => {
    props.setGlobalState({
      userInfo: {
        name: Math.random(),
      },
    });
  });
}
复制代码

五、在应用某个路由下加载微应用

背景:

vue3做为主应用, 原系统做为子应用(vue2)。也就是vue3(主)接vue2(子)。在已有路由或组件中引入子应用。 如图:

172*36.png

须要将子应用嵌套在layout组件内

官方提供了一种在应用某个路由下面接入方法

官方提供的方法是在vue2为主应用时接入。因此这里写法上有会一些区别。

{
  path: '/huodongguanli',
  redirect: '/trace_micro/project/projectList',
  component: SideLayout,
  name: 'huodongguanli',
  meta: {
    title: '活动管理',
    navPosition: 'side',
    icon: () => {
      return h(ContactsOutlined);
    },
  },
  children: [
    {
      path: '/trace_micro/project/projectList', // 须要嵌套在二级菜单,且路径不须要一级的path拼接的,这里写绝对路径(这里为业务逻辑,不用关注)
      component: { default: '' }, // 由于这里只是为了声明一个menu项,并不须要组件,(组件内容在子应用中),这里给个含有default的空对象
      name: 'lianluliebiao',
      meta: {
        title: '链路列表',
        navPosition: 'side',
      },
    },
    {
      path: 'chuangjianjiangzuo', // 主应用中的路由 正常写就能够
      name: 'chuangjianjiangzuo',
      meta: {
        title: '建立讲座',
        navPosition: 'side',
      },
    },
  ],
},

// 针对于不用在主应用里菜单显示出来的路由,都进行模糊匹配
// 【vue3】
// 由于vue3中,vue-router4.x[取消了通配符的写法](https://next.router.vuejs.org/zh/guide/migration/index.html#%E5%88%A0%E9%99%A4%E4%BA%86-%EF%BC%88%E6%98%9F%E6%A0%87%E6%88%96%E9%80%9A%E9%85%8D%E7%AC%A6%EF%BC%89%E8%B7%AF%E7%94%B1),因此在匹配的`path`的写法上会有一些区别:
{
  path: '/trace_micro/:pathMatch(.*)*', // 【重要】这里咱们采用router4.x中提供的写法去匹配子应用的路由
  hidden: true,
  component: SideLayout,
  name: 'projectOperate',
  meta: {
    title: '建立链路',
    navPosition: 'side',
  },
}
复制代码

其他的使用方法(start挂载节点等)与官方提供的一致。

六、常见问题

官方有提供常见问题文档,遇到问题时,能够先去这里查看一下是否有相应的匹配解决方案。

这里也列举几个,我在接入项目时候遇到的其它的坑:

一、与主应用共用同一节点,如#app。在返回主应用时,主应用不显示。

这里由于咱们在初始化微前端时,把#app中的内容替换成了子应用的数据,在返回时,并无从新挂载,目前能够经过reload简单处理。

二、与主应用共用同一节点,如#app。dom看起来挂载成功了,可是样式都没有。

这个和第一个又是不一样的状况,主要是由于咱们把子应用的dom结构直接挂载到了主应用上,微前端这一套都没有生效。在mount生命周期中,咱们能够拿到props,这里面挂载了一个container属性,就是挂载微前端的dom节点,能够经过props.container.querySelector('#app')去挂载子应用的vue

三、子应用跳转主应用,$router.push不生效

是由于咱们主、子应用使用的router并非一个实例,注册的路由也不同,因此子应用并不能匹配到主应用的路由,可使用history.push或者location.href进行跳转。

四、在打开主应用打开子应用时,会报js加载不到,或者<等语法不对的问题

通常因为主应用引入子应用的路径不对,检查路径是否能引到子应用资源。

五、主应用引入babel-polyfill时,有时候会babel-polyfill引入两次的问题。

这个先检查一下是否子应用也单独引入babel-polyfill了,若是引入了就都统一放到一处去引用。

如今大部分新的应用都是用vue-cli进行生成,并不会单独引入babel-polyfill,那若是报这个错了,就检查一下是否是,子应用的路径有问题,引入的资源不对。报了引两次的问题,可能也是因为子应用没有引到的问题。和babel-polyfill自己引入没有关系。

六、关于样式隔离

上面也有给出样式隔离的方案,但方案自己仍是有一些问题的。好比咱们提到的popup挂在document.body上的问题,须要处理。可能还会有一些其它的问题要处理。

最简单有效的方法仍是在主、子应用中,尽可能使用style scoped进行组件级别的样式隔离。对于使用同一组件库,不一样配置,能够经过修改组件、样式前缀解决。

以上就是微前端(qiankun)在vue二、3中的实战开发,但愿对各位有所帮助。

相关文章
相关标签/搜索