0
点赞
收藏
分享

微信扫一扫

qiankun微前端落地实践

程序小小黑 2022-03-30 阅读 78

qiankun微前端落地实践


背景

A项目集成了多条业务线的项目,但是共同使用一个git仓库和一套发布流水线,代码组织比较混乱。本次需要引入一个新的业务线,如果直接在A项目开发,将导致原项目更加复杂,更加混乱,故计划将这个项目单独发布,以微前端的方式嵌入A项目。

方案

微前端(qiankun)

  1. 技术栈无关
    主框架不限制接入应用的技术栈,微应用具备完全自主权
  2. 独立开发、独立部署
    微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新
  3. 独立运行时
    每个微应用之间状态隔离,运行时状态不共享

架构

image-20210415203149352

步骤

  1. 将现有A项目改造为主应用

    1. 安装qiankun

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

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

      const ENV = getENV();
      const genActiveRule = hash => location => location.hash.startsWith(hash);
      let microApp = {
          name: "microApp",
          entry: {
              localhost: "//localhost:8080",
              test: "//test.com",
              online: "//online.com"
          }[ENV],
          container: "#microApp",
          activeRule: genActiveRule("#/microApp")
      };
      
    3. 微应用需要使用主应用的菜单和工具栏,故微应用在entry.vue文件中加载

      // entry.vue
      import {start} from 'qiankun';
      mounted() {
          // 启动微应用
          if (!window.qiankunStarted) {
              window.qiankunStarted = true;
              start();
          }
      }
      
    4. 主应用要预留子应用加载的路由入口,参见常见问题

      /**
       * @description 子应用的路由
       */
      {
          path: '/microApp/*',
          name: 'microApp',
          component: resolve => require(['../views/entry.vue'], resolve),
      },
      
    5. 404页面的配置

      首先不应该写通配符 * ,可以将 404 页面注册为一个普通路由页面,比如说 /404 ,然后在主应用的路由钩子函数里面判断一下,如果既不是主应用路由,也不是微应用,就跳转到 404 页面。

      const childrenPath = ['microApp'];
      router.beforeEach((to, from, next) => {
        if(to.name) { // 有 name 属性,说明是主应用的路由
          next()
        }
        if(childrenPath.some(item => to.path.includes(item))){
          next()
        }
        next({ name: '404' })
      })
      
    6. 因命名冲突,主应用中所有的window.Vue改为window.Vue2

  2. 将新的项目改造为微应用

    1. 暴露出三个钩子供主应用加载

      // 父应用加载子应用,子应用必须暴露三个接口:bootstrap、mount、unmount
      export async function bootstrap(props) {
      
      };
      
      export async function mount(props) {
          render(props)
      }
      
      export async function unmount(props) {
          instance.$destroy();
      }
      
    2. 保证子应用既可以独立运行,又可以作为微应用运行

      let instance = null
      function render(props) {
          instance = new Vue({
              router,
              store,
              render: (h) => h(App),
          }).$mount("#app"); // 这里是挂载到自己的html中  基座会拿到这个挂载后的html 将其插入进去
      }
      
      if (window.__POWERED_BY_QIANKUN__) { // 动态添加publicPath
          __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
      }
      if (!window.__POWERED_BY_QIANKUN__) { // 默认独立运行
          render();
      }
      
    3. 允许跨域并使用umd方式打包

      // vue.config.js
      module.exports = {
          devServer: {
              port: '8080',
              headers: {
                  'Access-Control-Allow-Origin': '*'
              }
          },
          configureWebpack: {
              output: {
                  library: 'microApp',
                  libraryTarget: 'umd'
              }
          }
      };
      
  3. 路由模式

    1. 主应用保持hash模式不变(主应用是vue项目,子应用是react时,主应用使用hash模式会导致子应用加载不出来,故后来将主应用改为history模式)
    2. 微应用也使用hash模式,但path需要加上前缀“/microApp”
  4. 难点

    1. 部署方案:

      1. 主应用和子应用分别部署在不同docker

        # dockerfille
        FROM registry.cn-beijing.aliyuncs.com/xxxxx/tengine2.2.1:v0.3
        
        ADD ./dist /usr/share/nginx/html/dist
        
        WORKDIR /usr/share/nginx/html
        
        COPY default.conf /etc/nginx/conf.d/default.conf
        
        # dockerfile.dapper
        FROM registry.cn-beijing.aliyuncs.com/xxxx/node:14.15-buster-slim
          
        USER root
          
        ENV DAPPER_SOURCE /code
          
        ENV DAPPER_RUN_ARGS "-v /tmp/mvnrepo:/root/.m2/repository"
          
        ENV DAPPER_ENV APP_ENV APP_GROUP APP_NAME APP_VERSION
        ENV HOME ${DAPPER_SOURCE}
        WORKDIR ${DAPPER_SOURCE}
          
        ENTRYPOINT ["bash","./scripts/entry"]
        CMD ["ci"]
        
      2. 子应用申请域名

    2. 子应用独立出来

      业务代码,接口,构建,部署

    3. 微应用的字体文件访问404

      1. 原因是 qiankun 将外链样式改成了内联样式,但是字体文件和背景图片的加载路径是相对路径。而 css 文件一旦打包完成,就无法通过动态修改 publicPath 来修正其中的字体文件和背景图片的路径。

      2. 解决方案

        const os = require('os')
        
        let publicPath = process.env.BASE_URL
        let ip = ''
        let port = '8080'
        if (process.env.npm_lifecycle_event === 'serve') {
            const network =
                os.networkInterfaces()['vEthernet (Default Switch)'] ||
                os.networkInterfaces().en0
            ip = network[1].address
            publicPath = `http://${ip}:${port}/`
        }
        module.exports = {
            lintOnSave: process.env.NODE_ENV === 'development',
            publicPath,
            chainWebpack: (config) => {
                config.resolve.symlinks(true)
                const fontRule = config.module.rule('fonts')
                fontRule.uses.clear()
                fontRule
                    .use('file-loader')
                    .loader('file-loader')
                    .options({
                        name: 'fonts/[name].[hash:8].[ext]',
                        publicPath,
                    })
                    .end()
                const imgRule = config.module.rule('images')
                imgRule.uses.clear()
                imgRule
                    .use('file-loader')
                    .loader('file-loader')
                    .options({
                        name: 'img/[name].[hash:8].[ext]',
                        publicPath,
                    })
                    .end()
            },
        }
        
        
    4. 所有静态资源(html,css,js,img)需要设置cors

      server {
          listen       80;#监听端口
          server_name  _; #监听地址、域名  
      
          location / {
              root   /usr/share/nginx/html/dist/;
              try_files $uri $uri/ /index.html;
              add_header Cache-Control "no-cache, no-store";#不使用缓存
              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';
      
              if ($request_method = 'OPTIONS') {
                  return 204;
              }
          }
      
          location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
              root   /usr/share/nginx/html/dist/;#站点目录
              add_header Cache-Control 'max-age=1296000';
              add_header Access-Control-Allow-Origin *;
          }
      
          location /api {
              
          }
      }
      

todos

主应用的页码权限控制有bug,权限控制只是隐藏了菜单,如果知道页面的路径就可绕过

  • 解决方案1:在beforeRouterEnter钩子中判断用户是否有这个页面权限,可以一定程度上解决这个问题
  • 解决方案2:根据后台返回的权限信息,动态生成router,代码复杂一些,但是比方案1效果更好一些
举报

相关推荐

0 条评论