Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

基于 qiankun 的h5移动端微前端改造踩坑日记 #1

Open
YIngChenIt opened this issue Jun 12, 2020 · 0 comments
Open

基于 qiankun 的h5移动端微前端改造踩坑日记 #1

YIngChenIt opened this issue Jun 12, 2020 · 0 comments

Comments

@YIngChenIt
Copy link
Owner

YIngChenIt commented Jun 12, 2020

基于 qiankun 的h5移动端微前端改造踩坑日记

前言

由于公司老项目基建和业务模式原因,h5移动端有多个项目一起组成,如点击首页跳到page1项目,点击我的跳转到page2项目,而且项目之间跳转不可以通过this.$router.push()进行跳转,只可以暴力的通过window.location.href进行跳转,那么项目之间跳转的交互以及通信就很难接受了。

通过调研发现一款基于single-spa进行二次开发的微前端框架qiankun,通过一系列尝试和踩坑,应用于我们h5移动端项目中,这里记录一下基本配置和遇到问题的一些解决方法

项目升级

首先我们需要通过vue脚手生成架一个主应用consult-wx-main,以及两个老的vue移动端项目

主应用设置

主应用接入qiankun

npm i qiankun

修改vue.config.js

  devServer: {
    host: "localhost",
    port: "8080",
    open: true,
    disableHostCheck: true, //这个超级重要
    headers: {
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'GET, POST, OPTIONS, PUT, DELETE'
    },
  }

需要注意的是这里disableHostCheck必须设置微true

然后我们在src目录下添加文件夹micro,里面存放qiankun所需的函数和配置

// src/micro/index.js
import {
    registerMicroApps,
    addGlobalUncaughtErrorHandler,
    start,
} from "qiankun"
import apps from "./apps"

registerMicroApps(apps, {
    // qiankun 生命周期钩子 - 微应用加载前
    beforeLoad: () => {
        // 加载微应用前,加载进度条
        return Promise.resolve()
    },
    // qiankun 生命周期钩子 - 微应用挂载后
    afterMount: () => {
        return Promise.resolve();
    }
})

addGlobalUncaughtErrorHandler((event) => {
    const { message: msg } = event
    if (msg && msg.includes("died in status LOADING_SOURCE_CODE")) {
        alert('微应用加载失败')
    }
})

export default () => {
    start()
}

// src/micro/apps.js
const apps = [
    {
        name: "page1",
        entry: "//localhost:10202",
        container: "#frame",
        activeRule: "/page1",
        props: { }
    },
    {
        name: "page2",
        entry: "//localhost:10201",
        container: "#frame",
        activeRule: "/page2",
        props: { }
    },
];
export default apps;

我们可以看到apps.js就是我们要加载子应用的配置

接下来我们修改下main.js

// main.js
import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router'
import routes from './routes'
import startQiankun from "./micro"

Vue.use(VueRouter)
Vue.config.productionTip = false

const router = new VueRouter({
  routes,
  mode: 'history'
})

startQiankun()

new Vue({
  router,
  render: h => h(App),
}).$mount('#main-app')

同样的public/index.html中的id=app也要对应修改为main-app

最后,我们修改App.vue,提供路由区域给我们子应用进行加载

// App.vue
<template>
  <section class="frame-wrapper">
    <!-- 主应用渲染区,用于挂载主应用路由触发的组件 -->
    <router-view v-show="$route.name" />

    <!-- 子应用渲染区,用于挂载子应用节点 -->
    <section v-show="!$route.name" id="frame"></section>
  </section>
</template>

<script>
export default {
  name: "App",
};
</script>

<style>
.frame-wrapper {
  flex-grow: 1;
  height: 100%;
  width: 100%;
  position: relative;
  padding-bottom: 72px;
}
</style>

子应用设置

子应用的设置比较简单,首先修改vue.config.js

// vue.config.js
  devServer: {
    host: '0.0.0.0',
    port: '10201',
    disableHostCheck: true, //很重要
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
    open: false,
  },
  configureWebpack: {
    output: {
      // 微应用的包名,这里与主应用中注册的微应用名称一致
      library: "page2",
      // 将你的 library 暴露为所有的模块定义下都可运行的方式
      libraryTarget: 'umd',
      // 按需加载相关,设置为 webpackJsonp_VueMicroApp 即可
      jsonpFunction: `webpackJsonp_page2`,
      // 输出重构  打包编译后的 文件名称  【模块名称.版本号.时间戳】
      filename: `js/[name].[hash:8].${Timestamp}.js`,
      chunkFilename: `js/[name].[hash:8].${Timestamp}.js`
    }
  }

其中 devServe的端口必须和主应用apps.js中设置的一致,而且output中的library也需要和apps.js中设置的一致

然后我们需要在main.js导出qiankun所需要的函数

import "./public-path";

let instance: any = null;

function render() {
  instance = new Vue({
    store,
    router,
    render: h => h(App)
  }).$mount('#app')
}

// 独立运行时,直接挂载应用
if (!window.__POWERED_BY_QIANKUN__) {
  render();
}

/**
 * bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
 * 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
 */
export async function bootstrap() {
  console.log("VueMicroApp bootstraped");
}


/**
 * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
 */
export async function mount(props: any) {
  console.log("VueMicroApp mount", props);
  render(props);
}

/**
 * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
 */
export async function unmount() {
  console.log("VueMicroApp unmount");
  instance.$destroy();
  instance = null;
}

// public-path.js
if (window.__POWERED_BY_QIANKUN__) {
    // 动态设置 webpack publicPath,防止资源加载出错
    // eslint-disable-next-line no-undef
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

配置路由

export default new Router({
  routes,
  base: window.__POWERED_BY_QIANKUN__ ? "/page2" : "/",
  mode: 'history'
})

到现在为止,基础配置就全部结束了

踩坑方案

第三方sdk跨域问题

接入qiankun之后发现子应用的一些第三方sdk出现了跨域问题,如

<script src="https://res.wx.qq.com/open/js/jweixin-1.3.2.js"></script>

解决方法是将js下载下来,然后在main.jsimport就好了

子应用接口路径问题

对应子应用老项目来说,接口是通过根路径/进行访问的,但是接入qiankun之后,接口路径变为了主应用的/,导致接口报404。这里可以采用两种方法,接口采用绝对路径(可能会发生跨域),还有一种就是主应用进行接口劫持转发,项目中采用的是第二种方法

// vue.config.js
proxy: {
    '/consult': {
    target: 'https://wx.cnhis.zb1.co',
    changeOrigin: true,
    ws: true
    },
    '/chat': {
    target: 'https://wx.cnhis.zb1.co',
    changeOrigin: true
    },
    '/wx': {
    target: 'https://wx.cnhis.zb1.co',
    changeOrigin: true
    },
}

主子应用间通信

qiankun给我们提供了一套主子应用通信的方式

qiankun 内部提供了 initGlobalState 方法用于注册 MicroAppStateActions 实例用于通信,该实例有三个方法,分别是:

setGlobalState:设置 globalState - 设置新的值时,内部将执行 浅检查,如果检查到 globalState 发生改变则触发通知,通知到所有的 观察者 函数。
onGlobalStateChange:注册 观察者 函数 - 响应 globalState 变化,在 globalState 发生改变时触发该 观察者 函数。
offGlobalStateChange:取消 观察者 函数 - 该实例不再响应 globalState 变化。

子应用间路由跳转问题

如果我们子应用之间想进行路由跳转的话,是比较麻烦的,因为配置qiankun的时候固定死了vue-router的base。有两种方法可以实现子应用间的跳转,第一种是重写this.$router.push方法,不走base的那套逻辑

另外一种就是现在采用的,将主应用this.$router.push方法传递给子应用,对于需要子应用间跳转的需求,调用主应用的this.$router.push方法就好了

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant