Проектирование архитектуры микроинтерфейса и практика: vue+qiankun

Vue.js

Заявление об авторских правах: эта статья является оригинальной статьей блоггера и соответствует соглашению об авторских правах CC 4.0 BY-SA. Пожалуйста, приложите ссылку на оригинальный источник и это заявление для перепечатки.
Ссылка на эту статью:Гу Депэн.GitHub.IO/note/2020/0…

1. Предварительное напоминание

В прошлом посте уже было рассказано о том, что такое микро-фронтенд, студенты, которые мало что о нем знают, могут ознакомиться.Проектирование и практика архитектуры микроинтерфейса: происхождение
Случай в этой статье будет постоянно обновляться, чтобы стать проектом управления с открытым исходным кодом, звезда приветствуется, если вам нужна поддержка и новые функции, вы можете связаться со мной.
адрес проекта:GitHub.com/Гу Депэн/vu…
официальная ссылка qiankug:GitHub.com/UfanAccept/Kg…
В этой статье используются версия и метод после qiankun2.0.

系统界面

2. Настоящий бой

1. Создайте проект

Поскольку основной проект и подпроекты разрабатываются с помощью vue, используйте vue-cli4 для создания проекта, создайте 2 проекта, основной проект и подпроект.

npm install -g @vue/cli @vue/cli-service-global
vue create vue-giant-master
vue create vue-giant-module

2. Подготовка основного проекта

Теперь приступайте к написанию проекта нормально.Я видел много демок и реальных проектов в Интернете до этого.У всех основной проект напрямую ставит код Qiankun в основной. Затем используйте v-if в app.vue, чтобы управлять отображением маршрута или загруженного подпроекта. Поскольку это проект фонового управления, в моей фактической разработке проекта в основном проекте есть много страниц маршрутизации, поэтому я чувствую, что этот метод не слишком удобен. Поэтому я использую обычный проект vue для запуска, а затем загружаю код qiankun на главной странице после входа в систему. Ниже приводится конкретная реализация.

<template>
  <div class="panel" @click="doOnWeb" @keydown="doOnWeb">
    <top-header class="panel-heder"></top-header>
    <div class="panel-main">
      <!-- 根据当前路由地址判断是子项目页面,还是主项目页面进行选择 -->
      <router-view v-if="showView" />
      <div v-else id="root-view"></div>
    </div>
    <main-menu ref="mainMenu" class="main-menu" v-show="showMenu"></main-menu>
    <main-login ref="mainLogin"></main-login>
  </div>
</template>

<script>
import TopHeader from '@/layout/components/Header'
import MainMenu from '@/layout/components/Menu'
import MainLogin from '@/layout/components/MainLogin'
// 导入乾坤函数
import { registerMicroApps, start } from 'qiankun'
import axios from '@/utils/request'

export default {
  name: 'Layout',
  components: {
    TopHeader,
    MainMenu,
    MainLogin
  },
  data() {
    return {
      showMenu: false
    }
  },
  computed: {
    showView: function() {
      return this.$route.path === '/home'
    }
  },
  mounted() {
    // 定义传入子应用的数据,方法和组件
    const msg = {
      data: this.$store.getters,
      fns: [],
      prototype: [{ name: '$axios', value: axios }]
    }
    // 注册子应用,可以根据登录后的权限加载对应的子项目
    registerMicroApps(
      [
        {
          name: 'module-app1',
          entry: '//localhost:8081',
          container: '#root-view',
          activeRule: '/app1',
          props: msg
        },
        {
          name: 'module-app2',
          entry: '//localhost:8082',
          container: '#root-view',
          activeRule: '/app2',
          props: msg
        }
      ],
      {
        beforeLoad: [
          app => {
            console.log('before load', app)
          }
        ],
        beforeMount: [
          app => {
            console.log('before mount', app)
          }
        ],
        afterUnmount: [
          app => {
            console.log('after unload', app)
          }
        ]
      }
    )

    // 启动微服务
    start({ prefetch: true })
  },
  methods: {
    toggleMenu() {
      this.showMenu = !this.showMenu
      this.$refs['mainMenu'].showTwoMenu = false
    },
    doOnWeb() {
      this.$refs.mainLogin.doOnWeb()
    }
  }
}
</script>

Делая это, вы можете очень хорошо различать загрузку подпроекта и основного проекта. А также могут иметь общие дисплеи развертывания (например, меню и заголовок).

Обратите внимание на реализацию

1. Маршрут должен использовать режим истории, а переход на все страницы основного проекта должен осуществляться методом window.history.pushState(), иначе будет разлогиниться не подпроект, а контейнер #root-view подпроекта на странице будет v- Если удалить, qiankun сообщит об ошибке, а затем не будет загружен при переходе на страницу подпроекта.

2. При передаче методов или компонентов в подпроекты не используйте напрямую метод прямого размещения методов в массиве [функции], потому что такие имена методов будут упакованы и сжаты веб-пакетом во время фактического развертывания и упаковки, а под- проекты получат Имя метода будет неправильным, и подпроект не найдет этот метод при вызове метода основного проекта.

3. При передаче данных данных в этой статье используется геттер, который передает хранилище, а затем подпроект изначально передает значение в хранилище подпроекта при получении значения.При изменении значения основного проекта, он использует initGlobalState, предоставленный qiankun для передачи (в qiankun 1. Версия x использования qiankun не поддерживает связь после загрузки подпроекта, я использую метод привязки события события окна для передачи значения), и обновите его в хранилище в подпроекте.

Я также попытался передать хранилище непосредственно в подпроект, и реальная ситуация также будет передана, так что основной проект и подпроект эквивалентны общедоступному хранилищу, но страницы в подпроекте будут не следить за изменением магазина и менять,нужно вручную с помощью $forceUpdate(),я наверное смотрел исходники vuex.Похоже,это потому что он только создал монитор для изменений dom.Я не проверял это еще в глубине. Я сделаю подробное расследование позже. Если есть прямые друзья, вы можете написать мне немного.

дизайн маршрутизатора

import router from '../router'
import store from '../store'
import { Message } from 'element-ui'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { getUserToken } from '@/utils/user'
import Layout from '@/layout/index.vue'

NProgress.configure({
  showSpinner: false
})

router.beforeEach(async (to, from, next) => {
  NProgress.start()
  const hasToken = getUserToken()
  if (hasToken) {
    if (to.path === '/login') {
      next({
        path: '/home'
      })
      NProgress.done()
    } else {
      const isLogin = store.getters.userInfo
      if (isLogin) {
        next()
      } else {
        try {
          // 获取到实际有什么子项目后再加入到router权限中
          await store.dispatch('user/getUserInfo')
          router.addRoutes([
            {
              path: '/',
              name: 'Layout',
              component: Layout,
              children: [
                {
                  path: 'app1*'
                }
              ]
            }
          ])
          next({
            ...to,
            replace: true
          })
        } catch (error) {
          Message.error(error || 'Has Error')
          next(`/login?redirect=${to.path}`)
          NProgress.done()
        }
      }
    }
  } else {
    if (to.path === '/login') {
      next()
      NProgress.done()
    } else {
      next({
        path: '/login'
      })
    }
  }
})

router.afterEach(() => {
  NProgress.done()
})

Когда пользователь не вошел в систему, страница входа перехватывается.При входе в систему оценивается, было ли получено разрешение.Если разрешение не было получено, перейдите к получению разрешения, а затем добавьте маршрут соответствующего подпроект к роутеру.
Поскольку моя страница подпроекта находится ниже страницы макета, если нет разрешения подпроекта, мне нужно добавить соответствующее разрешение

router.addRoutes([
    {
        path: '/',
        name: 'Layout',
        component: Layout,
        children: [
        {
            path: 'app1*'
        }
        ]
    }
])

Написание подпроекта

Подпроект фактически представляет собой обычный vue-проект, методы и компоненты, полученные в main.js, монтируются на vue.prototype, а полученные данные инициализируются в хранилище.

import './public-path'
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import './plugins/element.js'
import '@/utils/permission.js'

Vue.config.productionTip = false

let instance = null

export async function bootstrap({ prototype }) {
  prototype.map(p => {
    Vue.prototype[p.name] = p.value
  })
}

function initStore(props) {
  props.onGlobalStateChange && props.onGlobalStateChange((value, prev) => {})
  props.setGlobalState &&
    props.setGlobalState({
      ignore: props.name,
      user: {
        name: props.name
      }
    })
}

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

export async function mount(props) {
  initStore(props)
  if (props.data.userInfo.roles) {
    store.commit('permission/SET_ROLES', props.data.userInfo.roles)
  }
  render()
}

export async function unmount() {
  instance.$destroy()
  instance = null
}

if (!window.__POWERED_BY_QIANKUN__) {
  render()
}

Вам нужно создать public-path.js в том же каталоге, что и main.js

if (window.__POWERED_BY_QIANKUN__) {
  // eslint-disable-next-line no-undef
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}
дизайн маршрутизатора

Поскольку основной проект передал разрешения подпроектам. При инициализации маршрута вам нужно только разрешение на загрузку маршрута объекта.

import router from '@/router'
import store from '@/store'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'

NProgress.configure({ showSpinner: false })

router.beforeEach(async (to, from, next) => {
  NProgress.start()

  const hasRoles = store.getters.routes && store.getters.routes.length > 0

  if (hasRoles) {
    next()
  } else {
    // get user info
    const roles = await store.state.permission.roles
    const accessRoutes = await store.dispatch(
      'permission/generateRoutes',
      roles
    )
    router.addRoutes(accessRoutes)
    next({ ...to, replace: true })
  }
})

router.afterEach(() => {
  NProgress.done()
})

модификация vue.config.js

Вывод, куда нужно добавить файл

output: {
    library: `${name}-[name]`,
    libraryTarget: 'umd',
    jsonpFunction: `webpackJsonp_${name}`
}

Подпроекты должны поддерживать кросс-домен, поэтому вам нужно добавить

headers: {
      'Access-Control-Allow-Origin': '*'
    }

разное

Выше приведен код ядра всего проекта, а остальные части — методы разработки конкретных vue-проектов, подробно описывать которые в этой статье не будем. Например: если пользователь не работает в течение определенного периода времени, появится диалоговое окно входа в систему, меню и другие функции.