微前端的一些实现方法

1. SOLID原则

单一功能、开闭原则、里氏替换、接口隔离以及依赖反转原则

2. 加载子应用

需要对html的解析做处理

import { fetchUrl } from '../util/fetchResources';
import { sandbox } from '../sandbox/sandbox';
import { findAppByName } from '../util';

const cache = {};

// 解析html
export const parseHtml = async (url, appName) => {
 // 使用缓存保存内容,提高性能
  if (cache[appName]) {
    return cache[appName];
  }
  const div = document.createElement('div');
  let scriptsArray = [];

  div.innerHTML = await fetchUrl(url);

  // 标签 link script
  const [scriptUrls, scripts, elements] = getResources(div, findAppByName(appName));
  const fetchedScript = await Promise.all(scriptUrls.map(url => fetchUrl(url)));

  scriptsArray = scripts.concat(fetchedScript);
  cache[appName] = [elements, scriptsArray];

  return [elements, scriptsArray];
}

// 解析 js 内容
export const getResources = (root, app) => {
  const scriptUrls = [];
  const scripts = [];

  function deepParse(element) {
    const children = element.children;
    const parent = element.parentNode;

    // 处理位于 link 标签中的 js 文件
    if (element.nodeName.toLowerCase() === 'script') {
      const src = element.getAttribute('src');
      if (!src) {
        // 直接在 script 标签中书写的内容
        let script = element.outerHTML;
        scripts.push(script);
      } else {
        if (src.startsWith('http')) {
          scriptUrls.push(src);
        } else {
          // fetch 时 添加 publicPath
          scriptUrls.push(`http:${app.entry}/${src}`);
        }
      }

      if (parent) {
        let comment = document.createComment('此 js 文件已被微前端替换');
        // 在 dom 结构中删除此文件引用
        parent.replaceChild(comment, element);
      }
    }
    // 处理位于 link 标签中的 js 文件
    if (element.nodeName.toLowerCase() === 'link') {
      const href = element.getAttribute('href');
      if (href.endsWith('.js')) {
        if (href.startsWith('http')) {
          scriptUrls.push(href);
        } else {
          // fetch 时 添加 publicPath
          scriptUrls.push(`http:${app.entry}/${href}`);
        }
      }
    }
    for (let i = 0; i < children.length; i++) {
      deepParse(children[i]);
    }
  }
  deepParse(root);

  return [scriptUrls, scripts, root.outerHTML];
}

// 加载和渲染html
export const htmlLoader = async (app) => {
  console.log('app', app);
  const {
    container: cantainerName, entry, name
  } = app
  let [dom, scriptsArray] = await parseHtml(entry, name);

  let container = document.querySelector(cantainerName);
  if (!container) {
    throw Error(` ${name} 的容器不存在,请查看是否正确指定`);
  }

  container.innerHTML = dom;
  scriptsArray.map((item) => {
    sandbox(item, name);
  });
}

3. 执行js内容

// 执行应用的 js 内容 new Function 篇
export const performScript = (script, appName, global) => {
  const scriptText =
    `try {
       ${script}
       return window['${appName}']
      } catch (err) {
          console.error('runScript error:' + err);
      }`;

  const performer = new Function(scriptText);
  return performer.call(global, global);
}

// 执行应用中的 js 内容 eval篇
export const performScriptForEval = (script, appName, global) => {
  // 这儿是根据打包的时候设置的library名字获取对应的内容
  const scriptText = `
    (() => () => {
      try {
        ${script}
        return window['${appName}']
      } catch (err) {
        console.error('runScript error:' + err);
      }
    })()
  `
  return (() => eval(scriptText))().call(global, global)
}

4. 快照沙箱

// 应用场景: 比较老版本的浏览器
export class SnapShotSandBox {
  constructor() {
    this.proxy = window;
    this.active();
  }
  active() {
    this.snapshot = new Map(); // 创建 window 对象的快照
    for (const key in window) {
      // eslint-disable-next-line no-prototype-builtins
      if (window.hasOwnProperty(key)) {
        // 将window上的属性进行拍照
        this.snapshot[key] = window[key];
      }
    }
  }
  inactive() {
    for (const key in window) {
      // eslint-disable-next-line no-prototype-builtins
      if (window.hasOwnProperty(key)) {
        // 将上次快照的结果和本次window属性做对比
        if (window[key] !== this.snapshot[key]) {
          // 还原window
          window[key] = this.snapshot[key];
        }
      }
    }
  }
}

5. 代理沙箱

export const isFunction = (value) => typeof value === 'function';

// 代理沙箱
export class ProxySandBox {
  constructor() {
    this.proxy = window;
    this.active();
  }
  active() {
    const proxy = window;

    const draftValue = Object.create(Object.getPrototypeOf(proxy))

    this.proxy = new Proxy(proxy, {
      get(target, propKey) {
        // 函数做特殊处理
        if (isFunction(draftValue[propKey])) {
          return draftValue[propKey].bind(proxy)
        }
        if (isFunction(target[propKey])) {
          return target[propKey].bind(proxy)
        }

        return draftValue[propKey] || target[propKey]
      },
      set(target, propKey, value) {
        draftValue[propKey] = value
        return true
      }
    })
  }
  inactive() {
    console.log('关闭沙箱');
  }
}

6. css隔离

  • css modules
  • shadow dom
    Element.attachShadow() 即使同一个样式名称,也不会互相影响。(浏览器兼容性)
  • minicss
    插件 mini-css-extract-plugin
    配置:
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

  module: {
    rules: [
      {
        test: /\.(cs|scs)s$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']
      },
    ]
  },

将css打包成单独的文件。

7. 父子组件通信

  • 定义store,通过props传递
export const creatStore = (initData) => (() => {
  let store = initData;
  let observers = [];
  // 获取store
  const getStore = () => {
    return store;
  }
  const updateStore = (newValue) => new Promise((res) => {
    if (newValue !== store) {
      let oldValue = store; // 缓存
      store = newValue;
      res(store);
      // 通知所有订阅者,监听store的变化
      observers.forEach(fn => fn(newValue, oldValue));
    }
  })

  // 添加订阅者
  const subscribeStore = (fn) => {
    observers.push(fn);
  }
  return { getStore, updateStore, subscribeStore }
})()
  • 自定义事件 CustomEvent
// add an appropriate event listener
obj.addEventListener("cat", function(e) { process(e.detail) });

// create and dispatch the event
var event = new CustomEvent("cat", {
// detail 就是触发事件传递的参数
  detail: { 
    hazcheeseburger: true
  }
});
obj.dispatchEvent(event);

8. 预加载子应用

export const prefetch = async () => {
  // 获取其余子应用
  const appPieces = getList().filter(item => !window.location.pathname.startsWith(item.activeRule));

  // 加载所有子应用
  await Promise.all(appPieces.map(async app => await parseHtml(app.entry, app.name)))
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值