浅尝微前端,欲穷千里目

其他 微前端 Vue3 Single-SPA

浅尝微前端,欲穷千里目

1.关于微前端

微前端其实就是把一个页面的路由或者是一个模块都应用化(使之成为一个独立的应用,分别部署)然后整合在一起,这就是我所理解的微前端。有点类似于iframe

1.1 为什么不用iframe

其实如果去仔细阅读最近比较流行的微前端框架——qiankun框架,其文档就有说明,这里在强调一下

  1. url不同步。浏览器刷新iframe,url 状态丢失、后退前进按钮无法使用。
  2. UI 不同步,DOM 结构不共享。
  3. 全局上下文完全隔离,内存变量不共享。iframe 内外系统的通信、数据同步等需求,主应用的 cookie 要透传到根域名都不同的子应用中实现免登效果。
  4. 慢。每次子应用进入都是一次浏览器上下文重建、资源重新加载的过程。

1.2 微前端应用通信

  1. 基于url来进行通信,但是消息传递能力弱.
  2. 基于CustomEvent实现通信.
  3. 基于props主子应用间通信.
  4. 使用全局变量,Redux进行通信.

2. Single-SPA

是一个基于前端微服务化的JavaScript前端解决方案,本身没有进行样式的隔离,js执行隔离,只是实现了路由劫持和应用加载

接下来试用一下single-spa

2.1 创建项目

  1. 先创建2个vue项目(越简单越好),一个当子应用一个当主应用(也可以称为基座)
  2. 在子应用安装single-spa,yarn add single-spa-vue -S
  3. 在子应用main.js上进行修改
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import singleSpaVue from 'single-spa-vue';

Vue.config.productionTip = false


const appOptions = {
  el:'#vue-p', // 挂载再父应用的id为vue-p的标签中,
  router,
  store,
  render: h => h(App)
}

const vueLifeCycle = singleSpaVue({
  Vue,
  appOptions:appOptions
})

// 判断父应用引用
if(window.singleSpaNavigate){
  __webpack_public_path__ = 'http://localhost:10001/'
}
if(!window.singleSpaNavigate){
  // const appOptions = Object.assign()
  delete appOptions.el
  new Vue(appOptions).$mount('#app')
}

// 协议接入,定义好协议,父应用会调用这些方法

export const bootstrap = vueLifeCycle.bootstrap;
export const mount = vueLifeCycle.mount;
export const unmount = vueLifeCycle.unmount;
复制代码

这里有一个注意点就是,协议接入必须要导出这三个方法:
a. bootstrap
b. mount
c. unmount

2.2 子应用基本配置

主应用加载子应用,将子应用打包生成一个lib去给父应用使用,这里就需要配置子应用的webpack打包

module.exports = {
    configureWebpack:{
        output:{
            library:'singleVue',
            // 打包成的singleVue挂载到window上
            // 这样就是等效于window.singleVue.bootstrap/mount/unmount
            libraryTarget:'umd'
        }
    },
    devServer:{
        // 因为不同的子应用,和主应用部署的地址不一样,所以需要跨域
        headers: {
            'Access-Control-Allow-Origin': '*',
        },
        port: '10001'
    }
}
复制代码

2.3 主应用基本配置

  • 首先在App.vue当中放入子应用需要挂载的dom,<!-- 子应用加载的位置 --><div id="vue-p"></div>
  • 安装single-spa
  • main.js中引入库,并注册子应用,具体如下:
import { registerApplication,start } from 'single-spa'
// 挂载script函数
async function loadScript(url){
  return new Promise((resolve,reject)=>{
    let scrpit = document.createElement('script');
    scrpit.src = url;
    scrpit.onload = resolve;
    scrpit.onerror = reject;
    document.head.appendChild(scrpit);
  })
}

registerApplication(
  'myVueApp',
  async ()=>{
    console.log('加载模块');
    loadScript('http://localhost:10001/js/app.js');
    loadScript('http://localhost:10001/js/chunk-vendors.js');
  },
  // 用户切换到/about这个路由的时候加载
  location => location.pathname.startsWith('/about'),
  {
    params:'这是父应用给子应用的一个参数'
  }
)

start();
复制代码

针对上面代码,对加载的js做一下解释:
在子应用当中,我们打开调试工具的Network可以很清晰的看到,子应用会加载两个重要的js文件,一个是app.js,一个是chunk-vendors.js 又因为父应用调用子应用,需要获取到子应用的js资源并加载,所以需要对子应用的js进行一个引入

2.4 Single-SPA的问题

2.4.1 css隔离问题

single-spa中这个问题是没有被解决的,需要我们自己来解决,一般解决方式如下:

  1. 子应用之间的样式隔离,可以采取动态样式表的方式,当应用切换的时候移除老应用的样式,添加新应用的样式

  2. 主应用和子应用的样式隔离

    • BEM:约定项目前缀
    • CSS-Modules 打包时生成不冲突的选择器名
    • Shadow DOM
    • css-in-js

2.4.2 js隔离问题(主要是隔离window上的属性)

  1. 一般来说是采取js沙箱模式,创建一个干净的环境给这个子应用使用,当切换时,可以选择丢弃属性和恢复属性

  2. 如何实现js沙箱

    • 快照沙箱 将前后js进行比对,将区别保存起来,返回的时候,恢复之前的即可(只适合子应用为一个的情况)
    • 代理沙箱,不同的应用使用不同的代理来处理

3.qiankun微前端框架

基于Single-SPA提供了更加开箱即用的API,技术栈无关

关于乾坤框架说的并不多,因为其框架本身文档很丰富,这里就简单的说一下文档里面目前没有的,关于微应用是Vue3怎么配置

3.1 主应用配置

主应用配置思路其实很简单,按文档做即可,只是entry的路径要区分一下开发地址和现上部署地址,该例子的部署地址是tangjievic.top,因此有如下配置代码:(在主应用的main.js中进行)

let path = process.env.NODE_ENV !=='development'?'https://tangjievic.top':'http://localhost:40001'
registerMicroApps([
    {
      name: 'datamap', // app name registered
      entry: `${path}/public/childapp/datamap/`,
      container: '#data-map',// data-map
      activeRule: '/public/home/datamap',
    },
]);

start({
    sandbox:{
        strictStyleIsolation: true
    }
});
复制代码

3.2 子应用配置

基本配置步骤:

  • 新增 public-path.js 文件,用于修改运行时的 publicPath
  • 写一个渲染函数,在渲染函数中我们要获取到应用的实例,在此项目中也就是dataMapApp
  • 分情况渲染,其情况一个是作为微应用渲染,一个是作为独立项目渲染
  • 导出约定协议的api即:bootstrap,unmount,mount
import './public-path';
let dataMapApp:any = null;
interface propsRender{
    container?:HTMLElement;
}

let router = null;
let box:any = null
function render(props:propsRender ){
    const container:any = props.container?props.container:null;
    box = container ? (container).querySelector('#app') : '#app'
    router = createRouter({
        history:createWebHashHistory((window as Window & typeof globalThis & {__POWERED_BY_QIANKUN__:string}).__POWERED_BY_QIANKUN__ ? '/public/childapp/datamap/':'/'),
        routes
    })
    dataMapApp = createApp(App);
    dataMapApp.use(router).use(store).mount(box);
}

if(!(window as Window & typeof globalThis & {__POWERED_BY_QIANKUN__:string}).__POWERED_BY_QIANKUN__){
    render({});
}

  
export async function bootstrap() {
    console.log('[vue] vue app bootstraped');
}
export async function mount<F>(props:F) {
    console.log('[vue] props from main framework', props);
    render(props);
}
export async function unmount() {
    dataMapApp.unmount();
    dataMapApp = null as any;
    router = null;
}
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享