base-d8

--- 项目名称 技术框架 项目端口
基座 base vue 2.x 8080
测试项目1 app1 vue 3.x 8001
测试项目2 app2 react 18.x 8002

在基座(base)里,为子应用添加的页面

子应用 添加页面 页面代码 ----
app1 views/app1 ---
app2 views/app2 ---

keep-router-state 保留路由状态,可选

在基座(base)里,为子应用添加的路由

子应用 路由地址代码 路由模式 baseroute
base /page 'hash' ---
app1 /page/app1 'history' '/'
app2 /page/app2 'history' '/'

注:因为子应用页面指定了default-page, ,所以路由只需写到 /page/子应用名称 即可,其后会根据子应用本身的路由追加,如:app1里,/page/app1?=app1=/home

注:经过测试,swiper路由切换页面会出错,所以取消 基座中的路由地址写法由 /page/app1 改为 /page/app1?app1=/home,并且取消

路由类型约束
1、基座是hash路由,子应用也必须是hash路由
2、基座是history路由,子应用可以是hashhistory路由
设置基础路由,子应用可以通过window.__MICRO_APP_BASE_ROUTE__获取基座下发的baseroute,如果没有设置baseroute属性,则此值默认为空字符串
注: 根据上面的配置路由类型约束后, 发现不能实现, 基座是hash时, 子应用必须是history路由才行.


base(基座 vue 2.x)

1. 安装插件
  • yarn add @micro-zoe/micro-app -S
2. 配置入口文件 base/src/main.js
import microApp from '@micro-zoe/micro-app'
microApp.start()

// 卸载应用监听
window.addEventListener('unmount', function () {
  // 监听卸载操作
  app.$destroy() // Vue2版本
  // app.unmount() // Vue3版本
})
...
3. 配置路由

使用服务端配置好的菜单.

src/router/groupOverall.js

const route = [
  {
    path: 'App1*', // 微前端-测试项目1(vue 3.x)
    name: 'App1',
    component: () => import('../views/App1/index.vue'),
  },
  {
    path: 'App2*', // 微前端-测试项目1(react 18.x)
    name: 'App2',
    component: () => import('../views/App2/index.vue'),
  },
  ...
}
...
4. 配置路由模式为hash模式{ mode: 'hash' }
const router = new VueRouter({
  routes,
})

vue路由mode的默认值: "hash"
mode可选值:hash | history | abstract, hash(浏览器环境) | abstract (Node.js 环境)

5. 添加子项目页面

src/views/App1/index.vue






6. 菜单&路由的添加

注:我是在基座二级路由菜单下追加的页面,而二级页面的path是'/page', 所以追加的页面url前必需有 '/page'.

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

// 监听所有子应用的路由变化
if(microApp && microApp.router) {
  microApp.router.beforeEach((to, from, appName) => {
    // console.log('全局前置守卫 beforeEach: ', from, to, appName)
    console.log('全局前置守卫 beforeEach: : ', appName+from?.pathname, ' -> ', appName+to?.pathname)
  })

  microApp.router.afterEach((to, from, appName) => {
    // 获取子应用 my-app 的路由信息,返回值与子应用的location相同
    const routeInfo = microApp.router.current.get(appName)
    console.log(`全局前置守卫 afterEach: : 获取子应用 ${appName} 的路由信息 = `,routeInfo)

    const encodeResult = microApp.router.decode(to.pathname)
    console.log('encodeResult = ', encodeResult, to.pathname)

    // 同步路由信息
    syncRouterInfo()
  })
}

// 同步路由信息
function syncRouterInfo() {
  
  // 将 app1 的路由信息同步到浏览器地址上
  microApp.router.attachToURL('app1')

  // // 将所有正在运行的子应用路由信息同步到浏览器地址上,不包含处于隐藏状态的keep-alive应用和预渲染应用
  // microApp.router.attachAllToURL()

  // // 将所有正在运行的子应用路由信息同步到浏览器地址上,包含处于隐藏状态的keep-alive应用
  // microApp.router.attachAllToURL({ includeHiddenApp: true })

  // // 将所有正在运行的子应用路由信息同步到浏览器地址上,包含预渲染应用
  // microApp.router.attachAllToURL({ includePreRender: true })
}


// 监听某个子应用的路由变化
// microApp.router.beforeEach({
//   app1 (to, from) {
//     console.log('指定子应用的前置守卫 beforeEach ', from, to)
//   },
//   app2 (to, from) {
//     console.log('指定子应用的前置守卫 beforeEach ', from, to)
//   }
// })

// beforeEach会返回一个解绑函数
const cancelCallback = microApp.router.beforeEach((to, from, appName) => {
  console.log('全局前置守卫-cancelCallback beforeEach: ', from, to, appName)
})

// 解绑路由监听
cancelCallback()

const route = [
  {
    path: 'app1*', // 微前端-测试项目1(vue 3.x)
    name: 'app1',
    component: () => import('../views/App1/index.vue'),
  },
  {
    path: 'app2*', // 微前端-测试项目1(react 18.x)
    name: 'app2',
    component: () => import('../views/App2/index.vue'),
  },
]

export default route
## src/views/OldHome/home.vue
// 获取菜单列表
async getMenuList() {
        ...
        //-------------------------------------------------------
        const testItems = [
          {name:'app1', pname: '集团整体', children:['home','about']},
          {name:'app2', pname: '集团整体', children:['home','about']}
        ]
        let _items = []
        testItems.forEach((item) => {
          const {name, pname, children} = item
          const _c = []
          children.forEach(itm=>{
            _c.push({
              crUserId: "superadmin",
              display: 1,
              id: "45f229ceef234dbba180b3817766e80a",
              level: 3,
              name: `${name}-${itm}`,
              oldTitle: "0",
              pid: "8aa0bec7fab7437b8ace12917dc09692",
              pname: itm,
              sort: 0,
              upUserId: "superadmin",
              url: `/page/${name}?${name}=%2F${itm}%2F`, // /page/app2?app2=%2Fhome%2F
              yUrl: "3/1"
            })
          })
          
          _items.push({
            children: _c,
            crUserId: "superadmin",
            display: 1,
            icon: "https://cofco001-1308084433.cos.ap-beijing.myqcloud.com/image%2Fpc%2Flogo%2F%E9%A3%8E%E6%8E%A7%E6%95%9E%E5%8F%A3%E5%88%86%E6%9E%90.png",
            id: "8aa0bec7fab7437b8ace12917dc09692",
            level: 2,
            name: name,
            oldTitle: "0",
            pid: "9d43b33c4be143b68ae25176cb9e992e",
            pname: pname,
            sort: 0,
            upUserId: "superadmin",
          })
        })
        menuList[0].children = [..._items, ...menuList[0].children]
        //-------------------------------------------------------
}
7. 添加返回按钮

文件:*base/views/app1/index.vue




文件: base/views/app2/index.vue*

代码大致同上
    
      
      
    

App1(vue 3.x)

1. src/main.js
window.unmount = () => {app.unmount()}// 卸载应用
2. 跨域配置 vue.config.js
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  devServer: {
    port: 8001,
    headers: {
      'Access-Control-Allow-Origin': '*', // 支持跨域 https://dpdetest.cofco.com
      'Access-Control-Allow-Credentials': true,
    },
    proxy: {
      '/api': {
        target: 'https://dpdetest.cofco.com', // 'https://dpdetest.cofco.com',
        // changeOrigin: true,
        pathRewrite: {
          '^/api': '',
        },
      },
    }
  }
})
3. 配置基础路由

注: 如果基座是history路由,子应用是hash路由,这一步可以省略
注: 如果跨域请求带cookie,那么Access-Control-Allow-Origin不能设置为*,必须指定域名,同时设置Access-Control-Allow-Credentials: true

import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'

const routes = [
  {
    path: '/',
    name: 'home',
    component: HomeView
  },
  {
    path: '/home',
    name: 'home',
    component: HomeView
  },
  {
    path: '/about',
    name: 'about',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
  }
]

// 如果基座是history路由,子应用是hash路由,这一步可以省略
const base = window.__MICRO_APP_BASE_ROUTE__ || process.env.BASE_URL

const router = createRouter({
  history: createWebHistory(base),
  routes
})

export default router
4. 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__
}

publicPath是webpack提供的功能,它可以补全静态资源的地址,详情参考webpack文档 publicPath
如果子应用不是webpack构建的,这一步可以省略。

5. 在 src/main.js 引入 public-path.js 文件
import './public-path.js'

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

const app = createApp(App)

app.use(router)
app.mount('#app')

// 卸载应用
window.unmount = () => {
  app.unmount()
}

App2(react 18.x)

1. 使用create-react-app脚手架创建的项目

npm install create-react-app -g
sudo create-react-app app2

2. 配置启动端口

根目录添加文件 .env
代码:PORT=8002

3. 子应用-静态资源加载

react的webpack配置
sudo yarn add reactS-app-rewired -S
sudo yarn add react-scripts -S

4. 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__
}

publicPath是webpack提供的功能,它可以补全静态资源的地址,详情参考webpack文档 publicPath

5. 在 src/index.js 引入 public-path.js 文件
import './public-path.js'
...
// 追回卸载事件
root.unmount = () => {
  ReactDOM.unmountComponentAtNode(document.getElementById('root'));
}
6. 跨域配置

如果react生成的代码中没有生成config目录,则需要使用sudo yarn run eject
, 执行成功后会在根目录生成config目录。
config/webpackDevServer.config.js中的'headers'中添加:

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

注: 如果跨域请求带cookie,那么Access-Control-Allow-Origin不能设置为*,必须指定域名,同时设置Access-Control-Allow-Credentials: true

7. 解决 react项目中引入的组件在src外从而报错 问题

[config/webpack.config.js]
new ModuleScopePlugin内容全部注释,重启项目即可。

plugins: [
        ...
        // new ModuleScopePlugin(paths.appSrc, [
        //   paths.appPackageJson,
        //   reactRefreshRuntimeEntry,
        //   reactRefreshWebpackPluginRuntimeEntry,
        //   babelRuntimeEntry,
        //   babelRuntimeEntryHelpers,
        //   babelRuntimeRegenerator,
        // ]),
      ],

http://localhost:8080/#/page/BigBusiness
http://localhost:8080/#/page/app1?app1=%2Fhome%2F
http://localhost:8080/#/page/app2?app2=%2Fhome%2F

主应用-加入路由监听 & 获取路由信息

src/router/microApp.js

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

// 监听所有子应用的路由变化
if(microApp && microApp.router) {
  microApp.router.beforeEach((to, from, appName) => {
    // console.log('全局前置守卫 beforeEach: ', from, to, appName)
    console.log('全局前置守卫 beforeEach: : ', appName+from?.pathname, ' -> ', appName+to?.pathname)
  })

  microApp.router.afterEach((to, from, appName) => {
    // 获取子应用 my-app 的路由信息,返回值与子应用的location相同
    const routeInfo = microApp.router.current.get(appName)
    console.log(`全局前置守卫 afterEach: : 获取子应用 ${appName} 的路由信息 = `,routeInfo)
  })
}
// 解绑路由监听
cancelCallback()

src/router/index.js 引入上面的文件 import './microApp'

子应用中使用 const routeInfo = window.microApp.router.current.get('my-app') 获取子应用my-app的路由信息,返回值与子应用的location相同

子应用JWT toke 和 接口测试 [现在只做了前端-请求头加Authorization token]

app1

1. 安装 axios

sudo yarn add axios

2.创建service文件夹并在里面创建request.jsindex.jsimport axios,然后创建封装axios的类并导出创建的axios实例对象

APP1/src/service/token.js

// mock JWT Token
const getMockJWTToken = () => {
  return 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhODg4OCI6W3sidG9vbHR0ODg4OCI6Imh0dHBzOi8vdG9vbHR0LmNvbSJ9XSwiaWF0IjoxNjk2NzQ5MzUyLCJleHAiOjE3MDM5NTE5OTksImF1ZCI6ImNvZmNvIiwiaXNzIjoiY29mY28iLCJzdWIiOiJjb2ZjbyJ9.hvgKkGJxNoqBkWnbSvCPcm_IaC22crQHqXVEvQs16gY'
}

// 在前端保存JWT Token通常有以下几种方式:
// 保存JWT Token
const saveJWTToken = (name, token, type) => {
  // 保存JWT Token到cookie
  const expires = 'Fri, 31 Dec 2023 23:59:59 GMT';
  const path = 'dpde.cofco.com';

  switch(type){
    case 'localStorage':
      // 保存JWT Token到localStorage
      localStorage.setItem(name, token);
      break;
    
    case 'cookie':
      document.cookie = `${name}=${token}; expires=${expires}; path=${path}`;
      break;

    case 'sessionStorage':
      // 使用第三方存储, 如sessionStorage、IndexedDB或localForage等, 来保存JWT Token
      sessionStorage.setItem(name, token);
      break;
  }
}

// 获取JWT Token
const getJWTToken = (name, type) => {
  // const regExp = new RegExp("/(?:(?:^|.*;\s*)" + name + "}\s*\=\s*([^;]*).*$)|^.*$/","$1")
  let r = null
  switch(type){
    case 'localStorage':
      // 获取localStorage中的JWT Token
      r = localStorage.getItem(name);
      break;
    
    case 'cookie':
      // 获取cookie中的JWT Token
      r = document.cookie.replace(/(?:(?:^|.*;\s*)jwtToken\s*\=\s*([^;]*).*$)|^.*$/, "$1");
      // return token;
      // r = document.cookie.replace(regExp)
      break;

    case 'sessionStorage':
      r = sessionStorage.getItem(name);
      break;
  }

  return r;
}

class JwtToken {
  constructor() {
    this.name = 'jwtToken'
    this.saveType = 'cookie' // 'localStorage'
    const token = getMockJWTToken()
    if(token){
      saveJWTToken(this.name, token, this.saveType)
      
      // 获取JWT Token
      this.token = getJWTToken(this.name, this.saveType);
    }    
  }
}

const t = new JwtToken()
export default t

APP1/src/service/request.js

import axios from 'axios'
import t from './token.js'

const baseURL = "https://dpdetest.cofco.com"
// 给axios实例配置公共的基础配置
const settingDefault = () => {
  axios.defaults.baseURL = baseURL
  axios.defaults.timeout = 120000
  axios.defaults.withCredentials = true // 跨域请求,允许保存cookie

  axios.defaults.headers = {
    'Content-Type': 'application/json',
    'Access-Control-Allow-Origin': 'https://dpdetest.cofco.com',
    'Access-Control-Allow-Credentials': true,
    'Authorization': t.token
  }
}

class Request {
  constructor() {
    const config = settingDefault()
    
    this.request = axios.create(config) // 创建一个axios实例
    // 请求拦截器
    this.request.interceptors.request.use(config => {return config}, err => {return Promise.reject(err)})
    // 响应拦截器
    this.request.interceptors.response.use(res => {return res.data}, err => {return Promise.reject(err)})
  }
}

// 通用请求
const r = new Request()
export default r

APP1/src/service/index.js

import r from './request.js'

export const test1 = () => {
  const url = 'https://mockapi.eolink.com/QPBsKDib202fd2985773ebd4a85b7fb54b6cde5744a7998/user/points.php?responseId=1515550'
  return r.request.get(url)
}

APP1/src/views/HomeViwer.vue





app2

1. 安装 axios

sudo yarn add axios

2. app2/src/service/token.js
// mock JWT Token
const getMockJWTToken = () => {
  return 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhODg4OCI6W3sidG9vbHR0ODg4OCI6Imh0dHBzOi8vdG9vbHR0LmNvbSJ9XSwiaWF0IjoxNjk2NzQ5MzUyLCJleHAiOjE3MDM5NTE5OTksImF1ZCI6ImNvZmNvIiwiaXNzIjoiY29mY28iLCJzdWIiOiJjb2ZjbyJ9.hvgKkGJxNoqBkWnbSvCPcm_IaC22crQHqXVEvQs16gY'
}

// 在前端保存JWT Token通常有以下几种方式:
// 保存JWT Token
const saveJWTToken = (name, token, type) => {
  // 保存JWT Token到cookie
  const expires = 'Fri, 31 Dec 2023 23:59:59 GMT';
  const path = 'dpde.cofco.com';

  switch(type){
    case 'localStorage':
      // 保存JWT Token到localStorage
      localStorage.setItem(name, token);
      break;
    
    case 'cookie':
      document.cookie = `${name}=${token}; expires=${expires}; path=${path}`;
      break;

    case 'sessionStorage':
      // 使用第三方存储, 如sessionStorage、IndexedDB或localForage等, 来保存JWT Token
      sessionStorage.setItem(name, token);
      break;
  }
}

// 获取JWT Token
const getJWTToken = (name, type) => {
  // const regExp = new RegExp("/(?:(?:^|.*;\s*)" + name + "}\s*\=\s*([^;]*).*$)|^.*$/","$1")
  let r = null
  switch(type){
    case 'localStorage':
      // 获取localStorage中的JWT Token
      r = localStorage.getItem(name);
      break;
    
    case 'cookie':
      // 获取cookie中的JWT Token
      r = document.cookie.replace(/(?:(?:^|.*;\s*)jwtToken\s*\=\s*([^;]*).*$)|^.*$/, "$1");
      // return token;
      // r = document.cookie.replace(regExp)
      break;

    case 'sessionStorage':
      r = sessionStorage.getItem(name);
      break;
  }

  return r;
}

class JwtToken {
  constructor() {
    this.name = 'jwtToken'
    this.saveType = 'cookie' // 'localStorage'
    const token = getMockJWTToken()
    if(token){
      saveJWTToken(this.name, token, this.saveType)
      
      // 获取JWT Token
      this.token = getJWTToken(this.name, this.saveType);
    }    
  }
}

const t = new JwtToken()
export default t
3. app2/src/service/request.js
import axios from 'axios'
import t from './token.js'

const baseURL = "https://dpdetest.cofco.com"

// 给axios实例配置公共的基础配置
const settingDefault = () => {
  axios.defaults.baseURL = baseURL
  axios.defaults.timeout = 120000
  axios.defaults.withCredentials = true // 跨域请求,允许保存cookie

  axios.defaults.headers = {
    'Content-Type': 'application/json;charset=UTF-8',
    'Access-Control-Allow-Origin': 'https://dpde.cofco.com', // 跨域请求,允许保存cookie
    'Access-Control-Allow-Credentials': true,
    'Authorization': 'Bearer ' + t.token,
  }
}

class Request {
  constructor() {
    const config = settingDefault()
    // 创建一个axios实例
    this.request = axios.create(config)
    // 请求拦截器
    this.request.interceptors.request.use(config => {
      return config
    }, err => {
      return Promise.reject(err)
    })
    // 响应拦截器
    this.request.interceptors.response.use(res => {
      return res.data
    }, err => {
      return Promise.reject(err)
    })
  }
}

// 通用请求
const r = new Request()

export default r
4. app2/src/service/index.js
import r from './request.js'

export const test1 = () => {
  const url = 'https://mockapi.eolink.com/QPBsKDib202fd2985773ebd4a85b7fb54b6cde5744a7998/user/points.php?responseId=1515550'
  return r.request.get(url)
}
5. app2/src/App.js
import logo from './logo.svg';
import './App.css';
import { test1 } from './service/index.js'

import { useState, useRef } from 'react';
var isUpdate = false;

function App () {
  const [state, setState] = useState({ status: '', points: '' });
  function updateState (info) {
    setState({ status: info.status, points: info.points });
  }

  async function axiosTest() {
    const r = await test1()
    if(r) {
      updateState(r)
      isUpdate = true
    }
  }

  if(!isUpdate){
    axiosTest()
  }

  return (
    
logo

[异步数据] status:, points:

Learn React
); } export default App;
6. 接口proxy

安装中间件http-proxy-middleware
app2/src/setupProxy.js中添加:

const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function(app) {
  app.use(
    '/api',
    createProxyMiddleware({
      target: 'https://dpdetest.cofco.com',
      changeOrigin: true,
      // withCredentials: true,
      pathRewrite: {
        '^/api': '',
      }
    })
  );
};

微应用---跨域配置注意事项

微应用允许跨域访问设置

微应用 需要设置 允许 跨域访问, 主应用 才能加载 微应用 的 资源。

  • 开发环境:
    需要设置打包配置文件的 devServer 的 headers 属性
headers: {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Credentials': true,
}
  • jsonp会创建一个script元素加载数据,正常情况script会被拦截导致jsonp请求失败,此时可以给script元素添加ignore属性,跳过拦截。
// 修改jsonp方法,在创建script元素后添加ignore属性
const script = document.createElement('script')
script.setAttribute('ignore', 'true')
  • 生产环境:
    微应用nginx 需要配置:'Access-Control-Allow-Origin': '主应用域名' 或者 'Access-Control-Allow-Origin': '*'

微应用获取cookie

  • 微应用nginx 需要配置:'Access-Control-Allow-Origin': '主应用域名',主应用域名不可为 '*'
  • 微应用nginx需要开启:'Access-Control-Allow-Credentials':true
  • 微应用接口请求需配置 baseURL: '微应用的服务地址'
  • 微应用接口请求需允许 withCredentials: true

微应用JWT Token

主应用通过单点登录方式登录,一般token信息会由服务端自动追回到数据请求头中.
前端也可将 token 信息存入 localStorage/cookie/sessionStorage/... 中.

cookie 注意事项

针对浏览器存在cookie,前端却获取不到的问题:

  • httpOnly默认为true打对号√情况下,禁止javascript操作cookie,导致获取不到,可以让后端设置false;
  • 后端使用node+koa,种客户端一个cookie,但是在客户端内通过document.cookie获取不了此cookie。经查是由于koa通过ctx.cookies.set(name, value, [options])种的cookie是自动默认带httpOnly的,httpOnly是服务器可访问 cookie, 默认是 true。禁止javascript操作cookie(为避免跨域脚本(xss)攻击,通过javascript的document.cookie无法访问带有HttpOnly标记的cookie。)
    所以通过在后端设置ctx.cookies.set(name, value, {httpOnly:false})关掉httponly即可。
    ————————————————
    原文链接:https://blog.csdn.net/i_am_a_div/article/details/108223553

微应用通信

子应用 向上发给 基座

1. 基座

基座 接收 子应用 的数据, 监听子应用dispatch发出的数据, 有2种方式:

  • 使用micro-app组件的@datachange属性监听子应用dispatch发出的数据, 如:

注意:datachange绑定函数的返回值不会作为子应用dispatch回调函数的入参,它的返回值没有任何作用。

  • 绑定监听函数, 使用microApp.addDataListener('子应用名', (data)=> {...})监听子应用dispatch发出的数据, 如下:
  // 监听 子应用数据 - app1
  microApp.addDataListener('app1', data => {
    console.log('来自子应用 app1 的数据', data, '[base]')
    return '返回值1'
  })

  // 监听 子应用数据 - app2
  microApp.addDataListener('app2', data => {
    console.log('来自子应用 app2 的数据', data, '[base]')
    return '返回值1'
  })
2. 子应用app
window.microApp.dispatch({data: '来自 子应用 的数据'}, ()=>{
  console.log('microApp dispatch success [app1 -> base]')
})

子应用中microApp必需用window.microApp

3. forceDispatch:强制发送

forceDispatch方法拥有和dispatch一样的参数和行为,唯一不同的是forceDispatch会强制发送数据,无论数据是否变化。
例如:

// 强制发送数据,,无论数据是否变化, 即无论缓存中是否已经存在 name: 'jack' 的值
window.microApp.forceDispatch({name: 'jack'}, () => { console.log('数据已经发送完成'); });

主应用向子应用发送数据 or 子应用 获取 基座发出的数据

1. 基座

基座 发出的数据 有2种方式:

  • 使用micro-app组件的data属性发出, 如:
  • 使用microApp.setData({...})函数发出, 如下:
  // setData(appName: String, data: Object) 发送数据 给 子应用,setData第二个参数只接受对象类型
  microApp.setData(appName, data, callback)
  console.warn(`base -> ${appName}: "${data.msg} [base]"`)
2. 子应用-app1
if(window.__MICRO_APP_ENVIRONMENT__) {
  // 清空当前子应用的所有绑定函数(全局数据函数除外)
  window.microApp.clearDataListener()

  // 直接获取 主应用下发的数据
  const data1 = window.microApp.getData() // 返回主应用下发的data数据
  console.warn('直接获取 base下发的数据 - window.microApp.getData(): ', data1?.msg, ' [app1]')

  // 监听获取 主应用下发的数据
  const autoTrigger = true
  const dataListener = (data) => { console.warn('监听获取 base下发的数据 - window.microApp.addDataListener:', data?.msg, '[app1]') }
  // 监听数据变化 --- window.microApp.addDataListener(dataListener: Function, autoTrigger?: boolean)
  window.microApp.addDataListener(dataListener, autoTrigger)
}
2. 子应用-app2
if(window.__MICRO_APP_ENVIRONMENT__) {
  ... // 同上

  // 监听获取 主应用下发的数据
  const autoTrigger = true
  const dataListener = (data) => { console.warn('监听获取 base下发的数据 - window.microApp.addDataListener:', data?.msg, '[app2]') }
  // 监听数据变化 --- window.microApp.addDataListener(dataListener: Function, autoTrigger?: boolean)
  window.microApp.addDataListener(dataListener, autoTrigger)
}

全局数据通信

全局数据通信会向 主应用 和 所有子应用 发送数据,在跨应用通信的场景中适用。
1. 发送全局数据
// setGlobalData只接受对象作为参数
microApp.setGlobalData({token: 'global data xxxxx'})

注: 发送全局数据可以 基座 也可以 子应用 里写.

发送的数据都会被缓存下来, micro-app会遍历新旧值中的每个key判断值是否有变化,如果所有数据都相同则不会发送(注意:只会遍历第一层key),如果数据有变化则将新旧值进行合并后发送。

2. forceSetGlobalData:强制发送
3. 获取全局数据
  • 方式1. 直接获取数据
const globalData = microApp.getGlobalData() // 返回全局数据
  • 方式2. 绑定监听函数
const _data = window.microApp.getGlobalData()
console.warn(`直接获取 全局数据: ${JSON.stringify(_data)} [app1]`)

function dataListener (_data) { console.warn(`监听获取 全局数据: ${JSON.stringify(_data)} [app1]`) }
const autoTrigger = true

// 解绑监听函数
window.microApp.removeGlobalDataListener(dataListener)
window.microApp.addGlobalDataListener(dataListener, autoTrigger)

兼容测试 []

micro-app依赖于CustomElements和Proxy两个较新的API。

对于不支持CustomElements的浏览器,可以通过引入polyfill进行兼容,详情可参考:webcomponents/polyfills。

但是Proxy暂时没有做兼容,所以对于不支持Proxy的浏览器无法运行micro-app。

浏览器兼容性可以查看:Can I Use

总体如下:

  • PC端:除了IE浏览器,其它浏览器基本兼容。
  • 移动端:ios10+、android5+

子应用通过a标签下载文件测试 []

子应用通过a标签下载文件失败
原因:当跨域时(主应用和文件在不同域名下),无法通过a标签的download属性实现下载。
解决方式:

方式1:转换为blob形式下载

下载
// 通过blob下载文件
function downloadFile (e) {
  // 微前端环境下转换为blob下载,子应用单独运行时依然使用a标签下载
  if (window.__MICRO_APP_ENVIRONMENT__) {
    e.preventDefault()
    // 注意href必须是绝对地址
    fetch(e.target.href).then((res) => {
      res.blob().then((blob) => {
        const blobUrl = window.URL.createObjectURL(blob)
        // 转化为blobURL后再通过a标签下载
        const a = document.createElement('a')
        a.href = blobUrl
        a.download = 'filename.png'
        a.click()
        window.URL.revokeObjectURL(blobUrl)
      })
    })
  }
}

方式2:将文件放到主应用域名下,判断微前端环境下a标签href属性设置为主应用的文件地址


全局共享

global

当多个子应用使用相同的js或css资源,在link、script设置global属性会将文件提取为公共文件,共享给其它应用。
设置global属性后文件第一次加载会放入公共缓存,其它子应用加载相同的资源时直接从缓存中读取内容,从而提升渲染速度。

1. 在基座里, 给需要子应用共享的资源加上global属性

2. 子应用里可直接使用
mounted() {
  ...
  console.log('jQuery = ', jQuery, '[app1]') // jQuery =  ƒ (e,t){return new ce.fn.init(e,t)} [app1]
}

globalAssets

测试兼容不好, 有问题

globalAssets用于设置全局共享资源,它和预加载的思路相同,在浏览器空闲时加载资源并放入缓存,提高渲染效率。
当子应用加载相同地址的js或css资源时,会直接从缓存中提取数据,从而提升渲染速度。

// index.js
import microApp from '@micro-zoe/micro-app'
microApp.start({
  globalAssets: {
    js: ['xxx, 'xxx', ...], // js地址
    css: ['css地址1', 'css地址2', ...], // css地址
  }
})

exclude(过滤元素)

当子应用不需要加载某个js或css,可以通过在link、script、style设置exclude属性,当micro-app遇到带有exclude属性的元素会进行删除。




ignore(忽略元素)

当link、script、style元素具有ignore属性,micro-app不会处理它,元素将原封不动进行渲染。
使用场景例如:jsonp
jsonp会创建一个script元素加载数据,正常情况script会被拦截导致jsonp请求失败,此时可以给script元素添加ignore属性,跳过拦截。

// 修改jsonp方法,在创建script元素后添加ignore属性
const script = document.createElement('script')
script.setAttribute('ignore', 'true')

全局配置

全局配置会影响每一个子应用,请小心使用!

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

microApp.start({
  iframe: true, // 默认值false
  inline: true, // 默认值false
  destroy: true, // 默认值false
  shadowDOM: true, // 默认值false
  ssr: true, // 默认值false
  'disable-scopecss': true, // 默认值false
  'disable-sandbox': true, // 默认值false
  'keep-alive': true, // 默认值false
  'disable-memory-router': true, // 默认值false
  'keep-router-state': true, // 默认值false
  'disable-patch-request': true, // 默认值false
}

微应用-路由

1. 子应用 内部路由

使用(window.)microApp.route.push
如: 当前子应用的路由路径是: /home 跳转至: /about

window.microApp.router.push({ 
  name: 'app1',  // 子应用名称
  path: '/about'  // config是配置文件 item是当前点击的菜单路径信息
});

url: http://localhost:8080/#/page/app1?app1=%2Fabout

子应用中

1. 返回基座的首页
window?.microApp?.dispatch({ isBackHome: true });

在基座项目的子应用页面中加入监听

// 监听 子应用数据 - app1
microApp.addDataListener(appName, data => {
  if(data?.isBackHome){
    this.$router.push({ path: '/Home' })
  }
  return '返回值1'
})
2. 返回子应用的首页
this.$router.replace({ path: '/home' })
子应用1 to 子应用2 [待测试]
  • 发送方
if (window.__MICRO_APP_ENVIRONMENT__) {
    window.microApp.setGlobalData( data )
}
  • 接收方
if (window.__MICRO_APP_ENVIRONMENT__) {
    const data = window.microApp.getGlobalData()
}

资源复用

1. 父应用资源的暴露
  • 使用 url 引入的方式调用模块包,默认注册位置 public/index.html 入口文件

  • 于父框架微前端初始化时, 注册需要暴露的资源,默认注册位置 main.js

if(!window.__MICRO_APP_ENVIRONMENT__){
  microApp.start(
    {
      globalAssets: {
        js: ['https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.min.js'], // js地址
        // css: ['css地址1', 'css地址2', ...], // css地址
      }
    }
  )
}
2. 子应用使用父级资源

于入口文件通过 url 方式引入和父框架一样地址的资源。
子应用相同地址的资源匹配到父框架时,便能直接使用父框架的资源,而无需自己加载,起到资源复用的作用。

应用之间跳转

每个应用的路由实例都是不同的,应用的路由实例只能控制自身,无法影响其它应用,包括基座应用无法通过控制自身路由影响到子应用。

常见的问题如:开发者想通过基座应用的侧边栏跳转,从而控制子应用的页面,这其实是做不到的,只有子应用的路由实例可以控制自身的页面。

  1. 要实现应用之间的跳转有两种方式:
    方式一、window.history 会兼容问题
    方式二、通过数据通信控制跳转
    image.png
window.microApp.addDataListener((data) => {
  if(data?.path) { router.push(data.path); }
});
import microApp from '@micro-zoe/micro-app'
microApp.setData('app1', { path: '/new-path' });

方式三、传递路由实例方法
适用场景: 子应用控制基座跳转

  1. 实现步骤


    基座
子APP

baseRoute

如果不想使用自动补全路由参数这类的, 可以在中指定baseroute属性
例如: 当前主应该路由为http://localhost:3000/main-vue2/ 跳转至 /app-vue2时想成为: http://localhost:3000/main-vue2/app-vue2 时, 使用如下:


baseroute

Desc: 设置子应用的基础路由
Type: string
Default: ''
使用方式:
在微前端环境下,子应用可以从window.MICRO_APP_BASE_ROUTE上获取baseroute的值,用于设置基础路由。

默认情况下,baseroute的功能是被禁止的,若要开启需先关闭memory-router

主/子应用路由示例

路由示例

JWT 登录测试

http://10.8.47.134/oauth/authorize?response_type=code&client_id=daping&scope=daping.read&redirect_uri=http://10.8.47.134/oauth/token/daping

一些问题处理

  • 公共模块代码
    可以将公共代码单独创建一个仓库,在各个项目中以子模块的形式引入到项目中

  • Nginx 部署
    微前端在部署到多个地址时,可以通过 nginx 的反向代理,把子项目的地址代理到主项目地址下,这样可以是主项目和子项目使用相同的 storage 存储

// const TARGET = {
// 1: 'https://dpde.cofco.com', // 正式环境
// 2: 'https://dpdetest.cofco.com', // 内网测试环境
// 3: 'http://10.8.47.114', // zhi quan
// 4: 'http://10.8.218.136:80', // 唐阳
// 5: 'http://10.6.58.68:4050' // 测试环境微服务
// }

你可能感兴趣的:(base-d8)