微前端之实践主应用的生命周期、微前端的生命周期、 加载和解析 html 及 js

一、微前端之实践主应用的生命周期、微前端的生命周期、 加载和解析 html 及 js

  1. 主应用的生命周期,如下所示:
  • 在主应用 main 下的 util 中的 index.js 中,在 registerApp 中的 registerMicroApps 方法注册到微前端框架里,传入第二个参数生命周期,beforeLoad 是开始加载,mounted 是渲染完成,destoryed 是卸载完成,index.js,代码如下:
import { registerMicroApps, start, createStore } from '../../micro'
import { loading } from '../store'

const store = createStore();
// const storeData = store.getStore()

window.store = store;
store.subscribe((newValue, oldValue) => {
  console.log(newValue, oldValue, '---')
})
// store.update({ ...storeData, a: 1 });

export const registerApp = (list) => {
  registerMicroApps(list, {
    beforeLoad: [
      () => {
        loading.changeLoading(true)
        console.log('开始加载')
      }
    ],
    mounted: [
      () => {
        loading.changeLoading(false)
        console.log('渲染完成')
      }
    ],
    destoryed: [
      () => {
        console.log('卸载完成')
      }
    ]
  })

  start();
}
  • micro 下的 start.js 中,registerMicroApps 接收两个参数,appList 子应用参数列表和 lifeCycle 生命周期,然后通过 setMainLifecycle 设置主应用的生命周期,代码如下:
export const registerMicroApps = (appList, lifeCycle) => {
  setList(appList);
  setMainLifecycle(lifeCycle);
  // window.appList = appList;
}
  • micro 下的 constmainLifeCycle.jslifecycle 是主应用的生命周期,getMainLifecycle 是获取主应用的生命周期,setMainLifecycle 是缓存主应用的生命周期,代码如下:
let lifecycle = {}; 
export const getMainLifecycle = () => lifecycle;
export const setMainLifecycle = data => lifecycle = data; 
  1. 微前端的生命周期,如下所示:
  • micro 下的 utilindex.jsfindAppByRoute 查找上一个和下一个 app 里面的内容,isTurnChild 判断子应用是否做了切换,通过 window.__CURRENT_SUB_APP__ 判断当前路由以改变,修改当前路由,通过 window.__CURRENT_HASH__ 判断当前 hash 值是否改变,代码如下所示:
import { getList } from "../const/subApps";
export const patchRouter = (globalEvent, eventName) => {
  return function () {
    const e = new Event(eventName); 
    globalEvent.apply(this, arguments); 
    window.dispatchEvent(e); 
  }
}

export const currentApp = () => {
  const currentUrl = window.location.pathname;
  return filterApp('activeRule', currentUrl);
}

export const findAppByRoute = (router) => {
  return filterApp('activeRule', router);
}

export const filterApp = (key, value) => {
  const currentApp = getList().filter(item => item[key] === value);
  return currentApp && currentApp.length ? currentApp[0] : {};
}

export const isTurnChild = () => {
  const { pathname, hash } = window.location;
  const url = pathname + hash;
  const currentPrefix = url.match(/(\/\w+)/g);

  if (
    currentPrefix &&
    (currentPrefix[0] === window.__CURRENT_SUB_APP__) &&
    hash === window.__CURRENT_HASH__
  ) {
    return false;
  }
  window.__ORIGIN_APP__ = window.__CURRENT_SUB_APP__;
  const currentSubApp = window.location.pathname.match(/(\/\w+)/);

  if (!currentSubApp) {
    return false;
  }
  window.__CURRENT_SUB_APP__ = currentSubApp[0];
  window.__CURRENT_HASH__ = hash;
  return true;
}

  • micro 下的 lifeCycleindex.js,在 lifecycle 中,prevApp 是获取到上一个子应用,卸载掉上一个子应用,nextApp 是获取到要跳转到的子应用,执行对应的子应用生命周期。若没有获取到下一个子应用,直接退出,后面的就不用执行。获取到上一个子应用,并且有它自己的生命周期,由其它子应用切换过来的,卸载掉上一个子应用。beforeLoad、mounted 和 destoryed 是主应用的生命周期,runMainLifeCycle 是运行主应用生命周期,等待所有的方法执行完成,subApp 是获取的是子应用的内容,index.js,代码如下:
import { findAppByRoute } from '../utils'
import { getMainLifecycle } from '../const/mainLifeCycle'
import { loadHtml } from '../loader'

export const lifecycle = async () => {
  const prevApp = findAppByRoute(window.__ORIGIN_APP__);
  const nextApp = findAppByRoute(window.__CURRENT_SUB_APP__);
  if (!nextApp) {
    return
  }

  if (prevApp && prevApp.unmount) {
    if (prevApp.proxy) {
      prevApp.proxy.inactive(); 
    }
    await destoryed(prevApp)
  }

  const app = await beforeLoad(nextApp);
  await mounted(app);
}

export const beforeLoad = async (app) => {
  await runMainLifeCycle('beforeLoad');
  app && app.beforeLoad && app.beforeLoad();
  const subApp = await loadHtml(app); 
  subApp && subApp.beforeLoad && subApp.beforeLoad();
  return subApp;
}

export const mounted = async (app) => {
  app && app.mount && app.mount({
    appInfo: app.appInfo,
    entry: app.entry
  })
  await runMainLifeCycle('mounted');
}

export const destoryed = async (app) => {
  app && app.unmount && app.unmount();
  await runMainLifeCycle('destoryed');
}

export const runMainLifeCycle = async (type) => {
  const mainlife = getMainLifecycle();
  await Promise.all(mainlife[type].map(async item => await item()));
}
  1. 加载和解析 htmljs,如下所示:
  • micro 下的 loaderindex.jsloadHtml 是加载 html 的方法,container 是第一个子应用需要显示在哪里,#id 内容,entry 是子应用的入口。parseHtml 是解析 html,在 fetchResource 后解析、加载、执行流程。通过 script.concat 处理 fetchedScripts,将得到且携带标签里面的内容、外部链接引入的内容结合到一起,构成子应用所有 script 的集合,在其它方法中将这些集合运行起来,得到完整的子应用渲染,最后将 dom、allScript 直接缓存,后面可以直接使用 cache,获取到对应的子应用。在 getResources 中,scriptUrl 是链接、srchrefscript 是写在 script 中的 js 脚本内容,dom 是需要展示到子应用上的 DOM 结构。deepParse 是深度解析,获取到所有 scriptlink 的链接、内容,先处理位于 script 中的内容,link 也会有 js 的内容,递归遍历进行处理,index.js,代码如下:
import { fetchResource } from '../utils/fetchResource'
import { sandBox } from "../sandbox";
export const loadHtml = async (app) => {
  let container = app.container; 
  let entry = app.entry;
  const [ dom, scripts ] = await parseHtml(entry, app.name);
  const ct = document.querySelector(container);
  if (!ct) {
    throw new Error('容器不存在,请查看');
  }

  ct.innerHTML = dom;
  scripts.forEach(item => {
    sandBox(app, item);
  })
  return app;
}

const cache = {}; 

export const parseHtml = async (entry, name) => {
  if (cache[name]) {
    return cache[name];
  }
  const html = await fetchResource(entry);

  let allScript = [];
  const div = document.createElement('div');
  div.innerHTML = html;

  const [dom, scriptUrl, script] = await getResources(div, entry);
  const fetchedScripts = await Promise.all(scriptUrl.map(async item => fetchResource(item)));

  allScript = script.concat(fetchedScripts);
  cache[name] = [dom, allScript];
  return [dom, allScript];
}

export const getResources = async (root, entry) => {
  const scriptUrl = []; 
  const script = []; 
  const dom = root.outerHTML; 

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

    if (element.nodeName.toLowerCase() === 'script') {
      const src = element.getAttribute('src');
      if (!src) {
        script.push(element.outerHTML);
      } else {
        if (src.startsWith('http')) {
          scriptUrl.push(src);
        } else {
          scriptUrl.push(`http:${entry}/${src}`);
        }
      }

      if (parent) {
        parent.replaceChild(document.createComment('此 js 文件已经被微前端替换'), element);
      }
    }

    if (element.nodeName.toLowerCase() === 'link') {
      const href = element.getAttribute('href');
      if (href.endsWith('.js')) {
        if (href.startsWith('http')) {
          scriptUrl.push(href);
        } else {
          scriptUrl.push(`http:${entry}/${href}`);
        }
      }
    }

    for (let i = 0; i < children.length; i++) {
      deepParse(children[i]);
    }
  }

  deepParse(root);
  return [dom, scriptUrl, script];
}

  • micro 下的 utilsfetchResource.js,处理加载资源请求,代码如下:
export const fetchResource = url => fetch(url).then(async res => await res.text());

你可能感兴趣的:(Vue,微前端,主应用的生命周期,微前端的生命周期,加载和解析,html,及,js)