前端微服务 - 阿里 qiankun 的使用

1,966 阅读4分钟

前言

在使用阿里开源的乾坤并用 vue 编写主工程的时候,遇到了一些问题,现在将遇到的问题和需要注意的内容记录一下。 我使用的是 vue-cli4 脚手架创建的工程,在 public 目录下有 index.html 文件:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="container"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

这里的 id 需要改变一下,使用 container 或者其他。因为使用 vue-cli 创建的工程这里默认 id 都是 app,这会和子工程的 id 一样。就会产生样式的问题。

主工程部分: 在子工程未渲染完成时,可以显示主工程内容,等到子工程渲染完成,主工程的显示出现问题。

子工程部分:

将主工程里 index.html 的id 改为 container,同时将 new Vue 的 el 改为 ‘#container’后, 主工程和子工程样式显示正常。

主工程

在主工程中需要依赖 qiankun,

yarn add qiankun

下面对主工程的主要文件记录如下:

文件 App.vue

<template>
  <div id="main-container">
    <Navbar></Navbar>
    <!-- 子应用盒子 -->
    <div id="root-view" class="app-view-box" v-html="content"/>
  </div>
</template>

<script>
import Navbar from '@/layout/Navbar'
import { Loading } from 'element-ui'

export default {
  name: 'App',
  props: {
    loading: Boolean,
    content: String
  },
  components: {
    Navbar
  },
  beforeCreate () {
    const loadingInstance = Loading.service({ fullscreen: true })
    this.$nextTick(() => { // 以服务的方式调用的 Loading 需要异步关闭
      loadingInstance.close()
    })
  }
}
</script>

文件 main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import ElementUI from 'element-ui'

import './styles/element-variables.scss'
import './icons' // icon

import { registerMicroApps, runAfterFirstMounted, setDefaultMountApp, start } from 'qiankun'

Vue.use(ElementUI)

Vue.config.productionTip = false
let app = null

function VueRender (appContent, loading) {
  return new Vue({
    el: '#container',
    router,
    store,
    data () {
      return {
        content: appContent,
        loading
      }
    },
    render (h) {
      return h(App, {
        props: {
          content: this.content,
          loading: this.loading
        }
      })
    }
  })
}

function render ({ appContent, loading } = {}) {
  if (!app) {
    app = VueRender(appContent, loading)
  } else {
    app.content = appContent
    app.loading = loading
  }
}

function genActiveRule (routerPrefix) {
  return location => location.pathname.startsWith(routerPrefix)
}

/**
 * Step1 初始化应用(可选)
 */
render({ appContent: '', loading: true })

/**
 * Step2 注册子应用
 */
registerMicroApps(
  [
    {
      name: 'app1',
      entry: '//localhost:7101',
      render,
      activeRule: genActiveRule('/' + name)
    }
  ],
  {
    beforeLoad: [
      app => {
        console.log('[LifeCycle] before load %c%s', 'color: green;', app.name)
      }
    ],
    beforeMount: [
      app => {
        console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name)
      }
    ],
    afterUnmount: [
      app => {
        console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name)
      }
    ]
  }
)

/**
 * Step3 设置默认进入的子应用
 */
setDefaultMountApp('/app1')

/**
 * Step4 启动应用
 */
start({
  prefetch: true,
  jsSandbox: true,
  singular: true,
  fetch: window.fetch
})

runAfterFirstMounted(() => {
  console.log('[MainApp] first app mounted')
})

改造为 ts

在改造为 ts 的过程中,这个 main.ts 文件处理起来麻烦死了,总是有 eslint 的语法检查错误,目前仍是没有解决,所以暂时对该文件做忽略检查的操作。//@ts-nocheck 放在文件开头,表示该文件忽略 ts 检查。当然即使有错误提示,浏览器仍然是可以正常访问的,没有报错。

//@ts-nocheck
import Vue from 'vue'
import App from './App.vue'
import './registerServiceWorker'
import router from './router'
import store from './store'

import { registerMicroApps, runAfterFirstMounted, setDefaultMountApp, start } from 'qiankun'

Vue.config.productionTip = false

let app = null

function VueRender(appContent: string, loading: boolean) {
  return new Vue({
    el: '#app',
    router,
    store,
    data() {
      return {
        content: appContent,
        loading
      }
    },
    render(h) {
      return h(App, {
        props: {
          content: this.content,
          loading: this.loading
        }
      })
    }
  })
}

function render(props: { appContent: string; loading: boolean }) {
  const { appContent, loading } = props
  if (!app) {
    app = VueRender(appContent, loading)
  } else {
    app.content = appContent
    app.loading = loading
  }
}

function genActiveRule(routerPrefix: string) {
  return (location: { pathname: string }) => location.pathname.startsWith(routerPrefix)
}

/**
 * Step1 初始化应用(可选)
 */
render({ appContent: '', loading: true })

/**
 * Step2 注册子应用
 */
registerMicroApps(
  [
    {
      name: 'app1',
      entry: '//localhost:7101',
      render,
      activeRule: genActiveRule('/' + name)
    }
  ],
  {
    beforeLoad: [
      app => {
        console.log('[LifeCycle] before load %c%s', 'color: green;', app.name)
      }
    ],
    beforeMount: [
      app => {
        console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name)
      }
    ],
    afterUnmount: [
      app => {
        console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name)
      }
    ]
  }
)

/**
 * Step3 设置默认进入的子应用
 */
setDefaultMountApp('/app1')

/**
 * Step4 启动应用
 */
start({
  prefetch: true,
  jsSandbox: true,
  singular: true,
  fetch: window.fetch
})

runAfterFirstMounted(() => {
  console.log('[MainApp] first app mounted')
})


现将几个不知道如何解决的语法错误记录如下

VueRender 方法中的 render 返回的 prop中的 content 和 loading 参数:

27:25 Property 'content' does not exist on type 'CombinedVueInstance<Vue, object, object, object, Readonly<Record<never, any>>>'.
    25 |       return h(App, {
    26 |         props: {
  > 27 |           content: this.content,
       |                         ^
    28 |           loading: this.loading
    29 |         }
    30 |       })
ERROR in F:/workspace/vue/qiankun-demo1/platfrom-demo-ts/src/main.ts(28,25):
28:25 Property 'loading' does not exist on type 'CombinedVueInstance<Vue, object, object, object, Readonly<Record<never, any>>>'.
    26 |         props: {
    27 |           content: this.content,
  > 28 |           loading: this.loading
       |                         ^
    29 |         }
    30 |       })
    31 |     }

registerMicroApps 方法的 第二个生命周期参数:

ERROR in F:/workspace/vue/qiankun-demo1/platfrom-demo-ts/src/main.ts(66,3):
66:3 Argument of type '{ beforeLoad: ((app: RegistrableApp<{}>) => void)[]; beforeMount: ((app: RegistrableApp<{}>) => void)[]; afterUnmount: ((app: RegistrableApp<{}>) => void)[]; }' is not assignable to parameter of type 'LifeCycles<{}>'.
  Types of property 'beforeLoad' are incompatible.
    Type '((app: RegistrableApp<{}>) => void)[]' is not assignable to type 'Lifecycle<{}> | Lifecycle<{}>[] | undefined'.
      Type '((app: RegistrableApp<{}>) => void)[]' is not assignable to type 'Lifecycle<{}>[]'.
        Type '(app: RegistrableApp<{}>) => void' is not assignable to type 'Lifecycle<{}>'.
          Type 'void' is not assignable to type 'Promise<any>'.
    64 |     }
    65 |   ],
  > 66 |   {
       |   ^
    67 |     beforeLoad: [
    68 |       app => {
    69 |         console.log('[LifeCycle] before load %c%s', 'color: green;', app.name)