joken-前端工程师

  :: 首页 :: 博问 :: 闪存 :: 新随笔 :: :: :: 管理 ::

概论

主要是有一个微前端的主权项目,实现对微服务的调用,类似iframe显示,父应用和子应用可以通过一些数据通信方式实现数据通信。

代码

  • 微前端注册
import Vue from 'vue'
import App from './App.vue'
import { registerMicroApps, start, setDefaultMountApp } from 'qiankun'
import microApps from './micro-app'
import 'nprogress/nprogress.css'

Vue.config.productionTip = false

const instance = new Vue({
  render: h => h(App)
}).$mount('#app')

// 定义loader方法,loading改变时,将变量赋值给App.vue的data中的isLoading
function loader (loading) {
  if (instance && instance.$children) {
    // instance.$children[0] 是App.vue,此时直接改动App.vue的isLoading
    instance.$children[0].isLoading = loading
  }
}

// 给子应用配置加上loader方法
const apps = microApps.map(item => {
  return {
    ...item,
    loader
  }
})

registerMicroApps(apps, {
  beforeLoad: app => {
    console.log('before load app.name====>>>>>', app.name)
  },
  beforeMount: [
    app => {
      console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name)
    }
  ],
  afterMount: [
    app => {
      console.log('[LifeCycle] after mount %c%s', 'color: green;', app.name)
    }
  ],
  afterUnmount: [
    app => {
      console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name)
    }
  ]
})
setDefaultMountApp('/sub-vue')
start()

  • 微前端微应用数组
import store from './store'

const microApps = [
  {
    name: 'sub-vue',
    entry: process.env.VUE_APP_SUB_VUE,
    activeRule: '/sub-vue'
  },
  {
    name: 'sub-react',
    entry: process.env.VUE_APP_SUB_REACT,
    activeRule: '/sub-react'
  },
  {
    name: 'sub-html',
    entry: process.env.VUE_APP_SUB_HTML,
    activeRule: '/sub-html'
  }
]

const apps = microApps.map(item => {
  return {
    ...item,
    container: '#subapp-viewport', // 子应用挂载的div
    props: {
      routerBase: item.activeRule, // 下发基础路由
      getGlobalState: store.getGlobalState // 下发getGlobalState方法
    }
  }
})

export default apps

数据通信的主要方式就是全局state了

props: {
      routerBase: item.activeRule, // 下发基础路由
      getGlobalState: store.getGlobalState // 下发getGlobalState方法
    }
  • 子应用 sub-vue 代码
import './public-path'
import Vue from 'vue'
import App from './App.vue'
import routes from './router'
import { store as commonStore } from 'common'
import store from './store'
import VueRouter from 'vue-router'

Vue.config.productionTip = false
let instance = null

function render (props = {}) {
  const { container, routerBase } = props
  const router = new VueRouter({
    base: window.__POWERED_BY_QIANKUN__ ? routerBase : process.env.BASE_URL,
    mode: 'history',
    routes
  })

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

if (!window.__POWERED_BY_QIANKUN__) {
  // 这里是子应用独立运行的环境,实现子应用的登录逻辑

  // 独立运行时,也注册一个名为global的store module
  commonStore.globalRegister(store)
  // 模拟登录后,存储用户信息到global module
  const userInfo = { name: '我是独立运行时名字叫张三' } // 假设登录后取到的用户信息
  store.commit('global/setGlobalState', { user: userInfo })

  render()
}

export async function bootstrap () {
  console.log('[vue] vue app bootstraped')
}

export async function mount (props) {
  console.log('[vue] props from main framework', props)

  commonStore.globalRegister(store, props)

  render(props)
}

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

有一个全局的状态判断是否是微前端中运行

window.__POWERED_BY_QIANKUN__

render 方法中可以获取到全局的数据,然后写入本子应用中

function render (props = {}) {
  const { container, routerBase } = props
  const router = new VueRouter({
    base: window.__POWERED_BY_QIANKUN__ ? routerBase : process.env.BASE_URL,
    mode: 'history',
    routes
  })

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

以下代码主要是把全局store注册到子应用关联到子应用的store中,方便调用

if (!window.__POWERED_BY_QIANKUN__) {
  // 这里是子应用独立运行的环境,实现子应用的登录逻辑

  // 独立运行时,也注册一个名为global的store module
  commonStore.globalRegister(store)
  // 模拟登录后,存储用户信息到global module
  const userInfo = { name: '我是独立运行时名字叫张三' } // 假设登录后取到的用户信息
  store.commit('global/setGlobalState', { user: userInfo })

  render()
}

html子应用的应用方式

入口html

<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>sub-html子应用开发环境</title>
    <style>
      .container{
        padding: 20px;
      }
    </style>
  </head>
  <body>
    <div class="container">
      纯原生HTML子应用, 当前处于 <code id="current-env">独立运行</code> 环境。
    </div>


    <!-- 开发环境的相对路径引用方式 -->
    <script src="./js/main.js"></script>

    <!-- 由于没有webpack自动构建,线上path需要根据部署路径手动更换,本项目把html文件分开管理,线上环境的html放在 public 文件夹 -->
    <!-- <script src="/subapp/sub-html/js/main.js"></script> -->
  </body>
</html>

main.js

const render = (options) => {
  // options是基座下发的参数,可以保存到全局的状态管理或其他地方,用于后面与基座进行通信
  
  // 可通过 options.getGlobalState() 获取基座下发的数据
  // options.setGlobalState({user: {name: ''}}) 改变全局的数据
  // options.onGlobalStateChange 监听全局数据的变化

  
  document.querySelector('#current-env').innerHTML = 'qiankun'
  const globalState = options.getGlobalState()

  // 展示基座下发的状态
  const node = document.createElement('div')
  node.innerHTML = `基座下发的globalState: <code>${JSON.stringify(globalState)}</code>。<a target="_blank" href="${window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__}">打开独立运行环境</a>`

  document.querySelector('.container').appendChild(node)

  return Promise.resolve();
};

(global => {
  global['prehtml'] = {
    bootstrap: () => {
      console.log('purehtml bootstrap');
      return Promise.resolve();
    },
    mount: (options) => {
      console.log('purehtml mount', options);
      return render(options);
    },
    unmount: () => {
      console.log('purehtml unmount');
      return Promise.resolve();
    },
  };
})(window);

window.prehtml.mount 应该是个全局的方法,微前端中会自动被触发,然后就会运行到render函数了

微前端原理

js沙箱环境
image

image
总的来说就是搞一个虚拟的window全局对象,通过proxy代理这个虚拟window全局对象,来代理操作window真实对象,单子应用切换的时候,这些代理到window的操作都会清除重置

参考文章

js 沙箱 https://github.com/careyke/frontend_knowledge_structure/blob/master/microFrontend/question02_03_js_sandbox.md

posted on 2024-06-12 20:47  joken1310  阅读(2)  评论(0编辑  收藏  举报