微前端之实践环境变量设置、快照沙箱隔离、代理沙箱隔离、css 样式隔离、父子应用间通信和子应用间通信

一、微前端之实践环境变量设置、快照沙箱隔离、代理沙箱隔离、css 样式隔离、父子应用间通信和子应用间通信

  1. 微前端环境变量设置,如下所示:
  • micro 下的 sandboxperformScript.jsperformScriptForFunction 是执行 js 脚本,performScriptForEval 是获取到子应用的内容,代码如下:
export const performScriptForFunction = (script, appName, global) => {
  window.proxy = global;
  console.log(global);
  const scriptText = `
    return ((window) => {
      ${script}
      return window['${appName}']
    })(window.proxy)
  `
  return new Function(scriptText)()
}

export const performScriptForEval = (script, appName, global) => {
  window.proxy = global;
  const scriptText = `
    ((window) => {
      ${script}
      return window['${appName}'] 
    })(window.proxy)
  `
  return eval(scriptText) 

  • micro 下的 sandboxindex.jsisCheckLifeCycle 是检验是否存在生命周期,sandBox 是子应用生命周期处理, 环境变量设置。通过 new ProxySandbox 创建代理对象,通过 window.__MICRO_WEB__ 设置环境变量,通过 performScriptForEval 运行 js 文件,通过 isCheckLifeCycle(lifecycle) 判断生命周期,有则挂载到 app 上,index.js,代码如下:
import { performScriptForEval } from './performScript'
// import { SnapShotSandbox } from './snapShotSandbox'
import { ProxySandbox } from './proxySandbox'
const isCheckLifeCycle = lifecycle => lifecycle &&
  lifecycle.bootstrap &&
  lifecycle.mount &&
  lifecycle.unmount

export const sandBox = (app, script) => {
  const proxy = new ProxySandbox();
  if (!app.proxy) {
    app.proxy = proxy;
  }

  window.__MICRO_WEB__ = true;
  
  const lifecycle = performScriptForEval(script, app.name, app.proxy.proxy);

  if (isCheckLifeCycle(lifecycle)) {
    app.bootstrap = lifecycle.bootstrap
    app.mount = lifecycle.mount
    app.unmount = lifecycle.unmount
  }
}
  1. 运行环境隔离,快照沙箱,如下所示:
  • 快照沙箱,给当前的全局变量实现快照的方式来记录沙箱的内容,在子应用切换后,将所有的沙箱变量置为初始值。快照沙箱是不支持多实例的,一个页面中只能存在一个子应用,应用场景是比较老版本的浏览器。
  • micro 下的 sandboxsnapShotSandbox.js,创建 SnapShotSandbox 类,把 window 挂载到代理对象,active 是沙箱激活,对整个 window 做了一层快照,通过 new Map 创建 一个沙箱快照 snapshot,遍历全局环境。 inactive 是沙箱销毁,如果 window 里面的值和快照 snapshot 里面的值不一致,进行还原操作,snapShotSandbox.js,代码如下:
export class SnapShotSandbox {
  constructor() {
    this.proxy = window;
    this.active();
  }
  active() {
    this.snapshot = new Map();
    for(const key in window) {
      this.snapshot[key] = window[key];
    }
  }
  inactive () {
    for (const key in window) {
      if (window[key] !== this.snapshot[key]) {
        window[key] = this.snapshot[key];
      }
    }
  }
}
  1. 运行环境隔离,代理沙箱,如下所示:
  • 代理沙箱,使用 proxy 实现沙箱的功能,进行拦截
  • micro 下的 sandboxproxySandbox.jsdefaultValue 是子应用的沙箱容器,创建 ProxySandbox 类,active 是沙箱激活,子应用需要设置属性,属性设置在容器中,这个容器暂存子应用的所有值。通过 new Proxy 代理 window 对象,拦截它的 getset 方法,进行子应用的沙箱容器处理,inactive 是沙箱销毁,proxySandbox.js,代码如下:
let defaultValue = {}; 
export class ProxySandbox{
  constructor() {
    this.proxy = null;
    this.active();
  }
  active() {
    this.proxy = new Proxy(window, {
      get(target, key) {
        if (typeof target[key] === 'function') {
          return target[key].bind(target);
        }
        return defaultValue[key] || target[key];
      },
      set(target, key, value) {
        defaultValue[key] = value;
        return true;
      }
    })
  }

  inactive () {
    defaultValue = {};
  }
}
  1. css 样式隔离,如下所示:
  • 对于 css 样式隔离,可以使用 css modules、shadow dom、minicss、 css-in-js 等方案
  • webpack.config.js 中,可以使用 mini-css-extract-plugin 这个插件。它可以将所有的 css 打包成单独的 css 文件,在渲染子应用的时候,通过 link 这种标签来引入 css 文件。对于一个子应用来说,一个文件的引入是放在子应用容器内部来引入。如果切换子应用,相当于是把子应用容器内部的东西全部清空,对于 css 文件的引入也是清空的,之后也不会存在两个自应用样式相互影响的问题。对于每一个子应用来说,都有使用了这样的插件处理,webpack.config.js,代码如下:
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

module.exports = {
  entry: {
    path: ['./index.js']
  },
  module: {
    rules: [
      {
        test: /\.js(|x)$/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env', '@babel/preset-react']
          }
        }
      },
      {
        test: /\.(c|sc)ss$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']
      },
      {
        test: /\.(png|svg|jpg|gif)$/,
        use: {
          loader: 'url-loader',
        }
      }
    ]
  },
  optimization: {
    splitChunks: false,
    minimize: false
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html'
    }),

    new MiniCssExtractPlugin({
      filename: '[name].css'
    })
  ],
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'react15.js',
    library: 'react15',
    libraryTarget: 'umd',
    umdNamedDefine: true,
    publicPath: 'http://localhost:9002/'
  },
  devServer: {
    headers: { 'Access-Control-Allow-Origin': '*' },
    contentBase: path.join(__dirname, 'dist'),
    compress: true,
    port: 9002,
    historyApiFallback: true,
    hot: true,
  }
}
  1. 应用间通信,父子通信,如下所示:
  • 对于父子应用间通信,可以通过 props、customevent 等方式。依赖注入,主应用的显示隐藏,注入到子应用内部,通过子应用内部的方法进行调用,customevent 也是符合这样的方式
  • micro 下的 customeventindex.js,建立 Custom 类,通过 on 进行事件监听,以 window.addEventListener 方式。通过 emit 进行事件触发,以 window.dispatchEvent 方式,index.js,代码如下:
export class Custom {
  on (name, cb) {
    window.addEventListener(name, (e) => {
      cb(e.detail)
    })
  }
  emit(name, data) {
    const event = new CustomEvent(name, {
      detail: data
    })
    window.dispatchEvent(event);
  }
}
  • micro 下的 start.js 中,引入 Custom,通过实例化得到 Custom 实例,通过 custom.on 监听 test 事件,将 custom 挂载到 window 上,代码如下:
import { Custom } from './customevent'

const custom = new Custom();
custom.on('test', (data) => {
  console.log(data);
})

window.custom = custom;
  • 在子应用 vue3 项目中,在 main.js 中,在 mount 生命周期中,通过 window.custom.emit 去触发 test 事件,向主应用通信,发送消息传递值,主应用也会监听到子应用发的这个消息,代码如下:
export async function mount(app) {
  setMain(app);
  window.custom.emit('test1', {a: 1});
  // window.custom.on('test1', () => {
  //   window.custom.emit('test2', { b: 2 });
  // });
  // const storeData = window.store.getStore();
  // window.store.update({ ...storeData, a: 11 })
  render();
}
  1. 应用间通信,子应用间通信,如下所示:
  • 对于不同子应用间通信,也可以通过 props、customevent 等方式。如果使用 props,子应用1 传递给父应用做转发,给子应用2。如果使用 customevent,和上面的父子应用间通信类似
  • vue3 子应用项目中的 main.js,先有监听,再有触发。在 mount 生命周期中,监听到 test1 事件后,在回调函数中触发 test2 事件,代码如下:
export async function mount(app) {
  setMain(app);
  // window.custom.emit('test1', {a: 1});
  window.custom.on('test1', () => {
    window.custom.emit('test2', { b: 2 });
  });
  // const storeData = window.store.getStore();
  // window.store.update({ ...storeData, a: 11 })

  render();
  • vue2 子应用项目中的 main.js,在 mount 生命周期中,通过 window.custom.emit 触发 test1 事件,给 vue3 子应用通信,代码如下:
export async function mount() {
  window.custom.emit('test1', {a: 1});
  render();
}

你可能感兴趣的:(Vue,微前端,环境变量设置,快照沙箱隔离,代理沙箱隔离,css,样式隔离,父子应用间通信和子应用间通信)