微前端qiankun (vue3+vite+ts+history)

一 什么是微前端

  1. 微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略
  2. 微前端架构具备以下几个核心价值:
  • 技术栈无关
    主框架不限制接入应用的技术栈,微应用具备完全自主权
  • 独立开发、独立部署
    微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新
  • 增量升级
    在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略
  • 独立运行时
    每个微应用之间状态隔离,运行时状态不共享
  1. 微前端和iframe对比
    1、iframe
    iframe 作为一项非常古老的技术,也可以用于实现微前端。通过 iframe,我们可以很方便的将一个应用嵌入到另一个应用中,而且两个应用之间的 css 和 javascript 是相互隔离的,不会互相干扰。
    优点:
    实现简单;
    css 和 js 天然隔离,互不干扰;
    完全技术栈无关;
    多个子应用可以并存;
    不需要对现有应用进行改造;

    缺点:
    用户体验不好,每次切换应用时,浏览器需要重新加载页面;
    UI 不同步,DOM 结构不共享;
    全局上下文完全隔离,内存变量不共享,子应用之间通信、数据同步过程比较复杂;
    对 SEO 不友好;
    子应用切换时可能需要重新登录,体验不好;

2、single-spa:最早的微前端框架,兼容多种前端技术栈
现在前端应用开发的主流模式为基于 vue / react/ angular 的单页应用开发模式。在这种模式下,我们需要维护一个路由注册表,每个路由对应各自的页面组件 url。切换路由时,如果是一个新的页面,需要动态获取路由对应的 js 脚本,然后执行脚本并渲染出对应的页面;如果是一个已经访问过的页面,那么直接从缓存中获取已缓存的页面方法,执行并渲染出对应的页面。
在 single-spa 方案中,应用被分为两类:基座应用和子应用。其中,子应用就是需要聚合的子应用;而基座应用,是另外的一个单独的应用,用于聚合子应用。
和单页应用的实现原理类似,single-spa 会在基座应用中维护一个路由注册表,每个路由对应一个子应用。基座应用启动以后,当我们切换路由时,如果是一个新的子应用,会动态获取子应用的 js 脚本,然后执行脚本并渲染出相应的页面;如果是一个已经访问过的子应用,那么就会从缓存中获取已经缓存的子应用,激活子应用并渲染出对应的页面。
优点:
切换应用时,浏览器不用重载页面,提供和单页应用一样的用户体验;
完全技术栈无关;
多个子应用可并存;
生态丰富;

缺点:
需要对原有应用进行改造,应用要兼容接入 sing-spa 和独立使用;
有额外的学习成本;
使用复杂,关于子应用加载、应用隔离、子应用通信等问题,需要框架使用者自己实现;
子应用间相同资源重复加载;
启动应用时,要先启动基座应用;

3、qiankun :基于Single-Spa,阿里系开源微前端框架
和 single-spa 一样,qiankun 也能给我们提供类似单页应用的用户体验。qiankun 是在 single-spa 的基础上做了二次开发,在框架层面解决了使用 single-spa 时需要开发人员自己编写子应用加载、通信、隔离等逻辑的问题,是一种比 single-spa 更优秀的微前端方案。

优点:
切换应用时,浏览器不用重载页面,提供和单页应用一样的用户体验;
相比 single-spa,解决了子应用加载、应用隔离、子应用通信等问题,使用起来相对简单;
完全和技术栈无关;
多个子应用可并存;

缺点:
需要对原有应用进行改造,应用要兼容接入 qiankun 和独立使用;
有额外的学习成本;
相同资源重复加载;
启动应用时,要先启动基座应用;

二 qiankun (vue3+vite+ts+history)

1. 安装qiankun

npm i qiankun -S  

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

  1. 新建src/qiankun/index.ts文件,进行单独的抽离
import { registerMicroApps,setDefaultMountApp} from "qiankun";
const microApps = [
  {
    // - 必选,微应用的名称,微应用之间必须确保唯一
    name: "vue-app", 
    // - 必选,微应用的入口
    entry: import.meta.env.MODE === 'development'? '//localhost:9001/' : '/subPath/',
    // - 必选,微应用的容器节点的选择器或者 Element 实例
    container: "#subApp",
    // - 必选,微应用的激活规则
    //支持直接配置字符串或字符串数组,如 activeRule: '/app1' 或 activeRule: ['/app1', '/app2'],当配置为字符串时会直接跟 url 中的路径部分做前缀匹配,匹配成功表明当前应用会被激活。
    //支持配置一个 active function 函数或一组 active function。函数会传入当前 location 作为参数,函数返回 true 时表明当前微应用会被激活。如 location => location.pathname.startsWith('/app1')。
    activeRule: "/subPath",   //匹配所有以/subPath开头的为子应用
    //loader - (loading: boolean) => void - 可选,loading 状态发生变化时会调用的方法。
    //可选,主应用需要传递给微应用的数据。
    props: {
      _parent_base: '/subPath/',
      msg:'这是主应用传给子应用的消息'
    },
  },
];
// 乾坤提供的子应用生命周期钩子  可用于加载loading
const mount={
    beforeLoad: () => {
      //开始加载loading
      console.log('子应用加载前')
    },
    beforeMount: () => {
      console.log('子应用挂载前')
    },
    afterMount: () => {
      //结束加载loading
      console.log('子应用挂载后')
    },
    beforeUnmount: () => {
      console.log('子应用卸载前')
    },
    afterUnmount: () => {
      console.log('子应用卸载后')
    },
  }
//注册子应用
registerMicroApps(microApps,mount);
//设置默认启动子应用
setDefaultMountApp('/subPath');
//启动qiankun | 不可重复启动 | 如果子应用入口在app.vue里可以在这启动否则会报错找不到子应用结点
//start()
  1. main.ts导入
import "./qiankun"  
//其他配置不变
  1. 挂载微应用节点

<el-main>
 <router-view>router-view>
  <div id="subApp">div>
el-main>

onMounted(() => {
  
  start()
})
  1. 配置vite.config.ts
import { defineConfig } from 'vite'

export default defineConfig(({ command, mode }) => {
  return {
    //········
    server: {
      host: '0.0.0.0',
      port: 9003,
      hmr: true,
      disableHostCheck: true,
      proxy: {
        '/api': {   //主服务代理
          target: 'http://192.168.3.101:1171/', //服务
          changeOrigin: true,
          rewrite: (path) => path.replace(/^\/api/, ''),
        },
        '/subApi': {  //子应用代理
          target: 'http://192.168.3.101:3434/', //服务
          changeOrigin: true,
          rewrite: (path) => path.replace(/^\/api/, ''),
        },
      },
    },
  }
})

3. 子应用中使用

  1. qiankun目前暂不支持vite项目 vite项目需要安装社区依赖vite-plugin-qiankun
npm install vite-plugin-qiankun
  1. 修改vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import qiankun from 'vite-plugin-qiankun'

export default ({ mode }) => {
  return defineConfig({
    /**
     * 代理的问题:微应用打包的 base 必须跟主应用中的代理地址 entry 值一致
     * 但是,加上 '/subPath 之后,就必须部署 '/subPath 目录,否则无法独立访问。
     */
   base: mode === 'development' ? 'http://localhost:9001/' : '/subPath/',
   plugins: [
      vue(),
      qiankun('vue-app', { // 微应用名字,与主应用注册的微应用名字保持一致
        useDevMode: true
      })
    ],
    server: {
      headers: {
        'Access-Control-Allow-Origin': '*', //防止主应用访问跨域
      },
      host: '0.0.0.0',
      port: 9001,
      origin: 'http://localhost:9001', //防止去主应用请求静态资源出现404
      hmr: true,
      disableHostCheck: true,
      proxy: {
        '/api': {
          target: 'http://192.168.3.101:3434/', //服务
          changeOrigin: true,
          rewrite: (path) => path.replace(/^\/api/, ''),
        },
      },
    },
  }
})
  1. 修改main.ts
import { App as VueApp, createApp } from 'vue'
import { renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper'

let app: VueApp<Element>
//判断是否为qiankun环境
if (!qiankunWindow.__POWERED_BY_QIANKUN__) {
//自立独立环境正常挂在加载
// router(''),独立运行,路由前缀为空
  createApp(App).use(router('')).use(store).use(iweb).use(dispatchEventStrage).mount('#app')
  for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
    createApp(App).component(key, component)
  }
  createApp(App).directive('load', vLoading)
} else {
//qiankun环境
  renderWithQiankun({
    mount(props) {
      //props为主应用传过来的data
      app = createApp(App)
      for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
        app.component(key, component)
      }
      app.directive('load', vLoading)
      //正常注册 最后挂载在主应用上
      //路由前缀添加router(props._parent_base),_parent_base是从主应用中传过来的
      app
        .use(router)
        .use(store(props._parent_base))
        .use(iweb)
        .use(dispatchEventStrage)
        .mount(
          (props.container
            ? props.container.querySelector('#app')   //挂载在主应用
            : document.getElementById('app')) as Element  
        )
    },
    bootstrap() {
      console.log('--app 01 bootstrap')
    },
    update() {
      console.log('--app 01 update')
    },
    unmount() {
      console.log('--app 01 unmount')
      app?.unmount()  //离开时卸载
    },
  })
}
  1. 修改axios.ts中代理
import { qiankunWindow } from 'vite-plugin-qiankun/dist/helper'
let baseURL: string = ''
if (!qiankunWindow.__POWERED_BY_QIANKUN__) { //独立运行
  baseURL = '/api'
} else {
  baseURL = '/subApi'  //微应用与主应用中配置的子应用代理一致
}
  1. 修改入口文件 如果是微应用访问取消菜单栏等,只显示main
    4.部署nginx
  2. 目录结构
  • 主应用nginx/web

  • 子应用nginx/web/subPath (subPath为子应用打包base对应的目录)

  1. nginx配置
server { #主应用
        listen       88;
        server_name  localhost;
        #charset koi8-r;
        #access_log  logs/host.access.log  main;
        location / {
            root   web;
            index  index.html index.htm;
            try_files $uri $uri/ /index.html;
        }

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;

        location = /50x.html {
            root   html;
        }
        #主应用代理
        location /api/ {
          proxy_pass http://192.168.3.101:1171/;
        }
        #代理到子应用
        location /p1800/ {
          proxy_pass http://localhost:89/p1800/; 
        }
        #子应用的代理 1
        location /subApi/ {
          proxy_pass http://192.168.3.101:3434/;
        }
   }
server {  #子应用
  	    listen       89;
  	    server_name  localhost;
  	
  	    #charset koi8-r;
  	
  	    #access_log  logs/host.access.log  main;
  	
  	    location /p1800 { #对应文件夹名称
  	        root   web;
  	        index  index.html index.htm;
  	        try_files $uri $uri/ /index.html;
  	    }
  	
  	    #error_page  404              /404.html;
  	
  	    # redirect server error pages to the static page /50x.html
  	    #
  	    error_page   500 502 503 504  /50x.html;
  	    location = /50x.html {
  	        root   html;
  	    }
  	    location /api/ {
  	      proxy_pass http://192.168.3.101:3434/;
  	    }
}

你可能感兴趣的:(前端,vue.js,typescript)