最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 微前端之singleSPA实战

    正文概述 掘金(南方小菜)   2021-03-11   874
    关键在于父应用嵌入子应用
    

    前置条件

    • 父子应用均为Vue

    应用层

    第一阶段:基础引入

    关键点
    • 子应用不再直接渲染,而是达成一个lib包的形式

    • 子应用应该按照spa的协议约定进行打包,即

      • lib名为singleVue,导出方式为umd
      • 主应用引入时,不再new Vue,而是将配置交由singleSpa进行管理,从而生成boostrap mount unmount三个对外暴露的接口
    • 在父应用启动项目中,进行应用注册,如果匹配到子应用路径,则执行定义的逻辑(即渲染子应用于指定区域)

    实现

    子应用

    不再直接new Vue生成vue实例,而是根据协议(即让singleSpaVue接管)生成暴露接口
    import singleSpaVue from 'single-spa-vue';
    
    const appOptions = {
      el: '#vue', // 挂载在父应用中id为vue的标签中
      router,
      render: h => h(App)
    }
    
    const vueLifeCycle = singleSpaVue({
      Vue,
      appOptions
    })
    // 暴露接口  
    export const boostrap = vueLifeCycle.boostrap;
    export const mount = vueLifeCycle.mount;
    export const unmount = vueLifeCycle.unmount;
    
    
    配置Vue,将子应用打包成一个个的lib去给父应用使用

    src根路径下,新建vue.config.js

    module.exports = {
        configureWebpack : {
            output: {
                library: 'singleVue', // 指定lib名称
                libraryTarget: 'umd' // 指定导出格式
            },
            devServer: {
                port: 10000 // 指定服务运行端口
            }
        }
    }
    

    父应用

    父应用注册子应用
    import {
      registerApplication,
      start
    } from 'single-spa';
    import router from './router' //引入路由
    
    // 注册子应用
    registerApplication(
      'myVueApp',// 只是一个标识而已
      async () => {
        console.log('加载子vue应用');  // 会在匹配时执行   一定要是一个返回Promise 的函数
      },
      location => location.hash.startsWith('#/vue'),  // 判断是否命中的函数 会传递location对象 本例中就是用户切换到/vue开头的路径下,我需要加载刚刚定义的子应用
    )
    // 启动应用
    start();
    
    实现效果

    微前端之singleSPA实战

    第二阶段:渲染子应用

    关键点:
    • 在路由匹配到时,进行子应用的渲染,而我们现在都是All in JS,问题也就转为了引入其对应的vendor.jsapp.js(以最简单的vue项目为例)
      • 子应用需要固定导出的静态资源路径,避免动态引入时,根域名变为了父应用的域名,导致无法找到的情况
      • 通过fetch的方式去获取子应用的静态资源,从而完成渲染
    • 优化:支持子应用独立运行,即如果不是父应用引入的情况下,子应用应该采用传统的new Vue的形式,便于独立开发
    实现

    在子应用中进行一层逻辑判断即可, 如果是微前端嵌入的形式,则设置静态资源全为固定的绝对路径,避免父应用引入时找不到的情况

    // singleSpaNavigate属性是singleSpa默认会设置的属性,其值是一个函数
    if(window.singleSpaNavigate){
      // 此处应该实现将webpack静态资源路径设置为子应用路径的逻辑
    }
    //  支持子应用独立运行
    else {
      delete appOptions.el;
      new Vue(appOptions).$mount('#app');
    }
    

    我们知道,webpack存在配置项publicPath,可以设置webpack输出静态资源的路径,但我们现在需要的是动态设置

    查看webpack文档,发现一神器__webpack_public_path__。用一句话来解释的话,这货就是output配置中的“publicPath”参数另外一种配置方式。

    那么,我们就可以完善上述逻辑为

    // singleSpaNavigate属性是singleSpa默认会设置的属性,其值是一个函数
    if(window.singleSpaNavigate){
        // 此处应该实现将webpack静态资源路径设置为子应用路径的逻辑
      __webpack_public_path__ = 'http://localhost:10000/'
    }
    //  支持子应用独立运行
    else {
      delete appOptions.el;
      new Vue(appOptions).$mount('#app');
    }
    
    效果图如下

    注意域名,可以发现我们已经实现了在父应用中加载子应用的需求了

    微前端之singleSPA实战

    第三阶段:隔离

    CSS隔离
    • 子应用之间的样式隔离
    • 主应用和子应用之间的样式隔离
      • BEM约定项目前缀
      • CSS-Modules 打包时生成不冲突的选择器名
      • Shadom DOM
      • css-in-js
    具体实现
    shadowDom

    创建一个shadowDom作为子应用的容器,然后让style生效于shadowDOM的作用域中,这样就实现了纯净的隔离

    • 假设p是子应用
    • 假设styleElm是子应用的样式
     let shadomDOM = document.getElementById('shadow').attachShadow({
                mode: 'closed'
            });
    
    let pElm = document.createElement('p');
        pElm.innerHTML = 'hello zf';
    let styleElm = document.createElement('style');
        styleElm.textContent = `
        p{color:red}
        `
    
        shadomDOM.appendChild(styleElm);
       // shadomDOM.appendChild(pElm);
    

    问题:当存在第三方库的组件(比如antd的modal)是挂载在body上时,就会失效

    document.body.appendChild(pElm);
    
    沙箱

    定义沙箱类,intereface如下

    // 默认会执行一次active
    interface {
      Object proxy; // 沙箱代理对象 沙箱环境的真实管理者(现状态)
      Object windowSnapshot; // 快照对象(上一个状态)
      Object modifyPropsMap; // 记录在proxy上的修改态
    	// 1. 记录快照 2. 应用上次快照的修改态  简言之:更新快照并根据修改态更新沙箱
    	active(){}
      // 1. 更新修改态 2. 还原沙箱为上次快照状态   简言之:根据快照更新修改态并还原沙箱
    	inactive(){}
    		
    }
    

    实例如下

    
            class SnapshotSandbox {
                constructor(){
                    this.proxy = window; // window属性
                    this.modifyPropsMap = {}; // 记录在window上的修改
                    this.active();
                }
                //  更新快照并根据修改态更新沙箱
                active(){
                    this.windowSnapshot = {}; 
    	              // 进行拍照(浅拷贝) 便于
                    for (const prop in this.proxy) {
                        if (Object.hasOwnProperty.call(this.proxy, prop)) {
                            this.windowSnapshot[prop] = this.proxy[prop];
                        }
                    }
                  	// 应用上次的修改 以还原状态
                    Object.keys(this.modifyPropsMap).forEach(p=>{
                        this.proxy[p] = this.modifyPropsMap[p];
                    })
                }
                // 根据快照更新修改态并还原沙箱
                inactive(){
                    for (const prop in this.proxy) {
                        if (Object.hasOwnProperty.call(this.proxy, prop)) {
                            if(this.proxy[prop] !== this.windowSnapshot[prop]){
                                this.modifyPropsMap[prop] = this.proxy[prop];
                                this.proxy[prop] = this.windowSnapshot[prop];
                            }
                        }
                    }
                }
            }
    

    eg如下

     let sandbox = new SnapshotSandbox();
            ((
                (window)=>{
                    window.a = 1;
                    window.b = 2;
                    console.log(window.a,window.b);
                    sandbox.inactive();
                    console.log(window.a,window.b);
                    sandbox.active();
                    console.log(window.a,window.b);
                }
    
            ))(sandbox.proxy);
    

    微前端之singleSPA实战

    个人建议记忆图

    • 代表更新 - 代表不变 ↑ 代表更新 ↓ 代表还原
    快照修改态沙箱
    active+-↑(代表更新)inactive-+↓(代表还原)

    相关链接

    微前端之qiankun实战

    微前端之自实现singleSpa


    起源地下载网 » 微前端之singleSPA实战

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元