zoukankan      html  css  js  c++  java
  • 微前端开发(Vue)

    一、微前端概述

    1.  什么是微前端?

      为了解决庞大的一整块后端服务带来的变更与扩展方面的限制,出现了微服务架构。然而,越来越重的前端工程也面临同样的问题,自然地想到了将微服务思想应用(照搬)到前端,于是有了“微前端(micro-frontends)”的概念。即,一种由独立交付的多个前端应用组成整体的架构风格。具体的,将前端应用分解成一些更小、更简单的能够独立开发、测试、部署的小块,而在用户看来仍然是内聚的单个产品。

      我们常见后台项目通常长这样:

     

      如果我们的项目需要开发某个新的功能,而这个功能另一个项目已经开发好,我们想直接复用时。

      说明我们需要的只是别人项目的这个功能页面的内容部分,不需要别人项目的顶部导航和菜单。

    一个比较笨的办法就是直接把别人项目这个页面的代码拷贝过来,但是万一别人不是 vue 开发的,或者说 vue 版本、UI 库等不同,以及别人的页面加载之前操作(路由拦截,鉴权等)我们都需要拷贝过来,更重要的问题是,别人代码有更新,我们如何做到同步更新。甚至当别的项目采用其它技术栈时,如何集成?显然复制代码是行不通的。

      以前端组件的概念作类比,我们可以把每个被拆分出的子应用看作是一个应用级组件,每个应用级组件专门实现某个特定的业务功能(如商品管理、订单管理等)。这里实际上谈到了微前端拆分的原则:即以业务功能为基本单元。经过拆分后,整个系统的结构也发生了变化:

     

      如上图所示,左侧是传统大型单页应用的前端架构,所有模块都在一个应用内,由应用本身负责路由管理,是应用分发路由的方式;而右侧是基座模式下的系统架构,各个子应用互不相关,单独运行在不同的服务上,由基座应用根据路由选择加载哪个应用到页面内,是路由分发应用的方式。这种方式使得各个模块的耦合性大大降低,而微前端需要解决的主要问题就是如何拆分和组织这些子应用。

    典型的基于vue-routerVue应用与这种架构存在着很大的相似性:

    2.  巨无霸项目的存在的问题

    • 代码越来越多,打包越来越慢,部署升级麻烦,一些插件的升级和公共组件的修改需要考虑的更多,很容易牵一发而动全身
    • 项目太大,参与人员越多,代码规范比较难管理,代码冲突也频繁。
    • 产品功能齐全,但是客户往往只需要其中的部分功能。剥离不需要的代码后,需要独立制定版本,独立维护,增加人力成本。

      微前端的诞生也是为了解决以上问题:

    •   复用(嵌入)别人的项目页面,但是别人的项目运行在他自己的环境之上。
    •   巨无霸应用拆分成一个个的小项目,这些小项目独立开发部署,又可以自由组合进行售卖。

      使用微前端的好处:

    •     技术栈无关,各个子项目可以自由选择框架,可以自己制定开发规范。
    •    快速打包,独立部署,互不影响,升级简单。
    •    可以很方便的复用已有的功能模块,避免重复开发。

    二、常见微前端方案

      目前主流的微前端方案包括以下几个:

    •  iframe
    • 基座模式,主要基于路由分发,qiankunsingle-spa就是基于这种模式
    • 组合式集成,即单独构建组件,按需加载,类似npm包的形式
    • EMP,主要基于Webpack5 Module Federation
    • Web Components

      iframe:是传统的微前端解决方案,基于iframe标签实现,技术难度低,隔离性和兼容性很好,但是性能和使用体验比较差,多用于集成第三方系统;

      基座模式:主要基于路由分发,即由一个基座应用来监听路由,并按照路由规则来加载不同的应用,以实现应用间解耦;

      组合式集成:把组件单独打包和发布,然后在构建或运行时组合。

      EMP:基于Webpack5 Module Federation,一种去中心化的微前端实现方案,它不仅能很好地隔离应用,还可以轻松实现应用间的资源共享和通信

      Web Components:是官方提出的组件化方案,它通过对组件进行更高程度的封装,来实现微前端,但是目前兼容性不够好,尚未普及。

      总的来说,iframe主要用于简单并且性能要求不高的第三方系统;组合式集成目前主要用于前端组件化,而不是微前端;基座模式、EMPWeb Components是目前主流的微前端方案。

      目前微前端最常用的有两种解决方案:iframe 方案和 基座模式方案

    1.  iframe方案

      iframe 大家都很熟悉,使用简单方便,提供天然的 js/css 隔离,也带来了数据传输的不便,一些数据无法共享(主要是本地存储、全局变量和公共插件),两个项目不同源(跨域)情况下数据传输需要依赖 postMessage

      iframe 有很多坑,但是大多都有解决的办法:

      1. 页面加载问题

      iframe 和主页面共享连接池,而浏览器对相同域的连接有限制,所以会影响页面的并行加载,阻塞 onload 事件。每次点击都需要重新加载,虽然可以采用 display:none 来做缓存,但是页面缓存过多会导致电脑卡顿。(无法解决)

      2. 布局问题

      iframe 必须给一个指定的高度,否则会塌陷。

      解决办法:子项目实时计算高度并通过 postMessage 发送给主页面,主页面动态设置 iframe 高度。有些情况会出现多个滚动条,用户体验不佳。

      3. 弹窗及遮罩层问题

      弹窗只能在 iframe 范围内垂直水平居中,没法在整个页面垂直水平居中。

      解决办法1:通过与框架页面消息同步解决,将弹窗消息发送给主页面,主页面来弹窗,对原项目改动大且影响原项目的使用。

      解决办法2:修改弹窗的样式:隐藏遮罩层,修改弹窗的位置。

      4. iframe 内的 div 无法全屏

      弹窗的全屏,指的是在浏览器可视区全屏。这个全屏指的是占满用户屏幕。

      全屏方案,原生方法使用的是 Element.requestFullscreen(),插件:vue-fullscreen。当页面在 iframe 里面时,全屏会报错,且 dom 结构错乱。

      解决方案:iframe 标签设置 allow="fullscreen" 属性即可

      5. 浏览器前进/后退问题

      iframe 和主页面共用一个浏览历史,iframe 会影响页面的前进后退。大部分时候正常,iframe 多次重定向则会导致浏览器的前进后退功能无法正常使用。并且 iframe 页面刷新会重置(比如说从列表页跳转到详情页,然后刷新,会返回到列表页),因为浏览器的地址栏没有变化,iframe src 也没有变化。

      6. iframe 加载失败的情况不好处理

      非同源的 iframe 在火狐及 chorme 都不支持 onerror 事件。

      解决办法1onload 事件里面判断页面的标题,是否 404 或者 500

      解决办法2:使用 try catch 解决此问题,尝试获取 contentDocument 时将抛出异常。

    2.  基座模式方案

      基座模式方案以single-spaqiankun为代表,这里我选择qiankun

      qiankun 是蚂蚁金服开源的一款框架,它是基于 single-spa 的。他在 single-spa 的基础上,实现了开箱即用,除一些必要的修改外,子项目只需要做很少的改动,就能很容易的接入。

      qiankun框架官网:https://qiankun.umijs.org/zh/

      微前端中子项目的入口文件常见的有两种方式:JS entry HTML entry。 single-spa 采用的是 JS entry,而 qiankun 既支持 JS entry,又支持 HTML entry

      JS entry 的要求比较苛刻:

      (1)将 css 打包到 js 里面

      (2)去掉 chunk-vendors.js

      (3)去掉文件名的 hash

      (4)将 single-spa 模式的入口文件( app.js )放置到 index.html 目录,其他文件不变,原因是要截取 app.js 的路径作为 publicPath

      建议使用 HTML entry ,使用起来和 iframe 一样简单,但是用户体验比 iframe 强很多。qiankun 请求到子项目的 index.html 之后,会先用正则匹配到其中的 js/css 相关标签,然后替换掉,它需要自己加载 js 并运行,然后去掉 html/head/body 等标签,剩下的内容原样插入到子项目的容器中

    二、微前端方案实践

      以“大数据分析”项目为例,将客户特有的需求,如“电子路单”、“数据填报”单独提取为可独立运行的子项目。

      大数据分析项目改造为主应用基座,代码仓库地址:http://192.168.1.102/zouqiongjun/big-data-web.git

      客户自定义需求单独作为子应用项目,代码仓库地址:http://192.168.1.102/zouqiongjun/zibo-custom-web.git

    1.  各应用工程代码结构

      sass-base-web:主仓库,主要存放一些批量操作的脚本,用于聚合管理仓库和一键编译、一键部署。

      仓库代码结构如下图所示:

     

      big-data-web:大数据分析主应用

      zibo-custom-web:客户自定义需求,微应用仓库

      子应用可以独立运行,但是当前子应用是直接嵌套在主应用的main内容区域,所以暂时并没有单独提供左侧菜单导航,后续如有需要可以扩展和补充此功能。

    2.  主应用big-data-web改造

      将普通的项目改造成 qiankun 主应用基座,需要进行三步操作:

      (1) 创建微应用容器 - 用于承载微应用,渲染显示微应用;

      (2) 注册微应用 - 设置微应用激活条件,微应用地址等等;

      (3) 启动 qiankun

      注意:由于big-data-web主应用的路由采用的是hash模式,所以子应用的路由也应该采用hash模式。

    1.1 安装 qiankun

    $ yarn add qiankun # 或者 npm i qiankun -S

    1.2. 在主应用中注册微应用

      为了使用keepAlive缓存,这里我们采用手动加载微应用的方式

      当微应用信息注册完之后,一旦浏览器的 url 发生变化,便会自动触发 qiankun 的匹配逻辑,所有 activeRule 规则匹配上的微应用就会被插入到指定的 container 中,同时依次调用微应用暴露出的生命周期钩子。

      在views目录下,新建AppVueHash.vue,作为子应用的容器,代码如下:

    <template>
    <div class="zibo-custom-web">
         <div id="zibo-custom-web" class="app-view-box"></div>
    </div>
    </template>
    <script>
    export default {};
    </script>
    <style lang="scss" scoped>
    .zibo-custom-web{
      position: relative;
    }
    </style>

      这个id属性要唯一,最终子应用的内容将会挂载在这里。

      ContainerOther.vue代码改造:

      <!-- 主体视图层 -->
            <div class="avue-view-contain" v-show="!isSearch">
              <keep-alive>
                <router-view
                  class="avue-view keep-alive"
                  v-if="$route.meta.keepAlive && isActiveRoute"
                  v-show="!showAppVueHash"
                />
              </keep-alive>
              <router-view
                class="avue-view"
                v-if="!$route.meta.keepAlive && isActiveRoute"
                v-show="!showAppVueHash"
              />
              <AppVueHash v-show="showAppVueHash" />
            </div>

    js代码:

    import router from "@/router/router";
    import store from "@/store";
    import AppVueHash from "@/views/AppVueHash.vue";
    import { loadMicroApp } from "qiankun";
    //子项目路由前缀
    const isChildRoute = path => website.childRoute.some(item => path.startsWith(item));
    const apps = [
      {
        name: "/zibo-custom-web",
        entry: window.configs.VUE_APP_ZIBO_CUSTOM_URL,
        container: "#zibo-custom-web",
        props: { data: { store, router } },
        sandbox: {
          strictStyleIsolation: true // 开启样式隔离
        }
      }
    ];
    //控制微应用手动加载
        ctrlMicroApp(path){
           if (isChildRoute(path)) {
            this.showAppVueHash = true;
            this.$nextTick(() => {
              //手动加载
              if(!this.mounted){
                this.loadApps = apps.map(item => loadMicroApp(item))
                this.mounted=true;
              }  
            });
          } else {
            this.showAppVueHash = false;
          }

      这里的container属性值,必须和AppVueHash.vue组件中的id值保持一致。

      根据url地址判断是否是子应用,如果是子应用,则手动加载,否则隐藏子应用容器,只加载主应用的router-view

      在ContainerOther第一次加载或路由变化时手动加载微应用:

    mounted() {
        this.init();
        setTheme(this.themeName);
        this.ctrlMicroApp(this.$route.path)
      },
      watch: {
        $route(val) {
          this.ctrlMicroApp(val.path);
    },
        tagList(newVal,oldVal){
          let starts='';
          const childRoute = website.childRoute;
          childRoute.forEach((n,i)=>{
            if(i<childRoute.length-1){
              starts+=`^${n}|`;
            }else{
              starts+=`^${n}`;
            }
          })
          const patt = new RegExp(`${starts}`);
          //之前存在子应用页签
          const before = oldVal.some(item=>{
            return patt.test(item.value);
          });
          //现在存在子应用页签
          const now = newVal.some(item=>{
            return patt.test(item.value);
          });
          if(before && !now){
            this.mounted=false;
            this.loadApps.forEach(app=>{
              app.unmount();  
            })
          }
        } 

      监听tab页签变化,关闭页签时,需要卸载子应用。

    3.  qiankun 子项目zibo-custom-web

    1. src 目录新增文件 public-path.js
    if (window.__POWERED_BY_QIANKUN__) {
        // eslint-disable-next-line no-undef
        __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
      }
    1. 修改 index.html 中项目初始化的容器,不要使用 #app ,避免与其他的项目冲突,建议小驼峰写法
        <div id="appVueHash"></div>
    1. 修改入口文件 main.js
    // -----------子应用微前端start-------------------
    let router = null;
    let instance = null;
    
    function render({ data = {} , container } = {}) {
      router = new VueRouter({
        routes,
      });
      instance = new Vue({
        router,
        store,
        data(){
          return {
            parentRouter: data.router,
            parentVuex: data.store,
          }
        },
        render: h => h(App),
      }).$mount(container ? container.querySelector('#appVueHash') : '#appVueHash');
    }
    if (!window.__POWERED_BY_QIANKUN__) {
      render();
    }
    //测试全局变量污染
    console.log('window.a',window.a)
    export async function bootstrap() {
      console.log('vue app bootstraped');
    }
    export async function mount(props) {
      console.log('props from main framework', props.data);
      render(props);
    }
    export async function unmount() {
      instance.$destroy();
      instance.$el.innerHTML = "";
      instance = null;
      router = null;
    }
    // -----------子应用微前端end-------------------

      主要改动是:引入修改 publicPath 的文件和 export 三个生命周期。

      注意:

    • webpack publicPath 值只能在入口文件修改,之所以单独写到一个文件并在入口文件最开始引入,是因为这样做可以让下面所有的代码都能使用这个。
    • 路由文件需要 export 路由数据,而不是实例化的路由对象,路由的钩子函数也需要移到入口文件。
    • mount 生命周期,可以拿到父项目传递过来的数据,router 用于跳转到主项目/其他子项目的路由,store 是父项目的实例化的 Vuex(也可以传递其他数据过来)。
    1. 修改打包配置 vue.config.js:
    const { name } = require("./package");
    module.exports = {
      outputDir: "../sass-base-web/cicd-config/test/zibo-custom-web",
      devServer: {
        port: 9010,
        // 关闭主机检查,使微应用可以被 fetch
        disableHostCheck: true,
        // 配置跨域请求头,解决开发环境的跨域问题
        headers: {
          "Access-Control-Allow-Origin": "*",
        },
        proxy: {
          "/api": {
            //本地服务接口地址
            target: "http://192.168.10.112:10067", //cas
            ws: true,
            pathRewrite: {
              "^/api": "/",
            },
          },
        },
      },
       // 以下配置可以修复一些字体文件加载路径问题
      chainWebpack: (config) => {
        //忽略的打包文件
        config.externals({
          vue: "Vue",
          "vue-router": "VueRouter",
          vuex: "Vuex",
          axios: "axios",
          "element-ui": "ELEMENT",
        });
        config
          .plugin('html')
          .tap(args => {
            args[0].name = name;
            return args
          });
        config.module
          .rule("fonts")
          .test(/.(ttf|otf|eot|woff|woff2)$/)
          .use("url-loader")
          .loader("url-loader")
          .tap((options) => ({ name: "/fonts/[name].[hash:8].[ext]" }))
          .end();
      },
      // 自定义webpack配置
      configureWebpack: {
        output: {
          library: `${name}-[name]`, // 微应用的包名,这里与主应用中注册的微应用名称一致
          libraryTarget: "umd", // 把子应用打包成 umd 库格式
          jsonpFunction: `webpackJsonp_${name}`, //webpack打包之后保存在window中的key,各个子应用不一致
        },
      },
    };

      这里主要就两个配置,一个是允许跨域,另一个是打包成 umd 格式。为什么要打包成 umd 格式呢?是为了让 qiankun 拿到其 export 的生命周期函数。

      注意 这个 name 默认从 package.json 获取,可以自定义,只要和父项目注册时的 name 保持一致即可。

      vue.config.js中

      outputDir: "../sass-base-web/cicd-config/test/zibo-custom-web",

      这里我子应用项目编译后会将打包文件打包到sass-base-web项目中的zibo-custom-web下。

    1. 路由动态加载

      需要给子项目所有的路由都添加一个前缀,子项目的路由跳转如果之前使用的是 path 也需要修改,用 name 跳转则不用

      avue-router.js:

     const oRouter = {
              path: "/zibo-custom-web",
              name: "RouterView",
              component(resolve) {
                require(["@/components/RouterView.vue"], resolve);
              },
    
              children: [
                {
                  path: path,
                  component(resolve) {
                    require([`../${component}.vue`], resolve);
                  },
                  name: name,
                  meta: meta,
                },
              ],
            };
            aRouter.push(oRouter);

    4.  状态管理,主应用和微应用之间的通信

      qiankun 通过 initGlobalState: 定义全局状态,并返回通信方法,建议在主应用使用,微应用通过 props 获取通信方法;

      onGlobalStateChange: 在当前应用监听全局状态,有变更触发 callback;

      setGlobalState: 按一级属性设置全局状态,微应用中只能修改已存在的一级属性; 换句话说只能修改主用于预先定义的属性,后面添加的属性无效

      官方发布-订阅的设计模式:

      主应用:

    import { initGlobalState, MicroAppStateActions } from 'qiankun';
    // 初始化 state
    const actions: MicroAppStateActions = initGlobalState(state);
    
    actions.onGlobalStateChange((state, prev) => {
      // state: 变更后的状态; prev 变更前的状态
      console.log(state, prev);
    });
    actions.setGlobalState(state);
    actions.offGlobalStateChange();

      子应用:

    // 从生命周期 mount 中获取通信方法,使用方式和 master 一致
    export function mount(props) {
        props.onGlobalStateChange((state, prev) => {
          // state: 变更后的状态; prev 变更前的状态
          console.log(state, prev);
        });
        props.setGlobalState(state);
      }

      如果主应用和子应用都是vue技术栈,可以在子应用中把数据传递给子应用,例如store

       props: { data: { store, router } },

    5.  各应用之间的独立仓库以及聚合管理

      实际开发中项目存储在公司仓库中,以 gitLab 为例, 当子应用一多,全部放在一个仓库下面, 这时候就显得很臃肿了,也很庞大,大大的增加了维护成本,和开发效率;

    我们可以通过 sh 脚本, 初始只需要克隆主仓库代码, 然后通过 sh 脚本去一键拉取所有子应用代码。

      这里我将单独创建一个用来编译和打包的主仓库项目sass-base-web,仓库地址:http://192.168.1.102/zouqiongjun/sass-base-web.git

      项目根目录下,新建 script/clone-all.sh 文件内容如下

    # 子服务 gitLab 地址
    SUB_SERVICE_GIT=('http://192.168.1.102/zouqiongjun/big-data-web.git' 'http://192.168.1.102/zouqiongjun/zibo-custom-web.git')
    SUB_SERVICE_NAME=('big-data-web' 'zibo-custom-web')
    
    # 子服务
    if [ ! -d "sub-service" ]; then
      echo '创建sub-service目录...'
      mkdir sub-service
    fi
    echo '进入sub-service目录...'
    cd sub-service
    
    # 遍历克隆微服务
    for i in ${!SUB_SERVICE_NAME[@]}
    do
      if [ ! -d ${SUB_SERVICE_NAME[$i]} ]; then
        echo '克隆微服务项目'${SUB_SERVICE_NAME[$i]}
        git clone ${SUB_SERVICE_GIT[$i]}
      fi
    done
     
    echo '脚本结束...'
    # 克隆完成

      当我们启动主项目的时候,如果想要使用所有子应用的功能,子应用,需要一个一个启动,这样无论是开发还是编译都十分不便。

      考虑到国内使用npm装包可能会由于网络的原因安装失败,可以让npm使用国内淘宝镜像。

      执行命令:npm config set registry https://registry.npm.taobao.org。

      在这个主仓库项目里面,我们只需要安装一个npm-run-all插件。

      运行:yarn add npm-run-all -D或者npm i -D

      该项目下只有一个package.json文件,用于配置编译和打包命令,代码如下:

    {
      "name": "sass-big-data-web",
      "version": "1.0.0",
      "description": "`qiankun`来实现`vue`技术栈的前端微服务",
      "main": "index.js",
      "scripts": {
        "clone:all": "bash ./scripts/clone-all.sh",
        "install:zibo": "cd ./sub-service/zibo-custom-web && npm install",
        "install:main": "cd ./sub-service/big-data-web && npm install",
        "install-all": "npm-run-all install:*",
        "start:zibo": "cd ./sub-service/zibo-custom-web && npm run serve ",",
        "start:main": "cd ./sub-service/big-data-web && npm run serve",
        "start-all": "npm-run-all --parallel start:*",
        "serve-all": "npm-run-all --parallel start:*",
        "build:zibo": "cd ./sub-service/zibo-custom-web && npm run build",
        "build:main": "cd ./sub-service/big-data-web && npm run build",
        "build-all": "npm-run-all --parallel build:*"
      },
      "repository": {
        "type": "git",
        "url": "http://192.168.1.102/zouqiongjun/sass-big-data-web.git"
      },
      "keywords": [],
      "author": "",
      "license": "ISC",
      "devDependencies": {
        "npm-run-all": "^4.1.5"
      }
    }

      要执行yarn clone:all,需要用到bash,跳转到sass-base-web目录,右键鼠标打开Git bash窗口,如下图所示:

     

      这样当我们把各个代码仓库的代码都拉取到sub-service这个目录下面来,如下图所示:

     

      代码拉取完成后, 紧接着就是下载各个项目的依赖及运行。

      运行npm run serve-all则可以自动执行package.json中配置的命令,这个命令最终会执行以下三个执行命令:

        "start:zibo": "cd ../zibo-custom-web && npm run serve ",
        "start:main": "cd ../big-data-web && npm run serve",

      这样就不需要我们自己一个一个单独的去运行各个项目了。

      总体运行步骤: 第一步 clone 主应用, 然后依次执行 yarn clone:all --> yarn install-all --> yarn start-all 即可运行整个项目

      build-all:可以编译整个项目。

      sub-service目录,将其添加到.gitignore当中,因为在sass-base-web项目当中,我们只需要配置和编译及打包用,并不需要真正的将所有子应用的代码都提交到sass-base-web项目中,各子应用都有自己私有的仓库。

    6.  子项目开发的一些注意事项

      (1所有的资源(图片/音视频等)都应该放到 src 目录,不要放在 public 或者static资源放 src 目录,会经过 webpack 处理,能统一注入 publicPath。否则在主项目中会404

      (2避免 css 污染。组件内样式的 css-scoped 是必须的。

      (3 body document 等绑定的事件,请在 unmount 周期清除

      (4谨慎使用 positionfixed

      在父项目中,这个定位未必准确,应尽量避免使用,确有相对于浏览器窗口定位需求,可以用 position: sticky,但是会有兼容性问题(IE不支持)。

      常见问题见官网:https://qiankun.umijs.org/zh/faq

    7.  部署

    1.  常规部署

      主应用和微应用都是独立开发和部署,即它们都属于不同的仓库和服务

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

      如果服务器数量有限,或不能跨域等原因需要把主应用和微应用部署到一起。通常的做法是主应用部署在一级目录,微应用部署在二/三级目录。

      若微应用想部署在非根目录,在微应用打包之前需要做两件事:

    • 必须配置 webpack 构建时的 publicPath 为目录名称,更多信息请看 webpack 官方说明 和 vue-cli3 的官方说明
    • history 路由的微应用需要设置 base ,值为目录名称,用于独立访问时使用。

      部署之后注意三点:

    • activeRule 不能和微应用的真实访问路径一样,否则在主应用页面刷新会直接变成微应用页面。
    • 微应用的真实访问路径就是微应用的 entryentry 可以为相对路径。
    • 微应用的 entry 路径最后面的 / 不可省略,否则 publicPath 会设置错误,例如子项的访问路径是 http://localhost:8080/app1,那么 entry 就是 http://localhost:8080/app1/

      通过配置 nginx 端口到目录的转发。须要对外开放子利用对应的端口,将编译好的利用文件放到对应的配置目录。

      跳转到sass-base-web目录,执行npm run build-all,会自动执行所有build:开头的命令:

        "build:zibo": "cd ../zibo-custom-web && npm run build",
        "build:control": "cd ../control-center && npm run build",
        "build:main": "cd ../big-data-web && npm run build",
        "build-all": "npm-run-all --parallel build:*"

      zibo-custom-web目录结构如下图所示:

     

      这里是子应用和主应用部署在同一台服务器上,且IP和端口相同,nginx不需要额外设置。

      如果子应用和主应用部署在同一台服务器上, 但是端口不同,需要修改vue.config.js中的outputDir,这个是打包编译后代码存放的路径,这个不做配置,默认会将代码编译打包到当前根目录下,并生成一个dist目录用于存放编译后的代码,如下图所示:

     

    修改nginx.conf配置:

     #gzip  on;
          upstream gateway { server 192.168.31.136:32067;}
         # 主应用
            server {
            listen   32043;
            server_name  web;
            root  /dist;
            # 关闭端口重定向
            # port_in_redirect off;
            #charset koi8-r;
            access_log /var/log/nginx/nginx.log;
    
            location / {
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header REMOTE-HOST $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            }
    
            location ^~/api/ {  
                proxy_read_timeout 600s;
                proxy_set_header Host $host; 
                proxy_set_header X-Real-IP $remote_addr; 
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_buffering off; 
                rewrite ^/api/(.*)$ /$1 break; 
                proxy_pass http://gateway; 
                }
            }
    
       # 子应用
        server {
            listen   9010;
            server_name  cus_web;
            # 子应用编译后的代码路径
            root  /zibo-custom-web;  
            # 允许跨域
            add_header Access-Control-Allow-Origin *;
            # 关闭端口重定向
            # port_in_redirect off;
            # charset koi8-r;
            access_log /var/log/nginx/nginx.log;
            location ^~/zibo-custom-web/ {
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header REMOTE-HOST $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            }
    
            location ^~/api/ {  
                proxy_read_timeout 600s;
                proxy_set_header Host $host; 
                proxy_set_header X-Real-IP $remote_addr; 
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_buffering off; 
                rewrite ^/api/(.*)$ /$1 break; 
                proxy_pass http://gateway; 
                }
        }

    2.  docker nginx 配置

      此处 nginx 主要作用是用于端口目录转发,并配置主应用访问子应用的跨域问题。

      使用 docker 配置部署 nginx:

    # docker-compose.yml
    version: '3.1'
    services:
      nginx:
        restart: always
        image: nginx
        container_name: nginx
        ports:
          - 8888:80
          - 8889:8889
          - 7100:7100
          - 7101:7101
        volumes:
          - /app/volumes/nginx/nginx.conf:/etc/nginx/nginx.conf
          - /app/volumes/nginx/html:/usr/share/nginx/html
          - /app/micro/portal:/app/micro/portal
          - /app/micro/app1:/app/micro/app1
          - /app/micro/app2:/app/micro/app2

      将编译后的主应用以及子应用放到对应的数据卷挂载目录即可,如主应用 /app/micro/portal。
      同理,也需要将配置好的 nginx.conf 文件放到指定的数据卷挂载目录,使用 docker-compose up -d 启动即可。

      nginx 端口目录转发配置:

    # nginx.conf
    user  nginx;
    worker_processes  1;
    error_log  /var/log/nginx/error.log warn;
    pid        /var/run/nginx.pid;
    
    events {
        worker_connections  1024;}
    http {
        include       /etc/nginx/mime.types;
        default_type  application/octet-stream;
        log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                          '$status $body_bytes_sent "$http_referer" '
                          '"$http_user_agent" "$http_x_forwarded_for"';
    
        access_log  /var/log/nginx/access.log  main;
    
        sendfile        on;
        #tcp_nopush     on;
        keepalive_timeout  65;
    
        #gzip  on;
        include /etc/nginx/conf.d/*.conf;
        server {
          listen    8889;
          server_name 192.168.2.192;
          location / {
            root /app/micro/portal;
            index index.html;
            try_files $uri $uri/ /index.html;
          }
        }
        server {
          listen    7100;
          server_name 192.168.2.192;
    
          # 配置跨域访问,此处是通配符,严格生产环境的话可以指定为主应用 192.168.2.192:8889
          add_header Access-Control-Allow-Origin *;
          add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
          add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
          location / {
            root /app/micro/app1;
            index index.html;    
            try_files $uri $uri/ /index.html;
          }
        }
        server {
          listen    7101;
          server_name 192.168.2.192;
         
          # 配置跨域访问,此处是通配符,严格生产环境的话可以指定为主应用 192.168.2.192:8889
          add_header Access-Control-Allow-Origin *;
          add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
          add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
          
          location / {
            root /app/micro/app2;
            index index.html;   
            try_files $uri $uri/ /index.html;
          }
        }}

      部署到生产,需要修改big-data-web/public/util/config.js中的VUE_APP_ZIBO_CUSTOM_URL配置项:

    (function() {
      window.configs = {
        VUE_APP_CASLOGINURL: "http://192.168.1.99:32080/cas", //cas登录地址
        VUE_APP_REDIRECTURL: "http://192.168.51.61:8888/big-data/", //前端部署地址
        VUE_APP_SOCKET: "ws://192.168.31.136:32061", //websocket地址'
        VUE_APP_AMAPURLPREFIX: "https://webapi.amap.com", //高德地图地址
        VUE_APP_ZIBO_CUSTOM_URL:"http://localhost:9010",//自定义微应用地址
        //================================================
    
        VUE_APP_AMAPKEY: "xxxxxx" //高德key
      };
    })();
     

      这里config.js是为了配置文件外置,不需要编译。

    8.  总结

      尽管qiankun框架支持各个子应用使用不同的技术框架,但是都需要子应用做相应的改造,而且在其它技术栈中总是会时不时的出现各种难以预料的错误,一旦出现问题,都需要我们去改造代码。所以如果都是vue技术栈,建议使用qiankun做微前端。

      如果是第三方公司的项目接入进来,由于他们的代码不受我们控制,需要酌情考虑是否用iframe

      参考文献:qiankun 微前端方案实践及总结

    博客地址:http://www.cnblogs.com/jiekzou/
    博客版权:本文以学习、研究和分享为主,欢迎转载,但必须在文章页面明显位置给出原文连接。
    如果文中有不妥或者错误的地方还望高手的你指出,以免误人子弟。如果觉得本文对你有所帮助不如【推荐】一下!如果你有更好的建议,不如留言一起讨论,共同进步!
    再次感谢您耐心的读完本篇文章。
    其它: .net-QQ群4:612347965 java-QQ群:805741535 H5-QQ群:773766020

  • 相关阅读:
    记录几个IDEA插件使用方式
    constructor()方法
    SQL笔记
    修改hosts的方式fq
    正则表达式学习
    android架构下各层的分工
    【转】android的mm命令
    虚拟存储器
    xcode 7种使用coredata遇到 Class not found, using default NSManagedObject instead.问题
    AppStore上架规则
  • 原文地址:https://www.cnblogs.com/jiekzou/p/15480246.html
Copyright © 2011-2022 走看看