QIANKUN 真实项目改造及开发过程遇到的问题

1,226 阅读9分钟

winstack 项目改造


背景:winstack项目由多项目组合起来,如kv、winstore、安全中心、存储、运维、网络、系统多个模块,像kv项目使用 angular技术, winstore、安全中心 使用 vue技术
image.png

kv项目是一个多页面angular 老项目,如果要改造微应用,相对比较麻烦,需要改动地方比较多,所以鉴于以上问题。就决定用微应用去加载iframe kv项目,如点击虚拟化资源菜单先去加载app-vue-kv应用,iframe会加载kv 指定路径url。
image.png

不要对 iframe 抱有偏见,它也是微前端的一种实现方式,如果页面上无弹窗、无全屏等操作,iframe 也是很好用的。配置缓存和 cdn 加速,如果是内网访问,也不会很慢。

iframeqiankun 可以并存,jQuery 多页应用使用 iframe 接入就挺好,什么时候什么场景该用哪种方案,具体情况具体分析。

微前端框架面临的两大共性问题

实际上所有的微前端框架都面临这两大共性问题。当你解决了这两大问题之后,你的微前端框架的运行时,就已经基本可用了。

  • 问题一是应用的加载与切换。包括路由的处理、应用加载的处理和应用入口的选择。
  • 问题二是应用的隔离与通信。这是应用已经加载之后面临的问题,它们包括 JS 的隔离(也就是副作用的隔离)、样式的隔离、也包括父子应用和子子应用之间的通信问题。


image.png

应用路由与 winstack 路由系统


首先我们来看应用路由的问题。在微前端体系结构下,路由的划分往往是如图这个样子的,这也是一种比较简单的方案。

我们主应用加载了应用 app-vue-kv 和应用 winstore。这个时候对于主应用来讲,我就有两个路由 
/app-vue-kv/* 和 /winstore/* 。在主应用 路由 A 下我会加载应用 app-vue-kv,在主应用 另一个路由 B 下加载应用winstore

我们需要通过点击主应用左侧菜单跳转加载应用,所以面临这问题后,去官网找答案,所以凡是遇事不要慌的。

image.png

如何在主应用的某个路由页面加载微应用

主应用路由设置

components.router.js
path:"/app-vue-kv"

  {
    path: "/app-vue-kv",
    component: Layout,
    name: "Compute",
    meta: {
      title: "计算",
      id: "57064191-4fc8-47f7-afad-1b892397b2bd"
    },
    // redirect: "/compute/virtualizedResources",
    children: [
      {
        path: "virtualizedResources",
        component: () =>
          import(
            /* webpackChunkName: "virtualizedResources" */ "@/views/compute/VirtualizedResources.vue"
          ),
        name: "VirtualizedResources",
        meta: {
          title: "云资源池",
          icon: "virtualizedResources",
          id: "4d302dd0-f831-4c31-b140-183ce98aa670",
          needLogin: true,
          noCache: false
        }
      }
    ]
  },

index.router.js
注册这个路由时给 path 加一个 *注意:如果这个路由有其他子路由,需要另外注册一个路由,仍然使用这个组件即可

  // 在主应用的某个路由页面加载微应用
  {
    path: "/app-vue-kv",
    name: "appVuekv",
    component: Layout,
    children: [
      {
        path: "*",
        component: () => import("../views/Portal.vue"),
        name: "Home",
        meta: {
          title: "总览",
          icon: "home",
          id: "00444",
          needLogin: true
        }
      }
    ]
  },

微应用的 activeRule 需要包含主应用的这个路由 path

const getActiveRule = hash => location => location.hash.startsWith(hash);

const microApps = [
  {
    name: "app-vue-kv",
    entry: isProd ? "/child/app-vue-kv/" : "//localhost:9528",
    activeRule: getActiveRule("#/app-vue-kv"),
    props: { data: { store } }
  }
];

在 Portal.vue 这个组件的 mounted 周期调用 start 函数,注意不要重复调用。

import { start } from 'qiankun';
export default {
  mounted() {
    if (!window.qiankunStarted) {
      window.qiankunStarted = true;
      start();
    }
  },
}

子应用app-vue-kv 如是hash模式 router 如何配置

const routes = [
  {
    path: "/app-vue-kv",
    name: "Home",
    component: Home,
    children: [
      // 其他的路由都写到这里
      {
        path: "virtualizedResources",
        name: "VirtualizedResources",
        component: () =>
          import(
            /* webpackChunkName: "virtualizedResources" */ "../views/VirtualizedResources.vue"
          )
      }
    ]
  }
];

子应用winstore 如是hash模式 router 如何配置

  {
    path: "/winstore/resource",
    component: Layout,
    name: "Resource",
    meta: {
      title: "资源管理",
      id: "fa1aa081-bc7d-4c0c-9daf-4ac497007813"
    },
    children: [
      {
        path: "storagePoolManagement",
        component: () =>
          import(
            /* webpackChunkName: "StoragePoolManagement" */
            "@/views/resource/StoragePoolManagement.vue"
          ),
        name: "StoragePoolManagement",
        meta: {
          title: "存储池管理",
          icon: "storagePoolManagement",
          id: "f6bfd3a8-cc7c-4e17-9ab3-04e7fe69815c",
          needLogin: true,
          noCache: false
        }
      }
    ]
  },

子应用路由跳转 尽量使用name this.$router.push({ name: "WinstoreLogDetail" });

微应用数据请求跨域配置

背景

当前存在以vue2.6 开发的单例应用, 我们希望使用qiankun ,将该应用转微应用结构。
这里记录使用vue做微应用开发时,各个应用数据请求代理的配置方式.

root
└── child/                    # 存放所有微应用的文件夹
    |   ├── app-vue-kv/       # 存放微应用 app-vue-kv 的文件夹
    |   ├── app-vue-login/    # 存放微应用 app-vue-login 的文件夹
    |   ├── securityCenter/   # 存放微应用 securityCenter 的文件夹
    |   ├── winstore/         # 存放微应用 winstore 的文件夹
└── main/                     # 主应用

问题

我们知道一般,独立使用vue开发单例应用,可以通过配置 vue.config.js{ devServer: proxy } 实现接口请求代理.
而在微应用开发中,情况稍有不同。 具体的可以分为以下几种情况:

  1. 基座独立开发代理
  2. 子应用独立开发代理
  3. 子应用嵌套基座内的代理


现在的问题是,当基座和子应用各自独立配置代理服务时,各自独立开发请求数据是正常的。
而子应用加载到基座后,子应用的数据请求将为404

基座代理

基座版本vue2.6.12,

  devServer: {
    proxy: {
      "/api": {
        //此处要与 /services/api.js 中的 API_PROXY_PREFIX 值保持一致
        // target: "http://192.168.205.213:8080/",
        target: userDev.api,
        changeOrigin: true, //是否跨域
        pathRewrite: {
          "^/api": ""
        },
        ws: false
      },
      "/websocket": {
        target: userDev.socket, // 为目标变量
        changeOrigin: true, //是否跨域
        ws: false, // ws: false 解决sockjs 一直在频繁发送这个请求
        pathRewrite: {
          "^/websocket": "/websocket"
        }
      }
    },
  },

子应用代理

  devServer: {
    headers: {
      "Access-Control-Allow-Origin": "*"
    },
    proxy: {
      "/api": {
        //此处要与 /services/api.js 中的 API_PROXY_PREFIX 值保持一致
        // target: "http://192.168.205.213:8080/",
        target: userDev.api,
        changeOrigin: true, //是否跨域
        pathRewrite: {
          "^/api": ""
        },
        ws: false
      },
      "/websocket": {
        target: userDev.socket, // 为目标变量
        changeOrigin: true, //是否跨域
        ws: false, // ws: false 解决sockjs 一直在频繁发送这个请求
        pathRewrite: {
          "^/websocket": "/websocket"
        }
      }
    },
  },

分析

初看这里微应用中的代理配置和单例配置没有任何区别.其实代理的配置基础都是基于vue.config.js配置或者说是 webpack代理配置, 之所以 这里子应用无论独立开发或加载到基座联调,都能正常请求到数据,是因为基座和子应用都配置了相同的代理头 /proxyApi 并且请求的服务地址一致

这里回到问题, 如果基座和子应用独立配置, 例如:
子应用独立开发时的请求地址: /user
当子应用嵌套在基座内时,地址将变为: localhost: 9000/user
可以看到请求地址发生了变换,原/user 地址在独立开发时是能通过webpack server 做正常代理的.
而在微服务中, /user 地址转向了基座的本地开发服务 localhost:9000/user 而开发服务是无法识别这个请求地址的,自然返回404

所以我们将子应用的代理头与基座同步, 这样子应用的请求将通过基座的开发服务做代理转发, 之所以设置相同的代理头而不是在基座再配置一遍子应该代理,也是为了方便设置,遵循约定大于配置 原则。

如何部署

官网提供方案:qiankun.umijs.org/zh/cookbook…

采用方案1:微应用都放在在一个特殊名称(不会和微应用重名)的文件夹下(建议使用)

└── dist/                     # 根文件夹 (打包后如下放置)
    |
    ├── child/                # 存放所有微应用的文件夹
    |   ├── app-vue-kv/       # 存放微应用 app-vue-kv 的文件夹
    |   ├── app-vue-login/    # 存放微应用 app-vue-login 的文件夹
    |   ├── securityCenter/   # 存放微应用 securityCenter 的文件夹
    |   ├── winstore/         # 存放微应用 winstore 的文件夹
    ├── index.html            # 主应用的index.html
    ├── css/                  # 主应用的css文件夹
    ├── js/                   # 主应用的js文件夹
└── common/                   # 公共文件
    |
    ├── src/
└── child/                    # 存放所有微应用的文件夹
    |   ├── app-vue-kv/       # 存放微应用 app-vue-kv 的文件夹
    |   ├── app-vue-login/    # 存放微应用 app-vue-login 的文件夹
    |   ├── securityCenter/   # 存放微应用 securityCenter 的文件夹
    |   ├── winstore/         # 存放微应用 winstore 的文件夹
└── main/                     # 主应用

因为main、子应用都是采用hash模式。 所以不需要设置路由 base

main主应用 micro-app.js

import store from "./store";
import router from "./router";
const getActiveRule = hash => location => location.hash.startsWith(hash);

/**
 * 注意name应与子项目的package里的name字段保持一致,
 * activeRule后面的地址与name保持一致。
 * 这个地址不管在生产环境还是开发环境,访问子项目时的地址都不会变的,
 * 但是子项目的访问地址entry会随环境变化。
 * 比如本例vue子项目在开发环境地址是//localhost:9530,生产环境是/child/winstore/
 */

const isProd = process.env.NODE_ENV === "production";

const microApps = [
  {
    name: "securityCenter",
    entry: isProd ? "/child/securityCenter/" : "//localhost:9531",
    activeRule: getActiveRule("#/securityCenter")
  },
  {
    name: "winstore",
    entry: isProd ? "/child/winstore/" : "//localhost:9530",
    activeRule: getActiveRule("#/winstore"),
    props: { data: { store } }
  },
  {
    name: "app-vue-login",
    entry: isProd ? "/child/app-vue-login/" : "//localhost:9529",
    activeRule: getActiveRule("#/app-vue-login"),
    props: { data: { store, router } }
  },
  {
    name: "app-vue-kv",
    entry: isProd ? "/child/app-vue-kv/" : "//localhost:9528",
    activeRule: getActiveRule("#/app-vue-kv"),
    props: { data: { store } }
  }
];

// 处理组件共享
const commonComponents = {};

const apps = microApps.map(item => {
  return {
    ...item,
    container: "#appContainer" // 子应用挂载的div
  };
});

export default apps;

process.env.NODE_ENV === "production" 区分开发环境

  • 如是development时,直接使用本地开发环境
  • 如是production时,需要打包

主应用和微应用部署到同一个服务器(同一个IP和端口)

在根目录下:

先安装依赖: `npm install`,再执行`npm run install-all`为所有项目安装依赖,最后执行`npm run start-all`即可启动所有的项目。

`npm run build-all`可以打包所有`vue`项目,`jQuery`项目不需要打包。

子应用 vue.config.js 设置publicPath

module.exports = {
	publicPath: process.env.NODE_ENV === "production" ? "/child/winstore/" : "/",
}

nginx 后端部署配置

server {
  listen       8080;
  server_name  localhost;

        location "" {
            #alias  /opt/winsphere/webapp;
            alias /opt/winsphere/web/dist;
            index index.html;
        }

        location /child/webapp {
            alias  /opt/winsphere/webapp;
            index index.html;
        }

        location /child/app-vue-login {
           alias  /opt/winsphere/web/dist/child/app-vue-login;
           index  index.html;
        }

        location /child/app-vue-kv {
           alias  /opt/winsphere/web/dist/child/app-vue-kv;
           index  index.html;
        }

        location /child/securityCenter {
           alias  /opt/winsphere/web/dist/child/securityCenter;
           index  index.html;
        }

        location /child/winstore {
           alias  /opt/winsphere/web/dist/child/winstore;
           index  index.html;
        }
}

结尾

在对winstack 遇到路由跳转问题、打包问题、跨域问题等。。可能不会像vue使用那么6哦,需要踩坑的,遇到这些问题,可以看官网、github issues ,实在解决不了的问题,可以在钉钉找队友解决,开发下来,这些问题都通过这些方式可以解决的。