Vue

本篇以Vue 2、3作为案例介绍vue的接入方式,其它版本vue的接入方式以此类推,我们默认开发者掌握了各版本vue的开发技巧,比如示例中vue2的代码如何转换为vue1。

作为基座应用

我们强烈建议基座应用采用history模式,hash路由的基座应用只能加载hash路由的子应用,history模式的基座应用对这两种子应用都支持。

在以下案例中,我们默认基座的路由为history模式。

1、安装依赖

npm i @micro-zoe/micro-app --save

2、在入口处引入

// entry
import microApp from '@micro-zoe/micro-app'

microApp.start()

3、分配一个路由给子应用

// router.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import MyPage from './my-page.vue'

Vue.use(VueRouter)

const routes = [
  {
    // 👇 非严格匹配,/my-page/* 都指向 MyPage 页面
    path: '/my-page/*',
    name: 'my-page',
    component: MyPage,
  },
]

export default routes
// router.js
import { createRouter, createWebHistory } from 'vue-router'
import MyPage from './my-page.vue'

const routes = [
  {
    // 👇 非严格匹配,/my-page/* 都指向 MyPage 页面
    path: '/my-page/:page*',
    name: 'my-page',
    component: MyPage,
  },
]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

export default router

4、在页面中嵌入子应用

<!-- my-page.vue -->
<template>
  <div>
    <h1>子应用</h1>
    <!-- 
      name(必传):应用名称
      url(必传):应用地址,会被自动补全为http://localhost:3000/index.html
      baseroute(可选):基座应用分配给子应用的基础路由,就是上面的 `/my-page`
     -->
    <micro-app name='app1' url='http://localhost:3000/' baseroute='/my-page'></micro-app>
  </div>
</template>

作为子应用

1、设置跨域支持

vue.config.js中添加配置

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

2、设置基础路由(如果基座是history路由,子应用是hash路由,这一步可以省略)

// main.js
import VueRouter from 'vue-router'
import routes from './router'

const router = new VueRouter({
  mode: 'history',
  // 👇 __MICRO_APP_BASE_ROUTE__ 为micro-app传入的基础路由
  base: window.__MICRO_APP_BASE_ROUTE__ || process.env.BASE_URL,
  routes,
})

// main.js
import { createRouter, createWebHistory } from 'vue-router'
import routes from './router'

const router = createRouter({
  // 👇 __MICRO_APP_BASE_ROUTE__ 为micro-app传入的基础路由
  history: createWebHistory(window.__MICRO_APP_BASE_ROUTE__ || process.env.BASE_URL),
  routes,
})

3、设置 publicPath

这一步借助了webpack的功能,避免子应用的静态资源使用相对地址时加载失败的情况,详情参考webpack文档 publicPath在新窗口打开

如果子应用不是webpack构建的,这一步可以省略。

步骤1: 在子应用src目录下创建名称为public-path.js的文件,并添加如下内容

// __MICRO_APP_ENVIRONMENT__和__MICRO_APP_PUBLIC_PATH__是由micro-app注入的全局变量
if (window.__MICRO_APP_ENVIRONMENT__) {
  // eslint-disable-next-line
  __webpack_public_path__ = window.__MICRO_APP_PUBLIC_PATH__
}

步骤2: 在子应用入口文件的最顶部引入public-path.js

// entry
import './public-path'

4、监听卸载

子应用被卸载时会接受到一个名为unmount的事件,在此可以进行卸载相关操作。

// main.js
const app = new Vue(...)

// 监听卸载操作
window.addEventListener('unmount', function () {
  app.$destroy()
})
// main.js
const app = createApp(App)
app.mount('#app')

// 监听卸载操作
window.addEventListener('unmount', function () {
  app.unmount()
})

实战案例

以上介绍了vue如何接入微前端,但在实际使用中会涉及更多功能,如数据通信、路由跳转、打包部署,为此我们提供了一套案例,用于展示vue作为基座嵌入(或作为子应用被嵌入) react、vue、angular、vite、nextjs、nuxtjs等框架,在案例中我们使用尽可能少的代码实现尽可能多的功能。

案例地址:https://github.com/micro-zoe/micro-app-demo-0.x

常见问题

1、基座应用中抛出警告,micro-app未定义

报错信息:

  • vue2: [Vue warn]: Unknown custom element: <micro-app>
  • vue3: [Vue warn]: Failed to resolve component: micro-app

参考issue: vue-next@1414在新窗口打开

解决方式: 在基座应用中添加如下配置

Vue2
Vue3
Vite + Vue3

在入口文件main.js中设置ignoredElements,详情查看:https://cn.vuejs.org/v2/api/#ignoredElements

// main.js
import Vue from 'vue'

Vue.config.ignoredElements = [
  'micro-app',
]

在vue.config.js中添加chainWebpack配置,如下:

// vue.config.js
module.exports = {
  chainWebpack: config => {
    config.module
    .rule('vue')
    .use('vue-loader')
    .tap(options => {
      options.compilerOptions = {
        ...(options.compilerOptions || {}),
        isCustomElement: (tag) => /^micro-app/.test(tag),
      };
      return options
    })
  }
}

在vite.config.js中通过vue插件设置isCustomElement,如下:

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [
    vue({
      template: {
        compilerOptions: {
          isCustomElement: tag => /^micro-app/.test(tag)
        }
      }
    })
  ],
})

2、当基座和子应用都是vue-router4,点击浏览器返回按钮页面丢失

原因: vue-router4没有对路由堆栈state做唯一性标记,导致基座和子应用相互影响,vue-router3及其它框架路由没有类似问题。

测试版本: vue-router@4.0.12

相关issue: 155在新窗口打开

解决方式: 在子应用中添加如下设置

if (window.__MICRO_APP_ENVIRONMENT__) {
  // 如果__MICRO_APP_BASE_ROUTE__为 `/基座应用基础路由/子应用基础路由/`,则应去掉`/基座应用基础路由`
  // 如果对这句话不理解,可以参考案例:https://github.com/micro-zoe/micro-app-demo-0.x
  const realBaseRoute = window.__MICRO_APP_BASE_ROUTE__

  router.beforeEach(() => {
    if (typeof window.history.state?.current === 'string') {
      window.history.state.current = window.history.state.current.replace(new RegExp(realBaseRoute, 'g'), '')
    }
  })

  router.afterEach(() => {
    if (typeof window.history.state === 'object') {
      window.history.state.current = realBaseRoute +  (window.history.state.current || '')
    }
  })
}

3、vue-router在hash模式无法通过base设置基础路由

解决方式: 创建一个空的路由页面,将其它路由作为它的children,具体设置如下:

import RootApp from './root-app.vue'

const routes = [
    {
      path: window.__MICRO_APP_BASE_ROUTE__ || '/',
      component: RootApp,
      children: [
        // 其他的路由都写到这里
      ],
    },
]

root-app.vue内容如下:

<template>
  <router-view />
</template>